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 +282 -12
- package/README.md +279 -12
- package/dist/index.d.ts +1023 -5
- package/dist/index.js +2072 -101
- package/dist/index.js.map +1 -1
- package/dist/queue-ref-BDGh8FrV.d.ts +11 -0
- package/dist/testing.d.ts +92 -0
- package/dist/testing.js +2874 -0
- package/dist/testing.js.map +1 -0
- package/package.json +8 -1
package/README.md
CHANGED
|
@@ -12,16 +12,15 @@ TypeScript SDK for the Sabre APIs.
|
|
|
12
12
|
|
|
13
13
|
## Status
|
|
14
14
|
|
|
15
|
-
`0.3.0` is
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
Prereleases of the rewrite are published under the `next` tag:
|
|
15
|
+
`0.3.0` is the first 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).
|
|
19
17
|
|
|
20
18
|
```sh
|
|
21
|
-
npm install sabre-client
|
|
19
|
+
npm install sabre-client
|
|
22
20
|
```
|
|
23
21
|
|
|
24
|
-
The
|
|
22
|
+
The package is still `0.x`: a minor version may include breaking changes, and the
|
|
23
|
+
[CHANGELOG](./CHANGELOG.md) marks them.
|
|
25
24
|
|
|
26
25
|
Tier 1 covers the core Air Booking flow over REST:
|
|
27
26
|
|
|
@@ -33,7 +32,22 @@ Tier 1 covers the core Air Booking flow over REST:
|
|
|
33
32
|
| Booking | Create Booking (Booking Management API v1, ATPCO by `flightDetails`) | `sabre.booking.create` |
|
|
34
33
|
| Booking management | Get Booking and Cancel Booking (Booking Management API v1) | `sabre.booking.get`, `sabre.booking.cancel` |
|
|
35
34
|
|
|
36
|
-
|
|
35
|
+
Tier 2 adds seat selection:
|
|
36
|
+
|
|
37
|
+
| Step | Service | Method |
|
|
38
|
+
| - | - | - |
|
|
39
|
+
| Seat map | Get Seats v3 (`byReservationPayload` and `byPnrLocator`) | `sabre.air.getSeats` |
|
|
40
|
+
|
|
41
|
+
Tier 3 adds the `SabreLegacy` SOAP client, with a stateful session and Queue Management (see
|
|
42
|
+
[SOAP sessions and queues](#soap-sessions-and-queues-sabrelegacy)):
|
|
43
|
+
|
|
44
|
+
| Step | Service | Method |
|
|
45
|
+
| - | - | - |
|
|
46
|
+
| Session | `SessionCreateRQ` 2.0.0 / 1.0.0, `OTA_PingRQ`, `SessionCloseRQ` | `legacy.withSession`, `legacy.openSession` |
|
|
47
|
+
| Queues | Get Queue Count, Access Queue, Place Queue Message | `session.queue.count`, `list`, `access`, `ignore`, `remove`, `place`, `exit`, `process` |
|
|
48
|
+
| Queue paragraph | — (pure function) | `parseQueueParagraph`, `resolveHostDate` |
|
|
49
|
+
|
|
50
|
+
Planned after `0.3.0`: Get Ancillaries (NDC only).
|
|
37
51
|
|
|
38
52
|
## Requirements
|
|
39
53
|
|
|
@@ -171,6 +185,244 @@ Every request accepts an `AbortSignal`. Shopping calls also accept a per-request
|
|
|
171
185
|
await sabre.air.bargainFinderMax(search, { signal, retry: { maxRetries: 1 } });
|
|
172
186
|
```
|
|
173
187
|
|
|
188
|
+
## Choosing a seat
|
|
189
|
+
|
|
190
|
+
`sabre.air.getSeats` returns the seat map with availability and prices. It is one function
|
|
191
|
+
for both Get Seats endpoints: without `pnrLocator` it goes to `byReservationPayload` (before
|
|
192
|
+
booking), with `pnrLocator` it goes to `byPnrLocator` (an existing PNR).
|
|
193
|
+
|
|
194
|
+
Before booking, build the request from the revalidated itinerary with
|
|
195
|
+
`buildSeatAvailabilityRequest`, pick a seat, and pass it to `buildBookingFlightDetails`:
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
import { buildBookingFlightDetails, buildSeatAvailabilityRequest } from 'sabre-client';
|
|
199
|
+
|
|
200
|
+
// After step 2 of "Booking a flight": `confirmedGroup` and `confirmed`.
|
|
201
|
+
const seatMap = await sabre.air.getSeats(
|
|
202
|
+
buildSeatAvailabilityRequest({ group: confirmedGroup, itinerary: confirmed, currencyCode: 'USD' }),
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// seatMaps[].segmentRef is S1, S2, … in itinerary order. `occupationStatusCode: 'F'` is a free
|
|
206
|
+
// seat; one with `offerItemRefIds` may have a price (offerItems[].priceDefinitionRef →
|
|
207
|
+
// priceDefinitions[]). Here: the first free seat of the first flight with no offer item.
|
|
208
|
+
const rows = seatMap.response.seatMaps
|
|
209
|
+
.filter((map) => map.segmentRef === 'S1')
|
|
210
|
+
.flatMap((map) => map.cabinCompartments ?? [])
|
|
211
|
+
.flatMap((cabin) => cabin.seatRows ?? []);
|
|
212
|
+
const seat = rows
|
|
213
|
+
.flatMap((row) => row.seats.map((s) => ({ number: `${row.row}${s.column}`, ...s })))
|
|
214
|
+
.find((s) => s.occupationStatusCode === 'F' && (s.offerItemRefIds ?? []).length === 0);
|
|
215
|
+
|
|
216
|
+
const booking = await sabre.booking.create({
|
|
217
|
+
flightDetails: buildBookingFlightDetails({
|
|
218
|
+
group: confirmedGroup,
|
|
219
|
+
itinerary: confirmed,
|
|
220
|
+
// Both indexes start at 1: first flight of the itinerary, first traveler.
|
|
221
|
+
seats: seat === undefined ? [] : [{ segmentIndex: 1, travelerIndex: 1, number: seat.number }],
|
|
222
|
+
seatAvailability: seatMap,
|
|
223
|
+
}),
|
|
224
|
+
travelers: [{ givenName: 'JANE', surname: 'DOE', passengerCode: 'ADT' }],
|
|
225
|
+
contactInfo: { emails: ['jane@example.com'], phones: ['5550000000'] },
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
For a booking that already exists, ask by locator:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
const pnrSeats = await sabre.air.getSeats({ pnrLocator: booking.confirmationId! });
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Things to know:
|
|
236
|
+
|
|
237
|
+
- **Seats are booked by number, ATPCO only.** The seat goes to `flightDetails.flights[].seats[]`
|
|
238
|
+
(`BookSeat`). `seatOffers` and the Get Seats `offerId` are NDC only and are not supported.
|
|
239
|
+
`areaPreferences` and `changeOfGaugeSeats` are not supported by the helper either.
|
|
240
|
+
- **Prefer free seats.** It is not known what Sabre does with a chargeable seat requested by
|
|
241
|
+
number through ATPCO. If you pass `seatAvailability` and the chosen seat has a price,
|
|
242
|
+
`sabre.booking.create` logs a `sabre.booking.seat.priced` warning (segment, flight, seat,
|
|
243
|
+
amount and currency, no passenger data) and books anyway.
|
|
244
|
+
- **Local validation.** A seat number that does not match `^[0-9]+[A-Z]$`, a `segmentIndex` or
|
|
245
|
+
`travelerIndex` that does not exist, or a `pnrLocator` that is empty or mixed with payload
|
|
246
|
+
fields (`segments`, `passengers`, `returnSeatMapsOnlyForSegmentRefs`) throws a `TypeError`
|
|
247
|
+
before any request is sent.
|
|
248
|
+
- **Errors.** Get Seats has no published error catalog. `errors[]` without a seat map throws a
|
|
249
|
+
non-retryable `SabreBusinessError`; with a seat map, errors and `warnings[]` come back in
|
|
250
|
+
`seatMap.errors` and `seatMap.warnings`. Both endpoints are reads, so gateway `429`/`5xx`
|
|
251
|
+
are retried.
|
|
252
|
+
- **`byPnrLocator` only reads PNRs from the PCC you authenticate with.** Its request has no
|
|
253
|
+
`targetPcc`, unlike Create, Get and Cancel Booking. If the booking was created with
|
|
254
|
+
`targetPcc` in another PCC, Sabre answers `502` with `Viewership is restricted for the PNR`,
|
|
255
|
+
which surfaces as a `SabreGatewayError`.
|
|
256
|
+
- **What has been checked.** `byReservationPayload` was checked read-only in production, for
|
|
257
|
+
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` has not been
|
|
259
|
+
checked against Sabre yet, because the only sandbox bookings available live in another PCC.
|
|
260
|
+
Prices arrive as numbers even though the spec says strings, so the types accept both.
|
|
261
|
+
|
|
262
|
+
## SOAP sessions and queues (`SabreLegacy`)
|
|
263
|
+
|
|
264
|
+
`SabreLegacy` is the client for Sabre's SOAP services. It takes the same `SabreConfig` as `Sabre`.
|
|
265
|
+
The session's user, PCC and domain come from `credentials.username`
|
|
266
|
+
(`V1:<user>:<PCC>:<domain>` or `<user>-<PCC>-<domain>`). The `SessionCreateRQ` version comes from
|
|
267
|
+
the credentials: 2.0.0 with `clientId` and `clientSecret`, 1.0.0 without them. Passing only one of
|
|
268
|
+
the two, or a `username` in neither format, throws a `TypeError` when the client is created.
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
import { SabreLegacy } from 'sabre-client';
|
|
272
|
+
|
|
273
|
+
const legacy = new SabreLegacy({
|
|
274
|
+
environment: 'production',
|
|
275
|
+
authVersion: 'v2',
|
|
276
|
+
credentials: { username: 'V1:<user>:<PCC>:AA', password: process.env.SABRE_EPR_PASSWORD! },
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const counts = await legacy.withSession((session) => session.queue.count());
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Sessions
|
|
283
|
+
|
|
284
|
+
- **`withSession(callback)` is the recommended form.** It opens a session, lends it to the
|
|
285
|
+
callback and always closes it, also when the callback throws. It returns what the callback
|
|
286
|
+
returns. If closing fails, it logs `sabre.session.closeFailed` and keeps the callback's result
|
|
287
|
+
or error.
|
|
288
|
+
- **`openSession(options?)`** is for controlling the session's lifetime yourself. Close it with
|
|
289
|
+
`try { … } finally { await session.close() }`. `close()` is idempotent. If the runtime has
|
|
290
|
+
`Symbol.asyncDispose`, `await using` works too, but the types do not declare it.
|
|
291
|
+
- **A session is exclusive.** Sabre does not allow concurrent calls on one session, so a second
|
|
292
|
+
call while another is in flight throws `TypeError: sabre-client: session busy` without calling
|
|
293
|
+
the network. Calls after `close()` throw `session closed`.
|
|
294
|
+
- **Inactivity.** Sabre documents a 15-minute inactivity timeout (in production, sessions have
|
|
295
|
+
lasted longer). `session.idleMs` is the
|
|
296
|
+
time since Sabre last answered, and `session.ping()` resets it. With
|
|
297
|
+
`openSession({ keepAlive: true })`, the SDK pings after 13 minutes of inactivity. It is off by
|
|
298
|
+
default, because a timer does not survive between serverless or edge invocations.
|
|
299
|
+
- **No pool, and no automatic reopening.** Each `withSession` opens and closes one session. After
|
|
300
|
+
a `SabreSessionError`, the session is unusable: later calls throw `session unusable` without
|
|
301
|
+
calling the network, and `close()` resolves without calling it. The SDK does not reopen the
|
|
302
|
+
session, because the queue cursor is already lost. Starting over is your decision.
|
|
303
|
+
|
|
304
|
+
### Queues
|
|
305
|
+
|
|
306
|
+
The queue operations live in `session.queue`. `pcc` is optional in every call and defaults to the
|
|
307
|
+
session's PCC. A queue is a number from 0 to 511 or `{ name }` for alphabetic queues
|
|
308
|
+
(`QueueRef`). The queues returned by `count` can be passed straight to the other operations.
|
|
309
|
+
|
|
310
|
+
| Method | Sabre service | Returns |
|
|
311
|
+
| - | - | - |
|
|
312
|
+
| `count({ pcc? })` | `QueueCountLLSRQ` | `{ queues: [{ queue, count }], totals, countedAt, warnings }` |
|
|
313
|
+
| `list({ queue, pcc?, primaryPassenger? })` | `QueueAccessLLSRQ` (`List`) | `{ locators, entries, paragraph, warnings }`, without entering the queue |
|
|
314
|
+
| `access({ queue, pcc?, selection? })` | `QueueAccessLLSRQ` | `QueueStep` |
|
|
315
|
+
| `ignore()` | `QueueAccessLLSRQ` (`I`) | `QueueStep` |
|
|
316
|
+
| `remove({ from? })` | `QueueAccessLLSRQ` (`QR`) | `QueueStep` |
|
|
317
|
+
| `place({ to, locators? })` | `QueuePlaceLLSRQ` | `QueueStep` inside a queue, `{ placements, text, warnings }` outside |
|
|
318
|
+
| `exit()` | `QueueAccessLLSRQ` (`QXI`) | `void` |
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
type QueueStep =
|
|
322
|
+
| { done: false; item: QueueItem; warnings: SabreWarning[] }
|
|
323
|
+
| { done: true; reason: 'end-of-display' | 'empty'; message: string; warnings: SabreWarning[] };
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
- **The end of the queue is a result, not an error.** `END OF DISPLAY FOR REQUESTED DATA` gives
|
|
327
|
+
`{ done: true, reason: 'end-of-display' }`, and an empty queue (`QUEUE SELECTED WAS EMPTY`)
|
|
328
|
+
gives `reason: 'empty'`. In both cases the session is left outside the queue. Any other Sabre
|
|
329
|
+
error is thrown as a `SabreBusinessError`.
|
|
330
|
+
- **The operations that move forward return the next item**, so there is no extra call per PNR.
|
|
331
|
+
Inside a queue, `place` moves the current PNR to the target queues and returns the next one.
|
|
332
|
+
- **The SDK tracks whether the session is inside a queue** (`session.queue.state`: `'outside'`,
|
|
333
|
+
`'inside'` or `'unknown'`). `ignore`, `remove` and `exit` outside a queue throw a `TypeError`
|
|
334
|
+
without calling the network. So do `access` while inside a queue, and `place` with `locators`
|
|
335
|
+
inside a queue or in `'unknown'`. When an operation fails inside a queue with an error that is
|
|
336
|
+
not a session error, the state becomes `'unknown'`, because the SDK cannot tell whether Sabre
|
|
337
|
+
left the session inside. Call `exit()` to get out: in `'unknown'`, it tolerates
|
|
338
|
+
`CANNOT DO THIS IF OFF QUEUE`.
|
|
339
|
+
- **`place`**: inside a queue it places the current PNR. With `locators` (up to 6), it places
|
|
340
|
+
those PNRs from outside a queue. Without either, it places the PNR in the AAA.
|
|
341
|
+
- **`instructionCode` (the PIC) is required in every `place` target.** It must be 0–191 or 200–254.
|
|
342
|
+
Production rejects a `place` without it, although the design says it is optional.
|
|
343
|
+
- **`QueueItem`** has `locator`, the `placedAt` and `placedBy` of `Line`, `primaryPassenger` and
|
|
344
|
+
`group` when Sabre sends them, and `paragraph`: the PNR in host format, line by line and
|
|
345
|
+
untouched. `placedAt` is the text that arrived, with or without the year. `resolveHostDate`
|
|
346
|
+
turns it into a full date.
|
|
347
|
+
- `count` inside a queue does not count the PNR on screen.
|
|
348
|
+
|
|
349
|
+
### Walking a queue with `process`
|
|
350
|
+
|
|
351
|
+
`process` enters the queue, calls your callback for each PNR, runs the action it returns and stops
|
|
352
|
+
at the end of the queue. It does nothing the primitives cannot do: it covers the common case and
|
|
353
|
+
makes sure the session leaves the queue.
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
const result = await legacy.withSession((session) =>
|
|
357
|
+
session.queue.process({ queue: 12, limit: 200 }, (item) => {
|
|
358
|
+
if (isDone(item)) return { action: 'remove' };
|
|
359
|
+
if (needsReview(item)) return { action: 'place', to: [{ queue: 50, instructionCode: 11 }] };
|
|
360
|
+
return { action: 'ignore' };
|
|
361
|
+
}),
|
|
362
|
+
);
|
|
363
|
+
// result: { visited: number; ended: 'end-of-display' | 'empty' | 'limit' }
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
- When it reaches `limit`, it leaves the queue with `QXI`. `limit` is optional and has no default
|
|
367
|
+
cap, but passing one is recommended.
|
|
368
|
+
- If the callback throws, `process` leaves the queue with `QXI` and rethrows. It does not turn the
|
|
369
|
+
error into `ignore`. To carry on, catch the error in the callback and return `ignore`.
|
|
370
|
+
|
|
371
|
+
### Reading the paragraph
|
|
372
|
+
|
|
373
|
+
`parseQueueParagraph(item.paragraph, { referenceDate })` is a separate pure function that does not
|
|
374
|
+
run on its own. It returns `{ messages, passengers, segments, ticketing, receivedFrom, unparsed }`:
|
|
375
|
+
the queue notices (`011 SEE REMARKS` → `{ instructionCode: 11, text: 'SEE REMARKS' }`), names,
|
|
376
|
+
segments with their status, the ticketing field with an `issued` flag, and the received-from line.
|
|
377
|
+
|
|
378
|
+
- **It is best effort.** The host format has no contract. A line the parser does not recognize
|
|
379
|
+
goes to `unparsed`, and the parser does not throw.
|
|
380
|
+
- **`referenceDate` is required.** Host dates have no year (`06SEP`), so the year is resolved
|
|
381
|
+
against that date. Segments and ticketing take the nearest date within ±6 months, and the
|
|
382
|
+
received-from line goes backwards. Dates come out as ISO strings without a time zone.
|
|
383
|
+
- It does not read deadlines that airlines write as free text in remarks, SSR or OSI. Their format
|
|
384
|
+
belongs to each airline. The text is still in `paragraph`.
|
|
385
|
+
|
|
386
|
+
### Testing your queue logic: `sabre-client/testing`
|
|
387
|
+
|
|
388
|
+
`sabre-client/testing` is a separate entry point with a fake of Sabre's sessions and queues. It is
|
|
389
|
+
not part of the main bundle. `createQueueFake` returns a `fetch` that you pass in `config.fetch`,
|
|
390
|
+
so your test goes through the SDK's real transport, session and parsing, without the network:
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
import { SabreLegacy } from 'sabre-client';
|
|
394
|
+
import { createQueueFake } from 'sabre-client/testing';
|
|
395
|
+
|
|
396
|
+
const fake = createQueueFake({
|
|
397
|
+
queues: {
|
|
398
|
+
'12': [
|
|
399
|
+
[' 1.1DOE/JANE', ' 1 XX 123Y 15DEC 2 MEXCUN HK1', 'ABCD.ABCD*AAA 1200/01NOV26 AAAAAA H'],
|
|
400
|
+
{ locator: 'BBBBBB', paragraph: [' 1.1ROE/JOHN'] },
|
|
401
|
+
],
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
const legacy = new SabreLegacy({ ...config, fetch: fake.fetch });
|
|
405
|
+
|
|
406
|
+
await legacy.withSession((session) => session.queue.process({ queue: 12 }, () => ({ action: 'remove' })));
|
|
407
|
+
|
|
408
|
+
fake.locators(12); // []
|
|
409
|
+
fake.openSessions; // 0: the session was closed
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
- Each PNR is a paragraph, with the locator taken from its signature line, or
|
|
413
|
+
`{ locator, paragraph?, placedAt?, placedBy?, primaryPassenger? }`. A paragraph without a
|
|
414
|
+
signature line gets a synthetic one. `pnrs` adds PNRs that are not in any queue, for
|
|
415
|
+
`place({ locators })`. The fake has no AAA: outside a queue, `place` needs `locators`.
|
|
416
|
+
- It answers like production: empty queues, the end of the queue, `place` moving the PNR, the
|
|
417
|
+
required PIC, and an expired session with `fake.expireSessions()`. `fake.calls` lists what the
|
|
418
|
+
SDK asked for.
|
|
419
|
+
- **Not simulated**: PCCs (the queues are the same for any `pcc`) and `selection`, which gets an
|
|
420
|
+
error instead of the whole queue.
|
|
421
|
+
- `queueFixtures` holds the anonymized responses recorded in production (`walk`, `emptyQueue`,
|
|
422
|
+
`place`, …), as `{ operation, status, body }`.
|
|
423
|
+
|
|
424
|
+
`examples/queue-review.mjs` is a full example, and its test runs it against the fake.
|
|
425
|
+
|
|
174
426
|
## Error handling
|
|
175
427
|
|
|
176
428
|
Every error thrown by the SDK extends `SabreError`:
|
|
@@ -180,6 +432,7 @@ Every error thrown by the SDK extends `SabreError`:
|
|
|
180
432
|
| `SabreGatewayError` | HTTP or gateway failure (`ERR.2SG.*`, `401`, `429`, `5xx`, OAuth errors) | `protocol`, `status`, `code` |
|
|
181
433
|
| `SabreOrchestrationError` | Orchestrated service error inside a `200` (`SP.*`) | `code` |
|
|
182
434
|
| `SabreBusinessError` | Error from a service's own catalog (e.g. `No Availability`, a booking that was not created) | `service`, `code`, `warnings`, `errors` |
|
|
435
|
+
| `SabreSessionError` | SOAP session failure in `SabreLegacy`, a subclass of `SabreGatewayError` | `kind`: `'limit'` (more than 50 sessions), `'invalid'`, `'expired'`, `'blocked'` |
|
|
183
436
|
| `SabreWarning` | Non-fatal warning (e.g. `category: WARNING` in Create Booking) | `service`, `code` |
|
|
184
437
|
|
|
185
438
|
All of them have `retryable`, which says whether the retry policy may try again.
|
|
@@ -207,10 +460,12 @@ try {
|
|
|
207
460
|
|
|
208
461
|
## Raw response
|
|
209
462
|
|
|
210
|
-
Every REST operation (`air.bargainFinderMax`, `air.revalidateItinerary`, `
|
|
211
|
-
`booking.get` and `booking.cancel`)
|
|
212
|
-
`
|
|
213
|
-
|
|
463
|
+
Every REST operation (`air.bargainFinderMax`, `air.revalidateItinerary`, `air.getSeats`,
|
|
464
|
+
`booking.create`, `booking.get` and `booking.cancel`), and every `SabreLegacy` operation, accepts
|
|
465
|
+
an `onRawResponse` callback in its
|
|
466
|
+
options, next to `signal`. It receives Sabre's response as it arrived, for example to store it
|
|
467
|
+
next to the typed result. For `air.getSeats`, `operation` names the endpoint that was called
|
|
468
|
+
(`air.getSeatsByPayload` or `air.getSeatsByPnrLocator`):
|
|
214
469
|
|
|
215
470
|
```ts
|
|
216
471
|
import type { SabreRawResponse } from 'sabre-client';
|
|
@@ -230,6 +485,10 @@ const booking = await sabre.booking.get({ confirmationId }, { onRawResponse: (r)
|
|
|
230
485
|
failing callback never makes `booking.create` lose its `confirmationId`. To persist
|
|
231
486
|
asynchronously, keep the value and write it after the call returns.
|
|
232
487
|
|
|
488
|
+
In `SabreLegacy`, `protocol` is `'soap'`, `body` is the whole SOAP envelope, and `operation` is
|
|
489
|
+
`'openSession'`, `'close'`, `'ping'` or `'queue.<method>'`. The session token
|
|
490
|
+
(`BinarySecurityToken`) is replaced by `[REDACTED]` wherever it appears. Nothing else is changed.
|
|
491
|
+
|
|
233
492
|
> [!WARNING]
|
|
234
493
|
> `body` is the raw response and **contains passenger data**. The SDK never sends it to the
|
|
235
494
|
> logger. Where you store it is up to you.
|
|
@@ -241,6 +500,12 @@ are read-only `POST`s). Create Booking is never retried. Retries use exponential
|
|
|
241
500
|
full jitter and a 500 ms floor, on `429`, `500`, `503`, `504`, `SP.PROVIDER_ERROR` and the
|
|
242
501
|
temporary errors in the Bargain Finder Max catalog.
|
|
243
502
|
|
|
503
|
+
In `SabreLegacy`, `count`, `list` and `ping` are reads and are retried on transient failures. The
|
|
504
|
+
operations that move the queue cursor (`access`, `ignore`, `remove`, `place`, `exit`) are retried
|
|
505
|
+
only on the `USG_*_BUSY` codes, which are taken to mean that Sabre did not process the request, and on
|
|
506
|
+
`PREVIOUS ENTRY ACTIVE` (3 × 1 s). They are never retried after a network failure, because the PNR
|
|
507
|
+
may already have moved: the queue state becomes `'unknown'`.
|
|
508
|
+
|
|
244
509
|
## Token reuse
|
|
245
510
|
|
|
246
511
|
By default the token lives in memory, one per `Sabre` instance. To share it between
|
|
@@ -285,7 +550,9 @@ const sabre = new Sabre({
|
|
|
285
550
|
|
|
286
551
|
The SDK logs structured events (`sabre.request.start`, `sabre.request.end`,
|
|
287
552
|
`sabre.request.retry`, `sabre.request.rawResponseHookFailed`, `sabre.auth.token.refresh`,
|
|
288
|
-
`sabre.auth.token.store`
|
|
553
|
+
`sabre.auth.token.store`, `sabre.booking.seat.priced`, `sabre.session.open`,
|
|
554
|
+
`sabre.session.close`, `sabre.session.closeFailed`, `sabre.session.keepAliveFailed`,
|
|
555
|
+
`sabre.queue.exitTolerated`, `sabre.queue.exitFailed`), never raw
|
|
289
556
|
request or response bodies. Secrets, the `Authorization` header and passenger data are always
|
|
290
557
|
redacted before they reach your logger.
|
|
291
558
|
|