sabre-client 0.3.0-next.2 → 0.4.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/dist/index.js CHANGED
@@ -1491,14 +1491,16 @@ var TRANSIENT_CODES = /* @__PURE__ */ new Set([
1491
1491
  var PREVIOUS_ENTRY_ACTIVE_RETRIES = 3;
1492
1492
  var PREVIOUS_ENTRY_ACTIVE_DELAY_MS = 1e3;
1493
1493
  var PREVIOUS_ENTRY_ACTIVE = /PREVIOUS ENTRY ACTIVE/i;
1494
- var parser = new XMLParser({
1494
+ var PARSER_OPTIONS = {
1495
1495
  ignoreAttributes: false,
1496
1496
  attributeNamePrefix: "@",
1497
1497
  removeNSPrefix: true,
1498
1498
  parseTagValue: false,
1499
1499
  parseAttributeValue: false,
1500
1500
  processEntities: true
1501
- });
1501
+ };
1502
+ var parser = new XMLParser(PARSER_OPTIONS);
1503
+ var untrimmedParser = new XMLParser({ ...PARSER_OPTIONS, trimValues: false });
1502
1504
  var SoapTransport = class {
1503
1505
  #config;
1504
1506
  #log;
@@ -1738,11 +1740,11 @@ function scrubToken(text, security) {
1738
1740
  const token = security.binarySecurityToken;
1739
1741
  return text.split(token).join(REDACTED).split(xmlEscape(token)).join(REDACTED);
1740
1742
  }
1741
- function parseEnvelope(text) {
1743
+ function parseEnvelope(text, xmlParser = parser) {
1742
1744
  if (text.trim() === "") return void 0;
1743
1745
  let parsed;
1744
1746
  try {
1745
- parsed = parser.parse(text);
1747
+ parsed = xmlParser.parse(text);
1746
1748
  } catch {
1747
1749
  return void 0;
1748
1750
  }
@@ -1807,6 +1809,10 @@ function retryReason(error, idempotent) {
1807
1809
  if (error.status === void 0 || TRANSIENT_STATUS.has(error.status)) return "transient";
1808
1810
  return void 0;
1809
1811
  }
1812
+ function untrimmedPayload(raw) {
1813
+ const document = parseEnvelope(raw, untrimmedParser);
1814
+ return document === void 0 ? void 0 : asNode(firstElement(document.Body)[1]);
1815
+ }
1810
1816
  function firstElement(node2) {
1811
1817
  for (const [key, value] of Object.entries(node2)) {
1812
1818
  if (!key.startsWith("@") && key !== "#text") return [key, Array.isArray(value) ? value[0] : value];
@@ -2511,11 +2517,63 @@ function validateSelection(selection) {
2511
2517
  });
2512
2518
  }
2513
2519
 
2520
+ // src/session/command.ts
2521
+ var SABRE_COMMAND_SERVICE = "sabreCommand";
2522
+ var NAMESPACE5 = "http://webservices.sabre.com/sabreXML/2011/10";
2523
+ var MAX_COMMAND_LENGTH = 1023;
2524
+ var DOUBLE_WIDTH = /* @__PURE__ */ new Set(["!", "%", "$", "&", "\xA5", "\xA7"]);
2525
+ function fail4(message) {
2526
+ throw new TypeError(`sabre-client: ${message}`);
2527
+ }
2528
+ function sabreCommandPayload(command) {
2529
+ return `<SabreCommandLLSRQ xmlns="${NAMESPACE5}" Version="2.0.0" ReturnHostCommand="true"><Request Output="SCREEN"><HostCommand>${xmlEscape(command)}</HostCommand></Request></SabreCommandLLSRQ>`;
2530
+ }
2531
+ function commandToken(command) {
2532
+ return (command.trim().split(/[¥‡\s]/u)[0] ?? "").slice(0, 4);
2533
+ }
2534
+ function sabreCommandLength(command) {
2535
+ let length = 0;
2536
+ for (const char of command) length += DOUBLE_WIDTH.has(char) ? 2 : 1;
2537
+ return length;
2538
+ }
2539
+ function sabreCommandRequest(command, options) {
2540
+ if (typeof command !== "string" || command.trim() === "") fail4("command es requerido y debe ser un string no vac\xEDo");
2541
+ const length = sabreCommandLength(command);
2542
+ if (length > MAX_COMMAND_LENGTH) {
2543
+ fail4(`command mide ${length} y el m\xE1ximo es ${MAX_COMMAND_LENGTH} (!, %, $, &, \xA5 y \xA7 cuentan 2); Sabre responder\xEDa REQUEST EXCEEDED MAXIMUM LENGTH`);
2544
+ }
2545
+ const { idempotent = false, ...requestOptions } = options;
2546
+ if (typeof idempotent !== "boolean") fail4("idempotent debe ser un boolean");
2547
+ return {
2548
+ service: SABRE_COMMAND_SERVICE,
2549
+ action: "SabreCommandLLSRQ",
2550
+ payload: sabreCommandPayload(command),
2551
+ idempotent,
2552
+ operation: "command",
2553
+ ...requestOptions
2554
+ };
2555
+ }
2556
+ function parseSabreCommandResponse(response) {
2557
+ if (response.payloadName !== "SabreCommandLLSRS") {
2558
+ throw new SabreGatewayError(`${SABRE_COMMAND_SERVICE}: respuesta no reconocida (${response.payloadName})`, {
2559
+ protocol: "soap",
2560
+ status: response.status,
2561
+ retryable: false,
2562
+ raw: response.raw
2563
+ });
2564
+ }
2565
+ return {
2566
+ text: textOf((untrimmedPayload(response.raw) ?? response.payload).Response) ?? "",
2567
+ warnings: response.warnings,
2568
+ messageId: response.messageId
2569
+ };
2570
+ }
2571
+
2514
2572
  // src/session/session.ts
2515
2573
  var OTA_NAMESPACE = "http://www.opentravel.org/OTA/2002/11";
2516
2574
  var SWS_NAMESPACE = "http://webservices.sabre.com";
2517
2575
  var sessionSend = /* @__PURE__ */ Symbol("sabre-client.session.send");
2518
- function fail4(message) {
2576
+ function fail5(message) {
2519
2577
  throw new TypeError(`sabre-client: ${message}`);
2520
2578
  }
2521
2579
  var SESSION_ERROR_KINDS = /* @__PURE__ */ new Map([
@@ -2587,6 +2645,18 @@ var SabreSession = class {
2587
2645
  async ping(options = {}) {
2588
2646
  checkPingResponse(await this[sessionSend]({ ...pingRequest(), ...options }));
2589
2647
  }
2648
+ /**
2649
+ * Manda un comando de host (`SabreCommandLLSRQ` 2.0.0) y devuelve la pantalla como texto, sin
2650
+ * interpretarla. Es una vía de escape: sin tipos ni garantías, y un comando puede cambiar la
2651
+ * configuración del PCC o emitir. El comando va tal cual; el separador de Sabre Red (`‡`) se
2652
+ * escribe `¥`. Sin reintento salvo `{ idempotent: true }`, para comandos de solo lectura. La
2653
+ * pantalla llega sin recortar, y más de 1023 caracteres (contados como Sabre) lanza sin red.
2654
+ */
2655
+ async command(command, options = {}) {
2656
+ const request = sabreCommandRequest(command, options);
2657
+ this.#transport.log.emit("debug", "sabre.session.command", { command: commandToken(command) });
2658
+ return parseSabreCommandResponse(await this[sessionSend](request));
2659
+ }
2590
2660
  /**
2591
2661
  * Cierra la sesión con `SessionCloseRQ`: Sabre descarta el AAA e invalida el token. Es
2592
2662
  * idempotente: las llamadas siguientes devuelven la misma promesa, sin volver a la red. Si el
@@ -2614,11 +2684,11 @@ var SabreSession = class {
2614
2684
  return this.#send(request);
2615
2685
  }
2616
2686
  async #send(request) {
2617
- if (this.#closing !== void 0) fail4("session closed");
2687
+ if (this.#closing !== void 0) fail5("session closed");
2618
2688
  if (this.#unusable !== void 0) {
2619
2689
  throw new TypeError(`sabre-client: session unusable (${this.#unusable.kind})`, { cause: this.#unusable });
2620
2690
  }
2621
- if (this.#busy) fail4("session busy");
2691
+ if (this.#busy) fail5("session busy");
2622
2692
  this.#busy = true;
2623
2693
  try {
2624
2694
  const response = await this.#transport.request({ ...request, ...this.#envelopeFields() });
@@ -2773,7 +2843,7 @@ function usernameToken(credentials) {
2773
2843
  }
2774
2844
  async function openSession(transport, credentials, options = {}) {
2775
2845
  const { keepAlive = false, ...requestOptions } = options;
2776
- if (typeof keepAlive !== "boolean") fail4("keepAlive debe ser un boolean");
2846
+ if (typeof keepAlive !== "boolean") fail5("keepAlive debe ser un boolean");
2777
2847
  const conversationId = globalThis.crypto.randomUUID();
2778
2848
  const request = {
2779
2849
  service: "sessionCreate",
@@ -2876,6 +2946,38 @@ var SabreLegacy = class {
2876
2946
  }
2877
2947
  };
2878
2948
 
2949
+ // src/auth/credentials.ts
2950
+ var DEFAULT_DOMAIN = "AA";
2951
+ function fail6(message) {
2952
+ throw new TypeError(`sabre-client: ${message}`);
2953
+ }
2954
+ function requireString2(value, field) {
2955
+ if (typeof value !== "string" || value.trim() === "") fail6(`${field} es requerido y debe ser un string no vac\xEDo`);
2956
+ }
2957
+ function buildCredentials(options) {
2958
+ if (typeof options !== "object" || options === null) fail6("buildCredentials recibe un objeto");
2959
+ const authVersion = options.authVersion ?? "v3";
2960
+ if (authVersion !== "v3" && authVersion !== "v2") {
2961
+ fail6(`authVersion debe ser 'v3' o 'v2' (recibido: ${JSON.stringify(authVersion)})`);
2962
+ }
2963
+ const { user, pcc, password, clientId, clientSecret } = options;
2964
+ const domain = options.domain ?? DEFAULT_DOMAIN;
2965
+ requireString2(user, "user");
2966
+ requireString2(password, "password");
2967
+ if (!isPseudoCityCode(pcc)) fail6("pcc debe tener 3 o 4 caracteres alfanum\xE9ricos");
2968
+ requireString2(domain, "domain");
2969
+ if (/[-:]/.test(domain)) fail6("domain no puede contener '-' ni ':'");
2970
+ if (clientId === void 0 !== (clientSecret === void 0)) {
2971
+ fail6("clientId y clientSecret van juntos: pasa los dos o ninguno");
2972
+ }
2973
+ if (authVersion === "v3" || clientId !== void 0) {
2974
+ requireString2(clientId, authVersion === "v3" ? "clientId (requerido con authVersion v3)" : "clientId");
2975
+ requireString2(clientSecret, authVersion === "v3" ? "clientSecret (requerido con authVersion v3)" : "clientSecret");
2976
+ }
2977
+ const username = authVersion === "v3" ? `${user}-${pcc}-${domain}` : `V1:${user}:${pcc}:${domain}`;
2978
+ return clientId === void 0 ? { username, password } : { clientId, clientSecret, username, password };
2979
+ }
2980
+
2879
2981
  // src/queue/host-date.ts
2880
2982
  var MONTHS = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
2881
2983
  var DAY_MS = 864e5;
@@ -3258,7 +3360,7 @@ function fareComponent(fareComponentId, component) {
3258
3360
  }
3259
3361
 
3260
3362
  // src/index.ts
3261
- var VERSION = "0.3.0";
3363
+ var VERSION = "0.4.0";
3262
3364
  export {
3263
3365
  Sabre,
3264
3366
  SabreBusinessError,
@@ -3270,6 +3372,7 @@ export {
3270
3372
  SabreWarning,
3271
3373
  VERSION,
3272
3374
  buildBookingFlightDetails,
3375
+ buildCredentials,
3273
3376
  buildRevalidateItineraryRequest,
3274
3377
  buildSeatAvailabilityRequest,
3275
3378
  parseQueueParagraph,