sabre-client 0.3.0-next.1 → 0.3.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 CHANGED
@@ -12,16 +12,15 @@ SDK de TypeScript para las APIs de Sabre.
12
12
 
13
13
  ## Estado
14
14
 
15
- `0.3.0` **todavía no está publicada**. Se publica cuando estén terminados el Tier 1 y el
16
- Tier 2 del flujo de Air Booking. Mientras tanto, `npm install sabre-client` instala `0.2.4`.
17
-
18
- Las prereleases de la reescritura se publican con el tag `next`:
15
+ `0.3.0` es la primera versión publicada de la reescritura. Cubre el Tier 1 y el Tier 2 del
16
+ flujo de Air Booking por REST, y el Tier 3 (sesiones SOAP y colas).
19
17
 
20
18
  ```sh
21
- npm install sabre-client@next
19
+ npm install sabre-client
22
20
  ```
23
21
 
24
- La API todavía puede cambiar antes de `0.3.0`.
22
+ El paquete sigue en `0.x`: una versión minor puede traer cambios incompatibles, y el
23
+ [CHANGELOG](./CHANGELOG.md) los marca.
25
24
 
26
25
  El Tier 1 cubre el flujo básico de Air Booking por REST:
27
26
 
@@ -33,7 +32,22 @@ El Tier 1 cubre el flujo básico de Air Booking por REST:
33
32
  | Reserva | Create Booking (Booking Management API v1, ATPCO por `flightDetails`) | `sabre.booking.create` |
34
33
  | Gestión de la reserva | Get Booking y Cancel Booking (Booking Management API v1) | `sabre.booking.get`, `sabre.booking.cancel` |
35
34
 
