sabre-client 0.5.0 → 0.6.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 +70 -11
- package/README.md +70 -11
- 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 +61 -10
- package/dist/index.js +82 -17
- package/dist/index.js.map +1 -1
- package/dist/{reservation-history-bKDqkf8T.d.ts → reservation-history-C6giSdFd.d.ts} +33 -3
- package/dist/testing.d.ts +20 -57
- package/dist/testing.js +423 -24
- package/dist/testing.js.map +1 -1
- package/package.json +5 -1
package/README.es.md
CHANGED
|
@@ -12,14 +12,16 @@ SDK de TypeScript para las APIs de Sabre.
|
|
|
12
12
|
|
|
13
13
|
## Estado
|
|
14
14
|
|
|
15
|
-
La `0.
|
|
15
|
+
La `0.6.0` está publicada con el dist-tag `next` mientras se prueba; `latest` sigue en la `0.5.1`.
|
|
16
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
17
|
el Tier 4: actividad de colas, historial del PNR, la cola de mensajes `S`, `selection` en `list` y
|
|
18
|
-
`onRawRequest`.
|
|
18
|
+
`onRawRequest`. La `0.6.0` suma filtros de cola: `count` con una cola, un rango o `selection`,
|
|
19
|
+
`activity` con una cola o un rango, y `selection` en el fake de colas. No trae cambios
|
|
20
|
+
incompatibles; la línea `0.5` trae tres (ver [Actualizar desde la 0.4](#actualizar-desde-la-04)).
|
|
19
21
|
|
|
20
22
|
```sh
|
|
21
|
-
npm install sabre-client@next # 0.
|
|
22
|
-
npm install sabre-client # 0.
|
|
23
|
+
npm install sabre-client@next # 0.6.0
|
|
24
|
+
npm install sabre-client # 0.5.1
|
|
23
25
|
```
|
|
24
26
|
|
|
25
27
|
El paquete sigue en `0.x`: una versión minor puede traer cambios incompatibles, y el
|
|
@@ -445,7 +447,15 @@ mensajes** del PCC: tiene mensajes y no PNR, y Sabre no la lista, así que `list
|
|
|
445
447
|
lanza un `TypeError` sin llamar a Sabre. Se recorre a mano: `access({ queue: 'S' })` devuelve el
|
|
446
448
|
primer mensaje (`kind: 'message'`) e `ignore()` el siguiente, sin sacarlo de la cola. `process` y
|
|
447
449
|
`place` dentro de la `S` lanzan un `TypeError` sin llamar a Sabre. `remove()` manda `QR`, pero
|
|
448
|
-
todavía no hay evidencia de qué hace Sabre con el mensaje.
|
|
450
|
+
todavía no hay evidencia de qué hace Sabre con el mensaje. Para distinguirla, `isMessageQueue(queue)`
|
|
451
|
+
devuelve `true` solo con `'S'`, y `MESSAGE_QUEUE` es `'S'`:
|
|
452
|
+
|
|
453
|
+
```ts
|
|
454
|
+
import { isMessageQueue } from 'sabre-client';
|
|
455
|
+
|
|
456
|
+
const { queues } = await session.queue.count();
|
|
457
|
+
const pnrQueues = queues.filter(({ queue }) => !isMessageQueue(queue));
|
|
458
|
+
```
|
|
449
459
|
|
|
450
460
|
> **Migración desde la 0.4.x**: `count` devolvía la cola `S` como `{ name: 'S' }`, que Sabre
|
|
451
461
|
> rechaza (`INVALID QUEUE NAME FORMAT`). Ahora devuelve `'S'`. Un código como
|
|
@@ -455,8 +465,8 @@ todavía no hay evidencia de qué hace Sabre con el mensaje.
|
|
|
455
465
|
|
|
456
466
|
| Método | Servicio de Sabre | Devuelve |
|
|
457
467
|
| - | - | - |
|
|
458
|
-
| `count({ pcc? })` | `QueueCountLLSRQ` | `{ queues: [{ queue, count }], totals, countedAt, warnings }` |
|
|
459
|
-
| `activity({ pcc? })` | `QueueAnalysisLLSRQ` | `{ queues: [{ queue, numMessages, ended, ignored, … }], totals, analyzedAt, warnings }` |
|
|
468
|
+
| `count({ pcc?, queue?, range?, selection? })` | `QueueCountLLSRQ` | `{ queues: [{ queue, count }], totals, countedAt, additionalInfo?, warnings }` |
|
|
469
|
+
| `activity({ pcc?, queue?, range? })` | `QueueAnalysisLLSRQ` | `{ queues: [{ queue, numMessages, ended, ignored, … }], totals, analyzedAt, warnings }` |
|
|
460
470
|
| `list({ queue, pcc?, primaryPassenger?, selection? })` | `QueueAccessLLSRQ` (`List`) | `{ locators, entries, paragraph, warnings }`, sin entrar a la cola |
|
|
461
471
|
| `access({ queue, pcc?, selection? })` | `QueueAccessLLSRQ` | `QueueStep` |
|
|
462
472
|
| `ignore()` | `QueueAccessLLSRQ` (`I`) | `QueueStep` |
|
|
@@ -480,12 +490,29 @@ type QueueStep =
|
|
|
480
490
|
agente que lo colocó, PCC más firma, p. ej. `'PCC1AAT'`; todavía no se probó en la lista). Otro
|
|
481
491
|
código lanza `TypeError` sin llamar a Sabre. El `placedAt` de una entrada de la lista es cuándo
|
|
482
492
|
se colocó el PNR en la cola, la fecha por la que filtra `QD`.
|
|
493
|
+
- **Filtros de `count`**: `queue` cuenta una sola cola, y `range: { start, end }`, un rango de
|
|
494
|
+
colas numéricas; no se combinan. Con `range`, Sabre devuelve cada cola del rango, **también las
|
|
495
|
+
vacías**, con `count: 0` (sin filtros las omite). `selection` admite hasta 8 filtros, como
|
|
496
|
+
`access`, y cuenta solo los PNR que cumplen: el host acepta cualquier código (`QD`, `AK`, `AL`,
|
|
497
|
+
`PI`, `AC`… y combinaciones), y con el mismo `QD` la cuenta coincide con `list`. Necesita
|
|
498
|
+
`queue`: sin cola, Sabre responde `QUEUE SELECTED NOT VALID OR CLOSED`, y con un rango,
|
|
499
|
+
`KEYS NOT ALLOWED`, así que el SDK lanza `TypeError` antes. Con `selection`, `totals.unfiltered`
|
|
500
|
+
es cuántos PNR tiene la cola sin filtrar (el `1 MATCHING PNRS OUT OF 80` del host); con `queue`
|
|
501
|
+
sola, Sabre no manda `totals`. `additionalInfo` lee `AdditionalQueueInfo` (una cola transferida a
|
|
502
|
+
otra), tipado desde el design: ninguna respuesta real lo trajo todavía.
|
|
503
|
+
|
|
504
|
+
```ts
|
|
505
|
+
const { queues, totals } = await session.queue.count({ queue: 20, selection: [{ function: 'QD', criteria: '08NOV' }] });
|
|
506
|
+
// queues: [{ queue: 20, count: 1 }], totals: { unfiltered: 80 }
|
|
507
|
+
```
|
|
483
508
|
- **`activity`** devuelve, por cola, los ocho contadores de la pantalla `QA` del host:
|
|
484
509
|
`numMessages` (los que están hoy en la cola), `ended`, `ignored`, `moved`, `removed`,
|
|
485
510
|
`transferred`, `leftMessageToContact` y `unableToReach`, más `totals` para el PCC. Sabre lista
|
|
486
511
|
también colas vacías con contadores. Ninguna fuente dice desde cuándo cuentan, así que el SDK los
|
|
487
512
|
devuelve tal cual. Como `count`, es una lectura: se reintenta, funciona dentro o fuera de una
|
|
488
|
-
cola y no pide duty code 9.
|
|
513
|
+
cola y no pide duty code 9. Acepta los mismos `queue` o `range` que `count` (no los dos): con
|
|
514
|
+
`queue`, solo esa cola y sin `totals`; con `range`, cada cola del rango, también las que tienen
|
|
515
|
+
todos los contadores en 0, y los `totals` del rango.
|
|
489
516
|
- **Cada resultado de colas trae `respondedAt`**: el `eb:Timestamp` de Sabre, en ISO 8601 UTC
|
|
490
517
|
(`'2026-11-06T17:34:16Z'`). A diferencia de `countedAt` y `placedAt`, que llegan como los manda
|
|
491
518
|
el host (sin año y en hora local del PCC), trae año y zona. Solo falta si Sabre no lo manda.
|
|
@@ -614,8 +641,25 @@ fake.openSessions; // 0: la sesión se cerró
|
|
|
614
641
|
- Responde como producción: colas vacías, el fin de cola, `place` que mueve el PNR, el PIC
|
|
615
642
|
obligatorio y una sesión vencida con `fake.expireSessions()`. `fake.calls` lista lo que pidió el
|
|
616
643
|
SDK.
|
|
617
|
-
-
|
|
618
|
-
|
|
644
|
+
- `queue.count` y `queue.activity` con `queue` o `range` responden como producción: una cola sin
|
|
645
|
+
`totals`, o cada cola del rango, también las vacías.
|
|
646
|
+
- `selection` se simula para `QD`, `AK`, `AL`, `PI` y `AC` (`QUEUE_FAKE_SELECTION_FUNCTIONS`), en
|
|
647
|
+
`access`, `list` y `count`, combinados con Y:
|
|
648
|
+
- `QD` filtra por `placedAt` (`'05NOV'` o `'5NOV'`);
|
|
649
|
+
- `AK` filtra por `placedBy`, PCC más firma (`'ABCDAAT'`);
|
|
650
|
+
- `AL` filtra por la aerolínea de los segmentos aéreos del párrafo, y `AC` por su estado (una
|
|
651
|
+
letra cubre todos los estados que empiezan con ella);
|
|
652
|
+
- `PI` filtra por el PIC con que lo colocó `place` o, si no, por el aviso de PIC del párrafo
|
|
653
|
+
(`011 SEE REMARKS`).
|
|
654
|
+
|
|
655
|
+
`access` recorre solo los PNR que cumplen y termina en `END OF DISPLAY`, también al entrar si
|
|
656
|
+
ninguno cumple (un supuesto del fake), e `ignore`, `remove` y `place` saltan los demás. `list`
|
|
657
|
+
devuelve solo las `Line` que cumplen y, como Sabre, rechaza un código que no sea `QD` ni `AK`.
|
|
658
|
+
`count` con `queue` cuenta los que cumplen y devuelve `totals.unfiltered`; con `range` o sin
|
|
659
|
+
cola, da los errores de Sabre. Otro código recibe un error de host marcado como no simulado,
|
|
660
|
+
nunca la cola entera.
|
|
661
|
+
- **No simula** los PCC (las colas son las mismas para cualquier `pcc`) ni las reglas de
|
|
662
|
+
combinación de `selection` del host.
|
|
619
663
|
- `queueFixtures` trae las respuestas anonimizadas que se grabaron en producción (`walk`,
|
|
620
664
|
`emptyQueue`, `place`…), como `{ operation, status, body }`. Cada una tiene sus propios alias de
|
|
621
665
|
localizador (`walk`: `AAAAAA`–`AAAAAD`, `place`: `AAAAAE`–`AAAAAF`,
|
|
@@ -623,13 +667,20 @@ fake.openSessions; // 0: la sesión se cerró
|
|
|
623
667
|
forma (`… <alias> H`).
|
|
624
668
|
- `queueFakePnrs(fixture)` convierte una grabación en los PNR del fake: primero los de `list`, en
|
|
625
669
|
orden, con su `Line`, y el párrafo que mostró `access`, `ignore`, `remove` o `place` para cada
|
|
626
|
-
uno. La cola la eliges tú, y puedes juntar fixtures
|
|
670
|
+
uno. La cola la eliges tú, y puedes juntar fixtures. Los PNR de `queueFixtures` no traen
|
|
671
|
+
`primaryPassenger`: el `list` se grabó sin `primaryPassenger: true` (`/LN`), así que Sabre no
|
|
672
|
+
mandó el nombre. Si necesitas uno, agrégalo con un `map`:
|
|
627
673
|
|
|
628
674
|
```ts
|
|
629
675
|
const fake = createQueueFake({
|
|
630
676
|
queues: { '9': queueFakePnrs(queueFixtures.walk), '50': [] },
|
|
631
677
|
pnrs: queueFakePnrs(queueFixtures.place),
|
|
632
678
|
});
|
|
679
|
+
|
|
680
|
+
const withPassenger = queueFakePnrs(queueFixtures.walk).map((pnr, index) => ({
|
|
681
|
+
...pnr,
|
|
682
|
+
primaryPassenger: `TEST/PASSENGER${index + 1}`,
|
|
683
|
+
}));
|
|
633
684
|
```
|
|
634
685
|
|
|
635
686
|
`examples/queue-review.mjs` es un ejemplo completo, y su test lo corre contra el fake.
|
|
@@ -648,6 +699,14 @@ anonymizer.maskParagraph(item.paragraph); // líneas en formato de host
|
|
|
648
699
|
if (anonymizer.leaks(safe).length > 0) throw new Error('quedan datos reales');
|
|
649
700
|
```
|
|
650
701
|
|
|
702
|
+
En un bundle de navegador, impórtalo de `sabre-client/anonymize`. Trae solo `createAnonymizer`,
|
|
703
|
+
`isSafeHostLine` y sus tipos, sin el fake ni las `queueFixtures`, que `sabre-client/testing` lleva
|
|
704
|
+
en un solo módulo (unos 230 KB) que los bundlers no pueden descartar:
|
|
705
|
+
|
|
706
|
+
```ts
|
|
707
|
+
import { isSafeHostLine } from 'sabre-client/anonymize';
|
|
708
|
+
```
|
|
709
|
+
|
|
651
710
|
- Cada localizador recibe un alias (`AAAAAA`, `AAAAAB`…), el mismo en todos los lugares donde
|
|
652
711
|
aparece, también en los textos que pasen después por el mismo anonimizador. `PassengerName` pasa
|
|
653
712
|
a `PASAJERO/UNO`, `Group` a `GRUPO`, `AgentSine` a `AAA` (salvo `SABRE`), `PseudoCityCode` a
|
package/README.md
CHANGED
|
@@ -12,14 +12,16 @@ TypeScript SDK for the Sabre APIs.
|
|
|
12
12
|
|
|
13
13
|
## Status
|
|
14
14
|
|
|
15
|
-
`0.
|
|
15
|
+
`0.6.0` is published under the `next` dist-tag while it is tested; `latest` stays on `0.5.1`.
|
|
16
16
|
It covers Tier 1 and Tier 2 of the Air Booking flow over REST, Tier 3 (SOAP sessions and queues)
|
|
17
17
|
and Tier 4: queue activity, PNR history, the `S` message queue, `selection` in `list` and
|
|
18
|
-
`onRawRequest`.
|
|
18
|
+
`onRawRequest`. `0.6.0` adds queue filters: `count` with one queue, a range or `selection`,
|
|
19
|
+
`activity` with one queue or a range, and `selection` in the queue fake. It has no breaking
|
|
20
|
+
changes; the `0.5` line has three (see [Upgrading from 0.4](#upgrading-from-04)).
|
|
19
21
|
|
|
20
22
|
```sh
|
|
21
|
-
npm install sabre-client@next # 0.
|
|
22
|
-
npm install sabre-client # 0.
|
|
23
|
+
npm install sabre-client@next # 0.6.0
|
|
24
|
+
npm install sabre-client # 0.5.1
|
|
23
25
|
```
|
|
24
26
|
|
|
25
27
|
The package is still `0.x`: a minor version may include breaking changes, and the
|
|
@@ -441,7 +443,15 @@ The queues returned by `count` can be passed straight to the other operations. `
|
|
|
441
443
|
throws a `TypeError` without calling Sabre. Walk it by hand: `access({ queue: 'S' })` returns the
|
|
442
444
|
first message (`kind: 'message'`) and `ignore()` the next one, leaving it in the queue. `process`
|
|
443
445
|
and `place` inside `S` throw a `TypeError` without calling Sabre. `remove()` sends `QR`, but there
|
|
444
|
-
is no evidence yet of what Sabre does with the message.
|
|
446
|
+
is no evidence yet of what Sabre does with the message. To tell it apart, `isMessageQueue(queue)`
|
|
447
|
+
returns `true` only for `'S'`, and `MESSAGE_QUEUE` is `'S'`:
|
|
448
|
+
|
|
449
|
+
```ts
|
|
450
|
+
import { isMessageQueue } from 'sabre-client';
|
|
451
|
+
|
|
452
|
+
const { queues } = await session.queue.count();
|
|
453
|
+
const pnrQueues = queues.filter(({ queue }) => !isMessageQueue(queue));
|
|
454
|
+
```
|
|
445
455
|
|
|
446
456
|
> **Migrating from 0.4.x**: `count` used to return the `S` queue as `{ name: 'S' }`, which Sabre
|
|
447
457
|
> rejects (`INVALID QUEUE NAME FORMAT`). It now returns `'S'`. Code that does
|
|
@@ -451,8 +461,8 @@ is no evidence yet of what Sabre does with the message.
|
|
|
451
461
|
|
|
452
462
|
| Method | Sabre service | Returns |
|
|
453
463
|
| - | - | - |
|
|
454
|
-
| `count({ pcc? })` | `QueueCountLLSRQ` | `{ queues: [{ queue, count }], totals, countedAt, warnings }` |
|
|
455
|
-
| `activity({ pcc? })` | `QueueAnalysisLLSRQ` | `{ queues: [{ queue, numMessages, ended, ignored, … }], totals, analyzedAt, warnings }` |
|
|
464
|
+
| `count({ pcc?, queue?, range?, selection? })` | `QueueCountLLSRQ` | `{ queues: [{ queue, count }], totals, countedAt, additionalInfo?, warnings }` |
|
|
465
|
+
| `activity({ pcc?, queue?, range? })` | `QueueAnalysisLLSRQ` | `{ queues: [{ queue, numMessages, ended, ignored, … }], totals, analyzedAt, warnings }` |
|
|
456
466
|
| `list({ queue, pcc?, primaryPassenger?, selection? })` | `QueueAccessLLSRQ` (`List`) | `{ locators, entries, paragraph, warnings }`, without entering the queue |
|
|
457
467
|
| `access({ queue, pcc?, selection? })` | `QueueAccessLLSRQ` | `QueueStep` |
|
|
458
468
|
| `ignore()` | `QueueAccessLLSRQ` (`I`) | `QueueStep` |
|
|
@@ -476,12 +486,30 @@ type QueueStep =
|
|
|
476
486
|
(the agent who placed it, PCC plus sine, e.g. `'PCC1AAT'`; not tested in the list yet).
|
|
477
487
|
Any other code throws a `TypeError` without calling Sabre. The `placedAt` of a list entry is
|
|
478
488
|
when the PNR was placed on the queue, the date `QD` filters on.
|
|
489
|
+
- **`count` filters**: `queue` counts one queue, and `range: { start, end }` a range of numeric
|
|
490
|
+
queues; the two do not combine. With `range`, Sabre returns every queue in the range, **empty
|
|
491
|
+
ones included** with `count: 0` (without filters it leaves them out). `selection` takes up to 8
|
|
492
|
+
filters, like `access`, and counts only the PNRs that match: the host accepts any code (`QD`,
|
|
493
|
+
`AK`, `AL`, `PI`, `AC`… and combinations), and with the same `QD` the count matches `list`. It
|
|
494
|
+
needs `queue`: without a queue Sabre answers `QUEUE SELECTED NOT VALID OR CLOSED`, and with a
|
|
495
|
+
range, `KEYS NOT ALLOWED`, so the SDK throws a `TypeError` first. With `selection`,
|
|
496
|
+
`totals.unfiltered` is how many PNRs the queue has before filtering (the host's
|
|
497
|
+
`1 MATCHING PNRS OUT OF 80`); with `queue` alone Sabre sends no `totals`. `additionalInfo` reads
|
|
498
|
+
`AdditionalQueueInfo` (a queue transferred elsewhere), typed from the design: no real response
|
|
499
|
+
has carried it yet.
|
|
500
|
+
|
|
501
|
+
```ts
|
|
502
|
+
const { queues, totals } = await session.queue.count({ queue: 20, selection: [{ function: 'QD', criteria: '08NOV' }] });
|
|
503
|
+
// queues: [{ queue: 20, count: 1 }], totals: { unfiltered: 80 }
|
|
504
|
+
```
|
|
479
505
|
- **`activity`** returns, per queue, the eight counters of the host's `QA` screen: `numMessages`
|
|
480
506
|
(on the queue now), `ended`, `ignored`, `moved`, `removed`, `transferred`,
|
|
481
507
|
`leftMessageToContact` and `unableToReach`, plus `totals` for the PCC. Sabre also lists empty
|
|
482
508
|
queues that have counters. No source says since when the counters run, so the SDK returns them
|
|
483
509
|
as they come. Like `count`, it is a read: it is retried, it works inside or outside a queue, and
|
|
484
|
-
it does not need duty code 9.
|
|
510
|
+
it does not need duty code 9. It takes the same `queue` or `range` as `count` (not both): with
|
|
511
|
+
`queue`, only that queue and no `totals`; with `range`, every queue in the range, even those
|
|
512
|
+
with all counters at 0, and the range's `totals`.
|
|
485
513
|
- **Every queue result carries `respondedAt`**: Sabre's `eb:Timestamp`, in ISO 8601 UTC
|
|
486
514
|
(`'2026-11-06T17:34:16Z'`). Unlike `countedAt` and `placedAt`, which come as the host sends them
|
|
487
515
|
(no year, PCC local time), it has a year and a zone. It is missing only if Sabre does not send it.
|
|
@@ -608,21 +636,44 @@ fake.openSessions; // 0: the session was closed
|
|
|
608
636
|
- It answers like production: empty queues, the end of the queue, `place` moving the PNR, the
|
|
609
637
|
required PIC, and an expired session with `fake.expireSessions()`. `fake.calls` lists what the
|
|
610
638
|
SDK asked for.
|
|
611
|
-
-
|
|
612
|
-
|
|
639
|
+
- `queue.count` and `queue.activity` with `queue` or `range` answer like production: one queue
|
|
640
|
+
without `totals`, or every queue in the range, empty ones included.
|
|
641
|
+
- `selection` is simulated for `QD`, `AK`, `AL`, `PI` and `AC` (`QUEUE_FAKE_SELECTION_FUNCTIONS`),
|
|
642
|
+
in `access`, `list` and `count`, combined with AND:
|
|
643
|
+
- `QD` filters on `placedAt` (`'05NOV'` or `'5NOV'`);
|
|
644
|
+
- `AK` filters on `placedBy`, PCC plus sine (`'ABCDAAT'`);
|
|
645
|
+
- `AL` filters on the airline of the paragraph's air segments, and `AC` on their status (one
|
|
646
|
+
letter matches every status that starts with it);
|
|
647
|
+
- `PI` filters on the PIC `place` used, or else on the paragraph's PIC notice (`011 SEE REMARKS`).
|
|
648
|
+
|
|
649
|
+
`access` walks only the PNRs that match and ends with `END OF DISPLAY`, also on entering when
|
|
650
|
+
none match (an assumption of the fake), and `ignore`, `remove` and `place` skip the rest. `list`
|
|
651
|
+
returns only the matching `Line`s and, like Sabre, rejects any code other than `QD` or `AK`.
|
|
652
|
+
`count` with `queue` counts the matches and returns `totals.unfiltered`; with `range` or without
|
|
653
|
+
a queue it gives Sabre's errors. Any other code gets a host error marked as not simulated,
|
|
654
|
+
never the whole queue.
|
|
655
|
+
- **Not simulated**: PCCs (the queues are the same for any `pcc`) and the host's combination rules
|
|
656
|
+
for `selection`.
|
|
613
657
|
- `queueFixtures` holds the anonymized responses recorded in production (`walk`, `emptyQueue`,
|
|
614
658
|
`place`, …), as `{ operation, status, body }`. Each one has its own locator aliases (`walk`:
|
|
615
659
|
`AAAAAA`–`AAAAAD`, `place`: `AAAAAE`–`AAAAAF`, `placeWithoutInstructionCodeInside`:
|
|
616
660
|
`AAAAAG`–`AAAAAH`), and their signature lines keep their shape (`… <alias> H`).
|
|
617
661
|
- `queueFakePnrs(fixture)` turns a recording into the fake's PNRs: those of `list` first, in
|
|
618
662
|
order, with their `Line`, and the paragraph `access`, `ignore`, `remove` or `place` showed for
|
|
619
|
-
each one. You choose the queue, and you can mix fixtures
|
|
663
|
+
each one. You choose the queue, and you can mix fixtures. The PNRs of `queueFixtures` have no
|
|
664
|
+
`primaryPassenger`: `list` was recorded without `primaryPassenger: true` (`/LN`), so Sabre did
|
|
665
|
+
not send the name. If you need one, add it with a `map`:
|
|
620
666
|
|
|
621
667
|
```ts
|
|
622
668
|
const fake = createQueueFake({
|
|
623
669
|
queues: { '9': queueFakePnrs(queueFixtures.walk), '50': [] },
|
|
624
670
|
pnrs: queueFakePnrs(queueFixtures.place),
|
|
625
671
|
});
|
|
672
|
+
|
|
673
|
+
const withPassenger = queueFakePnrs(queueFixtures.walk).map((pnr, index) => ({
|
|
674
|
+
...pnr,
|
|
675
|
+
primaryPassenger: `TEST/PASSENGER${index + 1}`,
|
|
676
|
+
}));
|
|
626
677
|
```
|
|
627
678
|
|
|
628
679
|
`examples/queue-review.mjs` is a full example, and its test runs it against the fake.
|
|
@@ -641,6 +692,14 @@ anonymizer.maskParagraph(item.paragraph); // host-format lines
|
|
|
641
692
|
if (anonymizer.leaks(safe).length > 0) throw new Error('real data left');
|
|
642
693
|
```
|
|
643
694
|
|
|
695
|
+
In a browser bundle, import it from `sabre-client/anonymize` instead. It has only
|
|
696
|
+
`createAnonymizer`, `isSafeHostLine` and their types, without the fake or `queueFixtures`, which
|
|
697
|
+
`sabre-client/testing` carries in a single module (about 230 KB) that bundlers cannot drop:
|
|
698
|
+
|
|
699
|
+
```ts
|
|
700
|
+
import { isSafeHostLine } from 'sabre-client/anonymize';
|
|
701
|
+
```
|
|
702
|
+
|
|
644
703
|
- Each locator gets an alias (`AAAAAA`, `AAAAAB`…), the same one everywhere it appears, also in
|
|
645
704
|
later texts passed to the same anonymizer. `PassengerName` becomes `PASAJERO/UNO`, `Group`
|
|
646
705
|
becomes `GRUPO`, `AgentSine` becomes `AAA` (except `SABRE`), `PseudoCityCode` becomes `XXXX`,
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** Qué dato real quedó en un texto: lo que devuelve `Anonymizer.leaks`. */
|
|
2
|
+
type AnonymizerLeak = 'secreto' | 'localizador' | 'nombre' | 'línea del PNR' | 'token';
|
|
3
|
+
interface AnonymizerOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Valores que nunca deben quedar: credenciales, usuario, PCC. Cada uno pasa a `XXXX` donde
|
|
6
|
+
* aparezca. Se ignoran los que no son string o tienen menos de 3 caracteres.
|
|
7
|
+
*/
|
|
8
|
+
secrets?: ReadonlyArray<string | undefined>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Una anonimización con memoria: el mismo localizador da siempre el mismo alias y el mismo
|
|
12
|
+
* pasajero el mismo nombre falso, en todos los textos que pasen por ella. Una por grabación.
|
|
13
|
+
*/
|
|
14
|
+
interface Anonymizer {
|
|
15
|
+
/**
|
|
16
|
+
* Un sobre SOAP o cualquier texto con su XML (también un JSON con XML adentro):
|
|
17
|
+
* - cada `UniqueID@ID` pasa a un alias (`AAAAAA`, `AAAAAB`…), y el localizador real se reemplaza
|
|
18
|
+
* por su alias en todo el texto;
|
|
19
|
+
* - `PassengerName` a `PASAJERO/UNO`, `PASAJERO/DOS`…, y `Group` a `GRUPO`;
|
|
20
|
+
* - `AgentSine` a `AAA` (salvo `SABRE`), `PseudoCityCode` a `XXXX` y `LNIATA` a `XXXXXX`;
|
|
21
|
+
* - cada `<Text>` pasa por `maskLine`;
|
|
22
|
+
* - los `secrets`, a `XXXX`.
|
|
23
|
+
*/
|
|
24
|
+
anonymize(text: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Una línea del PNR en formato de host. Las de la lista blanca (`isSafeHostLine`) quedan tal
|
|
27
|
+
* cual; en las demás, letras a `X` y dígitos a `9`. La cabecera `QUEUE LIST FOR <PCC>` solo
|
|
28
|
+
* tapa el PCC, y `RECEIVED FROM - ` conserva el prefijo. La línea de firma
|
|
29
|
+
* (`… HHMM/DDMMMYY <localizador> H`) conserva su forma, y el localizador solo si ya es un alias:
|
|
30
|
+
* de ella sale el localizador del PNR siguiente en `place` dentro de una cola. Llamarla después
|
|
31
|
+
* de dar alias a los localizadores.
|
|
32
|
+
*/
|
|
33
|
+
maskLine(line: string): string;
|
|
34
|
+
/** `maskLine` sobre cada línea de un párrafo, p. ej. `QueueItem.paragraph`. */
|
|
35
|
+
maskParagraph(lines: readonly string[]): string[];
|
|
36
|
+
/** El alias de un localizador: lo asigna la primera vez, en orden (`AAAAAA`, `AAAAAB`…). */
|
|
37
|
+
aliasOf(locator: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Qué datos reales siguen en `text`, sin repetir: un `secret`, un localizador o un nombre que ya
|
|
40
|
+
* tienen alias, una línea del PNR que se enmascaró, o un token de sesión. Vacío si no queda
|
|
41
|
+
* ninguno. Sirve para no escribir una fixture que todavía trae datos reales.
|
|
42
|
+
*/
|
|
43
|
+
leaks(text: string): AnonymizerLeak[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Si una línea del PNR en formato de host queda tal cual al anonimizar: está en la lista blanca de
|
|
47
|
+
* #96 (líneas vacías, avisos PIC como `011 SEE REMARKS`, títulos de sección como `REMARKS`, el fin
|
|
48
|
+
* de cola).
|
|
49
|
+
*/
|
|
50
|
+
declare function isSafeHostLine(line: string): boolean;
|
|
51
|
+
/** Ver `Anonymizer`. */
|
|
52
|
+
declare function createAnonymizer(options?: AnonymizerOptions): Anonymizer;
|
|
53
|
+
|
|
54
|
+
export { type Anonymizer, type AnonymizerLeak, type AnonymizerOptions, createAnonymizer, isSafeHostLine };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// src/testing/anonymize.ts
|
|
2
|
+
var NAMES = ["UNO", "DOS", "TRES", "CUATRO", "CINCO", "SEIS", "SIETE", "OCHO", "NUEVE", "DIEZ"];
|
|
3
|
+
var SAFE_LINES = [
|
|
4
|
+
/^\s*$/,
|
|
5
|
+
// Aviso PIC: 011 SEE REMARKS. Sin dígitos después del número: una línea de `list`
|
|
6
|
+
// (`100 ABC123 PLT MAM 01JUN26 1948`) también empieza con 3 dígitos y trae el sine.
|
|
7
|
+
/^\s*\d{3}\s{2,}[A-Z][A-Z ,./-]*$/,
|
|
8
|
+
/^\s*END OF DISPLAY FOR REQUESTED DATA\s*$/,
|
|
9
|
+
/^\s*(TKT\/TIME LIMIT|PHONES|REMARKS|ADDRESS|INVOICED|GENERAL FACTS|[A-Z0-9]{2} FACTS|NO ITIN)\s*$/,
|
|
10
|
+
/^\s*PASSENGER EMAIL DATA EXISTS\s+\*PE TO DISPLAY ALL\s*$/
|
|
11
|
+
];
|
|
12
|
+
function isSafeHostLine(line) {
|
|
13
|
+
return SAFE_LINES.some((pattern) => pattern.test(line));
|
|
14
|
+
}
|
|
15
|
+
var ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" };
|
|
16
|
+
var decode = (text) => text.replace(/&(amp|lt|gt|quot|apos);/g, (_, entity) => ENTITIES[entity] ?? "");
|
|
17
|
+
var encode = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
18
|
+
var masked = (text) => text.replace(/\p{L}/gu, "X").replace(/\p{N}/gu, "9");
|
|
19
|
+
function createAnonymizer(options = {}) {
|
|
20
|
+
const secrets = (options.secrets ?? []).filter((value) => typeof value === "string" && value.length >= 3).sort((a, b) => b.length - a.length);
|
|
21
|
+
const redactSecrets = (text) => secrets.reduce((value, secret) => value.split(secret).join("XXXX"), text);
|
|
22
|
+
const locatorAliases = /* @__PURE__ */ new Map();
|
|
23
|
+
const realNames = /* @__PURE__ */ new Map();
|
|
24
|
+
const maskedLines = /* @__PURE__ */ new Set();
|
|
25
|
+
const aliasOf = (locator) => {
|
|
26
|
+
let alias = locatorAliases.get(locator);
|
|
27
|
+
if (alias === void 0) {
|
|
28
|
+
const n = locatorAliases.size;
|
|
29
|
+
const letters = [17576, 676, 26, 1].map((size) => String.fromCharCode(65 + Math.floor(n / size) % 26)).join("");
|
|
30
|
+
alias = `AA${letters}`;
|
|
31
|
+
locatorAliases.set(locator, alias);
|
|
32
|
+
}
|
|
33
|
+
return alias;
|
|
34
|
+
};
|
|
35
|
+
const fakeName = (name) => {
|
|
36
|
+
let fake = realNames.get(name);
|
|
37
|
+
if (fake === void 0) {
|
|
38
|
+
fake = `PASAJERO/${NAMES[realNames.size] ?? realNames.size + 1}`;
|
|
39
|
+
realNames.set(name, fake);
|
|
40
|
+
}
|
|
41
|
+
return fake;
|
|
42
|
+
};
|
|
43
|
+
function anonymizeLine(line) {
|
|
44
|
+
if (isSafeHostLine(line)) return line;
|
|
45
|
+
const header = /^(\s*QUEUE LIST FOR )\S+(\s+QUEUE )(\S+)\s*$/.exec(line);
|
|
46
|
+
if (header !== null) return `${header[1]}XXXX${header[2]}${header[3]}`;
|
|
47
|
+
const received = /^(\s*RECEIVED FROM - )(.*)$/.exec(line);
|
|
48
|
+
const signature = /^(.*\s)(\d{4}\/\d{2}[A-Z]{3}\d{2})(\s+)([A-Z0-9]{6})(\s+H\s*)$/.exec(line);
|
|
49
|
+
const aliases = new Set(locatorAliases.values());
|
|
50
|
+
let result;
|
|
51
|
+
if (received !== null) {
|
|
52
|
+
result = `${received[1]}${masked(received[2] ?? "")}`;
|
|
53
|
+
} else if (signature !== null) {
|
|
54
|
+
const [, before = "", stamp = "", gap = "", locator = "", end = ""] = signature;
|
|
55
|
+
result = `${masked(before)}${masked(stamp)}${gap}${aliases.has(locator) ? locator : masked(locator)}${end}`;
|
|
56
|
+
} else {
|
|
57
|
+
result = masked(line);
|
|
58
|
+
}
|
|
59
|
+
if (line.trim().length >= 4 && result.trim() !== line.trim()) maskedLines.add(line.trim());
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
function anonymize(body) {
|
|
63
|
+
let text = String(body).replace(
|
|
64
|
+
/(<(?:\w+:)?UniqueID\b[^>]*\bID=")([^"]+)(")/g,
|
|
65
|
+
(_, a, locator, b) => `${a}${aliasOf(locator)}${b}`
|
|
66
|
+
);
|
|
67
|
+
for (const [locator, alias] of locatorAliases) text = text.split(locator).join(alias);
|
|
68
|
+
text = text.replace(
|
|
69
|
+
/(<(?:\w+:)?PassengerName>)([^<]*)(<\/)/g,
|
|
70
|
+
(_, a, name, b) => `${a}${fakeName(decode(name))}${b}`
|
|
71
|
+
);
|
|
72
|
+
text = text.replace(
|
|
73
|
+
/(<(?:\w+:)?Group>)([^<]*)(<\/)/g,
|
|
74
|
+
(_, a, value, b) => value === "" ? `${a}${b}` : `${a}GRUPO${b}`
|
|
75
|
+
);
|
|
76
|
+
text = text.replace(
|
|
77
|
+
/(\bAgentSine=")([^"]*)(")/g,
|
|
78
|
+
(_, a, sine, b) => `${a}${sine === "SABRE" ? sine : "AAA"}${b}`
|
|
79
|
+
);
|
|
80
|
+
text = text.replace(/(\bPseudoCityCode=")([^"]*)(")/g, "$1XXXX$3");
|
|
81
|
+
text = text.replace(/(\bLNIATA=")([^"]*)(")/g, "$1XXXXXX$3");
|
|
82
|
+
text = text.replace(
|
|
83
|
+
/(<(?:\w+:)?Text\b[^>/]*>)([\s\S]*?)(<\/(?:\w+:)?Text>)/g,
|
|
84
|
+
(_, a, line, b) => `${a}${encode(anonymizeLine(decode(line)))}${b}`
|
|
85
|
+
);
|
|
86
|
+
return redactSecrets(text);
|
|
87
|
+
}
|
|
88
|
+
const maskLine = (line) => redactSecrets(anonymizeLine(line));
|
|
89
|
+
function leaks(text) {
|
|
90
|
+
const found = [
|
|
91
|
+
...secrets.filter((secret) => text.includes(secret)).map(() => "secreto"),
|
|
92
|
+
...[...locatorAliases.keys()].filter((locator) => text.includes(locator)).map(() => "localizador"),
|
|
93
|
+
...[...realNames.keys()].filter((realName) => realName.length >= 3 && text.includes(realName)).map(() => "nombre"),
|
|
94
|
+
...[...maskedLines].filter((line) => text.includes(line)).map(() => "l\xEDnea del PNR"),
|
|
95
|
+
.../IceSess|Shared\/IDL/.test(text) ? ["token"] : []
|
|
96
|
+
];
|
|
97
|
+
return [...new Set(found)];
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
anonymize,
|
|
101
|
+
maskLine,
|
|
102
|
+
maskParagraph: (lines) => lines.map(maskLine),
|
|
103
|
+
aliasOf,
|
|
104
|
+
leaks
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
export {
|
|
108
|
+
createAnonymizer,
|
|
109
|
+
isSafeHostLine
|
|
110
|
+
};
|
|
111
|
+
//# sourceMappingURL=anonymize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/testing/anonymize.ts"],"sourcesContent":["// Anonimización de las respuestas de colas grabadas (la regla de #96, ADR-018), la misma que usan\n// los scripts que graban fixtures. Exportada para que los consumidores no la repitan (ADR-027, D1).\n// Solo Web Standard APIs: corre en cualquier runtime.\n\n/** Qué dato real quedó en un texto: lo que devuelve `Anonymizer.leaks`. */\nexport type AnonymizerLeak = 'secreto' | 'localizador' | 'nombre' | 'línea del PNR' | 'token';\n\nexport interface AnonymizerOptions {\n /**\n * Valores que nunca deben quedar: credenciales, usuario, PCC. Cada uno pasa a `XXXX` donde\n * aparezca. Se ignoran los que no son string o tienen menos de 3 caracteres.\n */\n secrets?: ReadonlyArray<string | undefined>;\n}\n\n/**\n * Una anonimización con memoria: el mismo localizador da siempre el mismo alias y el mismo\n * pasajero el mismo nombre falso, en todos los textos que pasen por ella. Una por grabación.\n */\nexport interface Anonymizer {\n /**\n * Un sobre SOAP o cualquier texto con su XML (también un JSON con XML adentro):\n * - cada `UniqueID@ID` pasa a un alias (`AAAAAA`, `AAAAAB`…), y el localizador real se reemplaza\n * por su alias en todo el texto;\n * - `PassengerName` a `PASAJERO/UNO`, `PASAJERO/DOS`…, y `Group` a `GRUPO`;\n * - `AgentSine` a `AAA` (salvo `SABRE`), `PseudoCityCode` a `XXXX` y `LNIATA` a `XXXXXX`;\n * - cada `<Text>` pasa por `maskLine`;\n * - los `secrets`, a `XXXX`.\n */\n anonymize(text: string): string;\n /**\n * Una línea del PNR en formato de host. Las de la lista blanca (`isSafeHostLine`) quedan tal\n * cual; en las demás, letras a `X` y dígitos a `9`. La cabecera `QUEUE LIST FOR <PCC>` solo\n * tapa el PCC, y `RECEIVED FROM - ` conserva el prefijo. La línea de firma\n * (`… HHMM/DDMMMYY <localizador> H`) conserva su forma, y el localizador solo si ya es un alias:\n * de ella sale el localizador del PNR siguiente en `place` dentro de una cola. Llamarla después\n * de dar alias a los localizadores.\n */\n maskLine(line: string): string;\n /** `maskLine` sobre cada línea de un párrafo, p. ej. `QueueItem.paragraph`. */\n maskParagraph(lines: readonly string[]): string[];\n /** El alias de un localizador: lo asigna la primera vez, en orden (`AAAAAA`, `AAAAAB`…). */\n aliasOf(locator: string): string;\n /**\n * Qué datos reales siguen en `text`, sin repetir: un `secret`, un localizador o un nombre que ya\n * tienen alias, una línea del PNR que se enmascaró, o un token de sesión. Vacío si no queda\n * ninguno. Sirve para no escribir una fixture que todavía trae datos reales.\n */\n leaks(text: string): AnonymizerLeak[];\n}\n\nconst NAMES = ['UNO', 'DOS', 'TRES', 'CUATRO', 'CINCO', 'SEIS', 'SIETE', 'OCHO', 'NUEVE', 'DIEZ'];\n\n/** Líneas de un PNR en formato de host que quedan tal cual: no traen datos del pasajero. */\nconst SAFE_LINES: readonly RegExp[] = [\n /^\\s*$/,\n // Aviso PIC: 011 SEE REMARKS. Sin dígitos después del número: una línea de `list`\n // (`100 ABC123 PLT MAM 01JUN26 1948`) también empieza con 3 dígitos y trae el sine.\n /^\\s*\\d{3}\\s{2,}[A-Z][A-Z ,./-]*$/,\n /^\\s*END OF DISPLAY FOR REQUESTED DATA\\s*$/,\n /^\\s*(TKT\\/TIME LIMIT|PHONES|REMARKS|ADDRESS|INVOICED|GENERAL FACTS|[A-Z0-9]{2} FACTS|NO ITIN)\\s*$/,\n /^\\s*PASSENGER EMAIL DATA EXISTS\\s+\\*PE TO DISPLAY ALL\\s*$/,\n];\n\n/**\n * Si una línea del PNR en formato de host queda tal cual al anonimizar: está en la lista blanca de\n * #96 (líneas vacías, avisos PIC como `011 SEE REMARKS`, títulos de sección como `REMARKS`, el fin\n * de cola).\n */\nexport function isSafeHostLine(line: string): boolean {\n return SAFE_LINES.some((pattern) => pattern.test(line));\n}\n\nconst ENTITIES: Record<string, string> = { amp: '&', lt: '<', gt: '>', quot: '\"', apos: \"'\" };\nconst decode = (text: string) => text.replace(/&(amp|lt|gt|quot|apos);/g, (_, entity: string) => ENTITIES[entity] ?? '');\nconst encode = (text: string) => text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\nconst masked = (text: string) => text.replace(/\\p{L}/gu, 'X').replace(/\\p{N}/gu, '9');\n\n/** Ver `Anonymizer`. */\nexport function createAnonymizer(options: AnonymizerOptions = {}): Anonymizer {\n const secrets = (options.secrets ?? [])\n .filter((value): value is string => typeof value === 'string' && value.length >= 3)\n .sort((a, b) => b.length - a.length);\n const redactSecrets = (text: string) => secrets.reduce((value, secret) => value.split(secret).join('XXXX'), text);\n const locatorAliases = new Map<string, string>();\n const realNames = new Map<string, string>();\n const maskedLines = new Set<string>();\n\n const aliasOf = (locator: string): string => {\n let alias = locatorAliases.get(locator);\n if (alias === undefined) {\n const n = locatorAliases.size;\n // Base 26 en cuatro letras: `AAAAAA`, `AAAAAB`… y sigue en letras después del 676 (`AAABAA`).\n const letters = [17576, 676, 26, 1].map((size) => String.fromCharCode(65 + (Math.floor(n / size) % 26))).join('');\n alias = `AA${letters}`;\n locatorAliases.set(locator, alias);\n }\n return alias;\n };\n const fakeName = (name: string): string => {\n let fake = realNames.get(name);\n if (fake === undefined) {\n fake = `PASAJERO/${NAMES[realNames.size] ?? realNames.size + 1}`;\n realNames.set(name, fake);\n }\n return fake;\n };\n\n function anonymizeLine(line: string): string {\n if (isSafeHostLine(line)) return line;\n const header = /^(\\s*QUEUE LIST FOR )\\S+(\\s+QUEUE )(\\S+)\\s*$/.exec(line);\n if (header !== null) return `${header[1]}XXXX${header[2]}${header[3]}`;\n const received = /^(\\s*RECEIVED FROM - )(.*)$/.exec(line);\n const signature = /^(.*\\s)(\\d{4}\\/\\d{2}[A-Z]{3}\\d{2})(\\s+)([A-Z0-9]{6})(\\s+H\\s*)$/.exec(line);\n const aliases = new Set(locatorAliases.values());\n let result: string;\n if (received !== null) {\n result = `${received[1]}${masked(received[2] ?? '')}`;\n } else if (signature !== null) {\n const [, before = '', stamp = '', gap = '', locator = '', end = ''] = signature;\n result = `${masked(before)}${masked(stamp)}${gap}${aliases.has(locator) ? locator : masked(locator)}${end}`;\n } else {\n result = masked(line);\n }\n if (line.trim().length >= 4 && result.trim() !== line.trim()) maskedLines.add(line.trim());\n return result;\n }\n\n function anonymize(body: string): string {\n let text = String(body).replace(\n /(<(?:\\w+:)?UniqueID\\b[^>]*\\bID=\")([^\"]+)(\")/g,\n (_, a: string, locator: string, b: string) => `${a}${aliasOf(locator)}${b}`,\n );\n for (const [locator, alias] of locatorAliases) text = text.split(locator).join(alias);\n text = text.replace(\n /(<(?:\\w+:)?PassengerName>)([^<]*)(<\\/)/g,\n (_, a: string, name: string, b: string) => `${a}${fakeName(decode(name))}${b}`,\n );\n text = text.replace(/(<(?:\\w+:)?Group>)([^<]*)(<\\/)/g, (_, a: string, value: string, b: string) =>\n value === '' ? `${a}${b}` : `${a}GRUPO${b}`,\n );\n text = text.replace(/(\\bAgentSine=\")([^\"]*)(\")/g, (_, a: string, sine: string, b: string) =>\n `${a}${sine === 'SABRE' ? sine : 'AAA'}${b}`,\n );\n text = text.replace(/(\\bPseudoCityCode=\")([^\"]*)(\")/g, '$1XXXX$3');\n // La dirección de la terminal (`stl:HostCommand@LNIATA`, en `session.command()`).\n text = text.replace(/(\\bLNIATA=\")([^\"]*)(\")/g, '$1XXXXXX$3');\n text = text.replace(\n /(<(?:\\w+:)?Text\\b[^>/]*>)([\\s\\S]*?)(<\\/(?:\\w+:)?Text>)/g,\n (_, a: string, line: string, b: string) => `${a}${encode(anonymizeLine(decode(line)))}${b}`,\n );\n return redactSecrets(text);\n }\n\n const maskLine = (line: string) => redactSecrets(anonymizeLine(line));\n\n function leaks(text: string): AnonymizerLeak[] {\n const found: AnonymizerLeak[] = [\n ...secrets.filter((secret) => text.includes(secret)).map((): AnonymizerLeak => 'secreto'),\n ...[...locatorAliases.keys()].filter((locator) => text.includes(locator)).map((): AnonymizerLeak => 'localizador'),\n ...[...realNames.keys()]\n .filter((realName) => realName.length >= 3 && text.includes(realName))\n .map((): AnonymizerLeak => 'nombre'),\n ...[...maskedLines].filter((line) => text.includes(line)).map((): AnonymizerLeak => 'línea del PNR'),\n ...(/IceSess|Shared\\/IDL/.test(text) ? (['token'] as const) : []),\n ];\n return [...new Set(found)];\n }\n\n return {\n anonymize,\n maskLine,\n maskParagraph: (lines) => lines.map(maskLine),\n aliasOf,\n leaks,\n };\n}\n"],"mappings":"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","names":[]}
|