sabre-client 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.es.md +97 -11
- package/README.md +94 -11
- package/dist/index.d.ts +110 -6
- package/dist/index.js +124 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1207,12 +1207,20 @@ function resolveConfig(config) {
|
|
|
1207
1207
|
if (environment !== "sandbox" && environment !== "production") {
|
|
1208
1208
|
fail(`environment debe ser 'sandbox' o 'production' (recibido: ${JSON.stringify(environment)})`);
|
|
1209
1209
|
}
|
|
1210
|
-
const
|
|
1211
|
-
if (
|
|
1212
|
-
fail(`authVersion debe ser 'v3' o 'v2' (recibido: ${JSON.stringify(
|
|
1210
|
+
const configVersion = config.authVersion ?? void 0;
|
|
1211
|
+
if (configVersion !== void 0 && configVersion !== "v3" && configVersion !== "v2") {
|
|
1212
|
+
fail(`authVersion debe ser 'v3' o 'v2' (recibido: ${JSON.stringify(configVersion)})`);
|
|
1213
1213
|
}
|
|
1214
1214
|
const { credentials } = config;
|
|
1215
1215
|
if (!isObject3(credentials)) fail("credentials es requerido");
|
|
1216
|
+
const credentialsVersion = credentials.authVersion ?? void 0;
|
|
1217
|
+
if (credentialsVersion !== void 0 && credentialsVersion !== "v3" && credentialsVersion !== "v2") {
|
|
1218
|
+
fail(`credentials.authVersion debe ser 'v3' o 'v2' (recibido: ${JSON.stringify(credentialsVersion)})`);
|
|
1219
|
+
}
|
|
1220
|
+
if (configVersion !== void 0 && credentialsVersion !== void 0 && configVersion !== credentialsVersion) {
|
|
1221
|
+
fail(`authVersion '${configVersion}' no coincide con credentials.authVersion '${credentialsVersion}'`);
|
|
1222
|
+
}
|
|
1223
|
+
const authVersion = configVersion ?? credentialsVersion ?? "v3";
|
|
1216
1224
|
requireString(credentials.username, "credentials.username");
|
|
1217
1225
|
requireString(credentials.password, "credentials.password");
|
|
1218
1226
|
if (authVersion === "v3") {
|
|
@@ -1491,14 +1499,16 @@ var TRANSIENT_CODES = /* @__PURE__ */ new Set([
|
|
|
1491
1499
|
var PREVIOUS_ENTRY_ACTIVE_RETRIES = 3;
|
|
1492
1500
|
var PREVIOUS_ENTRY_ACTIVE_DELAY_MS = 1e3;
|
|
1493
1501
|
var PREVIOUS_ENTRY_ACTIVE = /PREVIOUS ENTRY ACTIVE/i;
|
|
1494
|
-
var
|
|
1502
|
+
var PARSER_OPTIONS = {
|
|
1495
1503
|
ignoreAttributes: false,
|
|
1496
1504
|
attributeNamePrefix: "@",
|
|
1497
1505
|
removeNSPrefix: true,
|
|
1498
1506
|
parseTagValue: false,
|
|
1499
1507
|
parseAttributeValue: false,
|
|
1500
1508
|
processEntities: true
|
|
1501
|
-
}
|
|
1509
|
+
};
|
|
1510
|
+
var parser = new XMLParser(PARSER_OPTIONS);
|
|
1511
|
+
var untrimmedParser = new XMLParser({ ...PARSER_OPTIONS, trimValues: false });
|
|
1502
1512
|
var SoapTransport = class {
|
|
1503
1513
|
#config;
|
|
1504
1514
|
#log;
|
|
@@ -1738,11 +1748,11 @@ function scrubToken(text, security) {
|
|
|
1738
1748
|
const token = security.binarySecurityToken;
|
|
1739
1749
|
return text.split(token).join(REDACTED).split(xmlEscape(token)).join(REDACTED);
|
|
1740
1750
|
}
|
|
1741
|
-
function parseEnvelope(text) {
|
|
1751
|
+
function parseEnvelope(text, xmlParser = parser) {
|
|
1742
1752
|
if (text.trim() === "") return void 0;
|
|
1743
1753
|
let parsed;
|
|
1744
1754
|
try {
|
|
1745
|
-
parsed =
|
|
1755
|
+
parsed = xmlParser.parse(text);
|
|
1746
1756
|
} catch {
|
|
1747
1757
|
return void 0;
|
|
1748
1758
|
}
|
|
@@ -1807,6 +1817,10 @@ function retryReason(error, idempotent) {
|
|
|
1807
1817
|
if (error.status === void 0 || TRANSIENT_STATUS.has(error.status)) return "transient";
|
|
1808
1818
|
return void 0;
|
|
1809
1819
|
}
|
|
1820
|
+
function untrimmedPayload(raw) {
|
|
1821
|
+
const document = parseEnvelope(raw, untrimmedParser);
|
|
1822
|
+
return document === void 0 ? void 0 : asNode(firstElement(document.Body)[1]);
|
|
1823
|
+
}
|
|
1810
1824
|
function firstElement(node2) {
|
|
1811
1825
|
for (const [key, value] of Object.entries(node2)) {
|
|
1812
1826
|
if (!key.startsWith("@") && key !== "#text") return [key, Array.isArray(value) ? value[0] : value];
|
|
@@ -2511,11 +2525,63 @@ function validateSelection(selection) {
|
|
|
2511
2525
|
});
|
|
2512
2526
|
}
|
|
2513
2527
|
|
|
2528
|
+
// src/session/command.ts
|
|
2529
|
+
var SABRE_COMMAND_SERVICE = "sabreCommand";
|
|
2530
|
+
var NAMESPACE5 = "http://webservices.sabre.com/sabreXML/2011/10";
|
|
2531
|
+
var MAX_COMMAND_LENGTH = 1023;
|
|
2532
|
+
var DOUBLE_WIDTH = /* @__PURE__ */ new Set(["!", "%", "$", "&", "\xA5", "\xA7"]);
|
|
2533
|
+
function fail4(message) {
|
|
2534
|
+
throw new TypeError(`sabre-client: ${message}`);
|
|
2535
|
+
}
|
|
2536
|
+
function sabreCommandPayload(command) {
|
|
2537
|
+
return `<SabreCommandLLSRQ xmlns="${NAMESPACE5}" Version="2.0.0" ReturnHostCommand="true"><Request Output="SCREEN"><HostCommand>${xmlEscape(command)}</HostCommand></Request></SabreCommandLLSRQ>`;
|
|
2538
|
+
}
|
|
2539
|
+
function commandToken(command) {
|
|
2540
|
+
return (command.trim().split(/[¥‡\s]/u)[0] ?? "").slice(0, 4);
|
|
2541
|
+
}
|
|
2542
|
+
function sabreCommandLength(command) {
|
|
2543
|
+
let length = 0;
|
|
2544
|
+
for (const char of command) length += DOUBLE_WIDTH.has(char) ? 2 : 1;
|
|
2545
|
+
return length;
|
|
2546
|
+
}
|
|
2547
|
+
function sabreCommandRequest(command, options) {
|
|
2548
|
+
if (typeof command !== "string" || command.trim() === "") fail4("command es requerido y debe ser un string no vac\xEDo");
|
|
2549
|
+
const length = sabreCommandLength(command);
|
|
2550
|
+
if (length > MAX_COMMAND_LENGTH) {
|
|
2551
|
+
fail4(`command mide ${length} y el m\xE1ximo es ${MAX_COMMAND_LENGTH} (!, %, $, &, \xA5 y \xA7 cuentan 2); Sabre responder\xEDa REQUEST EXCEEDED MAXIMUM LENGTH`);
|
|
2552
|
+
}
|
|
2553
|
+
const { idempotent = false, ...requestOptions } = options;
|
|
2554
|
+
if (typeof idempotent !== "boolean") fail4("idempotent debe ser un boolean");
|
|
2555
|
+
return {
|
|
2556
|
+
service: SABRE_COMMAND_SERVICE,
|
|
2557
|
+
action: "SabreCommandLLSRQ",
|
|
2558
|
+
payload: sabreCommandPayload(command),
|
|
2559
|
+
idempotent,
|
|
2560
|
+
operation: "command",
|
|
2561
|
+
...requestOptions
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
function parseSabreCommandResponse(response) {
|
|
2565
|
+
if (response.payloadName !== "SabreCommandLLSRS") {
|
|
2566
|
+
throw new SabreGatewayError(`${SABRE_COMMAND_SERVICE}: respuesta no reconocida (${response.payloadName})`, {
|
|
2567
|
+
protocol: "soap",
|
|
2568
|
+
status: response.status,
|
|
2569
|
+
retryable: false,
|
|
2570
|
+
raw: response.raw
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
2573
|
+
return {
|
|
2574
|
+
text: textOf((untrimmedPayload(response.raw) ?? response.payload).Response) ?? "",
|
|
2575
|
+
warnings: response.warnings,
|
|
2576
|
+
messageId: response.messageId
|
|
2577
|
+
};
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2514
2580
|
// src/session/session.ts
|
|
2515
2581
|
var OTA_NAMESPACE = "http://www.opentravel.org/OTA/2002/11";
|
|
2516
2582
|
var SWS_NAMESPACE = "http://webservices.sabre.com";
|
|
2517
2583
|
var sessionSend = /* @__PURE__ */ Symbol("sabre-client.session.send");
|
|
2518
|
-
function
|
|
2584
|
+
function fail5(message) {
|
|
2519
2585
|
throw new TypeError(`sabre-client: ${message}`);
|
|
2520
2586
|
}
|
|
2521
2587
|
var SESSION_ERROR_KINDS = /* @__PURE__ */ new Map([
|
|
@@ -2587,6 +2653,18 @@ var SabreSession = class {
|
|
|
2587
2653
|
async ping(options = {}) {
|
|
2588
2654
|
checkPingResponse(await this[sessionSend]({ ...pingRequest(), ...options }));
|
|
2589
2655
|
}
|
|
2656
|
+
/**
|
|
2657
|
+
* Manda un comando de host (`SabreCommandLLSRQ` 2.0.0) y devuelve la pantalla como texto, sin
|
|
2658
|
+
* interpretarla. Es una vía de escape: sin tipos ni garantías, y un comando puede cambiar la
|
|
2659
|
+
* configuración del PCC o emitir. El comando va tal cual; el separador de Sabre Red (`‡`) se
|
|
2660
|
+
* escribe `¥`. Sin reintento salvo `{ idempotent: true }`, para comandos de solo lectura. La
|
|
2661
|
+
* pantalla llega sin recortar, y más de 1023 caracteres (contados como Sabre) lanza sin red.
|
|
2662
|
+
*/
|
|
2663
|
+
async command(command, options = {}) {
|
|
2664
|
+
const request = sabreCommandRequest(command, options);
|
|
2665
|
+
this.#transport.log.emit("debug", "sabre.session.command", { command: commandToken(command) });
|
|
2666
|
+
return parseSabreCommandResponse(await this[sessionSend](request));
|
|
2667
|
+
}
|
|
2590
2668
|
/**
|
|
2591
2669
|
* Cierra la sesión con `SessionCloseRQ`: Sabre descarta el AAA e invalida el token. Es
|
|
2592
2670
|
* idempotente: las llamadas siguientes devuelven la misma promesa, sin volver a la red. Si el
|
|
@@ -2614,11 +2692,11 @@ var SabreSession = class {
|
|
|
2614
2692
|
return this.#send(request);
|
|
2615
2693
|
}
|
|
2616
2694
|
async #send(request) {
|
|
2617
|
-
if (this.#closing !== void 0)
|
|
2695
|
+
if (this.#closing !== void 0) fail5("session closed");
|
|
2618
2696
|
if (this.#unusable !== void 0) {
|
|
2619
2697
|
throw new TypeError(`sabre-client: session unusable (${this.#unusable.kind})`, { cause: this.#unusable });
|
|
2620
2698
|
}
|
|
2621
|
-
if (this.#busy)
|
|
2699
|
+
if (this.#busy) fail5("session busy");
|
|
2622
2700
|
this.#busy = true;
|
|
2623
2701
|
try {
|
|
2624
2702
|
const response = await this.#transport.request({ ...request, ...this.#envelopeFields() });
|
|
@@ -2773,7 +2851,7 @@ function usernameToken(credentials) {
|
|
|
2773
2851
|
}
|
|
2774
2852
|
async function openSession(transport, credentials, options = {}) {
|
|
2775
2853
|
const { keepAlive = false, ...requestOptions } = options;
|
|
2776
|
-
if (typeof keepAlive !== "boolean")
|
|
2854
|
+
if (typeof keepAlive !== "boolean") fail5("keepAlive debe ser un boolean");
|
|
2777
2855
|
const conversationId = globalThis.crypto.randomUUID();
|
|
2778
2856
|
const request = {
|
|
2779
2857
|
service: "sessionCreate",
|
|
@@ -2876,6 +2954,39 @@ var SabreLegacy = class {
|
|
|
2876
2954
|
}
|
|
2877
2955
|
};
|
|
2878
2956
|
|
|
2957
|
+
// src/auth/credentials.ts
|
|
2958
|
+
var DEFAULT_DOMAIN = "AA";
|
|
2959
|
+
function fail6(message) {
|
|
2960
|
+
throw new TypeError(`sabre-client: ${message}`);
|
|
2961
|
+
}
|
|
2962
|
+
function requireString2(value, field) {
|
|
2963
|
+
if (typeof value !== "string" || value.trim() === "") fail6(`${field} es requerido y debe ser un string no vac\xEDo`);
|
|
2964
|
+
}
|
|
2965
|
+
function buildCredentials(options) {
|
|
2966
|
+
if (typeof options !== "object" || options === null) fail6("buildCredentials recibe un objeto");
|
|
2967
|
+
const authVersion = options.authVersion ?? "v3";
|
|
2968
|
+
if (authVersion !== "v3" && authVersion !== "v2") {
|
|
2969
|
+
fail6(`authVersion debe ser 'v3' o 'v2' (recibido: ${JSON.stringify(authVersion)})`);
|
|
2970
|
+
}
|
|
2971
|
+
const { user, pcc, password, clientId, clientSecret } = options;
|
|
2972
|
+
const domain = options.domain ?? DEFAULT_DOMAIN;
|
|
2973
|
+
requireString2(user, "user");
|
|
2974
|
+
requireString2(password, "password");
|
|
2975
|
+
if (!isPseudoCityCode(pcc)) fail6("pcc debe tener 3 o 4 caracteres alfanum\xE9ricos");
|
|
2976
|
+
requireString2(domain, "domain");
|
|
2977
|
+
if (/[-:]/.test(domain)) fail6("domain no puede contener '-' ni ':'");
|
|
2978
|
+
if (clientId === void 0 !== (clientSecret === void 0)) {
|
|
2979
|
+
fail6("clientId y clientSecret van juntos: pasa los dos o ninguno");
|
|
2980
|
+
}
|
|
2981
|
+
if (authVersion === "v3" || clientId !== void 0) {
|
|
2982
|
+
requireString2(clientId, authVersion === "v3" ? "clientId (requerido con authVersion v3)" : "clientId");
|
|
2983
|
+
requireString2(clientSecret, authVersion === "v3" ? "clientSecret (requerido con authVersion v3)" : "clientSecret");
|
|
2984
|
+
}
|
|
2985
|
+
const username = authVersion === "v3" ? `${user}-${pcc}-${domain}` : `V1:${user}:${pcc}:${domain}`;
|
|
2986
|
+
if (authVersion === "v3") return { authVersion, clientId, clientSecret, username, password };
|
|
2987
|
+
return clientId === void 0 ? { authVersion, username, password } : { authVersion, clientId, clientSecret, username, password };
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2879
2990
|
// src/queue/host-date.ts
|
|
2880
2991
|
var MONTHS = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
|
|
2881
2992
|
var DAY_MS = 864e5;
|
|
@@ -3258,7 +3369,7 @@ function fareComponent(fareComponentId, component) {
|
|
|
3258
3369
|
}
|
|
3259
3370
|
|
|
3260
3371
|
// src/index.ts
|
|
3261
|
-
var VERSION = "0.
|
|
3372
|
+
var VERSION = "0.4.1";
|
|
3262
3373
|
export {
|
|
3263
3374
|
Sabre,
|
|
3264
3375
|
SabreBusinessError,
|
|
@@ -3270,6 +3381,7 @@ export {
|
|
|
3270
3381
|
SabreWarning,
|
|
3271
3382
|
VERSION,
|
|
3272
3383
|
buildBookingFlightDetails,
|
|
3384
|
+
buildCredentials,
|
|
3273
3385
|
buildRevalidateItineraryRequest,
|
|
3274
3386
|
buildSeatAvailabilityRequest,
|
|
3275
3387
|
parseQueueParagraph,
|