36
- Planeado: Get Seats y Get Ancillaries (Tier 2), y un cliente `SabreLegacy` para los servicios SOAP.
35
+ El Tier 2 agrega la elección de asiento:
36
+
37
+ | Paso | Servicio | Método |
38
+ | - | - | - |
39
+ | Mapa de asientos | Get Seats v3 (`byReservationPayload` y `byPnrLocator`) | `sabre.air.getSeats` |
40
+
41
+ El Tier 3 agrega el cliente SOAP `SabreLegacy`, con una sesión stateful y Queue Management (ver
42
+ [Sesiones SOAP y colas](#sesiones-soap-y-colas-sabrelegacy)):
43
+
44
+ | Paso | Servicio | Método |
45
+ | - | - | - |
46
+ | Sesión | `SessionCreateRQ` 2.0.0 / 1.0.0, `OTA_PingRQ`, `SessionCloseRQ` | `legacy.withSession`, `legacy.openSession` |
47
+ | Colas | Get Queue Count, Access Queue, Place Queue Message | `session.queue.count`, `list`, `access`, `ignore`, `remove`, `place`, `exit`, `process` |
48
+ | Párrafo de cola | — (función pura) | `parseQueueParagraph`, `resolveHostDate` |
49
+
50
+ Planeado después de `0.3.0`: Get Ancillaries (solo NDC).
37
51
 
38
52
  ## Requisitos
39
53
 
@@ -172,6 +186,247 @@ propia:
172
186
  await sabre.air.bargainFinderMax(search, { signal, retry: { maxRetries: 1 } });
173
187
  ```
174
188
 
189
+ ## Elegir un asiento
190
+
191
+ `sabre.air.getSeats` devuelve el mapa de asientos con disponibilidad y precio. Es una sola
192
+ función para los dos endpoints de Get Seats: sin `pnrLocator` va a `byReservationPayload`
193
+ (antes de reservar), y con `pnrLocator` va a `byPnrLocator` (un PNR ya creado).
194
+
195
+ Antes de reservar, arma el request desde el itinerario revalidado con
196
+ `buildSeatAvailabilityRequest`, elige un asiento y pásalo a `buildBookingFlightDetails`:
197
+
198
+ ```ts
199
+ import { buildBookingFlightDetails, buildSeatAvailabilityRequest } from 'sabre-client';
200
+
201
+ // Después del paso 2 de "Reservar un vuelo": `confirmedGroup` y `confirmed`.
202
+ const seatMap = await sabre.air.getSeats(
203
+ buildSeatAvailabilityRequest({ group: confirmedGroup, itinerary: confirmed, currencyCode: 'USD' }),
204
+ );
205
+
206
+ // seatMaps[].segmentRef es S1, S2, … en el orden del itinerario. `occupationStatusCode: 'F'` es
207
+ // un asiento libre; uno con `offerItemRefIds` puede tener precio (offerItems[].priceDefinitionRef
208
+ // → priceDefinitions[]). Acá: el primer asiento libre del primer vuelo sin offer item.
209
+ const rows = seatMap.response.seatMaps
210
+ .filter((map) => map.segmentRef === 'S1')
211
+ .flatMap((map) => map.cabinCompartments ?? [])
212
+ .flatMap((cabin) => cabin.seatRows ?? []);
213
+ const seat = rows
214
+ .flatMap((row) => row.seats.map((s) => ({ number: `${row.row}${s.column}`, ...s })))
215
+ .find((s) => s.occupationStatusCode === 'F' && (s.offerItemRefIds ?? []).length === 0);
216
+
217
+ const booking = await sabre.booking.create({
218
+ flightDetails: buildBookingFlightDetails({
219
+ group: confirmedGroup,
220
+ itinerary: confirmed,
221
+ // Los dos índices empiezan en 1: primer vuelo del itinerario, primer viajero.
222
+ seats: seat === undefined ? [] : [{ segmentIndex: 1, travelerIndex: 1, number: seat.number }],
223
+ seatAvailability: seatMap,
224
+ }),
225
+ travelers: [{ givenName: 'JANE', surname: 'DOE', passengerCode: 'ADT' }],
226
+ contactInfo: { emails: ['jane@example.com'], phones: ['5550000000'] },
227
+ });
228
+ ```
229
+
230
+ Para una reserva que ya existe, pide el mapa por localizador:
231
+
232
+ ```ts
233
+ const pnrSeats = await sabre.air.getSeats({ pnrLocator: booking.confirmationId! });
234
+ ```
235
+
236
+ A tener en cuenta:
237
+
238
+ - **El asiento se reserva por número, solo por ATPCO.** Va en `flightDetails.flights[].seats[]`
239
+ (`BookSeat`). `seatOffers` y el `offerId` de Get Seats son solo NDC y no están soportados.
240
+ El helper tampoco soporta `areaPreferences` ni `changeOfGaugeSeats`.
241
+ - **Mejor asientos sin cargo.** No se sabe qué hace Sabre con un asiento cobrable pedido por
242
+ número por ATPCO. Si pasas `seatAvailability` y el asiento elegido tiene precio,
243
+ `sabre.booking.create` registra el aviso `sabre.booking.seat.priced` (segmento, vuelo,
244
+ asiento, monto y moneda, sin datos del pasajero) y reserva igual.
245
+ - **Validación local.** Un número de asiento que no cumple `^[0-9]+[A-Z]$`, un `segmentIndex` o
246
+ `travelerIndex` que no existe, o un `pnrLocator` vacío o mezclado con campos del payload
247
+ (`segments`, `passengers`, `returnSeatMapsOnlyForSegmentRefs`) lanzan `TypeError` antes de
248
+ enviar la request.
249
+ - **Errores.** Get Seats no tiene catálogo de errores publicado. Un `errors[]` sin mapa lanza
250
+ `SabreBusinessError` no reintentable; con mapa, los errores y los `warnings[]` vuelven en
251
+ `seatMap.errors` y `seatMap.warnings`. Los dos endpoints son de lectura, así que los `429` y
252
+ `5xx` del gateway se reintentan.
253
+ - **`byPnrLocator` solo lee PNR del PCC con el que te autenticas.** Su request no tiene
254
+ `targetPcc`, a diferencia de Create, Get y Cancel Booking. Si la reserva se creó con
255
+ `targetPcc` en otro PCC, Sabre responde `502` con `Viewership is restricted for the PNR`,
256
+ que llega como `SabreGatewayError`.
257
+ - **Qué está comprobado.** `byReservationPayload` se probó en producción, en solo lectura, con
258
+ 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` todavía no se
260
+ probó contra Sabre, porque las únicas reservas posibles en sandbox viven en otro PCC. Los
261
+ precios llegan como número aunque el spec diga string, así que los tipos aceptan los dos.
262
+
263
+ ## Sesiones SOAP y colas (`SabreLegacy`)
264
+
265
+ `SabreLegacy` es el cliente de los servicios SOAP de Sabre. Recibe la misma `SabreConfig` que
266
+ `Sabre`. Usuario, PCC y domain de la sesión salen de `credentials.username`
267
+ (`V1:<user>:<PCC>:<domain>` o `<user>-<PCC>-<domain>`). La versión de `SessionCreateRQ` sale de
268
+ las credenciales: 2.0.0 con `clientId` y `clientSecret`, 1.0.0 sin ellos. Si pasas solo uno de los
269
+ dos, o un `username` que no está en ninguno de los dos formatos, se lanza un `TypeError` al crear
270
+ el cliente.
271
+
272
+ ```ts
273
+ import { SabreLegacy } from 'sabre-client';
274
+
275
+ const legacy = new SabreLegacy({
276
+ environment: 'production',
277
+ authVersion: 'v2',
278
+ credentials: { username: 'V1:<user>:<PCC>:AA', password: process.env.SABRE_EPR_PASSWORD! },
279
+ });
280
+
281
+ const counts = await legacy.withSession((session) => session.queue.count());
282
+ ```
283
+
284
+ ### Sesiones
285
+
286
+ - **`withSession(callback)` es la forma recomendada.** Abre una sesión, se la presta al callback y
287
+ la cierra siempre, también si el callback lanza. Devuelve lo mismo que el callback. Si el cierre
288
+ falla, registra `sabre.session.closeFailed` y conserva el resultado o el error del callback.
289
+ - **`openSession(options?)`** sirve para controlar tú la vida de la sesión. Se cierra con
290
+ `try { … } finally { await session.close() }`. `close()` es idempotente. Si el runtime tiene
291
+ `Symbol.asyncDispose`, también funciona `await using`, pero los tipos no lo declaran.
292
+ - **Una sesión es exclusiva.** Sabre no admite llamadas concurrentes en una misma sesión, así que
293
+ una segunda llamada mientras otra está en vuelo lanza `TypeError: sabre-client: session busy`
294
+ sin llamar a la red. Las llamadas después de `close()` lanzan `session closed`.
295
+ - **Inactividad.** Sabre documenta un timeout de 15 minutos de inactividad (en producción, las
296
+ sesiones duraron más).
297
+ `session.idleMs` es el tiempo desde la última respuesta de Sabre, y `session.ping()` lo
298
+ reinicia. Con `openSession({ keepAlive: true })`, el SDK manda un ping a los 13 minutos de
299
+ inactividad. Viene apagado, porque un timer no sobrevive entre invocaciones serverless o edge.
300
+ - **Sin pool, y sin reabrir sola.** Cada `withSession` abre y cierra una sesión. Después de un
301
+ `SabreSessionError`, la sesión queda inutilizable: las llamadas siguientes lanzan
302
+ `session unusable` sin red, y `close()` resuelve sin red. El SDK no la reabre, porque el cursor
303
+ de la cola ya se perdió. Volver a empezar es decisión tuya.
304
+
305
+ ### Colas
306
+
307
+ Las operaciones de cola están en `session.queue`. `pcc` es opcional en cada llamada, y por defecto
308
+ es el de la sesión. Una cola es un número de 0 a 511, o `{ name }` para las colas alfabéticas
309
+ (`QueueRef`). Las colas que devuelve `count` se pasan tal cual a las demás operaciones.
310
+
311
+ | Método | Servicio de Sabre | Devuelve |
312
+ | - | - | - |
313
+ | `count({ pcc? })` | `QueueCountLLSRQ` | `{ queues: [{ queue, count }], totals, countedAt, warnings }` |
314
+ | `list({ queue, pcc?, primaryPassenger? })` | `QueueAccessLLSRQ` (`List`) | `{ locators, entries, paragraph, warnings }`, sin entrar a la cola |
315
+ | `access({ queue, pcc?, selection? })` | `QueueAccessLLSRQ` | `QueueStep` |
316
+ | `ignore()` | `QueueAccessLLSRQ` (`I`) | `QueueStep` |
317
+ | `remove({ from? })` | `QueueAccessLLSRQ` (`QR`) | `QueueStep` |
318
+ | `place({ to, locators? })` | `QueuePlaceLLSRQ` | `QueueStep` dentro de una cola, `{ placements, text, warnings }` fuera |
319
+ | `exit()` | `QueueAccessLLSRQ` (`QXI`) | `void` |
320
+
321
+ ```ts
322
+ type QueueStep =
323
+ | { done: false; item: QueueItem; warnings: SabreWarning[] }
324
+ | { done: true; reason: 'end-of-display' | 'empty'; message: string; warnings: SabreWarning[] };
325
+ ```
326
+
327
+ - **El fin de cola es un resultado, no un error.** `END OF DISPLAY FOR REQUESTED DATA` da
328
+ `{ done: true, reason: 'end-of-display' }`, y una cola vacía (`QUEUE SELECTED WAS EMPTY`) da
329
+ `reason: 'empty'`. En los dos casos la sesión queda fuera de la cola. Cualquier otro error de
330
+ Sabre se lanza como `SabreBusinessError`.
331
+ - **Las operaciones que avanzan devuelven el ítem siguiente**, así que no hace falta una llamada
332
+ extra por PNR. Dentro de una cola, `place` mueve el PNR actual a las colas de destino y devuelve
333
+ el siguiente.
334
+ - **El SDK lleva la cuenta de si la sesión está dentro de una cola** (`session.queue.state`:
335
+ `'outside'`, `'inside'` o `'unknown'`). Fuera de una cola, `ignore`, `remove` y `exit` lanzan un
336
+ `TypeError` sin llamar a la red. Lo mismo pasa con `access` estando dentro de una cola, y con
337
+ `place` con `locators` dentro de una cola o en `'unknown'`. Si una operación falla
338
+ dentro de una cola con un error que no es de sesión, el estado pasa a `'unknown'`, porque el SDK
339
+ no sabe si Sabre dejó la sesión adentro. Para salir, llama a `exit()`: con `'unknown'`, tolera
340
+ `CANNOT DO THIS IF OFF QUEUE`.
341
+ - **`place`**: dentro de una cola coloca el PNR actual. Con `locators` (hasta 6), coloca esos PNR
342
+ desde fuera de una cola. Sin ninguno de los dos, coloca el PNR del AAA.
343
+ - **`instructionCode` (el PIC) es obligatorio en cada destino de `place`.** Tiene que estar en
344
+ 0–191 o 200–254. Producción rechaza un `place` sin PIC, aunque el design lo dé como opcional.
345
+ - **`QueueItem`** trae `locator`, el `placedAt` y el `placedBy` de `Line`, `primaryPassenger` y
346
+ `group` cuando Sabre los manda, y `paragraph`: el PNR en formato de host, línea por línea y
347
+ sin tocar. `placedAt` es el texto que llegó, con o sin año. `resolveHostDate` lo convierte en una
348
+ fecha completa.
349
+ - `count` dentro de una cola no cuenta el PNR que está en pantalla.
350
+
351
+ ### Recorrer una cola con `process`
352
+
353
+ `process` entra a la cola, llama a tu callback por cada PNR, ejecuta la acción que devuelve y
354
+ termina en el fin de cola. No hace nada que no se pueda hacer con las primitivas: cubre el caso
355
+ común y se asegura de que la sesión salga de la cola.
356
+
357
+ ```ts
358
+ const result = await legacy.withSession((session) =>
359
+ session.queue.process({ queue: 12, limit: 200 }, (item) => {
360
+ if (isDone(item)) return { action: 'remove' };
361
+ if (needsReview(item)) return { action: 'place', to: [{ queue: 50, instructionCode: 11 }] };
362
+ return { action: 'ignore' };
363
+ }),
364
+ );
365
+ // result: { visited: number; ended: 'end-of-display' | 'empty' | 'limit' }
366
+ ```
367
+
368
+ - Al llegar a `limit`, sale de la cola con `QXI`. `limit` es opcional y no tiene tope por defecto,
369
+ pero conviene pasarlo.
370
+ - Si el callback lanza, `process` sale de la cola con `QXI` y propaga el error. No lo convierte en
371
+ `ignore`. Para seguir de largo, atrapa el error en el callback y devuelve `ignore`.
372
+
373
+ ### Leer el párrafo
374
+
375
+ `parseQueueParagraph(item.paragraph, { referenceDate })` es una función pura aparte, que no corre
376
+ sola. Devuelve `{ messages, passengers, segments, ticketing, receivedFrom, unparsed }`: los avisos
377
+ de cola (`011 SEE REMARKS` → `{ instructionCode: 11, text: 'SEE REMARKS' }`), los nombres, los
378
+ segmentos con su estado, el campo de ticketing con una marca `issued` y la línea de recibido.
379
+
380
+ - **Es best effort.** El formato de host no tiene contrato. Una línea que el parser no reconoce va
381
+ a `unparsed`, y el parser no lanza.
382
+ - **`referenceDate` es obligatorio.** Las fechas del host vienen sin año (`06SEP`), así que el año
383
+ se resuelve contra esa fecha. Los segmentos y el ticketing toman la fecha más cercana dentro de
384
+ ±6 meses, y la línea de recibido se resuelve hacia atrás. Las fechas salen como strings ISO sin
385
+ zona horaria.
386
+ - No lee los plazos que las aerolíneas escriben como texto libre en remarks, SSR u OSI. Su formato
387
+ lo pone cada aerolínea. El texto sigue en `paragraph`.
388
+
389
+ ### Probar tu lógica de colas: `sabre-client/testing`
390
+
391
+ `sabre-client/testing` es una entrada aparte, con un fake de las sesiones y las colas de Sabre. No
392
+ forma parte del bundle principal. `createQueueFake` devuelve un `fetch` que pasas en
393
+ `config.fetch`, así que tu test pasa por el transporte, la sesión y el parseo reales del SDK, sin
394
+ red:
395
+
396
+ ```ts
397
+ import { SabreLegacy } from 'sabre-client';
398
+ import { createQueueFake } from 'sabre-client/testing';
399
+
400
+ const fake = createQueueFake({
401
+ queues: {
402
+ '12': [
403
+ [' 1.1DOE/JANE', ' 1 XX 123Y 15DEC 2 MEXCUN HK1', 'ABCD.ABCD*AAA 1200/01NOV26 AAAAAA H'],
404
+ { locator: 'BBBBBB', paragraph: [' 1.1ROE/JOHN'] },
405
+ ],
406
+ },
407
+ });
408
+ const legacy = new SabreLegacy({ ...config, fetch: fake.fetch });
409
+
410
+ await legacy.withSession((session) => session.queue.process({ queue: 12 }, () => ({ action: 'remove' })));
411
+
412
+ fake.locators(12); // []
413
+ fake.openSessions; // 0: la sesión se cerró
414
+ ```
415
+
416
+ - Cada PNR es un párrafo, con el localizador tomado de su línea de firma, o
417
+ `{ locator, paragraph?, placedAt?, placedBy?, primaryPassenger? }`. A un párrafo sin línea de
418
+ firma se le agrega una sintética. `pnrs` agrega PNR que no están en ninguna cola, para
419
+ `place({ locators })`. El fake no tiene AAA: fuera de una cola, `place` necesita `locators`.
420
+ - Responde como producción: colas vacías, el fin de cola, `place` que mueve el PNR, el PIC
421
+ obligatorio y una sesión vencida con `fake.expireSessions()`. `fake.calls` lista lo que pidió el
422
+ SDK.
423
+ - **No simula** los PCC (las colas son las mismas para cualquier `pcc`) ni `selection`, que recibe
424
+ un error en vez de la cola entera.
425
+ - `queueFixtures` trae las respuestas anonimizadas que se grabaron en producción (`walk`,
426
+ `emptyQueue`, `place`…), como `{ operation, status, body }`.
427
+
428
+ `examples/queue-review.mjs` es un ejemplo completo, y su test lo corre contra el fake.
429
+
175
430
  ## Manejo de errores
176
431
 
177
432
  Todos los errores que lanza el SDK extienden `SabreError`:
@@ -181,6 +436,7 @@ Todos los errores que lanza el SDK extienden `SabreError`:
181
436
  | `SabreGatewayError` | Falla HTTP o del gateway (`ERR.2SG.*`, `401`, `429`, `5xx`, errores de OAuth) | `protocol`, `status`, `code` |
182
437
  | `SabreOrchestrationError` | Error de un servicio orquestado dentro de un `200` (`SP.*`) | `code` |
183
438
  | `SabreBusinessError` | Error del catálogo propio de un servicio (p. ej. `No Availability`, una reserva que no se creó) | `service`, `code`, `warnings`, `errors` |
439
+ | `SabreSessionError` | Falla de la sesión SOAP de `SabreLegacy`, subclase de `SabreGatewayError` | `kind`: `'limit'` (más de 50 sesiones), `'invalid'`, `'expired'`, `'blocked'` |
184
440
  | `SabreWarning` | Aviso no fatal (p. ej. `category: WARNING` en Create Booking) | `service`, `code` |
185
441
 
186
442
  Todos tienen `retryable`, que indica si la política de reintentos puede volver a intentar.
@@ -208,10 +464,12 @@ try {
208
464
 
209
465
  ## Respuesta cruda
210
466
 
211
- Toda operación REST (`air.bargainFinderMax`, `air.revalidateItinerary`, `booking.create`,
212
- `booking.get` y `booking.cancel`) acepta un callback `onRawResponse` en sus opciones, junto a
213
- `signal`. Recibe la respuesta de Sabre tal cual llegó, por ejemplo para guardarla junto al
214
- resultado tipado:
467
+ Toda operación REST (`air.bargainFinderMax`, `air.revalidateItinerary`, `air.getSeats`,
468
+ `booking.create`, `booking.get` y `booking.cancel`), y toda operación de `SabreLegacy`, acepta un
469
+ callback `onRawResponse` en sus
470
+ opciones, junto a `signal`. Recibe la respuesta de Sabre tal cual llegó, por ejemplo para
471
+ guardarla junto al resultado tipado. En `air.getSeats`, `operation` indica el endpoint que se
472
+ llamó (`air.getSeatsByPayload` o `air.getSeatsByPnrLocator`):
215
473
 
216
474
  ```ts
217
475
  import type { SabreRawResponse } from 'sabre-client';
@@ -231,6 +489,10 @@ const booking = await sabre.booking.get({ confirmationId }, { onRawResponse: (r)
231
489
  nunca hace que `booking.create` pierda su `confirmationId`. Para guardar de forma asíncrona,
232
490
  conserva el valor y escríbelo cuando la llamada vuelva.
233
491
 
492
+ 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
494
+ (`BinarySecurityToken`) se reemplaza por `[REDACTED]` donde aparezca. No se cambia nada más.
495
+
234
496
  > [!WARNING]
235
497
  > `body` es la respuesta cruda y **trae datos de pasajeros**. El SDK nunca lo manda al logger.
236
498
  > Dónde lo guardas es tu responsabilidad.
@@ -242,6 +504,12 @@ Solo se reintentan las operaciones idempotentes: Bargain Finder Max y Revalidate
242
504
  backoff exponencial con full jitter y un piso de 500 ms, ante `429`, `500`, `503`, `504`,
243
505
  `SP.PROVIDER_ERROR` y los errores temporales del catálogo de Bargain Finder Max.
244
506
 
507
+ En `SabreLegacy`, `count`, `list` y `ping` son lecturas y se reintentan ante fallos transitorios.
508
+ Las operaciones que mueven el cursor de la cola (`access`, `ignore`, `remove`, `place`, `exit`) se
509
+ reintentan solo ante los `USG_*_BUSY`, que se toman como señal de que Sabre no procesó el request, y ante
510
+ `PREVIOUS ENTRY ACTIVE` (3 × 1 s). Nunca se reintentan después de un fallo de red, porque el PNR
511
+ pudo haberse movido: el estado de la cola pasa a `'unknown'`.
512
+
245
513
  ## Reutilizar el token
246
514
 
247
515
  Por defecto el token vive en memoria, uno por instancia de `Sabre`. Para compartirlo entre
@@ -285,7 +553,9 @@ const sabre = new Sabre({
285
553
 
286
554
  El SDK registra eventos estructurados (`sabre.request.start`, `sabre.request.end`,
287
555
  `sabre.request.retry`, `sabre.request.rawResponseHookFailed`, `sabre.auth.token.refresh`,
288
- `sabre.auth.token.store`) y nunca los bodies
556
+ `sabre.auth.token.store`, `sabre.booking.seat.priced`, `sabre.session.open`,
557
+ `sabre.session.close`, `sabre.session.closeFailed`, `sabre.session.keepAliveFailed`,
558
+ `sabre.queue.exitTolerated`, `sabre.queue.exitFailed`) y nunca los bodies
289
559
  crudos de request o response. Los secretos, el header `Authorization` y los datos de pasajeros
290
560
  se redactan siempre antes de llegar a tu logger.
291
561