sabre-client 0.4.1 → 0.5.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 +226 -21
- package/README.md +226 -21
- package/dist/anonymize.d.ts +54 -0
- package/dist/anonymize.js +111 -0
- package/dist/anonymize.js.map +1 -0
- package/dist/index.d.ts +2726 -693
- package/dist/index.js +497 -115
- package/dist/index.js.map +1 -1
- package/dist/reservation-history-BKRFlxpA.d.ts +792 -0
- package/dist/testing.d.ts +84 -7
- package/dist/testing.js +475 -57
- package/dist/testing.js.map +1 -1
- package/package.json +5 -1
- package/dist/queue-ref-BDGh8FrV.d.ts +0 -11
package/README.es.md
CHANGED
|
@@ -12,17 +12,32 @@ SDK de TypeScript para las APIs de Sabre.
|
|
|
12
12
|
|
|
13
13
|
## Estado
|
|
14
14
|
|
|
15
|
-
`0.
|
|
16
|
-
Booking por REST,
|
|
17
|
-
|
|
15
|
+
La `0.5.1` está publicada con el dist-tag `next` mientras se prueba; `latest` sigue en la `0.4.1`.
|
|
16
|
+
Cubre el Tier 1 y el Tier 2 del flujo de Air Booking por REST, el Tier 3 (sesiones SOAP y colas) y
|
|
17
|
+
el Tier 4: actividad de colas, historial del PNR, la cola de mensajes `S`, `selection` en `list` y
|
|
18
|
+
`onRawRequest`. La línea `0.5` trae tres cambios incompatibles (ver
|
|
19
|
+
[Actualizar desde la 0.4](#actualizar-desde-la-04)).
|
|
18
20
|
|
|
19
21
|
```sh
|
|
20
|
-
npm install sabre-client
|
|
22
|
+
npm install sabre-client@next # 0.5.1
|
|
23
|
+
npm install sabre-client # 0.4.1
|
|
21
24
|
```
|
|
22
25
|
|
|
23
26
|
El paquete sigue en `0.x`: una versión minor puede traer cambios incompatibles, y el
|
|
24
27
|
[CHANGELOG](./CHANGELOG.md) los marca.
|
|
25
28
|
|
|
29
|
+
### Actualizar desde la 0.4
|
|
30
|
+
|
|
31
|
+
- **`pcc` ya no está en la config.** Pasarlo a `new Sabre` o a `new SabreLegacy` da un error de
|
|
32
|
+
tipos, y en JavaScript se ignora. El PCC con el que te autenticas va en `credentials` (ver
|
|
33
|
+
[Credenciales](#credenciales)); otro PCC se pasa en cada operación, como `targetPcc` en REST o
|
|
34
|
+
`pcc` en las operaciones de colas.
|
|
35
|
+
- **`QueueRef` puede ser un `string`.** Una cola alfabética como `'S'` es `'S'`, y `count` y
|
|
36
|
+
`place` la devuelven así en lugar de `{ name: 'S' }`. El código que hace
|
|
37
|
+
`typeof q === 'number' ? … : q.name` tiene que manejar el caso `string`.
|
|
38
|
+
- **Un `QueueStep` que no terminó tiene `kind`.** Revisa `step.kind === 'pnr'` antes de leer
|
|
39
|
+
`step.item`; `kind: 'message'` trae un mensaje de la cola `S`.
|
|
40
|
+
|
|
26
41
|
El Tier 1 cubre el flujo básico de Air Booking por REST:
|
|
27
42
|
|
|
28
43
|
| Paso | Servicio | Método |
|
|
@@ -45,11 +60,12 @@ El Tier 3 agrega el cliente SOAP `SabreLegacy`, con una sesión stateful y Queue
|
|
|
45
60
|
| Paso | Servicio | Método |
|
|
46
61
|
| - | - | - |
|
|
47
62
|
| Sesión | `SessionCreateRQ` 2.0.0 / 1.0.0, `OTA_PingRQ`, `SessionCloseRQ` | `legacy.withSession`, `legacy.openSession` |
|
|
48
|
-
| Colas | Get Queue Count, Access Queue, Place Queue Message | `session.queue.count`, `list`, `access`, `ignore`, `remove`, `place`, `exit`, `process` |
|
|
49
|
-
| Párrafo de cola | — (función pura) | `parseQueueParagraph`, `resolveHostDate` |
|
|
63
|
+
| Colas | Get Queue Count, Get Queue Activity, Access Queue, Place Queue Message | `session.queue.count`, `activity`, `list`, `access`, `ignore`, `remove`, `place`, `exit`, `process` |
|
|
64
|
+
| Párrafo de cola | — (función pura) | `parseQueueParagraph`, `parseQueuePlaceRemark`, `resolveHostDate` |
|
|
65
|
+
| Historial del PNR | `TravelItineraryHistoryLLSRQ` 2.4.0 | `session.reservation.history` |
|
|
50
66
|
| Comandos de host | `SabreCommandLLSRQ` 2.0.0 | `session.command` |
|
|
51
67
|
|
|
52
|
-
Planeado después de `0.
|
|
68
|
+
Planeado después de `0.5.0`: Get Ancillaries (solo NDC).
|
|
53
69
|
|
|
54
70
|
## Requisitos
|
|
55
71
|
|
|
@@ -88,11 +104,6 @@ const sabre = new Sabre({
|
|
|
88
104
|
| `fetch` | `fetch` global | `fetch` propio (proxies, tests, otros runtimes) |
|
|
89
105
|
| `tokenStore` | solo en memoria | Comparte el token entre procesos (ver [Reutilizar el token](#reutilizar-el-token)) |
|
|
90
106
|
|
|
91
|
-
> **`pcc` está deprecado** desde la `0.4.1` y se quita en la `0.5.0`. Era un campo reservado, y
|
|
92
|
-
> ningún cliente lo lee. El PCC con el que te autenticas va en `credentials` (ver
|
|
93
|
-
> [Credenciales](#credenciales) y `buildCredentials`). Para trabajar en otro PCC, pásalo en cada
|
|
94
|
-
> operación: `targetPcc` en REST y `pcc` en las operaciones de colas.
|
|
95
|
-
|
|
96
107
|
### Credenciales
|
|
97
108
|
|
|
98
109
|
Hay **un solo juego de credenciales** para REST y SOAP: el mismo objeto `credentials` sirve para
|
|
@@ -124,7 +135,9 @@ El objeto que devuelve `buildCredentials` lleva su `authVersion`, así que la co
|
|
|
124
135
|
repetirlo: con `authVersion: 'v2'` en `buildCredentials`, `{ environment, credentials }` ya es una
|
|
125
136
|
config v2. Si también pasas `authVersion` en la config, tiene que coincidir; si no, el cliente lanza
|
|
126
137
|
un `TypeError`. Las credenciales que armas a mano no llevan `authVersion`, así que con v2 la config
|
|
127
|
-
sigue necesitando `authVersion: 'v2'`.
|
|
138
|
+
sigue necesitando `authVersion: 'v2'`. Si la versión solo se conoce en runtime (por ejemplo, leída
|
|
139
|
+
de una variable de entorno a un `BuildCredentialsOptions`), el resultado de `buildCredentials`
|
|
140
|
+
también va directo a la config, sin `authVersion` en ella.
|
|
128
141
|
|
|
129
142
|
El PCC de las credenciales es con el que te autenticas. Trabajar en otro PCC no requiere otro
|
|
130
143
|
juego de credenciales: se pasa en cada operación, como `targetPcc` en `booking.create`, `get` y
|
|
@@ -361,6 +374,7 @@ const screen = await legacy.withSession((session) => session.command('W/PT¥*',
|
|
|
361
374
|
// screen.text → 'PSGR TYPE - ON' (como lo manda Sabre, sin recortar)
|
|
362
375
|
// screen.warnings → los stl:Warning, si los hay
|
|
363
376
|
// screen.messageId → eb:MessageId, para soporte de Sabre
|
|
377
|
+
// screen.respondedAt → eb:Timestamp en UTC, p. ej. '2026-11-06T17:34:16Z'
|
|
364
378
|
```
|
|
365
379
|
|
|
366
380
|
- **El comando va tal cual.** En este servicio, el separador `‡` de Sabre se escribe `¥`
|
|
@@ -388,16 +402,71 @@ const screen = await legacy.withSession((session) => session.command('W/PT¥*',
|
|
|
388
402
|
caracteres (`W/PT`, `DC`, `*IM`), nunca el comando entero ni la pantalla. La respuesta completa
|
|
389
403
|
está disponible en `onRawResponse`.
|
|
390
404
|
|
|
405
|
+
### Historial del PNR
|
|
406
|
+
|
|
407
|
+
`session.reservation.history(locator, options?)` lee el historial del PNR (Get Itinerary History,
|
|
408
|
+
`TravelItineraryHistoryLLSRQ` 2.4.0, el `*H` del host), una entrada por transacción:
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
const { transactions } = await legacy.withSession((session) => session.reservation.history('ABC123'));
|
|
412
|
+
// transactions[0] → { rph, createdAt: '2026-11-04T17:15', agent, agentDutyCode, pcc, aaaPcc,
|
|
413
|
+
// receivedFrom, flightSegments, generalInfo, remarks, accounting }
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
- **Quién, cuándo y desde dónde**: `createdAt` (con año, sin zona horaria), el sine y el duty code
|
|
417
|
+
del agente, el `pcc` de la firma, el `aaaPcc` del AAA y `receivedFrom`.
|
|
418
|
+
- **Qué cambió, con su `action`**: `flightSegments` con su estado en ese momento (`AS` alta, `XS`
|
|
419
|
+
baja; fechas sin año, como las manda el host), y SSR, ticketing, teléfonos, nombres y emails como
|
|
420
|
+
texto en `generalInfo` (`A4S`, `A7`, `A9`, `AN`, `APE`…). Sabre no publica la lista de valores de
|
|
421
|
+
`action`. Asientos, autos, hoteles, tren, seguros y facturas no se tipan: quedan en la respuesta
|
|
422
|
+
cruda (`onRawResponse`).
|
|
423
|
+
- **Trae datos del pasajero** (nombres, teléfonos, emails). El SDK nunca los manda al log.
|
|
424
|
+
- **No registra las colocaciones en cola**, ni las del sistema ni un `place` manual (CERT): para eso están
|
|
425
|
+
`queue.list`/`access` o los remarks `QUEUE_PLACE` de Get Booking.
|
|
426
|
+
- **Llámalo fuera de un recorrido de cola.** Dentro de una cola, Sabre rechaza el historial de otro
|
|
427
|
+
PNR con `NAK6 -NOT ALLOWED- FINISH OR IGNORE OTHER ACTIVITY`, un `SabreBusinessError` (CERT). El
|
|
428
|
+
PNR del AAA no cambia, `queue.state` sigue en `'inside'` y el recorrido sigue con `ignore`.
|
|
429
|
+
- Es una lectura: se reintenta. Un `locator` que no es de 6 caracteres alfanuméricos en mayúscula
|
|
430
|
+
lanza un `TypeError` sin llamar a la red. Un PNR que no existe lanza un `SabreBusinessError`
|
|
431
|
+
(`service: 'itineraryHistory'`).
|
|
432
|
+
|
|
391
433
|
### Colas
|
|
392
434
|
|
|
393
435
|
Las operaciones de cola están en `session.queue`. `pcc` es opcional en cada llamada, y por defecto
|
|
394
|
-
es el de la sesión. Una cola
|
|
395
|
-
|
|
436
|
+
es el de la sesión. Una cola (`QueueRef`) tiene tres formas:
|
|
437
|
+
|
|
438
|
+
| Cola | `QueueRef` | Viaja como |
|
|
439
|
+
| - | - | - |
|
|
440
|
+
| Numérica, de 0 a 511 | `400` | `Number="400"` |
|
|
441
|
+
| Alfabética, una o dos letras mayúsculas | `'S'` | `Number="S"` |
|
|
442
|
+
| Con nombre (tablas QN y QNB) | `{ name: 'HOTELS' }` | `Name="HOTELS"` |
|
|
443
|
+
|
|
444
|
+
Las colas que devuelve `count` se pasan tal cual a las demás operaciones. `'S'` es la **cola de
|
|
445
|
+
mensajes** del PCC: tiene mensajes y no PNR, y Sabre no la lista, así que `list({ queue: 'S' })`
|
|
446
|
+
lanza un `TypeError` sin llamar a Sabre. Se recorre a mano: `access({ queue: 'S' })` devuelve el
|
|
447
|
+
primer mensaje (`kind: 'message'`) e `ignore()` el siguiente, sin sacarlo de la cola. `process` y
|
|
448
|
+
`place` dentro de la `S` lanzan un `TypeError` sin llamar a Sabre. `remove()` manda `QR`, pero
|
|
449
|
+
todavía no hay evidencia de qué hace Sabre con el mensaje. Para distinguirla, `isMessageQueue(queue)`
|
|
450
|
+
devuelve `true` solo con `'S'`, y `MESSAGE_QUEUE` es `'S'`:
|
|
451
|
+
|
|
452
|
+
```ts
|
|
453
|
+
import { isMessageQueue } from 'sabre-client';
|
|
454
|
+
|
|
455
|
+
const { queues } = await session.queue.count();
|
|
456
|
+
const pnrQueues = queues.filter(({ queue }) => !isMessageQueue(queue));
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
> **Migración desde la 0.4.x**: `count` devolvía la cola `S` como `{ name: 'S' }`, que Sabre
|
|
460
|
+
> rechaza (`INVALID QUEUE NAME FORMAT`). Ahora devuelve `'S'`. Un código como
|
|
461
|
+
> `typeof q === 'number' ? … : q.name` deja de compilar, porque `q` puede ser un `string`. Un
|
|
462
|
+
> `{ name }` de una o dos letras mayúsculas ahora lanza un `TypeError` que pide pasar la letra, y
|
|
463
|
+
> `'12'` o `'s'` también lanzan: cada cola tiene una sola forma.
|
|
396
464
|
|
|
397
465
|
| Método | Servicio de Sabre | Devuelve |
|
|
398
466
|
| - | - | - |
|
|
399
467
|
| `count({ pcc? })` | `QueueCountLLSRQ` | `{ queues: [{ queue, count }], totals, countedAt, warnings }` |
|
|
400
|
-
| `
|
|
468
|
+
| `activity({ pcc? })` | `QueueAnalysisLLSRQ` | `{ queues: [{ queue, numMessages, ended, ignored, … }], totals, analyzedAt, warnings }` |
|
|
469
|
+
| `list({ queue, pcc?, primaryPassenger?, selection? })` | `QueueAccessLLSRQ` (`List`) | `{ locators, entries, paragraph, warnings }`, sin entrar a la cola |
|
|
401
470
|
| `access({ queue, pcc?, selection? })` | `QueueAccessLLSRQ` | `QueueStep` |
|
|
402
471
|
| `ignore()` | `QueueAccessLLSRQ` (`I`) | `QueueStep` |
|
|
403
472
|
| `remove({ from? })` | `QueueAccessLLSRQ` (`QR`) | `QueueStep` |
|
|
@@ -406,10 +475,30 @@ es el de la sesión. Una cola es un número de 0 a 511, o `{ name }` para las co
|
|
|
406
475
|
|
|
407
476
|
```ts
|
|
408
477
|
type QueueStep =
|
|
409
|
-
| { done: false; item: QueueItem; warnings: SabreWarning[] }
|
|
478
|
+
| { done: false; kind: 'pnr'; item: QueueItem; warnings: SabreWarning[] }
|
|
479
|
+
| { done: false; kind: 'message'; message: { text: string[] }; warnings: SabreWarning[] } // cola 'S'
|
|
410
480
|
| { done: true; reason: 'end-of-display' | 'empty'; message: string; warnings: SabreWarning[] };
|
|
411
481
|
```
|
|
412
482
|
|
|
483
|
+
> **Migración desde la 0.4.x**: un paso que no terminó ahora trae `kind`. Comprueba
|
|
484
|
+
> `step.kind === 'pnr'` antes de leer `step.item`. Solo la cola `S` devuelve `kind: 'message'`.
|
|
485
|
+
|
|
486
|
+
- **`list` devuelve la cola entera en una respuesta**: no pagina, ni con más de 1000 PNR (1221 en
|
|
487
|
+
unos 279 KB, en producción). `selection` la acota, pero en la lista Sabre solo acepta `QD`
|
|
488
|
+
(fecha de colocación en la cola, p. ej. `{ function: 'QD', criteria: '05NOV' }`) o `AK` (el
|
|
489
|
+
agente que lo colocó, PCC más firma, p. ej. `'PCC1AAT'`; todavía no se probó en la lista). Otro
|
|
490
|
+
código lanza `TypeError` sin llamar a Sabre. El `placedAt` de una entrada de la lista es cuándo
|
|
491
|
+
se colocó el PNR en la cola, la fecha por la que filtra `QD`.
|
|
492
|
+
- **`activity`** devuelve, por cola, los ocho contadores de la pantalla `QA` del host:
|
|
493
|
+
`numMessages` (los que están hoy en la cola), `ended`, `ignored`, `moved`, `removed`,
|
|
494
|
+
`transferred`, `leftMessageToContact` y `unableToReach`, más `totals` para el PCC. Sabre lista
|
|
495
|
+
también colas vacías con contadores. Ninguna fuente dice desde cuándo cuentan, así que el SDK los
|
|
496
|
+
devuelve tal cual. Como `count`, es una lectura: se reintenta, funciona dentro o fuera de una
|
|
497
|
+
cola y no pide duty code 9.
|
|
498
|
+
- **Cada resultado de colas trae `respondedAt`**: el `eb:Timestamp` de Sabre, en ISO 8601 UTC
|
|
499
|
+
(`'2026-11-06T17:34:16Z'`). A diferencia de `countedAt` y `placedAt`, que llegan como los manda
|
|
500
|
+
el host (sin año y en hora local del PCC), trae año y zona. Solo falta si Sabre no lo manda.
|
|
501
|
+
|
|
413
502
|
- **El fin de cola es un resultado, no un error.** `END OF DISPLAY FOR REQUESTED DATA` da
|
|
414
503
|
`{ done: true, reason: 'end-of-display' }`, y una cola vacía (`QUEUE SELECTED WAS EMPTY`) da
|
|
415
504
|
`reason: 'empty'`. En los dos casos la sesión queda fuera de la cola. Cualquier otro error de
|
|
@@ -430,7 +519,9 @@ type QueueStep =
|
|
|
430
519
|
0–191 o 200–254. Producción rechaza un `place` sin PIC, aunque el design lo dé como opcional.
|
|
431
520
|
- **`QueueItem`** trae `locator`, el `placedAt` y el `placedBy` de `Line`, `primaryPassenger` y
|
|
432
521
|
`group` cuando Sabre los manda, y `paragraph`: el PNR en formato de host, línea por línea y
|
|
433
|
-
sin tocar. `placedAt` es el texto que llegó, con o sin año.
|
|
522
|
+
sin tocar. `placedAt` es el texto que llegó, con o sin año. Dentro de una cola es la última
|
|
523
|
+
modificación del PNR, no su colocación en la cola: esa es el `placedAt` de `list`, y para un
|
|
524
|
+
mismo PNR no coinciden. `resolveHostDate` lo convierte en una
|
|
434
525
|
fecha completa.
|
|
435
526
|
- `count` dentro de una cola no cuenta el PNR que está en pantalla.
|
|
436
527
|
|
|
@@ -472,6 +563,19 @@ segmentos con su estado, el campo de ticketing con una marca `issued` y la líne
|
|
|
472
563
|
- No lee los plazos que las aerolíneas escriben como texto libre en remarks, SSR u OSI. Su formato
|
|
473
564
|
lo pone cada aerolínea. El texto sigue en `paragraph`.
|
|
474
565
|
|
|
566
|
+
### Leer un remark `QUEUE_PLACE`
|
|
567
|
+
|
|
568
|
+
Get Booking devuelve dónde se colocó el PNR como un remark con `type: 'QUEUE_PLACE'`. Su `text`
|
|
569
|
+
junta el PCC, la fecha, la cola y el PIC sin separadores. `parseQueuePlaceRemark(text)` lo separa:
|
|
570
|
+
|
|
571
|
+
```ts
|
|
572
|
+
parseQueuePlaceRemark('MEX25SEP146/015'); // { pcc: 'MEX', date: '25SEP', queue: 146, pic: 15 }
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
Devuelve `null` si el texto no tiene esa forma, o si el día, el mes, la cola (0–511) o el PIC
|
|
576
|
+
(0–191, 200–254) están fuera de rango. `date` viene sin año: se resuelve con
|
|
577
|
+
`resolveHostDate(date, referenceDate)`. Leer los remarks de la reserva le toca al consumidor.
|
|
578
|
+
|
|
475
579
|
### Probar tu lógica de colas: `sabre-client/testing`
|
|
476
580
|
|
|
477
581
|
`sabre-client/testing` es una entrada aparte, con un fake de las sesiones y las colas de Sabre. No
|
|
@@ -503,16 +607,83 @@ fake.openSessions; // 0: la sesión se cerró
|
|
|
503
607
|
`{ locator, paragraph?, placedAt?, placedBy?, primaryPassenger? }`. A un párrafo sin línea de
|
|
504
608
|
firma se le agrega una sintética. `pnrs` agrega PNR que no están en ninguna cola, para
|
|
505
609
|
`place({ locators })`. El fake no tiene AAA: fuera de una cola, `place` necesita `locators`.
|
|
610
|
+
- `queues['S']` es la cola de mensajes: cada elemento es el texto de un mensaje (`string[]`), no
|
|
611
|
+
un PNR. `access` lo muestra sin `Line`, como Sabre, y `fake.messages()` da los que quedan. Que
|
|
612
|
+
`remove` saque el mensaje es un supuesto del fake, no se probó con Sabre.
|
|
613
|
+
- `queue.activity`: `numMessages` sale de las colas del fake (sin el PNR en pantalla, como en
|
|
614
|
+
`count`, igual que CERT). Los otros siete contadores valen 0, salvo que los siembres con
|
|
615
|
+
`activity: { '9': { ignored: 27 } }`. Las acciones del fake **no** los cambian: nadie probó qué
|
|
616
|
+
contador sube Sabre con cada acción.
|
|
617
|
+
- `reservation.history`: `histories: { AAAAAA: [transacciones] }` fija lo que devuelve cada PNR,
|
|
618
|
+
con la forma de `ReservationHistoryTransaction` (las listas que falten llegan vacías). Un PNR que
|
|
619
|
+
el fake conoce sin historial sembrado devuelve una transacción sintética (su creación), y un
|
|
620
|
+
localizador desconocido da un error de host marcado como no simulado. Dentro de una cola, el
|
|
621
|
+
historial de otro PNR se rechaza con `NAK6 -NOT ALLOWED- FINISH OR IGNORE OTHER ACTIVITY` y el del
|
|
622
|
+
PNR en pantalla se devuelve, como en CERT.
|
|
506
623
|
- Responde como producción: colas vacías, el fin de cola, `place` que mueve el PNR, el PIC
|
|
507
624
|
obligatorio y una sesión vencida con `fake.expireSessions()`. `fake.calls` lista lo que pidió el
|
|
508
625
|
SDK.
|
|
509
626
|
- **No simula** los PCC (las colas son las mismas para cualquier `pcc`) ni `selection`, que recibe
|
|
510
|
-
un error en vez de la cola entera
|
|
627
|
+
un error en vez de la cola entera, en `access` y en `list`.
|
|
511
628
|
- `queueFixtures` trae las respuestas anonimizadas que se grabaron en producción (`walk`,
|
|
512
|
-
`emptyQueue`, `place`…), como `{ operation, status, body }`.
|
|
629
|
+
`emptyQueue`, `place`…), como `{ operation, status, body }`. Cada una tiene sus propios alias de
|
|
630
|
+
localizador (`walk`: `AAAAAA`–`AAAAAD`, `place`: `AAAAAE`–`AAAAAF`,
|
|
631
|
+
`placeWithoutInstructionCodeInside`: `AAAAAG`–`AAAAAH`), y sus líneas de firma conservan la
|
|
632
|
+
forma (`… <alias> H`).
|
|
633
|
+
- `queueFakePnrs(fixture)` convierte una grabación en los PNR del fake: primero los de `list`, en
|
|
634
|
+
orden, con su `Line`, y el párrafo que mostró `access`, `ignore`, `remove` o `place` para cada
|
|
635
|
+
uno. La cola la eliges tú, y puedes juntar fixtures. Los PNR de `queueFixtures` no traen
|
|
636
|
+
`primaryPassenger`: el `list` se grabó sin `primaryPassenger: true` (`/LN`), así que Sabre no
|
|
637
|
+
mandó el nombre. Si necesitas uno, agrégalo con un `map`:
|
|
638
|
+
|
|
639
|
+
```ts
|
|
640
|
+
const fake = createQueueFake({
|
|
641
|
+
queues: { '9': queueFakePnrs(queueFixtures.walk), '50': [] },
|
|
642
|
+
pnrs: queueFakePnrs(queueFixtures.place),
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
const withPassenger = queueFakePnrs(queueFixtures.walk).map((pnr, index) => ({
|
|
646
|
+
...pnr,
|
|
647
|
+
primaryPassenger: `TEST/PASSENGER${index + 1}`,
|
|
648
|
+
}));
|
|
649
|
+
```
|
|
513
650
|
|
|
514
651
|
`examples/queue-review.mjs` es un ejemplo completo, y su test lo corre contra el fake.
|
|
515
652
|
|
|
653
|
+
#### Anonimizar respuestas grabadas
|
|
654
|
+
|
|
655
|
+
`createAnonymizer` aplica la regla con la que se grabaron las fixtures del SDK, para guardar las
|
|
656
|
+
respuestas de Sabre (por ejemplo, las de `onRawResponse`) sin datos de pasajeros:
|
|
657
|
+
|
|
658
|
+
```ts
|
|
659
|
+
import { createAnonymizer } from 'sabre-client/testing';
|
|
660
|
+
|
|
661
|
+
const anonymizer = createAnonymizer({ secrets: [pcc, username] });
|
|
662
|
+
const safe = anonymizer.anonymize(raw.body); // un sobre SOAP entero
|
|
663
|
+
anonymizer.maskParagraph(item.paragraph); // líneas en formato de host
|
|
664
|
+
if (anonymizer.leaks(safe).length > 0) throw new Error('quedan datos reales');
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
En un bundle de navegador, impórtalo de `sabre-client/anonymize`. Trae solo `createAnonymizer`,
|
|
668
|
+
`isSafeHostLine` y sus tipos, sin el fake ni las `queueFixtures`, que `sabre-client/testing` lleva
|
|
669
|
+
en un solo módulo (unos 230 KB) que los bundlers no pueden descartar:
|
|
670
|
+
|
|
671
|
+
```ts
|
|
672
|
+
import { isSafeHostLine } from 'sabre-client/anonymize';
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
- Cada localizador recibe un alias (`AAAAAA`, `AAAAAB`…), el mismo en todos los lugares donde
|
|
676
|
+
aparece, también en los textos que pasen después por el mismo anonimizador. `PassengerName` pasa
|
|
677
|
+
a `PASAJERO/UNO`, `Group` a `GRUPO`, `AgentSine` a `AAA` (salvo `SABRE`), `PseudoCityCode` a
|
|
678
|
+
`XXXX`, `LNIATA` a `XXXXXX` y cada `secret` a `XXXX`.
|
|
679
|
+
- En las líneas en formato de host (`<Text>`, o `maskLine` y `maskParagraph`), solo quedan tal cual
|
|
680
|
+
las de una lista blanca (`isSafeHostLine`: líneas vacías, avisos PIC como `011 SEE REMARKS`,
|
|
681
|
+
títulos de sección, el fin de cola). En las demás, las letras pasan a `X` y los dígitos a `9`. La
|
|
682
|
+
línea de firma conserva su forma, y su localizador solo si ya es un alias.
|
|
683
|
+
- `leaks(text)` lista los datos reales que siguen en `text` (`'secreto'`, `'localizador'`,
|
|
684
|
+
`'nombre'`, `'línea del PNR'`, `'token'`): úsalo antes de escribir un archivo.
|
|
685
|
+
- Usa un anonimizador por grabación. Es una regla, no una garantía: revisa lo que guardas.
|
|
686
|
+
|
|
516
687
|
## Manejo de errores
|
|
517
688
|
|
|
518
689
|
Todos los errores que lanza el SDK extienden `SabreError`:
|
|
@@ -583,6 +754,39 @@ En `SabreLegacy`, `protocol` es `'soap'`, `body` es el sobre SOAP completo, y `o
|
|
|
583
754
|
> `body` es la respuesta cruda y **trae datos de pasajeros**. El SDK nunca lo manda al logger.
|
|
584
755
|
> Dónde lo guardas es tu responsabilidad.
|
|
585
756
|
|
|
757
|
+
## Request crudo
|
|
758
|
+
|
|
759
|
+
Las mismas operaciones aceptan `onRawRequest`, al lado de `onRawResponse`. Recibe lo que el SDK
|
|
760
|
+
mandó por la red, por ejemplo para mostrar el request real junto a la respuesta:
|
|
761
|
+
|
|
762
|
+
```ts
|
|
763
|
+
import type { SabreRawRequest } from 'sabre-client';
|
|
764
|
+
|
|
765
|
+
let sent: SabreRawRequest | undefined;
|
|
766
|
+
await sabre.booking.get({ confirmationId }, { onRawRequest: (r) => (sent = r) });
|
|
767
|
+
// sent: { operation: 'booking.get', protocol: 'rest', method, url, headers, body, attempts }
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
- `operation` es el mismo valor que recibe `onRawResponse`. `url` es la URL completa, con su query
|
|
771
|
+
string. `headers` son los que se mandaron, **sin `Authorization`** (se quita el header entero).
|
|
772
|
+
`body` es el texto que se mandó, o `undefined` si no hubo. En REST va tal cual; en
|
|
773
|
+
`SabreLegacy` es el sobre SOAP completo con el `BinarySecurityToken` reemplazado por
|
|
774
|
+
`[REDACTED]`, igual que en la respuesta.
|
|
775
|
+
- Se llama **una vez**, con el último intento que se mandó, antes de que la operación devuelva o
|
|
776
|
+
lance y antes que `onRawResponse`. A diferencia de este, se llama también si ningún intento tuvo
|
|
777
|
+
respuesta HTTP (error de red, abort), siempre que se haya mandado al menos uno. No se llama si la
|
|
778
|
+
operación lanza antes de mandar nada (por ejemplo, un `TypeError` de validación).
|
|
779
|
+
- En SOAP, `eb:MessageId` y `eb:Timestamp` son nuevos en cada intento, así que el sobre que llega
|
|
780
|
+
es el del último intento.
|
|
781
|
+
- No se llama con la autenticación: el request de token de OAuth, `openSession`
|
|
782
|
+
(`SessionCreateRQ`, cuyo sobre lleva la contraseña del EPR) y `close` (`SessionCloseRQ`).
|
|
783
|
+
- Es síncrono y su retorno se ignora. Si lanza, el SDK registra
|
|
784
|
+
`sabre.request.rawRequestHookFailed` sin el body y la operación sigue.
|
|
785
|
+
|
|
786
|
+
> [!WARNING]
|
|
787
|
+
> `body` y `url` **traen datos de pasajeros** (localizadores, nombres, contactos). El SDK nunca los
|
|
788
|
+
> manda al logger. Dónde los guardas es tu responsabilidad.
|
|
789
|
+
|
|
586
790
|
## Reintentos
|
|
587
791
|
|
|
588
792
|
Solo se reintentan las operaciones idempotentes: Bargain Finder Max y Revalidate Itinerary
|
|
@@ -638,7 +842,8 @@ const sabre = new Sabre({
|
|
|
638
842
|
```
|
|
639
843
|
|
|
640
844
|
El SDK registra eventos estructurados (`sabre.request.start`, `sabre.request.end`,
|
|
641
|
-
`sabre.request.retry`, `sabre.request.rawResponseHookFailed`,
|
|
845
|
+
`sabre.request.retry`, `sabre.request.rawResponseHookFailed`,
|
|
846
|
+
`sabre.request.rawRequestHookFailed`, `sabre.auth.token.refresh`,
|
|
642
847
|
`sabre.auth.token.store`, `sabre.booking.seat.priced`, `sabre.session.open`,
|
|
643
848
|
`sabre.session.close`, `sabre.session.closeFailed`, `sabre.session.keepAliveFailed`,
|
|
644
849
|
`sabre.session.command`, `sabre.queue.exitTolerated`, `sabre.queue.exitFailed`) y nunca los bodies
|