sabre-client 0.3.0 → 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/README.es.md +86 -11
- package/README.md +83 -11
- package/dist/index.d.ts +68 -3
- package/dist/index.js +112 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.es.md
CHANGED
|
@@ -12,8 +12,9 @@ SDK de TypeScript para las APIs de Sabre.
|
|
|
12
12
|
|
|
13
13
|
## Estado
|
|
14
14
|
|
|
15
|
-
`0.
|
|
16
|
-
|
|
15
|
+
`0.4.0` es la versión actual de la reescritura. Cubre el Tier 1 y el Tier 2 del flujo de Air
|
|
16
|
+
Booking por REST, y el Tier 3 (sesiones SOAP y colas). Sobre la `0.3.0` agrega `buildCredentials`
|
|
17
|
+
y los comandos de host (`session.command`), sin cambios incompatibles.
|
|
17
18
|
|
|
18
19
|
```sh
|
|
19
20
|
npm install sabre-client
|
|
@@ -46,8 +47,9 @@ El Tier 3 agrega el cliente SOAP `SabreLegacy`, con una sesión stateful y Queue
|
|
|
46
47
|
| Sesión | `SessionCreateRQ` 2.0.0 / 1.0.0, `OTA_PingRQ`, `SessionCloseRQ` | `legacy.withSession`, `legacy.openSession` |
|
|
47
48
|
| Colas | Get Queue Count, Access Queue, Place Queue Message | `session.queue.count`, `list`, `access`, `ignore`, `remove`, `place`, `exit`, `process` |
|
|
48
49
|
| Párrafo de cola | — (función pura) | `parseQueueParagraph`, `resolveHostDate` |
|
|
50
|
+
| Comandos de host | `SabreCommandLLSRQ` 2.0.0 | `session.command` |
|
|
49
51
|
|
|
50
|
-
Planeado después de `0.
|
|
52
|
+
Planeado después de `0.4.0`: Get Ancillaries (solo NDC).
|
|
51
53
|
|
|
52
54
|
## Requisitos
|
|
53
55
|
|
|
@@ -70,8 +72,8 @@ const sabre = new Sabre({
|
|
|
70
72
|
credentials: {
|
|
71
73
|
clientId: process.env.SABRE_CLIENT_ID!,
|
|
72
74
|
clientSecret: process.env.SABRE_CLIENT_SECRET!,
|
|
73
|
-
username:
|
|
74
|
-
password: process.env.
|
|
75
|
+
username: '<user>-<PCC>-AA', // usuario EPR ya armado (ver Credenciales abajo)
|
|
76
|
+
password: process.env.SABRE_PASSWORD!,
|
|
75
77
|
},
|
|
76
78
|
});
|
|
77
79
|
```
|
|
@@ -86,6 +88,37 @@ const sabre = new Sabre({
|
|
|
86
88
|
| `fetch` | `fetch` global | `fetch` propio (proxies, tests, otros runtimes) |
|
|
87
89
|
| `tokenStore` | solo en memoria | Comparte el token entre procesos (ver [Reutilizar el token](#reutilizar-el-token)) |
|
|
88
90
|
|
|
91
|
+
### Credenciales
|
|
92
|
+
|
|
93
|
+
Hay **un solo juego de credenciales** para REST y SOAP: el mismo objeto `credentials` sirve para
|
|
94
|
+
`Sabre` y para `SabreLegacy`. Trae el usuario EPR, su PCC y su domain, el password del EPR y, de
|
|
95
|
+
forma opcional, el `clientId`/`clientSecret` de la app. La versión de OAuth solo cambia cómo se
|
|
96
|
+
escribe el `username` del EPR: `<user>-<PCC>-<domain>` (v3) o `V1:<user>:<PCC>:<domain>` (v2).
|
|
97
|
+
|
|
98
|
+
`buildCredentials` arma ese objeto a partir de las partes, así que no necesitas conocer ninguno de
|
|
99
|
+
los dos formatos. No lee variables de entorno y lanza un `TypeError` si algo no es válido:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { Sabre, SabreLegacy, buildCredentials } from 'sabre-client';
|
|
103
|
+
|
|
104
|
+
const credentials = buildCredentials({
|
|
105
|
+
authVersion: 'v3', // por defecto; 'v2' → `V1:<user>:<PCC>:<domain>`
|
|
106
|
+
user: process.env.SABRE_USERNAME!, // solo el usuario EPR
|
|
107
|
+
pcc: process.env.SABRE_PCC!, // el PCC al que pertenece el EPR
|
|
108
|
+
domain: process.env.SABRE_DOMAIN || undefined, // 'AA' por defecto
|
|
109
|
+
password: process.env.SABRE_PASSWORD!,
|
|
110
|
+
clientId: process.env.SABRE_CLIENT_ID!, // obligatorio con v3; con v2, los dos o ninguno
|
|
111
|
+
clientSecret: process.env.SABRE_CLIENT_SECRET!,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const sabre = new Sabre({ environment: 'sandbox', credentials });
|
|
115
|
+
const legacy = new SabreLegacy({ environment: 'sandbox', credentials });
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
El PCC de las credenciales es con el que te autenticas. Trabajar en otro PCC no requiere otro
|
|
119
|
+
juego de credenciales: se pasa en cada operación, como `targetPcc` en `booking.create`, `get` y
|
|
120
|
+
`cancel`, o como `pcc` en las operaciones de colas.
|
|
121
|
+
|
|
89
122
|
### Autenticación
|
|
90
123
|
|
|
91
124
|
Nunca pides el token tú. El cliente lo obtiene en la primera llamada, lo cachea, lo renueva
|
|
@@ -256,9 +289,12 @@ A tener en cuenta:
|
|
|
256
289
|
que llega como `SabreGatewayError`.
|
|
257
290
|
- **Qué está comprobado.** `byReservationPayload` se probó en producción, en solo lectura, con
|
|
258
291
|
un vuelo directo y una conexión. La reserva con asiento se probó en sandbox: el asiento llega
|
|
259
|
-
al PNR y `sabre.booking.get` lo devuelve en `flights[].seats[]`. `byPnrLocator`
|
|
260
|
-
|
|
261
|
-
|
|
292
|
+
al PNR y `sabre.booking.get` lo devuelve en `flights[].seats[]`. `byPnrLocator` se probó en
|
|
293
|
+
sandbox con una reserva hecha en el PCC con el que te autenticas: devuelve el mapa de los
|
|
294
|
+
segmentos del PNR, con ids propios de Sabre para pasajero y segmento (`pnr_pax_1`,
|
|
295
|
+
`segment_ancs_1`) en lugar de `P1` y `S1`. En sandbox ninguno de los dos endpoints refleja la
|
|
296
|
+
ocupación: todos los asientos vienen en `F`, también el que tiene el PNR. Los precios llegan
|
|
297
|
+
como número aunque el spec diga string, así que los tipos aceptan los dos.
|
|
262
298
|
|
|
263
299
|
## Sesiones SOAP y colas (`SabreLegacy`)
|
|
264
300
|
|
|
@@ -275,7 +311,7 @@ import { SabreLegacy } from 'sabre-client';
|
|
|
275
311
|
const legacy = new SabreLegacy({
|
|
276
312
|
environment: 'production',
|
|
277
313
|
authVersion: 'v2',
|
|
278
|
-
credentials: { username: 'V1:<user>:<PCC>:AA', password: process.env.
|
|
314
|
+
credentials: { username: 'V1:<user>:<PCC>:AA', password: process.env.SABRE_PASSWORD! },
|
|
279
315
|
});
|
|
280
316
|
|
|
281
317
|
const counts = await legacy.withSession((session) => session.queue.count());
|
|
@@ -302,6 +338,45 @@ const counts = await legacy.withSession((session) => session.queue.count());
|
|
|
302
338
|
`session unusable` sin red, y `close()` resuelve sin red. El SDK no la reabre, porque el cursor
|
|
303
339
|
de la cola ya se perdió. Volver a empezar es decisión tuya.
|
|
304
340
|
|
|
341
|
+
### Comandos de host
|
|
342
|
+
|
|
343
|
+
`session.command(command, options?)` manda un comando de host de Sabre (`SabreCommandLLSRQ` 2.0.0)
|
|
344
|
+
dentro de la sesión y devuelve la pantalla como texto. **Es una vía de escape**, sin tipos ni
|
|
345
|
+
garantías: el SDK no interpreta la pantalla, y un comando puede cambiar la configuración del PCC o
|
|
346
|
+
emitir boletos. El SDK no tiene una lista de comandos permitidos o bloqueados.
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
const screen = await legacy.withSession((session) => session.command('W/PT¥*', { idempotent: true }));
|
|
350
|
+
// screen.text → 'PSGR TYPE - ON' (como lo manda Sabre, sin recortar)
|
|
351
|
+
// screen.warnings → los stl:Warning, si los hay
|
|
352
|
+
// screen.messageId → eb:MessageId, para soporte de Sabre
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
- **El comando va tal cual.** En este servicio, el separador `‡` de Sabre se escribe `¥`
|
|
356
|
+
(`W/PT‡*` en Sabre Red es `W/PT¥*` aquí); el SDK no lo convierte.
|
|
357
|
+
- **La pantalla no se recorta.** Las pantallas del host van alineadas por columna y pueden empezar
|
|
358
|
+
con un espacio (` 17MAR WED…`), así que `text` es el contenido de `Response` tal cual. Si
|
|
359
|
+
necesitas recortarla, hazlo tú.
|
|
360
|
+
- **Hasta 1023 caracteres, contados como Sabre:** `!`, `%`, `$`, `&`, `¥` y `§` cuentan 2. Un
|
|
361
|
+
comando más largo lanza un `TypeError` sin llamar a la red (Sabre respondería
|
|
362
|
+
`REQUEST EXCEEDED MAXIMUM LENGTH`).
|
|
363
|
+
- **Sabre bloquea algunos comandos por su cuenta** (`SO`, `/@`, `¤¤`, según su whitelist), aunque
|
|
364
|
+
el SDK no tenga una lista propia.
|
|
365
|
+
- **Algunos comandos cuestan más.** `JR` y `WPNI` cuentan como búsquedas "Worldfare".
|
|
366
|
+
- **Las máscaras del host** se responden con los dos primeros caracteres de la máscara.
|
|
367
|
+
- **Prefiere una API estructurada.** Sabre pide no usar este servicio para lo que ya tiene una:
|
|
368
|
+
si el SDK tiene una operación para eso, úsala.
|
|
369
|
+
- **Sin reintentos por defecto**, porque el SDK no distingue una lectura de un cambio. Pasa
|
|
370
|
+
`{ idempotent: true }` en los comandos de solo lectura y siguen la
|
|
371
|
+
[política de reintentos](#reintentos).
|
|
372
|
+
- **Un mensaje del host no es un error.** `DUTY CODE 9 REQUIRED` o `FORMAT` llegan en `text`. Un
|
|
373
|
+
SOAP fault o un `stl:Error` lanzan, como en cualquier otra llamada de la sesión.
|
|
374
|
+
- Los comandos que dependen de la misma firma (`SI9` y luego `W/PT¥ON`) tienen que ir en la misma
|
|
375
|
+
sesión.
|
|
376
|
+
- El log de debug (`sabre.session.command`) lleva solo el primer token del comando, cortado a 4
|
|
377
|
+
caracteres (`W/PT`, `DC`, `*IM`), nunca el comando entero ni la pantalla. La respuesta completa
|
|
378
|
+
está disponible en `onRawResponse`.
|
|
379
|
+
|
|
305
380
|
### Colas
|
|
306
381
|
|
|
307
382
|
Las operaciones de cola están en `session.queue`. `pcc` es opcional en cada llamada, y por defecto
|
|
@@ -490,7 +565,7 @@ const booking = await sabre.booking.get({ confirmationId }, { onRawResponse: (r)
|
|
|
490
565
|
conserva el valor y escríbelo cuando la llamada vuelva.
|
|
491
566
|
|
|
492
567
|
En `SabreLegacy`, `protocol` es `'soap'`, `body` es el sobre SOAP completo, y `operation` es
|
|
493
|
-
`'openSession'`, `'close'`, `'ping'` o `'queue.<método>'`. El token de la sesión
|
|
568
|
+
`'openSession'`, `'close'`, `'ping'`, `'command'` o `'queue.<método>'`. El token de la sesión
|
|
494
569
|
(`BinarySecurityToken`) se reemplaza por `[REDACTED]` donde aparezca. No se cambia nada más.
|
|
495
570
|
|
|
496
571
|
> [!WARNING]
|
|
@@ -555,7 +630,7 @@ El SDK registra eventos estructurados (`sabre.request.start`, `sabre.request.end
|
|
|
555
630
|
`sabre.request.retry`, `sabre.request.rawResponseHookFailed`, `sabre.auth.token.refresh`,
|
|
556
631
|
`sabre.auth.token.store`, `sabre.booking.seat.priced`, `sabre.session.open`,
|
|
557
632
|
`sabre.session.close`, `sabre.session.closeFailed`, `sabre.session.keepAliveFailed`,
|
|
558
|
-
`sabre.queue.exitTolerated`, `sabre.queue.exitFailed`) y nunca los bodies
|
|
633
|
+
`sabre.session.command`, `sabre.queue.exitTolerated`, `sabre.queue.exitFailed`) y nunca los bodies
|
|
559
634
|
crudos de request o response. Los secretos, el header `Authorization` y los datos de pasajeros
|
|
560
635
|
se redactan siempre antes de llegar a tu logger.
|
|
561
636
|
|
package/README.md
CHANGED
|
@@ -12,8 +12,9 @@ TypeScript SDK for the Sabre APIs.
|
|
|
12
12
|
|
|
13
13
|
## Status
|
|
14
14
|
|
|
15
|
-
`0.
|
|
16
|
-
flow over REST, and Tier 3 (SOAP sessions and queues).
|
|
15
|
+
`0.4.0` is the current release of the rewrite. It covers Tier 1 and Tier 2 of the Air Booking
|
|
16
|
+
flow over REST, and Tier 3 (SOAP sessions and queues). It adds `buildCredentials` and host
|
|
17
|
+
commands (`session.command`) to `0.3.0`, with no breaking changes.
|
|
17
18
|
|
|
18
19
|
```sh
|
|
19
20
|
npm install sabre-client
|
|
@@ -46,8 +47,9 @@ Tier 3 adds the `SabreLegacy` SOAP client, with a stateful session and Queue Man
|
|
|
46
47
|
| Session | `SessionCreateRQ` 2.0.0 / 1.0.0, `OTA_PingRQ`, `SessionCloseRQ` | `legacy.withSession`, `legacy.openSession` |
|
|
47
48
|
| Queues | Get Queue Count, Access Queue, Place Queue Message | `session.queue.count`, `list`, `access`, `ignore`, `remove`, `place`, `exit`, `process` |
|
|
48
49
|
| Queue paragraph | — (pure function) | `parseQueueParagraph`, `resolveHostDate` |
|
|
50
|
+
| Host commands | `SabreCommandLLSRQ` 2.0.0 | `session.command` |
|
|
49
51
|
|
|
50
|
-
Planned after `0.
|
|
52
|
+
Planned after `0.4.0`: Get Ancillaries (NDC only).
|
|
51
53
|
|
|
52
54
|
## Requirements
|
|
53
55
|
|
|
@@ -70,8 +72,8 @@ const sabre = new Sabre({
|
|
|
70
72
|
credentials: {
|
|
71
73
|
clientId: process.env.SABRE_CLIENT_ID!,
|
|
72
74
|
clientSecret: process.env.SABRE_CLIENT_SECRET!,
|
|
73
|
-
username:
|
|
74
|
-
password: process.env.
|
|
75
|
+
username: '<user>-<PCC>-AA', // EPR username, already built (see Credentials below)
|
|
76
|
+
password: process.env.SABRE_PASSWORD!,
|
|
75
77
|
},
|
|
76
78
|
});
|
|
77
79
|
```
|
|
@@ -86,6 +88,37 @@ const sabre = new Sabre({
|
|
|
86
88
|
| `fetch` | global `fetch` | Custom `fetch` (proxies, tests, other runtimes) |
|
|
87
89
|
| `tokenStore` | in-memory only | Shares the token between processes (see [Token reuse](#token-reuse)) |
|
|
88
90
|
|
|
91
|
+
### Credentials
|
|
92
|
+
|
|
93
|
+
There is **one set of credentials** for REST and SOAP: the same `credentials` object works for
|
|
94
|
+
`Sabre` and for `SabreLegacy`. It holds the EPR user, its PCC and domain, the EPR password and,
|
|
95
|
+
optionally, the app's `clientId`/`clientSecret`. The OAuth version only changes how the EPR
|
|
96
|
+
`username` is written: `<user>-<PCC>-<domain>` (v3) or `V1:<user>:<PCC>:<domain>` (v2).
|
|
97
|
+
|
|
98
|
+
`buildCredentials` builds that object from the parts, so you don't have to know either format.
|
|
99
|
+
It does not read environment variables and throws a `TypeError` on invalid input:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { Sabre, SabreLegacy, buildCredentials } from 'sabre-client';
|
|
103
|
+
|
|
104
|
+
const credentials = buildCredentials({
|
|
105
|
+
authVersion: 'v3', // default; 'v2' → `V1:<user>:<PCC>:<domain>`
|
|
106
|
+
user: process.env.SABRE_USERNAME!, // the EPR user alone
|
|
107
|
+
pcc: process.env.SABRE_PCC!, // the PCC the EPR belongs to
|
|
108
|
+
domain: process.env.SABRE_DOMAIN || undefined, // 'AA' by default
|
|
109
|
+
password: process.env.SABRE_PASSWORD!,
|
|
110
|
+
clientId: process.env.SABRE_CLIENT_ID!, // required with v3; with v2, both or neither
|
|
111
|
+
clientSecret: process.env.SABRE_CLIENT_SECRET!,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const sabre = new Sabre({ environment: 'sandbox', credentials });
|
|
115
|
+
const legacy = new SabreLegacy({ environment: 'sandbox', credentials });
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The PCC in the credentials is the one you authenticate with. Working in another PCC is not a
|
|
119
|
+
second set of credentials: pass it per operation, as `targetPcc` in `booking.create`, `get` and
|
|
120
|
+
`cancel`, or as `pcc` in the queue operations.
|
|
121
|
+
|
|
89
122
|
### Authentication
|
|
90
123
|
|
|
91
124
|
You never request a token yourself. The client gets one on the first call, caches it, renews
|
|
@@ -255,9 +288,12 @@ Things to know:
|
|
|
255
288
|
which surfaces as a `SabreGatewayError`.
|
|
256
289
|
- **What has been checked.** `byReservationPayload` was checked read-only in production, for
|
|
257
290
|
a nonstop and a connection. Booking with a seat was checked in sandbox: the seat reaches the
|
|
258
|
-
PNR and `sabre.booking.get` returns it in `flights[].seats[]`. `byPnrLocator`
|
|
259
|
-
|
|
260
|
-
|
|
291
|
+
PNR and `sabre.booking.get` returns it in `flights[].seats[]`. `byPnrLocator` was checked in
|
|
292
|
+
sandbox with a booking made in the PCC you authenticate with: it returns the map for the PNR's
|
|
293
|
+
segments, with Sabre's own passenger and segment ids (`pnr_pax_1`, `segment_ancs_1`) instead
|
|
294
|
+
of `P1` and `S1`. In sandbox neither endpoint reflects occupancy: every seat comes back as
|
|
295
|
+
`F`, including the one the PNR holds. Prices arrive as numbers even though the spec says
|
|
296
|
+
strings, so the types accept both.
|
|
261
297
|
|
|
262
298
|
## SOAP sessions and queues (`SabreLegacy`)
|
|
263
299
|
|
|
@@ -273,7 +309,7 @@ import { SabreLegacy } from 'sabre-client';
|
|
|
273
309
|
const legacy = new SabreLegacy({
|
|
274
310
|
environment: 'production',
|
|
275
311
|
authVersion: 'v2',
|
|
276
|
-
credentials: { username: 'V1:<user>:<PCC>:AA', password: process.env.
|
|
312
|
+
credentials: { username: 'V1:<user>:<PCC>:AA', password: process.env.SABRE_PASSWORD! },
|
|
277
313
|
});
|
|
278
314
|
|
|
279
315
|
const counts = await legacy.withSession((session) => session.queue.count());
|
|
@@ -301,6 +337,42 @@ const counts = await legacy.withSession((session) => session.queue.count());
|
|
|
301
337
|
calling the network, and `close()` resolves without calling it. The SDK does not reopen the
|
|
302
338
|
session, because the queue cursor is already lost. Starting over is your decision.
|
|
303
339
|
|
|
340
|
+
### Host commands
|
|
341
|
+
|
|
342
|
+
`session.command(command, options?)` sends a Sabre host command (`SabreCommandLLSRQ` 2.0.0)
|
|
343
|
+
inside the session and returns the screen as text. **It is an escape hatch**, with no types and
|
|
344
|
+
no guarantees: the SDK does not parse the screen, and a command can change the PCC configuration
|
|
345
|
+
or issue tickets. There is no list of allowed or blocked commands.
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
const screen = await legacy.withSession((session) => session.command('W/PT¥*', { idempotent: true }));
|
|
349
|
+
// screen.text → 'PSGR TYPE - ON' (as Sabre sends it, not trimmed)
|
|
350
|
+
// screen.warnings → the stl:Warning entries, if any
|
|
351
|
+
// screen.messageId → eb:MessageId, for Sabre support
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
- **The command is sent as is.** In this service Sabre's `‡` separator is written `¥`
|
|
355
|
+
(`W/PT‡*` in Sabre Red is `W/PT¥*` here); the SDK does not convert it.
|
|
356
|
+
- **The screen is not trimmed.** Host screens are aligned by column and can start with a space
|
|
357
|
+
(` 17MAR WED…`), so `text` is the content of `Response` as is. Trim it yourself if you need to.
|
|
358
|
+
- **Up to 1023 characters, counted as Sabre does:** `!`, `%`, `$`, `&`, `¥` and `§` count 2. A
|
|
359
|
+
longer command throws a `TypeError` without calling the network (Sabre would answer
|
|
360
|
+
`REQUEST EXCEEDED MAXIMUM LENGTH`).
|
|
361
|
+
- **Sabre blocks some commands on its own** (`SO`, `/@`, `¤¤`, per its whitelist), even though
|
|
362
|
+
the SDK has no list of its own.
|
|
363
|
+
- **Some commands cost more.** `JR` and `WPNI` count as "Worldfare" searches.
|
|
364
|
+
- **Host masks** are answered with the first two characters of the mask.
|
|
365
|
+
- **Prefer a structured API.** Sabre asks not to use this service for what already has one: when
|
|
366
|
+
the SDK has an operation for it, use that.
|
|
367
|
+
- **No retries by default**, because the SDK cannot tell a read from a change. Pass
|
|
368
|
+
`{ idempotent: true }` for read-only commands and they follow the [retry policy](#retries).
|
|
369
|
+
- **A host message is not an error.** `DUTY CODE 9 REQUIRED` or `FORMAT` come back in `text`. A
|
|
370
|
+
SOAP fault or an `stl:Error` throws, like any other session call.
|
|
371
|
+
- Commands that need the same sign-in (`SI9` followed by `W/PT¥ON`) must run in the same session.
|
|
372
|
+
- The debug log (`sabre.session.command`) carries only the first token of the command, cut to 4
|
|
373
|
+
characters (`W/PT`, `DC`, `*IM`), never the whole command or the screen. The full response is
|
|
374
|
+
available through `onRawResponse`.
|
|
375
|
+
|
|
304
376
|
### Queues
|
|
305
377
|
|
|
306
378
|
The queue operations live in `session.queue`. `pcc` is optional in every call and defaults to the
|
|
@@ -486,7 +558,7 @@ const booking = await sabre.booking.get({ confirmationId }, { onRawResponse: (r)
|
|
|
486
558
|
asynchronously, keep the value and write it after the call returns.
|
|
487
559
|
|
|
488
560
|
In `SabreLegacy`, `protocol` is `'soap'`, `body` is the whole SOAP envelope, and `operation` is
|
|
489
|
-
`'openSession'`, `'close'`, `'ping'` or `'queue.<method>'`. The session token
|
|
561
|
+
`'openSession'`, `'close'`, `'ping'`, `'command'` or `'queue.<method>'`. The session token
|
|
490
562
|
(`BinarySecurityToken`) is replaced by `[REDACTED]` wherever it appears. Nothing else is changed.
|
|
491
563
|
|
|
492
564
|
> [!WARNING]
|
|
@@ -552,7 +624,7 @@ The SDK logs structured events (`sabre.request.start`, `sabre.request.end`,
|
|
|
552
624
|
`sabre.request.retry`, `sabre.request.rawResponseHookFailed`, `sabre.auth.token.refresh`,
|
|
553
625
|
`sabre.auth.token.store`, `sabre.booking.seat.priced`, `sabre.session.open`,
|
|
554
626
|
`sabre.session.close`, `sabre.session.closeFailed`, `sabre.session.keepAliveFailed`,
|
|
555
|
-
`sabre.queue.exitTolerated`, `sabre.queue.exitFailed`), never raw
|
|
627
|
+
`sabre.session.command`, `sabre.queue.exitTolerated`, `sabre.queue.exitFailed`), never raw
|
|
556
628
|
request or response bodies. Secrets, the `Authorization` header and passenger data are always
|
|
557
629
|
redacted before they reach your logger.
|
|
558
630
|
|
package/dist/index.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ interface SabreLogger {
|
|
|
41
41
|
}
|
|
42
42
|
type SabreLogLevel = keyof SabreLogger;
|
|
43
43
|
/** Eventos estables que emite el SDK como `message`. */
|
|
44
|
-
type SabreLogEvent = 'sabre.request.start' | 'sabre.request.end' | 'sabre.request.retry' | 'sabre.request.rawResponseHookFailed' | 'sabre.auth.token.refresh' | 'sabre.auth.token.store' | 'sabre.booking.seat.priced' | 'sabre.session.open' | 'sabre.session.close' | 'sabre.session.closeFailed' | 'sabre.session.keepAliveFailed' | 'sabre.queue.exitTolerated' | 'sabre.queue.exitFailed';
|
|
44
|
+
type SabreLogEvent = 'sabre.request.start' | 'sabre.request.end' | 'sabre.request.retry' | 'sabre.request.rawResponseHookFailed' | 'sabre.auth.token.refresh' | 'sabre.auth.token.store' | 'sabre.booking.seat.priced' | 'sabre.session.open' | 'sabre.session.close' | 'sabre.session.closeFailed' | 'sabre.session.keepAliveFailed' | 'sabre.session.command' | 'sabre.queue.exitTolerated' | 'sabre.queue.exitFailed';
|
|
45
45
|
/** Logger interno: redacta siempre el contexto y no hace nada si no hay logger. */
|
|
46
46
|
declare class EventLogger {
|
|
47
47
|
#private;
|
|
@@ -1893,6 +1893,27 @@ declare class QueueManagement {
|
|
|
1893
1893
|
place(request: QueuePlaceRequest, options?: QueuePlaceOptions): Promise<QueuePlaceResponse>;
|
|
1894
1894
|
}
|
|
1895
1895
|
|
|
1896
|
+
interface SabreCommandOptions extends RequestOptions {
|
|
1897
|
+
/**
|
|
1898
|
+
* `true` solo si el comando es una lectura (`W/PT¥*`, `DC¥…`): entra en la política de
|
|
1899
|
+
* reintentos. Por defecto `false`, porque el SDK no sabe si un comando cambia algo.
|
|
1900
|
+
*/
|
|
1901
|
+
idempotent?: boolean;
|
|
1902
|
+
}
|
|
1903
|
+
interface SabreCommandResponse {
|
|
1904
|
+
/**
|
|
1905
|
+
* La pantalla tal como la devuelve Sabre (`Response`), sin interpretar y sin recortar: las
|
|
1906
|
+
* pantallas van alineadas por columna y pueden empezar con un espacio (` 17MAR WED…`). Recortar
|
|
1907
|
+
* es decisión de quien la lee. Un mensaje de host (`DUTY CODE 9 REQUIRED`, `FORMAT`) llega aquí:
|
|
1908
|
+
* no lanza.
|
|
1909
|
+
*/
|
|
1910
|
+
text: string;
|
|
1911
|
+
/** Los `stl:Warning` de la respuesta. No lanzan. */
|
|
1912
|
+
warnings: SabreWarning[];
|
|
1913
|
+
/** `eb:MessageId` de la respuesta, el dato que pide soporte de Sabre. */
|
|
1914
|
+
messageId: string | undefined;
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1896
1917
|
/** Lo que una operación pide al transporte. La sesión agrega `CPAId`, `ConversationId` y el token. */
|
|
1897
1918
|
type SessionRequest = Omit<SoapRequest, 'cpaId' | 'conversationId' | 'security'>;
|
|
1898
1919
|
/**
|
|
@@ -1937,6 +1958,14 @@ declare class SabreSession {
|
|
|
1937
1958
|
* recorrido de cola no hace falta: cada operación ya lo reinicia.
|
|
1938
1959
|
*/
|
|
1939
1960
|
ping(options?: RequestOptions): Promise<void>;
|
|
1961
|
+
/**
|
|
1962
|
+
* Manda un comando de host (`SabreCommandLLSRQ` 2.0.0) y devuelve la pantalla como texto, sin
|
|
1963
|
+
* interpretarla. Es una vía de escape: sin tipos ni garantías, y un comando puede cambiar la
|
|
1964
|
+
* configuración del PCC o emitir. El comando va tal cual; el separador de Sabre Red (`‡`) se
|
|
1965
|
+
* escribe `¥`. Sin reintento salvo `{ idempotent: true }`, para comandos de solo lectura. La
|
|
1966
|
+
* pantalla llega sin recortar, y más de 1023 caracteres (contados como Sabre) lanza sin red.
|
|
1967
|
+
*/
|
|
1968
|
+
command(command: string, options?: SabreCommandOptions): Promise<SabreCommandResponse>;
|
|
1940
1969
|
/**
|
|
1941
1970
|
* Cierra la sesión con `SessionCloseRQ`: Sabre descarta el AAA e invalida el token. Es
|
|
1942
1971
|
* idempotente: las llamadas siguientes devuelven la misma promesa, sin volver a la red. Si el
|
|
@@ -1975,6 +2004,42 @@ declare class SabreLegacy {
|
|
|
1975
2004
|
withSession<T>(callback: (session: SabreSession) => Promise<T>): Promise<T>;
|
|
1976
2005
|
}
|
|
1977
2006
|
|
|
2007
|
+
interface BuildCredentialsBase {
|
|
2008
|
+
/** Usuario EPR, sin PCC ni domain. */
|
|
2009
|
+
user: string;
|
|
2010
|
+
/** PCC del EPR (el *group*): 3 o 4 caracteres alfanuméricos. */
|
|
2011
|
+
pcc: string;
|
|
2012
|
+
/** Domain del EPR. Default `'AA'`. */
|
|
2013
|
+
domain?: string;
|
|
2014
|
+
/** Contraseña EPR. */
|
|
2015
|
+
password: string;
|
|
2016
|
+
}
|
|
2017
|
+
/** Partes para OAuth Token Create v3 (default): `clientId` y `clientSecret` son obligatorios. */
|
|
2018
|
+
interface BuildCredentialsOptionsV3 extends BuildCredentialsBase {
|
|
2019
|
+
authVersion?: 'v3';
|
|
2020
|
+
clientId: string;
|
|
2021
|
+
clientSecret: string;
|
|
2022
|
+
}
|
|
2023
|
+
/**
|
|
2024
|
+
* Partes para OAuth Token Create v2: `clientId`/`clientSecret` son opcionales. REST los ignora,
|
|
2025
|
+
* pero si vienen los dos, `SabreLegacy` abre la sesión con `SessionCreateRQ` 2.0.0.
|
|
2026
|
+
*/
|
|
2027
|
+
interface BuildCredentialsOptionsV2 extends BuildCredentialsBase {
|
|
2028
|
+
authVersion: 'v2';
|
|
2029
|
+
clientId?: string;
|
|
2030
|
+
clientSecret?: string;
|
|
2031
|
+
}
|
|
2032
|
+
type BuildCredentialsOptions = BuildCredentialsOptionsV3 | BuildCredentialsOptionsV2;
|
|
2033
|
+
/**
|
|
2034
|
+
* Arma el `credentials` para `Sabre` y `SabreLegacy` a partir del usuario, el PCC, el domain y
|
|
2035
|
+
* la contraseña del EPR, más `clientId`/`clientSecret` cuando los hay. El username queda como
|
|
2036
|
+
* `<user>-<PCC>-<domain>` (v3) o `V1:<user>:<PCC>:<domain>` (v2). No lee variables de entorno
|
|
2037
|
+
* ni toca la red; un dato inválido lanza `TypeError` sin repetir secretos.
|
|
2038
|
+
*/
|
|
2039
|
+
declare function buildCredentials(options: BuildCredentialsOptionsV3): SabreCredentials;
|
|
2040
|
+
declare function buildCredentials(options: BuildCredentialsOptionsV2): SabreCredentialsV2;
|
|
2041
|
+
declare function buildCredentials(options: BuildCredentialsOptions): SabreCredentials | SabreCredentialsV2;
|
|
2042
|
+
|
|
1978
2043
|
/**
|
|
1979
2044
|
* Cómo se elige el año cuando el texto no lo trae:
|
|
1980
2045
|
*
|
|
@@ -2219,6 +2284,6 @@ interface BookingFlightDetailsSource {
|
|
|
2219
2284
|
*/
|
|
2220
2285
|
declare function buildBookingFlightDetails(source: BookingFlightDetailsSource): FlightDetails;
|
|
2221
2286
|
|
|
2222
|
-
declare const VERSION = "0.
|
|
2287
|
+
declare const VERSION = "0.4.0";
|
|
2223
2288
|
|
|
2224
|
-
export { Air, type BaggageAllowanceDesc, type BaggageInformation, type BargainFinderMaxRequest, type BargainFinderMaxResponse, type BookContactInformation, type BookGenericSeat, type BookSeat, type BookTraveler, type Booking, type BookingAgency, type BookingFlightDetailsSource, type BookingItemReference, BookingManagement, type BookingResponseError, type BookingSeatSelection, type BookingSource, type BrandedFare, type CancelBookingErrorPolicy, type CancelBookingNotification, type CancelBookingOptions, type CancelBookingRequest, type CancelBookingResponse, type CancelSegmentReference, type CreateBookingErrorPolicy, type CreateBookingOptions, type CreateBookingRequest, type CreateBookingResponse, type CreateBookingResponseError, type CurrencyConversion, type Fare, type FareComponent, type FareComponentDesc, type FareComponentDescSegment, type FareComponentSegment, type FareComponentSegmentDetail, type FareMessage, type FlightDetails, type FlightStatusCode, type FlightTicketOperation, type FlightToBook, type GetBookingExtraFeatures, type GetBookingOptions, type GetBookingRequest, type GetBookingResponse, type GetBookingReturnOnly, type GetSeatsRequest, type GetSeatsResponse, type GroupedItineraryResponse, type HostDateDirection, type Itinerary, type ItineraryGroup, type ItineraryMessage, type Leg, type LegDesc, type LegDescription, type LegSchedule, type LegScheduleRef, type LowFareSearchFlight, type LowFareSearchLocation, type LowFareSearchOriginDestination, type LowFareSearchPassengerTypeQuantity, type LowFareSearchRequestorId, type LowFareSearchSource, type LowFareSearchTpaExtensions, type LowFareSearchTravelPreferences, type LowFareSearchTravelerInfoSummary, type ObFeeDesc, type OpenSessionOptions, type OtaAirLowFareSearchRq, type PassengerInfo, type PassengerPricing, type PassengerTotalFare, type PaymentMethod, type PriceComparison, type PriceComparisonType, type PricedSeat, type PricingDetails, type PricingInformation, type PricingQualifiers, type QueueAccessOptions, type QueueAccessRequest, type QueueCount, type QueueCountOptions, type QueueCountRequest, type QueueCountResponse, type QueueEndReason, type QueueItem, type QueueListEntry, type QueueListOptions, type QueueListRequest, type QueueListResponse, QueueManagement, type QueueNavigationOptions, type QueueParagraph, type QueueParagraphAirSegment, type QueueParagraphMessage, type QueueParagraphOptions, type QueueParagraphOtherSegment, type QueueParagraphPassenger, type QueueParagraphReceivedFrom, type QueueParagraphSegment, type QueueParagraphTicketing, type QueueParagraphTicketingEntry, type QueuePlaceOptions, type QueuePlaceRequest, type QueuePlaceResponse, type QueuePlaceResult, type QueuePlaceTarget, type QueuePlacement, type QueueProcessAction, type QueueProcessRequest, type QueueProcessResult, QueueRef, type QueueRemoveRequest, type QueueRemoveTarget, type QueueSelection, type QueueState, type QueueStep, type RawSeatAvailabilityRs, type Ref, type RefundDocumentsType, type RequestOptions, type ResolveHostDateOptions, type RetryOptions, type RevalidateItineraryRequest, type RevalidateItineraryRequestSource, type RevalidateItineraryResponse, Sabre, type SabreAuthVersion, SabreBusinessError, type SabreBusinessErrorOptions, type SabreConfig, type SabreConfigV2, type SabreConfigV3, type SabreCredentials, type SabreCredentialsV2, type SabreEnvironment, SabreError, type SabreErrorOptions, SabreGatewayError, type SabreGatewayErrorOptions, SabreLegacy, type SabreLogEvent, type SabreLogLevel, type SabreLogger, SabreOrchestrationError, type SabreOrchestrationErrorOptions, type SabreProtocol, type SabreRawResponse, type SabreRawResponseCallback, type SabreServiceErrorOptions, SabreSession, SabreSessionError, type SabreSessionErrorKind, type SabreSessionErrorOptions, type SabreTokenStore, SabreWarning, type ScheduleCarrier, type ScheduleDesc, type ScheduleEndpoint, type ScheduleEquipment, type Seat, type SeatAreaPreference, type SeatAvailabilityByPayloadRq, type SeatAvailabilityByPnrLocatorRq, type SeatAvailabilityIssue, type SeatAvailabilityRequestSource, type SeatAvailabilityResponse, type SeatCabinCompartment, type SeatCabinFacility, type SeatCabinLayout, type SeatCabinRowPosition, type SeatCharacteristic, type SeatDisplayOnlyItem, type SeatFareComponent, type SeatFareDetail, type SeatFareSegmentAssociation, type SeatFlightLeg, type SeatKeyValuePair, type SeatLoyaltyProgramAccount, type SeatMap, type SeatOfferItem, type SeatOfferItemDetails, type SeatPassenger, type SeatPassengerPayloadRq, type SeatPassengerRs, type SeatPriceBreakdown, type SeatPriceDefinition, type SeatRow, type SeatSegment, type SeatSegmentPayloadRq, type SeatSegmentRs, type SeatServiceDefinition, type SeatSpecialServiceRequest, type SeatTax, type SpecificFare, type StoredToken, type TaxDesc, type TaxSummaryDesc, type TicketingPolicy, type TotalFare, type TravelerPhone, type TravelerTitle, VERSION, type ValidatingCarrierDesc, buildBookingFlightDetails, buildRevalidateItineraryRequest, buildSeatAvailabilityRequest, parseQueueParagraph, resolveHostDate };
|
|
2289
|
+
export { Air, type BaggageAllowanceDesc, type BaggageInformation, type BargainFinderMaxRequest, type BargainFinderMaxResponse, type BookContactInformation, type BookGenericSeat, type BookSeat, type BookTraveler, type Booking, type BookingAgency, type BookingFlightDetailsSource, type BookingItemReference, BookingManagement, type BookingResponseError, type BookingSeatSelection, type BookingSource, type BrandedFare, type BuildCredentialsOptions, type BuildCredentialsOptionsV2, type BuildCredentialsOptionsV3, type CancelBookingErrorPolicy, type CancelBookingNotification, type CancelBookingOptions, type CancelBookingRequest, type CancelBookingResponse, type CancelSegmentReference, type CreateBookingErrorPolicy, type CreateBookingOptions, type CreateBookingRequest, type CreateBookingResponse, type CreateBookingResponseError, type CurrencyConversion, type Fare, type FareComponent, type FareComponentDesc, type FareComponentDescSegment, type FareComponentSegment, type FareComponentSegmentDetail, type FareMessage, type FlightDetails, type FlightStatusCode, type FlightTicketOperation, type FlightToBook, type GetBookingExtraFeatures, type GetBookingOptions, type GetBookingRequest, type GetBookingResponse, type GetBookingReturnOnly, type GetSeatsRequest, type GetSeatsResponse, type GroupedItineraryResponse, type HostDateDirection, type Itinerary, type ItineraryGroup, type ItineraryMessage, type Leg, type LegDesc, type LegDescription, type LegSchedule, type LegScheduleRef, type LowFareSearchFlight, type LowFareSearchLocation, type LowFareSearchOriginDestination, type LowFareSearchPassengerTypeQuantity, type LowFareSearchRequestorId, type LowFareSearchSource, type LowFareSearchTpaExtensions, type LowFareSearchTravelPreferences, type LowFareSearchTravelerInfoSummary, type ObFeeDesc, type OpenSessionOptions, type OtaAirLowFareSearchRq, type PassengerInfo, type PassengerPricing, type PassengerTotalFare, type PaymentMethod, type PriceComparison, type PriceComparisonType, type PricedSeat, type PricingDetails, type PricingInformation, type PricingQualifiers, type QueueAccessOptions, type QueueAccessRequest, type QueueCount, type QueueCountOptions, type QueueCountRequest, type QueueCountResponse, type QueueEndReason, type QueueItem, type QueueListEntry, type QueueListOptions, type QueueListRequest, type QueueListResponse, QueueManagement, type QueueNavigationOptions, type QueueParagraph, type QueueParagraphAirSegment, type QueueParagraphMessage, type QueueParagraphOptions, type QueueParagraphOtherSegment, type QueueParagraphPassenger, type QueueParagraphReceivedFrom, type QueueParagraphSegment, type QueueParagraphTicketing, type QueueParagraphTicketingEntry, type QueuePlaceOptions, type QueuePlaceRequest, type QueuePlaceResponse, type QueuePlaceResult, type QueuePlaceTarget, type QueuePlacement, type QueueProcessAction, type QueueProcessRequest, type QueueProcessResult, QueueRef, type QueueRemoveRequest, type QueueRemoveTarget, type QueueSelection, type QueueState, type QueueStep, type RawSeatAvailabilityRs, type Ref, type RefundDocumentsType, type RequestOptions, type ResolveHostDateOptions, type RetryOptions, type RevalidateItineraryRequest, type RevalidateItineraryRequestSource, type RevalidateItineraryResponse, Sabre, type SabreAuthVersion, SabreBusinessError, type SabreBusinessErrorOptions, type SabreCommandOptions, type SabreCommandResponse, type SabreConfig, type SabreConfigV2, type SabreConfigV3, type SabreCredentials, type SabreCredentialsV2, type SabreEnvironment, SabreError, type SabreErrorOptions, SabreGatewayError, type SabreGatewayErrorOptions, SabreLegacy, type SabreLogEvent, type SabreLogLevel, type SabreLogger, SabreOrchestrationError, type SabreOrchestrationErrorOptions, type SabreProtocol, type SabreRawResponse, type SabreRawResponseCallback, type SabreServiceErrorOptions, SabreSession, SabreSessionError, type SabreSessionErrorKind, type SabreSessionErrorOptions, type SabreTokenStore, SabreWarning, type ScheduleCarrier, type ScheduleDesc, type ScheduleEndpoint, type ScheduleEquipment, type Seat, type SeatAreaPreference, type SeatAvailabilityByPayloadRq, type SeatAvailabilityByPnrLocatorRq, type SeatAvailabilityIssue, type SeatAvailabilityRequestSource, type SeatAvailabilityResponse, type SeatCabinCompartment, type SeatCabinFacility, type SeatCabinLayout, type SeatCabinRowPosition, type SeatCharacteristic, type SeatDisplayOnlyItem, type SeatFareComponent, type SeatFareDetail, type SeatFareSegmentAssociation, type SeatFlightLeg, type SeatKeyValuePair, type SeatLoyaltyProgramAccount, type SeatMap, type SeatOfferItem, type SeatOfferItemDetails, type SeatPassenger, type SeatPassengerPayloadRq, type SeatPassengerRs, type SeatPriceBreakdown, type SeatPriceDefinition, type SeatRow, type SeatSegment, type SeatSegmentPayloadRq, type SeatSegmentRs, type SeatServiceDefinition, type SeatSpecialServiceRequest, type SeatTax, type SpecificFare, type StoredToken, type TaxDesc, type TaxSummaryDesc, type TicketingPolicy, type TotalFare, type TravelerPhone, type TravelerTitle, VERSION, type ValidatingCarrierDesc, buildBookingFlightDetails, buildCredentials, buildRevalidateItineraryRequest, buildSeatAvailabilityRequest, parseQueueParagraph, resolveHostDate };
|
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
|
|
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 =
|
|
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
|
|
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)
|
|
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)
|
|
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")
|
|
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.
|
|
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,
|