sabre-client 0.5.1 → 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 +45 -10
- package/README.md +45 -10
- package/dist/index.d.ts +61 -10
- package/dist/index.js +80 -17
- package/dist/index.js.map +1 -1
- package/dist/{reservation-history-BKRFlxpA.d.ts → reservation-history-C6giSdFd.d.ts} +29 -3
- package/dist/testing.d.ts +13 -5
- package/dist/testing.js +423 -24
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
|
@@ -649,12 +649,31 @@ type QueueRef = number | string | {
|
|
|
649
649
|
};
|
|
650
650
|
/** La cola de mensajes del PCC: no tiene PNR y Sabre no la lista (`INVALID ENTRY`). */
|
|
651
651
|
declare const MESSAGE_QUEUE = "S";
|
|
652
|
+
/**
|
|
653
|
+
* Un rango de colas numéricas, de `start` a `end`, las dos incluidas: `{ start: 1, end: 25 }` es el
|
|
654
|
+
* `QC/<PCC>1-25` del host. Lo usan `count` y `activity`. A diferencia de pedir todo el PCC, Sabre
|
|
655
|
+
* devuelve cada cola del rango, también las vacías, con cuenta `0` (CERT y producción, #213).
|
|
656
|
+
*
|
|
657
|
+
* El design también acepta rangos de colas con nombre (`TEST1-TEST2`), pero ninguna prueba los vio:
|
|
658
|
+
* por ahora solo van números.
|
|
659
|
+
*/
|
|
660
|
+
interface QueueRange {
|
|
661
|
+
start: number;
|
|
662
|
+
end: number;
|
|
663
|
+
}
|
|
652
664
|
/** Si la cola es la de mensajes (`'S'`). */
|
|
653
665
|
declare function isMessageQueue(queue: QueueRef): boolean;
|
|
654
666
|
|
|
655
667
|
interface QueueActivityRequest {
|
|
656
668
|
/** PCC cuyas colas se analizan. Por defecto, el de la sesión. */
|
|
657
669
|
pcc?: string;
|
|
670
|
+
/** Analizar solo esta cola (`QA/<PCC><cola>`). No se combina con `range`. */
|
|
671
|
+
queue?: QueueRef;
|
|
672
|
+
/**
|
|
673
|
+
* Analizar un rango de colas (`QA/<PCC>1-25`). No se combina con `queue`. La respuesta trae cada
|
|
674
|
+
* cola del rango, también las que tienen todos los contadores en 0.
|
|
675
|
+
*/
|
|
676
|
+
range?: QueueRange;
|
|
658
677
|
}
|
|
659
678
|
type QueueActivityOptions = RequestOptions;
|
|
660
679
|
/**
|
|
@@ -686,9 +705,16 @@ interface QueueActivity extends QueueActivityCounters {
|
|
|
686
705
|
queue: QueueRef;
|
|
687
706
|
}
|
|
688
707
|
interface QueueActivityResponse {
|
|
689
|
-
/**
|
|
708
|
+
/**
|
|
709
|
+
* Las colas que trae la respuesta, en el orden de Sabre. Sin filtros, Sabre omite las que tienen
|
|
710
|
+
* todos los contadores en 0; con `range`, trae todas las del rango, y con `queue`, solo esa
|
|
711
|
+
* (CERT y producción, #213).
|
|
712
|
+
*/
|
|
690
713
|
queues: QueueActivity[];
|
|
691
|
-
/**
|
|
714
|
+
/**
|
|
715
|
+
* `Totals`: los ocho contadores sumados para el PCC, o para el rango con `range`. Con `queue`,
|
|
716
|
+
* Sabre no manda `Totals` y queda en `undefined` (#213).
|
|
717
|
+
*/
|
|
692
718
|
totals?: QueueActivityCounters;
|
|
693
719
|
/**
|
|
694
720
|
* `QueueInfo@DateTime` tal cual llegó, sin año (`"11-06T11:33"`): la hora del host al analizar.
|
|
@@ -789,4 +815,4 @@ interface ReservationHistoryResponse {
|
|
|
789
815
|
respondedAt?: string;
|
|
790
816
|
}
|
|
791
817
|
|
|
792
|
-
export { type
|
|
818
|
+
export { type PassengerTotalFare as $, type ItineraryMessage as A, type BargainFinderMaxRequest as B, type CurrencyConversion as C, type LegDesc as D, type LegDescription as E, type Fare as F, type GroupedItineraryResponse as G, type LegSchedule as H, type ItineraryGroup as I, type LegScheduleRef as J, type LowFareSearchFlight as K, type Leg as L, type LowFareSearchLocation as M, type LowFareSearchOriginDestination as N, type OtaAirLowFareSearchRq as O, type LowFareSearchPassengerTypeQuantity as P, type QueueRef as Q, type ReservationHistoryTransaction as R, SabreError as S, type LowFareSearchRequestorId as T, type LowFareSearchSource as U, type LowFareSearchTpaExtensions as V, type LowFareSearchTravelPreferences as W, type LowFareSearchTravelerInfoSummary as X, MESSAGE_QUEUE as Y, type ObFeeDesc as Z, type PassengerInfo as _, type QueueActivityCounters as a, type PricingInformation as a0, type QueueActivity as a1, type Ref as a2, type SabreBusinessErrorOptions as a3, type SabreErrorOptions as a4, SabreGatewayError as a5, type SabreGatewayErrorOptions as a6, SabreOrchestrationError as a7, type SabreOrchestrationErrorOptions as a8, type SabreProtocol as a9, type SabreRawRequest as aa, type SabreRawResponse as ab, type SabreServiceErrorOptions as ac, SabreSessionError as ad, type SabreSessionErrorKind as ae, type SabreSessionErrorOptions as af, type ScheduleCarrier as ag, type ScheduleDesc as ah, type ScheduleEndpoint as ai, type ScheduleEquipment as aj, type TaxDesc as ak, type TaxSummaryDesc as al, type TotalFare as am, type ValidatingCarrierDesc as an, isMessageQueue as ao, type ReservationHistoryFlightSegment as b, type ReservationHistoryText as c, type ReservationHistoryAccounting as d, type RetryOptions as e, type SabreRawResponseCallback as f, type SabreRawRequestCallback as g, SabreWarning as h, SabreBusinessError as i, type Itinerary as j, type RequestOptions as k, type BargainFinderMaxResponse as l, type QueueRange as m, type QueueActivityRequest as n, type QueueActivityOptions as o, type QueueActivityResponse as p, type ReservationHistoryOptions as q, type ReservationHistoryResponse as r, type BaggageAllowanceDesc as s, type BaggageInformation as t, type FareComponent as u, type FareComponentDesc as v, type FareComponentDescSegment as w, type FareComponentSegment as x, type FareComponentSegmentDetail as y, type FareMessage as z };
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Q as QueueRef, a as QueueActivityCounters, R as ReservationHistoryTransaction, b as ReservationHistoryFlightSegment, c as ReservationHistoryText, d as ReservationHistoryAccounting } from './reservation-history-
|
|
1
|
+
import { Q as QueueRef, a as QueueActivityCounters, R as ReservationHistoryTransaction, b as ReservationHistoryFlightSegment, c as ReservationHistoryText, d as ReservationHistoryAccounting } from './reservation-history-C6giSdFd.js';
|
|
2
2
|
export { Anonymizer, AnonymizerLeak, AnonymizerOptions, createAnonymizer, isSafeHostLine } from './anonymize.js';
|
|
3
3
|
|
|
4
4
|
/** Un PNR del fake, con los campos que Sabre manda en `Line`. */
|
|
@@ -8,12 +8,14 @@ interface QueueFakePnr {
|
|
|
8
8
|
/**
|
|
9
9
|
* Las líneas del PNR en formato de host, como el `Paragraph` de Access Queue. Si no termina en
|
|
10
10
|
* una línea de firma (`… HHMM/DDMMMYY <localizador> H`), el fake agrega una: `place` dentro de
|
|
11
|
-
* una cola saca el localizador de ahí, igual que con Sabre.
|
|
11
|
+
* una cola saca el localizador de ahí, igual que con Sabre. `selection` `AL` y `AC` filtran por
|
|
12
|
+
* los segmentos del párrafo, y `PI` por su aviso de PIC (`011 SEE REMARKS`), como los lee
|
|
13
|
+
* `parseQueueParagraph`.
|
|
12
14
|
*/
|
|
13
15
|
paragraph?: readonly string[];
|
|
14
|
-
/** `Line/DateTime`, tal cual. */
|
|
16
|
+
/** `Line/DateTime`, tal cual. Es la fecha por la que filtra `selection` `QD`. */
|
|
15
17
|
placedAt?: string;
|
|
16
|
-
/** `Line/POS/Source`. */
|
|
18
|
+
/** `Line/POS/Source`. `selection` `AK` filtra por el PCC más la firma (`'ABCDAAT'`). */
|
|
17
19
|
placedBy?: {
|
|
18
20
|
agentSine?: string;
|
|
19
21
|
pseudoCityCode?: string;
|
|
@@ -96,6 +98,12 @@ interface QueueFake {
|
|
|
96
98
|
*/
|
|
97
99
|
expireSessions(): void;
|
|
98
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Las funciones de `selection` que simula el fake (#202). En `list`, Sabre acepta solo `QD` y `AK`
|
|
103
|
+
* (producción, #155); en `access` y `count`, cualquiera (#213), pero el fake simula solo estas.
|
|
104
|
+
* Las demás dan un error de host marcado como no simulado.
|
|
105
|
+
*/
|
|
106
|
+
declare const QUEUE_FAKE_SELECTION_FUNCTIONS: readonly ["QD", "AK", "AL", "PI", "AC"];
|
|
99
107
|
declare function createQueueFake(options?: QueueFakeOptions): QueueFake;
|
|
100
108
|
|
|
101
109
|
/** Una respuesta grabada: la operación del SDK, el HTTP status y el sobre SOAP tal cual llegó. */
|
|
@@ -166,4 +174,4 @@ declare const queueFixtures: Readonly<{
|
|
|
166
174
|
*/
|
|
167
175
|
declare function queueFakePnrs(fixture: RecordedFixture): QueueFakePnr[];
|
|
168
176
|
|
|
169
|
-
export { type QueueFake, type QueueFakeActivity, type QueueFakeCall, type QueueFakeCommand, type QueueFakeHistoryTransaction, type QueueFakeItem, type QueueFakeOptions, type QueueFakePnr, type RecordedFixture, type RecordedResponse, createQueueFake, queueFakePnrs, queueFixtures };
|
|
177
|
+
export { QUEUE_FAKE_SELECTION_FUNCTIONS, type QueueFake, type QueueFakeActivity, type QueueFakeCall, type QueueFakeCommand, type QueueFakeHistoryTransaction, type QueueFakeItem, type QueueFakeOptions, type QueueFakePnr, type RecordedFixture, type RecordedResponse, createQueueFake, queueFakePnrs, queueFixtures };
|
package/dist/testing.js
CHANGED
|
@@ -126,6 +126,304 @@ function lineFields(value) {
|
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
// src/queue/host-date.ts
|
|
130
|
+
var MONTHS = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
|
|
131
|
+
var DAY_MS = 864e5;
|
|
132
|
+
var HALF_YEAR_DAYS = 184;
|
|
133
|
+
function resolveHostDate(text2, referenceDate, options = {}) {
|
|
134
|
+
const reference = referenceDay(referenceDate);
|
|
135
|
+
const direction = options.direction ?? "past";
|
|
136
|
+
if (direction !== "past" && direction !== "nearest") {
|
|
137
|
+
throw new TypeError("sabre-client: direction debe ser 'past' o 'nearest'");
|
|
138
|
+
}
|
|
139
|
+
if (typeof text2 !== "string") return void 0;
|
|
140
|
+
const parsed = parseHostDate(text2.trim().toUpperCase());
|
|
141
|
+
if (parsed === void 0) return void 0;
|
|
142
|
+
const year = parsed.year ?? resolveYear(parsed.month, parsed.day, reference, direction);
|
|
143
|
+
if (year === void 0 || !isValidDate(year, parsed.month, parsed.day)) return void 0;
|
|
144
|
+
const date = `${pad(year, 4)}-${pad(parsed.month, 2)}-${pad(parsed.day, 2)}`;
|
|
145
|
+
return parsed.time === void 0 ? date : `${date}T${parsed.time}`;
|
|
146
|
+
}
|
|
147
|
+
function parseHostDate(text2) {
|
|
148
|
+
let match = /^(\d{1,2})([A-Z]{3})(\d{2})?$/.exec(text2);
|
|
149
|
+
if (match !== null) {
|
|
150
|
+
const month2 = MONTHS.indexOf(match[2]) + 1;
|
|
151
|
+
if (month2 === 0) return void 0;
|
|
152
|
+
return {
|
|
153
|
+
...match[3] !== void 0 && { year: 2e3 + Number(match[3]) },
|
|
154
|
+
month: month2,
|
|
155
|
+
day: Number(match[1])
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
match = /^(?:(\d{4})-)?(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}))?$/.exec(text2);
|
|
159
|
+
if (match === null) return void 0;
|
|
160
|
+
const [, year, month, day, hours, minutes] = match;
|
|
161
|
+
if (hours !== void 0 && (Number(hours) > 23 || Number(minutes) > 59)) return void 0;
|
|
162
|
+
return {
|
|
163
|
+
...year !== void 0 && { year: Number(year) },
|
|
164
|
+
month: Number(month),
|
|
165
|
+
day: Number(day),
|
|
166
|
+
...hours !== void 0 && { time: `${hours}:${minutes}` }
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function resolveYear(month, day, reference, direction) {
|
|
170
|
+
const year = new Date(reference).getUTCFullYear();
|
|
171
|
+
if (direction === "past") {
|
|
172
|
+
for (let candidate = year; candidate >= year - 8; candidate--) {
|
|
173
|
+
if (isValidDate(candidate, month, day) && dayNumber(candidate, month, day) <= reference) return candidate;
|
|
174
|
+
}
|
|
175
|
+
return void 0;
|
|
176
|
+
}
|
|
177
|
+
let best;
|
|
178
|
+
for (const candidate of [year + 1, year, year - 1]) {
|
|
179
|
+
if (!isValidDate(candidate, month, day)) continue;
|
|
180
|
+
const distance = Math.abs(dayNumber(candidate, month, day) - reference);
|
|
181
|
+
if (best === void 0 || distance < best.distance) best = { year: candidate, distance };
|
|
182
|
+
}
|
|
183
|
+
return best !== void 0 && best.distance <= HALF_YEAR_DAYS * DAY_MS ? best.year : void 0;
|
|
184
|
+
}
|
|
185
|
+
function referenceDay(referenceDate) {
|
|
186
|
+
if (referenceDate instanceof Date) {
|
|
187
|
+
const time = referenceDate.getTime();
|
|
188
|
+
if (Number.isNaN(time)) throw new TypeError("sabre-client: referenceDate es un Date inv\xE1lido");
|
|
189
|
+
return time - (time % DAY_MS + DAY_MS) % DAY_MS;
|
|
190
|
+
}
|
|
191
|
+
if (typeof referenceDate === "string") {
|
|
192
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})(?:T.*)?$/.exec(referenceDate.trim());
|
|
193
|
+
if (match !== null) {
|
|
194
|
+
const [year, month, day] = [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
195
|
+
if (isValidDate(year, month, day)) return dayNumber(year, month, day);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
throw new TypeError("sabre-client: referenceDate es obligatoria, como 'YYYY-MM-DD' o un Date v\xE1lido");
|
|
199
|
+
}
|
|
200
|
+
function dayNumber(year, month, day) {
|
|
201
|
+
const date = /* @__PURE__ */ new Date(0);
|
|
202
|
+
date.setUTCFullYear(year, month - 1, day);
|
|
203
|
+
return date.getTime();
|
|
204
|
+
}
|
|
205
|
+
function isValidDate(year, month, day) {
|
|
206
|
+
if (month < 1 || month > 12 || day < 1) return false;
|
|
207
|
+
const date = /* @__PURE__ */ new Date(0);
|
|
208
|
+
date.setUTCFullYear(year, month - 1, day);
|
|
209
|
+
return date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
|
210
|
+
}
|
|
211
|
+
function pad(value, length) {
|
|
212
|
+
return String(value).padStart(length, "0");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/queue/queue-paragraph.ts
|
|
216
|
+
var MESSAGE = /^\s*(\d{3})\s{2,}(\S.*?)\s*$/;
|
|
217
|
+
var PASSENGER_LINE = /^\s*\d+\.\d+[A-Z]/;
|
|
218
|
+
var PASSENGER_SPLIT = /\s+(?=\d+\.\d+[A-Z])/;
|
|
219
|
+
var PASSENGER = /^(\d+\.\d+)([^/*]+?)\s*(?:\/([^*]*?))?\s*(?:\*(.*?))?\s*$/;
|
|
220
|
+
var AIR_SEGMENT = /^\s*(\d{1,2})\s+([A-Z0-9]{2})\s*(\d{1,4})([A-Z])\s+(\d{2}[A-Z]{3})\s+[A-Z0-9]\s+([A-Z]{3})([A-Z]{3})\s+([A-Z]{2})(\d{1,3})(?=\s|$)(.*)$/;
|
|
221
|
+
var SEGMENT_TIMES = /^\s+(\d{3,4}[APNM]?)\s+(\d{3,4}[APNM]?)(?=\s|$)/;
|
|
222
|
+
var OTHER_SEGMENT = /^\s*(\d{1,2})\s+([A-Z]{3})\s+([A-Z0-9]{2})\s+(\d{2}[A-Z]{3})\s+[A-Z0-9]\s+([A-Z]{2})(\d{1,3})(?=\s|$)/;
|
|
223
|
+
var NO_ITINERARY = /^\s*NO ITIN\s*$/;
|
|
224
|
+
var TICKETING_HEADER = /^\s*TKT\/TIME LIMIT\s*$/;
|
|
225
|
+
var TICKETING_ENTRY = /^\s*(\d+)\.(.*?)\s*$/;
|
|
226
|
+
var TICKETED = /^T-(\d{2}[A-Z]{3})?(?:-([A-Z0-9]{3,4})\*([A-Z0-9]+))?(?:\/.*)?$/;
|
|
227
|
+
var ARRANGEMENT = /^(TA[WCMTX])([A-Z0-9]{3,4})?(\d{2}[A-Z]{3})?\d{0,3}\/.*$/;
|
|
228
|
+
var TICKET = /^(TE|TK)\s+(\d{13})\b/;
|
|
229
|
+
var RECEIVED = /^\s*RECEIVED FROM -\s?(.*?)\s*$/;
|
|
230
|
+
var SIGNATURE = /^\s*([A-Z0-9]{3,4})\.(\S+)\s+(\d{2})(\d{2})\/(\d{2}[A-Z]{3}\d{2})\s+([A-Z0-9]{6})\s+H\s*$/;
|
|
231
|
+
var Phase = { Messages: 0, Passengers: 1, Segments: 2, Body: 3 };
|
|
232
|
+
function parseQueueParagraph(lines, options) {
|
|
233
|
+
if (!Array.isArray(lines)) throw new TypeError("sabre-client: lines debe ser un array de strings");
|
|
234
|
+
if (typeof options !== "object" || options === null) {
|
|
235
|
+
throw new TypeError("sabre-client: referenceDate es obligatoria, como 'YYYY-MM-DD' o un Date v\xE1lido");
|
|
236
|
+
}
|
|
237
|
+
const { referenceDate } = options;
|
|
238
|
+
referenceDay(referenceDate);
|
|
239
|
+
const result = { messages: [], passengers: [], segments: [], unparsed: [] };
|
|
240
|
+
const signatureIndex = lastSignatureIndex(lines);
|
|
241
|
+
let phase = Phase.Messages;
|
|
242
|
+
let inTicketing = false;
|
|
243
|
+
lines.forEach((value, index) => {
|
|
244
|
+
const line = typeof value === "string" ? value : "";
|
|
245
|
+
if (line.trim() === "") return;
|
|
246
|
+
if (index === signatureIndex) {
|
|
247
|
+
result.receivedFrom = { ...result.receivedFrom, ...parseSignature(line, referenceDate) };
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (inTicketing) {
|
|
251
|
+
const entry = TICKETING_ENTRY.exec(line);
|
|
252
|
+
if (entry !== null) {
|
|
253
|
+
const parsed = parseTicketingEntry(Number(entry[1]), entry[2], referenceDate);
|
|
254
|
+
if (parsed === void 0) result.unparsed.push(line);
|
|
255
|
+
else result.ticketing.entries.push(parsed);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
inTicketing = false;
|
|
259
|
+
}
|
|
260
|
+
if (TICKETING_HEADER.test(line)) {
|
|
261
|
+
phase = Phase.Body;
|
|
262
|
+
inTicketing = true;
|
|
263
|
+
result.ticketing ??= { entries: [], issued: false };
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const received = RECEIVED.exec(line);
|
|
267
|
+
if (received !== null) {
|
|
268
|
+
phase = Phase.Body;
|
|
269
|
+
result.receivedFrom = { ...result.receivedFrom, ...received[1] !== "" && { name: received[1] } };
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (phase === Phase.Messages) {
|
|
273
|
+
const message = MESSAGE.exec(line);
|
|
274
|
+
if (message !== null) {
|
|
275
|
+
result.messages.push({ instructionCode: Number(message[1]), text: message[2] });
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (phase <= Phase.Passengers && PASSENGER_LINE.test(line)) {
|
|
280
|
+
const passengers = parsePassengers(line);
|
|
281
|
+
if (passengers !== void 0) {
|
|
282
|
+
phase = Phase.Passengers;
|
|
283
|
+
result.passengers.push(...passengers);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (phase <= Phase.Segments) {
|
|
288
|
+
if (NO_ITINERARY.test(line)) {
|
|
289
|
+
phase = Phase.Segments;
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const segment = parseSegment(line, referenceDate);
|
|
293
|
+
if (segment !== void 0) {
|
|
294
|
+
phase = Phase.Segments;
|
|
295
|
+
result.segments.push(segment);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
result.unparsed.push(line);
|
|
300
|
+
});
|
|
301
|
+
if (result.ticketing !== void 0) {
|
|
302
|
+
result.ticketing.issued = result.ticketing.entries.some((entry) => entry.ticketNumber !== void 0);
|
|
303
|
+
}
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
function parsePassengers(line) {
|
|
307
|
+
const passengers = [];
|
|
308
|
+
for (const part of line.trim().split(PASSENGER_SPLIT)) {
|
|
309
|
+
const match = PASSENGER.exec(part);
|
|
310
|
+
if (match === null) return void 0;
|
|
311
|
+
const [, nameNumber, surname, givenName, nameRemark] = match;
|
|
312
|
+
passengers.push({
|
|
313
|
+
nameNumber,
|
|
314
|
+
surname: surname.trim(),
|
|
315
|
+
...givenName !== void 0 && givenName !== "" && { givenName },
|
|
316
|
+
...nameRemark !== void 0 && nameRemark !== "" && { nameRemark },
|
|
317
|
+
text: part.slice(nameNumber.length).trim()
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
return passengers;
|
|
321
|
+
}
|
|
322
|
+
function parseSegment(line, referenceDate) {
|
|
323
|
+
const air = AIR_SEGMENT.exec(line);
|
|
324
|
+
if (air !== null) {
|
|
325
|
+
const [, number2, airlineCode, flightNumber, bookingClass, date2, from, to, status2, quantity2, rest] = air;
|
|
326
|
+
const times = SEGMENT_TIMES.exec(rest);
|
|
327
|
+
const departureTime = times === null ? void 0 : hostTime(times[1]);
|
|
328
|
+
const arrivalTime = times === null ? void 0 : hostTime(times[2]);
|
|
329
|
+
return {
|
|
330
|
+
kind: "air",
|
|
331
|
+
number: Number(number2),
|
|
332
|
+
...withDate(date2, referenceDate),
|
|
333
|
+
status: status2,
|
|
334
|
+
quantity: Number(quantity2),
|
|
335
|
+
airlineCode,
|
|
336
|
+
flightNumber: Number(flightNumber),
|
|
337
|
+
bookingClass,
|
|
338
|
+
fromAirportCode: from,
|
|
339
|
+
toAirportCode: to,
|
|
340
|
+
...departureTime !== void 0 && { departureTime },
|
|
341
|
+
...arrivalTime !== void 0 && { arrivalTime },
|
|
342
|
+
text: line.trim()
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
const other = OTHER_SEGMENT.exec(line);
|
|
346
|
+
if (other === null) return void 0;
|
|
347
|
+
const [, number, segmentType, vendorCode, date, status, quantity] = other;
|
|
348
|
+
return {
|
|
349
|
+
kind: "other",
|
|
350
|
+
number: Number(number),
|
|
351
|
+
...withDate(date, referenceDate),
|
|
352
|
+
status,
|
|
353
|
+
quantity: Number(quantity),
|
|
354
|
+
segmentType,
|
|
355
|
+
vendorCode,
|
|
356
|
+
text: line.trim()
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function parseTicketingEntry(number, text2, referenceDate) {
|
|
360
|
+
const ticketed = TICKETED.exec(text2);
|
|
361
|
+
if (ticketed !== null) {
|
|
362
|
+
const [, date, pseudoCityCode, agentSine] = ticketed;
|
|
363
|
+
return {
|
|
364
|
+
number,
|
|
365
|
+
code: "T-",
|
|
366
|
+
...date !== void 0 && withDate(date, referenceDate),
|
|
367
|
+
...pseudoCityCode !== void 0 && { pseudoCityCode },
|
|
368
|
+
...agentSine !== void 0 && { agentSine },
|
|
369
|
+
text: text2
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
const arrangement = ARRANGEMENT.exec(text2);
|
|
373
|
+
if (arrangement !== null) {
|
|
374
|
+
const [, code, pseudoCityCode, date] = arrangement;
|
|
375
|
+
return {
|
|
376
|
+
number,
|
|
377
|
+
code,
|
|
378
|
+
...date !== void 0 && withDate(date, referenceDate),
|
|
379
|
+
...pseudoCityCode !== void 0 && { pseudoCityCode },
|
|
380
|
+
text: text2
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
const ticket = TICKET.exec(text2);
|
|
384
|
+
if (ticket !== null) return { number, code: ticket[1], ticketNumber: ticket[2], text: text2 };
|
|
385
|
+
return void 0;
|
|
386
|
+
}
|
|
387
|
+
function lastSignatureIndex(lines) {
|
|
388
|
+
for (let index = lines.length - 1; index >= 0; index--) {
|
|
389
|
+
const line = lines[index];
|
|
390
|
+
if (typeof line === "string" && SIGNATURE.test(line)) return index;
|
|
391
|
+
}
|
|
392
|
+
return -1;
|
|
393
|
+
}
|
|
394
|
+
function parseSignature(line, referenceDate) {
|
|
395
|
+
const [, pseudoCityCode, sign, hours, minutes, date, locator] = SIGNATURE.exec(line);
|
|
396
|
+
const agentSine = /\*([A-Z0-9]+)$/.exec(sign)?.[1];
|
|
397
|
+
const day = Number(hours) <= 23 && Number(minutes) <= 59 ? resolveHostDate(date, referenceDate) : void 0;
|
|
398
|
+
return {
|
|
399
|
+
pseudoCityCode,
|
|
400
|
+
...agentSine !== void 0 && { agentSine },
|
|
401
|
+
...day !== void 0 && { receivedAt: `${day}T${hours}:${minutes}` },
|
|
402
|
+
locator
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function withDate(text2, referenceDate) {
|
|
406
|
+
const date = resolveHostDate(text2, referenceDate, { direction: "nearest" });
|
|
407
|
+
return date === void 0 ? {} : { date };
|
|
408
|
+
}
|
|
409
|
+
function hostTime(text2) {
|
|
410
|
+
const match = /^(\d{1,2})(\d{2})([APNM])?$/.exec(text2);
|
|
411
|
+
if (match === null) return void 0;
|
|
412
|
+
let hours = Number(match[1]);
|
|
413
|
+
const minutes = Number(match[2]);
|
|
414
|
+
const suffix = match[3];
|
|
415
|
+
if (minutes > 59) return void 0;
|
|
416
|
+
if (suffix === void 0) {
|
|
417
|
+
if (hours > 23) return void 0;
|
|
418
|
+
} else {
|
|
419
|
+
if (hours < 1 || hours > 12) return void 0;
|
|
420
|
+
if (suffix === "A" || suffix === "M") hours %= 12;
|
|
421
|
+
else if (suffix === "P") hours = hours % 12 + 12;
|
|
422
|
+
else if (hours !== 12) return void 0;
|
|
423
|
+
}
|
|
424
|
+
return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`;
|
|
425
|
+
}
|
|
426
|
+
|
|
129
427
|
// src/queue/queue-place.ts
|
|
130
428
|
var SIGNATURE_LINE = /\s\d{4}\/\d{2}[A-Z]{3}\d{2}\s+([A-Z0-9]{6})\s+H\s*$/;
|
|
131
429
|
function signatureLocator(lines) {
|
|
@@ -142,6 +440,15 @@ var STL_NAMESPACE = "http://services.sabre.com/STL/v01";
|
|
|
142
440
|
var SIGNATURE_DATE = "1200/01JAN26";
|
|
143
441
|
var LOCATOR = /^[A-Z0-9]{6}$/;
|
|
144
442
|
var NAVIGATION = { I: "ignore", QR: "remove", QXI: "exit" };
|
|
443
|
+
var QUEUE_FAKE_SELECTION_FUNCTIONS = ["QD", "AK", "AL", "PI", "AC"];
|
|
444
|
+
var LIST_SELECTION_FUNCTIONS = ["QD", "AK"];
|
|
445
|
+
var MONTHS2 = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
|
|
446
|
+
function placedOn(placedAt, criteria) {
|
|
447
|
+
const date = /^(\d{1,2})([A-Z]{3})$/.exec(criteria.toUpperCase());
|
|
448
|
+
const placed = /(\d{2})-(\d{2})T/.exec(placedAt ?? "");
|
|
449
|
+
if (date === null || placed === null) return false;
|
|
450
|
+
return Number(placed[1]) === MONTHS2.indexOf(date[2]) + 1 && Number(placed[2]) === Number(date[1]);
|
|
451
|
+
}
|
|
145
452
|
function fail(message) {
|
|
146
453
|
throw new TypeError(`sabre-client/testing: ${message}`);
|
|
147
454
|
}
|
|
@@ -188,9 +495,9 @@ function refOfKey(key) {
|
|
|
188
495
|
if (key.startsWith(NAME_PREFIX)) return { name: key.slice(NAME_PREFIX.length) };
|
|
189
496
|
return /^\d+$/.test(key) ? Number(key) : key;
|
|
190
497
|
}
|
|
191
|
-
function keyAttribute(key,
|
|
498
|
+
function keyAttribute(key, pad2 = false) {
|
|
192
499
|
if (key.startsWith(NAME_PREFIX)) return `Name="${escape(key.slice(NAME_PREFIX.length))}"`;
|
|
193
|
-
return `Number="${escape(
|
|
500
|
+
return `Number="${escape(pad2 && /^\d+$/.test(key) ? key.padStart(4, "0") : key)}"`;
|
|
194
501
|
}
|
|
195
502
|
function validateKey(key) {
|
|
196
503
|
if (/^\d+$/.test(key)) {
|
|
@@ -438,7 +745,43 @@ function createQueueFake(options = {}) {
|
|
|
438
745
|
const placeRS = (inner) => `<QueuePlaceRS xmlns="${SWS_NAMESPACE}" xmlns:stl="${STL_NAMESPACE}" Version="2.0.4">${inner}</QueuePlaceRS>`;
|
|
439
746
|
const accessError = (message) => accessRS(results("NotProcessed", hostResult("Error", message)));
|
|
440
747
|
const placeError = (message) => placeRS(results("NotProcessed", hostResult("Error", message)));
|
|
748
|
+
const matchesFilter = (entry, fn, criteria) => {
|
|
749
|
+
if (entry.message !== void 0) return false;
|
|
750
|
+
const wanted = criteria.trim().toUpperCase();
|
|
751
|
+
const paragraph = () => parseQueueParagraph(pnrs.get(entry.locator)?.paragraph ?? [], { referenceDate: now() });
|
|
752
|
+
switch (fn.toUpperCase()) {
|
|
753
|
+
case "QD":
|
|
754
|
+
return placedOn(entry.placedAt, wanted);
|
|
755
|
+
case "AK":
|
|
756
|
+
return `${entry.placedBy?.pseudoCityCode ?? ""}${entry.placedBy?.agentSine ?? ""}`.toUpperCase() === wanted;
|
|
757
|
+
case "AL":
|
|
758
|
+
return paragraph().segments.some((segment) => segment.kind === "air" && segment.airlineCode === wanted);
|
|
759
|
+
case "AC":
|
|
760
|
+
return paragraph().segments.some((segment) => segment.status.startsWith(wanted));
|
|
761
|
+
case "PI": {
|
|
762
|
+
const code = Number(wanted);
|
|
763
|
+
if (entry.instructionCode !== void 0) return entry.instructionCode === code;
|
|
764
|
+
return paragraph().messages.some((message) => message.instructionCode === code);
|
|
765
|
+
}
|
|
766
|
+
default:
|
|
767
|
+
return void 0;
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
const selectionOf = (rq, allowed) => {
|
|
771
|
+
const filters = nodes(rq.Selection).map((selection) => ({ fn: (attr(selection, "Function") ?? "").toUpperCase(), criteria: attr(selection, "Criteria") ?? "" }));
|
|
772
|
+
if (filters.length === 0) return {};
|
|
773
|
+
const unsupported = filters.find((filter) => !allowed.includes(filter.fn));
|
|
774
|
+
if (unsupported !== void 0) return { error: `sabre-client/testing: selection ${unsupported.fn} no est\xE1 simulado (simula ${allowed.join(", ")})` };
|
|
775
|
+
return { matches: (entry) => filters.every((filter) => matchesFilter(entry, filter.fn, filter.criteria) === true) };
|
|
776
|
+
};
|
|
777
|
+
const advance = (session) => {
|
|
778
|
+
const inside = session.inside;
|
|
779
|
+
if (inside?.matches === void 0) return;
|
|
780
|
+
const entries = queue(inside.key);
|
|
781
|
+
while (inside.index < entries.length && !inside.matches(entries[inside.index])) inside.index += 1;
|
|
782
|
+
};
|
|
441
783
|
const step = (session) => {
|
|
784
|
+
advance(session);
|
|
442
785
|
const inside = session.inside;
|
|
443
786
|
const entry = inside === void 0 ? void 0 : queue(inside.key)[inside.index];
|
|
444
787
|
if (entry === void 0) {
|
|
@@ -452,19 +795,51 @@ function createQueueFake(options = {}) {
|
|
|
452
795
|
return accessRS(`${results("Complete", success)}${lineXml(entry)}<Paragraph>${textLines2(pnr.paragraph)}</Paragraph>`);
|
|
453
796
|
};
|
|
454
797
|
const waiting = (session, key, entries) => entries.length - (session.inside?.key === key && entries[session.inside.index] !== void 0 ? 1 : 0);
|
|
798
|
+
const countRS = (inner) => `<QueueCountRS xmlns="${SWS_NAMESPACE}" xmlns:stl="${STL_NAMESPACE}" Version="2.2.1">${inner}</QueueCountRS>`;
|
|
455
799
|
const count = (session, rq) => {
|
|
456
|
-
const
|
|
800
|
+
const identifier = node2(node2(rq.QueueInfo)?.QueueIdentifier);
|
|
801
|
+
const pcc = attr(identifier, "PseudoCityCode") ?? "";
|
|
802
|
+
const countError = (message) => countRS(results("NotProcessed", hostResult("Error", message)));
|
|
803
|
+
const range = node2(identifier?.Range);
|
|
804
|
+
const requested = requestedKey(identifier);
|
|
805
|
+
const { matches, error } = selectionOf(rq, QUEUE_FAKE_SELECTION_FUNCTIONS);
|
|
806
|
+
if (matches !== void 0 || error !== void 0) {
|
|
807
|
+
if (range !== void 0) return countError("KEYS NOT ALLOWED");
|
|
808
|
+
if (requested === void 0) return countError("QUEUE SELECTED NOT VALID OR CLOSED");
|
|
809
|
+
if (error !== void 0) return countError(error);
|
|
810
|
+
}
|
|
811
|
+
const info = (identifiers2) => `${results("Complete", success)}<QueueInfo DateTime="${shortDate(now())}" PseudoCityCode="${escape(pcc)}">${identifiers2.join("")}</QueueInfo>`;
|
|
812
|
+
const totalsXml = (messages2, pnrs2) => `<Totals Count="${messages2}" Type="MESSAGES"/><Totals Count="0" Type="SPECIALS"/><Totals Count="${pnrs2}" Type="PNRS"/>`;
|
|
813
|
+
const waitingIn = (key) => waiting(session, key, queues.get(key) ?? []);
|
|
814
|
+
if (range !== void 0) {
|
|
815
|
+
const start = Number(attr(range, "Start"));
|
|
816
|
+
const end = Number(attr(range, "End"));
|
|
817
|
+
const identifiers2 = [];
|
|
818
|
+
let pnrsTotal2 = 0;
|
|
819
|
+
for (let number = start; number <= end; number++) {
|
|
820
|
+
const total = waitingIn(String(number));
|
|
821
|
+
identifiers2.push(`<QueueIdentifier Count="${total}" Number="${number}"/>`);
|
|
822
|
+
pnrsTotal2 += total;
|
|
823
|
+
}
|
|
824
|
+
return countRS(`${info(identifiers2)}${totalsXml(0, pnrsTotal2)}`);
|
|
825
|
+
}
|
|
826
|
+
if (requested !== void 0) {
|
|
827
|
+
if (matches === void 0) return countRS(info([`<QueueIdentifier Count="${waitingIn(requested)}" ${keyAttribute(requested)}/>`]));
|
|
828
|
+
const onScreen = session.inside?.key === requested ? queue(requested)[session.inside.index] : void 0;
|
|
829
|
+
const matching = (queues.get(requested) ?? []).filter((entry) => entry !== onScreen && matches(entry)).length;
|
|
830
|
+
return countRS(`${info([`<QueueIdentifier Count="${matching}" ${keyAttribute(requested)}/>`])}<Totals Count="${waitingIn(requested)}"/>`);
|
|
831
|
+
}
|
|
457
832
|
let pnrsTotal = 0;
|
|
458
833
|
let messages = 0;
|
|
459
834
|
const identifiers = [];
|
|
460
|
-
for (const [
|
|
461
|
-
const total = waiting(session,
|
|
835
|
+
for (const [queueKey2, entries] of queues) {
|
|
836
|
+
const total = waiting(session, queueKey2, entries);
|
|
462
837
|
if (total <= 0) continue;
|
|
463
|
-
identifiers.push(`<QueueIdentifier Count="${total}" ${keyAttribute(
|
|
464
|
-
if (ALPHA_KEY.test(
|
|
838
|
+
identifiers.push(`<QueueIdentifier Count="${total}" ${keyAttribute(queueKey2)}/>`);
|
|
839
|
+
if (ALPHA_KEY.test(queueKey2)) messages += total;
|
|
465
840
|
else pnrsTotal += total;
|
|
466
841
|
}
|
|
467
|
-
return
|
|
842
|
+
return countRS(`${info(identifiers)}${totalsXml(messages, pnrsTotal)}`);
|
|
468
843
|
};
|
|
469
844
|
const access = (session, rq) => {
|
|
470
845
|
const navigation = attr(rq.Navigation, "Action");
|
|
@@ -474,15 +849,20 @@ function createQueueFake(options = {}) {
|
|
|
474
849
|
if (key === void 0) return accessError("sabre-client/testing: QueueIdentifier sin Number ni Name");
|
|
475
850
|
const isList = node2(identifier?.List) !== void 0;
|
|
476
851
|
calls.push({ command: isList ? "list" : "access", queue: refOfKey(key) });
|
|
477
|
-
if (nodes(rq.Selection).
|
|
852
|
+
if (isList && nodes(rq.Selection).some((selection) => !LIST_SELECTION_FUNCTIONS.includes((attr(selection, "Function") ?? "").toUpperCase()))) {
|
|
853
|
+
return accessError("VALID SELECTION CODE FOR QUEUE LIST ARE QD OR AK");
|
|
854
|
+
}
|
|
855
|
+
const { matches, error } = selectionOf(rq, QUEUE_FAKE_SELECTION_FUNCTIONS);
|
|
856
|
+
if (error !== void 0) return accessError(error);
|
|
478
857
|
const entries = queue(key);
|
|
479
858
|
if (entries.length === 0) return accessError(QUEUE_EMPTY);
|
|
480
859
|
if (isList) {
|
|
481
860
|
const passenger = attr(identifier?.List, "PrimaryPassenger") === "true";
|
|
482
|
-
const
|
|
861
|
+
const listed = matches === void 0 ? entries : entries.filter(matches);
|
|
862
|
+
const lines = listed.map((entry, index) => lineXml(entry, { number: index + 1, passenger })).join("");
|
|
483
863
|
return accessRS(`${results("Complete", success)}${lines}`);
|
|
484
864
|
}
|
|
485
|
-
session.inside = { key, index: 0 };
|
|
865
|
+
session.inside = { key, index: 0, ...matches === void 0 ? {} : { matches } };
|
|
486
866
|
return step(session);
|
|
487
867
|
};
|
|
488
868
|
const navigate = (session, rq, action) => {
|
|
@@ -532,10 +912,12 @@ function createQueueFake(options = {}) {
|
|
|
532
912
|
const add = (locator, target) => {
|
|
533
913
|
const entries = queue(target.key);
|
|
534
914
|
if (entries.some((entry) => entry.locator === locator)) return;
|
|
915
|
+
const code = Number(target.instructionCode);
|
|
535
916
|
entries.push({
|
|
536
917
|
locator,
|
|
537
918
|
placedAt: longDate(at),
|
|
538
|
-
placedBy: target.pcc === void 0 ? void 0 : { pseudoCityCode: target.pcc }
|
|
919
|
+
placedBy: target.pcc === void 0 ? void 0 : { pseudoCityCode: target.pcc },
|
|
920
|
+
...Number.isInteger(code) ? { instructionCode: code } : {}
|
|
539
921
|
});
|
|
540
922
|
};
|
|
541
923
|
if (moving) {
|
|
@@ -543,6 +925,7 @@ function createQueueFake(options = {}) {
|
|
|
543
925
|
if (current === void 0) return placeError("sabre-client/testing: no hay PNR en pantalla");
|
|
544
926
|
queue(inside.key).splice(inside.index, 1);
|
|
545
927
|
for (const target of targets) add(current.locator, target);
|
|
928
|
+
advance(session);
|
|
546
929
|
const next = queue(inside.key)[inside.index];
|
|
547
930
|
if (next === void 0) {
|
|
548
931
|
session.inside = void 0;
|
|
@@ -565,22 +948,37 @@ function createQueueFake(options = {}) {
|
|
|
565
948
|
};
|
|
566
949
|
const analyze = (session, rq) => {
|
|
567
950
|
calls.push({ command: "activity" });
|
|
568
|
-
const
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
const identifiers = [];
|
|
572
|
-
for (const key of /* @__PURE__ */ new Set([...queues.keys(), ...activity.keys()])) {
|
|
951
|
+
const identifier = node2(node2(rq.QueueInfo)?.QueueIdentifier);
|
|
952
|
+
const pcc = attr(identifier, "PseudoCityCode") ?? "";
|
|
953
|
+
const countersOf = (key) => {
|
|
573
954
|
const seeded = activity.get(key) ?? {};
|
|
574
955
|
const counters = { NumMessages: waiting(session, key, queues.get(key) ?? []) };
|
|
575
956
|
for (const [name, element2] of ACTIVITY_COUNTERS) counters[element2] = seeded[name] ?? 0;
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
957
|
+
return counters;
|
|
958
|
+
};
|
|
959
|
+
const countersXml = (counters) => Object.entries(counters).map(([element2, value]) => `<${element2} Count="${value}"/>`).join("");
|
|
960
|
+
let keys;
|
|
961
|
+
let withTotals = true;
|
|
962
|
+
const range = node2(identifier?.Range);
|
|
963
|
+
const requested = requestedKey(identifier);
|
|
964
|
+
if (range !== void 0) {
|
|
965
|
+
keys = [];
|
|
966
|
+
for (let number = Number(attr(range, "Start")); number <= Number(attr(range, "End")); number++) keys.push(String(number));
|
|
967
|
+
} else if (requested !== void 0) {
|
|
968
|
+
keys = [requested];
|
|
969
|
+
withTotals = false;
|
|
970
|
+
} else {
|
|
971
|
+
keys = [.../* @__PURE__ */ new Set([...queues.keys(), ...activity.keys()])].filter((key) => Object.values(countersOf(key)).some((value) => value !== 0));
|
|
581
972
|
}
|
|
582
|
-
const
|
|
583
|
-
|
|
973
|
+
const totals = { NumMessages: 0 };
|
|
974
|
+
for (const [, element2] of ACTIVITY_COUNTERS) totals[element2] = 0;
|
|
975
|
+
const identifiers = keys.map((key) => {
|
|
976
|
+
const counters = countersOf(key);
|
|
977
|
+
for (const [element2, value] of Object.entries(counters)) totals[element2] = (totals[element2] ?? 0) + value;
|
|
978
|
+
return `<QueueIdentifier ${keyAttribute(key)}>${countersXml(counters)}</QueueIdentifier>`;
|
|
979
|
+
});
|
|
980
|
+
const totalsXml = withTotals ? `<Totals>${countersXml(totals)}</Totals>` : "";
|
|
981
|
+
return `<QueueAnalysisRS xmlns="${SWS_NAMESPACE}" xmlns:stl="${STL_NAMESPACE}" Version="2.0.0">${results("Complete", success)}<QueueInfo DateTime="${shortDate(now())}" PseudoCityCode="${escape(pcc)}">${identifiers.join("")}</QueueInfo>${totalsXml}</QueueAnalysisRS>`;
|
|
584
982
|
};
|
|
585
983
|
const history = (session, rq) => {
|
|
586
984
|
const locator = attr(rq.UniqueID, "ID") ?? "";
|
|
@@ -3283,6 +3681,7 @@ var queueFixtures = Object.freeze({
|
|
|
3283
3681
|
placeWithoutInstructionCodeInside: fixture(queue_production_place_sin_pic_adentro_default)
|
|
3284
3682
|
});
|
|
3285
3683
|
export {
|
|
3684
|
+
QUEUE_FAKE_SELECTION_FUNCTIONS,
|
|
3286
3685
|
createAnonymizer,
|
|
3287
3686
|
createQueueFake,
|
|
3288
3687
|
isSafeHostLine,
|