google-fli 0.1.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/LICENSE +21 -0
- package/README.md +158 -0
- package/dist/core/airports.d.ts +31 -0
- package/dist/core/airports.d.ts.map +1 -0
- package/dist/core/airports.js +168 -0
- package/dist/core/airports.js.map +1 -0
- package/dist/core/builders.d.ts +30 -0
- package/dist/core/builders.d.ts.map +1 -0
- package/dist/core/builders.js +113 -0
- package/dist/core/builders.js.map +1 -0
- package/dist/core/currency.d.ts +21 -0
- package/dist/core/currency.d.ts.map +1 -0
- package/dist/core/currency.js +172 -0
- package/dist/core/currency.js.map +1 -0
- package/dist/core/dates.d.ts +22 -0
- package/dist/core/dates.d.ts.map +1 -0
- package/dist/core/dates.js +41 -0
- package/dist/core/dates.js.map +1 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +7 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/links.d.ts +39 -0
- package/dist/core/links.d.ts.map +1 -0
- package/dist/core/links.js +53 -0
- package/dist/core/links.js.map +1 -0
- package/dist/core/parsers.d.ts +34 -0
- package/dist/core/parsers.d.ts.map +1 -0
- package/dist/core/parsers.js +161 -0
- package/dist/core/parsers.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/models/airline.d.ts +1120 -0
- package/dist/models/airline.d.ts.map +1 -0
- package/dist/models/airline.js +2224 -0
- package/dist/models/airline.js.map +1 -0
- package/dist/models/airport.d.ts +7899 -0
- package/dist/models/airport.d.ts.map +1 -0
- package/dist/models/airport.js +15782 -0
- package/dist/models/airport.js.map +1 -0
- package/dist/models/google-flights/base.d.ts +232 -0
- package/dist/models/google-flights/base.d.ts.map +1 -0
- package/dist/models/google-flights/base.js +188 -0
- package/dist/models/google-flights/base.js.map +1 -0
- package/dist/models/google-flights/dates.d.ts +51 -0
- package/dist/models/google-flights/dates.d.ts.map +1 -0
- package/dist/models/google-flights/dates.js +222 -0
- package/dist/models/google-flights/dates.js.map +1 -0
- package/dist/models/google-flights/flights.d.ts +57 -0
- package/dist/models/google-flights/flights.d.ts.map +1 -0
- package/dist/models/google-flights/flights.js +224 -0
- package/dist/models/google-flights/flights.js.map +1 -0
- package/dist/models/google-flights/index.d.ts +4 -0
- package/dist/models/google-flights/index.d.ts.map +1 -0
- package/dist/models/google-flights/index.js +4 -0
- package/dist/models/google-flights/index.js.map +1 -0
- package/dist/models/index.d.ts +4 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +4 -0
- package/dist/models/index.js.map +1 -0
- package/dist/search/client.d.ts +66 -0
- package/dist/search/client.d.ts.map +1 -0
- package/dist/search/client.js +205 -0
- package/dist/search/client.js.map +1 -0
- package/dist/search/concurrency.d.ts +45 -0
- package/dist/search/concurrency.d.ts.map +1 -0
- package/dist/search/concurrency.js +148 -0
- package/dist/search/concurrency.js.map +1 -0
- package/dist/search/dates.d.ts +33 -0
- package/dist/search/dates.d.ts.map +1 -0
- package/dist/search/dates.js +159 -0
- package/dist/search/dates.js.map +1 -0
- package/dist/search/decoders.d.ts +22 -0
- package/dist/search/decoders.d.ts.map +1 -0
- package/dist/search/decoders.js +333 -0
- package/dist/search/decoders.js.map +1 -0
- package/dist/search/exceptions.d.ts +21 -0
- package/dist/search/exceptions.d.ts.map +1 -0
- package/dist/search/exceptions.js +37 -0
- package/dist/search/exceptions.js.map +1 -0
- package/dist/search/flights.d.ts +57 -0
- package/dist/search/flights.d.ts.map +1 -0
- package/dist/search/flights.js +298 -0
- package/dist/search/flights.js.map +1 -0
- package/dist/search/helpers.d.ts +10 -0
- package/dist/search/helpers.d.ts.map +1 -0
- package/dist/search/helpers.js +30 -0
- package/dist/search/helpers.js.map +1 -0
- package/dist/search/index.d.ts +10 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +10 -0
- package/dist/search/index.js.map +1 -0
- package/dist/search/proto.d.ts +69 -0
- package/dist/search/proto.d.ts.map +1 -0
- package/dist/search/proto.js +355 -0
- package/dist/search/proto.js.map +1 -0
- package/dist/search/urls.d.ts +9 -0
- package/dist/search/urls.d.ts.map +1 -0
- package/dist/search/urls.js +9 -0
- package/dist/search/urls.js.map +1 -0
- package/dist/search/wire.d.ts +14 -0
- package/dist/search/wire.d.ts.map +1 -0
- package/dist/search/wire.js +120 -0
- package/dist/search/wire.js.map +1 -0
- package/package.json +91 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client with rate limiting, retries, and proxy support.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the Python `curl_cffi` impersonation with a `fetch`-based
|
|
5
|
+
* client that:
|
|
6
|
+
*
|
|
7
|
+
* - sends realistic Chrome-like headers
|
|
8
|
+
* - honours HTTPS_PROXY / HTTP_PROXY env vars (Bun's fetch supports this
|
|
9
|
+
* via the `proxy` option)
|
|
10
|
+
* - rate-limits at 10 req/sec via {@link TokenBucketRateLimiter}
|
|
11
|
+
* - retries network errors with exponential backoff
|
|
12
|
+
* - wraps low-level errors into the typed {@link SearchClientError} family
|
|
13
|
+
*/
|
|
14
|
+
import { TokenBucketRateLimiter } from "./concurrency.js";
|
|
15
|
+
import { SearchClientError, SearchConnectionError, SearchHTTPError, SearchTimeoutError, } from "./exceptions.js";
|
|
16
|
+
const DEFAULT_CALLS_PER_SECOND = 10;
|
|
17
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
18
|
+
const DEFAULT_RETRIES = 3;
|
|
19
|
+
const DEFAULT_BACKOFF_MS = 1_000;
|
|
20
|
+
function parseEnvTimeoutMs() {
|
|
21
|
+
const raw = typeof process !== "undefined" ? process.env?.FLI_TIMEOUT : undefined;
|
|
22
|
+
if (raw == null)
|
|
23
|
+
return DEFAULT_TIMEOUT_MS;
|
|
24
|
+
const n = Number.parseFloat(raw);
|
|
25
|
+
if (!Number.isFinite(n)) {
|
|
26
|
+
throw new Error(`FLI_TIMEOUT must be a number of seconds, got: ${JSON.stringify(raw)}`);
|
|
27
|
+
}
|
|
28
|
+
if (n <= 0) {
|
|
29
|
+
throw new Error(`FLI_TIMEOUT must be a positive number, got: ${JSON.stringify(raw)}`);
|
|
30
|
+
}
|
|
31
|
+
return Math.round(n * 1000);
|
|
32
|
+
}
|
|
33
|
+
function resolveProxy() {
|
|
34
|
+
if (typeof process === "undefined" || !process.env)
|
|
35
|
+
return undefined;
|
|
36
|
+
const env = process.env;
|
|
37
|
+
return env.HTTPS_PROXY ?? env.https_proxy ?? env.HTTP_PROXY ?? env.http_proxy ?? undefined;
|
|
38
|
+
}
|
|
39
|
+
const DEFAULT_HEADERS = {
|
|
40
|
+
// A realistic recent-Chrome UA. Google's frontend is tolerant of mismatch
|
|
41
|
+
// between the UA and the actual TLS fingerprint (which we can't fake from
|
|
42
|
+
// a Node/Bun fetch) but a credible UA makes a difference vs the default
|
|
43
|
+
// "node-fetch" / "undici" strings.
|
|
44
|
+
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
|
45
|
+
accept: "*/*",
|
|
46
|
+
"accept-language": "en-US,en;q=0.9",
|
|
47
|
+
"sec-ch-ua": '"Chromium";v="131", "Not_A Brand";v="24", "Google Chrome";v="131"',
|
|
48
|
+
"sec-ch-ua-mobile": "?0",
|
|
49
|
+
"sec-ch-ua-platform": '"macOS"',
|
|
50
|
+
"sec-fetch-dest": "empty",
|
|
51
|
+
"sec-fetch-mode": "cors",
|
|
52
|
+
"sec-fetch-site": "same-origin",
|
|
53
|
+
"content-type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
54
|
+
};
|
|
55
|
+
function sleep(ms) {
|
|
56
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
57
|
+
}
|
|
58
|
+
function hostFromUrl(url) {
|
|
59
|
+
try {
|
|
60
|
+
return new URL(url).host || url;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return url;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function isAbortError(err) {
|
|
67
|
+
if (err instanceof DOMException && err.name === "AbortError")
|
|
68
|
+
return true;
|
|
69
|
+
if (typeof err === "object" && err != null && "name" in err) {
|
|
70
|
+
return err.name === "AbortError";
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
function wrapRequestError(method, url, err) {
|
|
75
|
+
if (err instanceof SearchClientError)
|
|
76
|
+
return err;
|
|
77
|
+
const host = hostFromUrl(url);
|
|
78
|
+
if (isAbortError(err)) {
|
|
79
|
+
return new SearchTimeoutError(`Timed out talking to Google Flights (${host}). The service may be slow or unreachable from your network — check your connection and try again.`);
|
|
80
|
+
}
|
|
81
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
82
|
+
// Network-y messages from undici/bun-internals get bucketed into Connection.
|
|
83
|
+
if (/(ECONNRESET|ENOTFOUND|ECONNREFUSED|EAI_AGAIN|fetch failed)/i.test(message)) {
|
|
84
|
+
return new SearchConnectionError(`Could not reach Google Flights (${host}). Check your internet connection or DNS and try again.`);
|
|
85
|
+
}
|
|
86
|
+
return new SearchClientError(`${method} request to Google Flights (${host}) failed: ${err instanceof Error ? err.name : "Error"}`);
|
|
87
|
+
}
|
|
88
|
+
export class Client {
|
|
89
|
+
rateLimiter;
|
|
90
|
+
timeoutMs;
|
|
91
|
+
retries;
|
|
92
|
+
backoffMs;
|
|
93
|
+
proxy;
|
|
94
|
+
fetchImpl;
|
|
95
|
+
constructor(options = {}) {
|
|
96
|
+
this.rateLimiter = new TokenBucketRateLimiter(options.callsPerSecond ?? DEFAULT_CALLS_PER_SECOND, 1.0);
|
|
97
|
+
this.timeoutMs = options.timeoutMs ?? parseEnvTimeoutMs();
|
|
98
|
+
this.retries = options.retries ?? DEFAULT_RETRIES;
|
|
99
|
+
this.backoffMs = options.backoffMs ?? DEFAULT_BACKOFF_MS;
|
|
100
|
+
this.proxy = options.proxy === null ? undefined : (options.proxy ?? resolveProxy());
|
|
101
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
102
|
+
}
|
|
103
|
+
async get(url, options = {}) {
|
|
104
|
+
return this.request("GET", url, options);
|
|
105
|
+
}
|
|
106
|
+
async post(url, options = {}) {
|
|
107
|
+
return this.request("POST", url, options);
|
|
108
|
+
}
|
|
109
|
+
async request(method, url, options) {
|
|
110
|
+
let lastError = null;
|
|
111
|
+
for (let attempt = 0; attempt < this.retries; attempt++) {
|
|
112
|
+
await this.rateLimiter.acquire();
|
|
113
|
+
const controller = new AbortController();
|
|
114
|
+
const externalSignal = options.signal;
|
|
115
|
+
let abortListener;
|
|
116
|
+
if (externalSignal) {
|
|
117
|
+
if (externalSignal.aborted)
|
|
118
|
+
controller.abort(externalSignal.reason);
|
|
119
|
+
else {
|
|
120
|
+
abortListener = () => controller.abort(externalSignal.reason);
|
|
121
|
+
externalSignal.addEventListener("abort", abortListener);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
125
|
+
try {
|
|
126
|
+
const init = {
|
|
127
|
+
method,
|
|
128
|
+
headers: { ...DEFAULT_HEADERS, ...options.headers },
|
|
129
|
+
signal: controller.signal,
|
|
130
|
+
};
|
|
131
|
+
if (method === "POST" && options.body != null) {
|
|
132
|
+
init.body =
|
|
133
|
+
options.body instanceof Uint8Array ? options.body : options.body;
|
|
134
|
+
}
|
|
135
|
+
if (this.proxy)
|
|
136
|
+
init.proxy = this.proxy;
|
|
137
|
+
const response = await this.fetchImpl(url, init);
|
|
138
|
+
if (!response.ok) {
|
|
139
|
+
// Match the Python `raise_for_status` semantics — surface non-2xx
|
|
140
|
+
// as a typed error and let the retry loop decide whether to back off.
|
|
141
|
+
throw new SearchHTTPError(`Google Flights returned an error response (HTTP ${response.status}). The request may be malformed, rate-limited, or blocked.`, response.status);
|
|
142
|
+
}
|
|
143
|
+
const text = await response.text();
|
|
144
|
+
return {
|
|
145
|
+
status: response.status,
|
|
146
|
+
statusText: response.statusText,
|
|
147
|
+
text,
|
|
148
|
+
headers: response.headers,
|
|
149
|
+
ok: response.ok,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
// Distinguish external cancellation from internal timeout: if the
|
|
154
|
+
// caller's AbortSignal triggered the abort, propagate the original
|
|
155
|
+
// error without retry and without relabelling it as a timeout. A
|
|
156
|
+
// consumer catching SearchTimeoutError to decide whether to retry
|
|
157
|
+
// would otherwise retry on a deliberate cancellation, and the
|
|
158
|
+
// "Google was slow" message would be misleading.
|
|
159
|
+
if (isAbortError(err) && externalSignal?.aborted) {
|
|
160
|
+
throw externalSignal.reason ?? err;
|
|
161
|
+
}
|
|
162
|
+
lastError = wrapRequestError(method, url, err);
|
|
163
|
+
// For HTTP errors we still respect the retry budget (matches the
|
|
164
|
+
// Python tenacity retry decorator behavior, which retries on any
|
|
165
|
+
// exception).
|
|
166
|
+
if (attempt < this.retries - 1) {
|
|
167
|
+
const wait = this.backoffMs * 2 ** attempt;
|
|
168
|
+
await sleep(wait);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
clearTimeout(timer);
|
|
175
|
+
if (abortListener && externalSignal) {
|
|
176
|
+
externalSignal.removeEventListener("abort", abortListener);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
throw lastError ?? new SearchClientError("Unknown request failure");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
let _sharedClient = null;
|
|
184
|
+
/**
|
|
185
|
+
* Return the process-wide shared client.
|
|
186
|
+
*
|
|
187
|
+
* Lazy on the first call. If `options` is passed on a later call, the
|
|
188
|
+
* existing singleton is replaced with a new instance configured against
|
|
189
|
+
* those options (and that new instance becomes the cached singleton for
|
|
190
|
+
* subsequent no-arg callers). Callers that want a fully isolated client
|
|
191
|
+
* — e.g. to use a different proxy in one place without affecting
|
|
192
|
+
* others — should construct `new Client(options)` directly and pass it
|
|
193
|
+
* to `SearchFlights` / `SearchDates`.
|
|
194
|
+
*/
|
|
195
|
+
export function getClient(options) {
|
|
196
|
+
if (_sharedClient == null || options != null) {
|
|
197
|
+
_sharedClient = new Client(options);
|
|
198
|
+
}
|
|
199
|
+
return _sharedClient;
|
|
200
|
+
}
|
|
201
|
+
/** Replace the shared client (test helper). */
|
|
202
|
+
export function _setSharedClient(client) {
|
|
203
|
+
_sharedClient = client;
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/search/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async concurrency primitives. 1:1 port of fli/search/_concurrency.py,
|
|
3
|
+
* adapted to JavaScript's single-threaded event loop (no need for a
|
|
4
|
+
* threading.Condition — `setTimeout`/`Promise` are the equivalent).
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Async token-bucket rate limiter.
|
|
8
|
+
*
|
|
9
|
+
* The bucket starts full (`capacity` tokens) and refills continuously at
|
|
10
|
+
* `capacity / period` tokens per second. `acquire()` resolves once a
|
|
11
|
+
* token has been taken; concurrent waiters are released in arrival order
|
|
12
|
+
* via a FIFO promise chain.
|
|
13
|
+
*/
|
|
14
|
+
export declare class TokenBucketRateLimiter {
|
|
15
|
+
private readonly capacityValue;
|
|
16
|
+
private readonly refillPerSecond;
|
|
17
|
+
private tokens;
|
|
18
|
+
private lastRefill;
|
|
19
|
+
/** Serialises waiters so they wake in arrival order. */
|
|
20
|
+
private queue;
|
|
21
|
+
constructor(calls: number, period: number);
|
|
22
|
+
get capacity(): number;
|
|
23
|
+
private refill;
|
|
24
|
+
/**
|
|
25
|
+
* Block until `tokens` are available; returns `false` on timeout.
|
|
26
|
+
*
|
|
27
|
+
* @param tokens count to acquire (must be ≤ capacity)
|
|
28
|
+
* @param timeoutMs optional timeout in milliseconds
|
|
29
|
+
*/
|
|
30
|
+
acquire(tokens?: number, timeoutMs?: number | null): Promise<boolean>;
|
|
31
|
+
}
|
|
32
|
+
export declare function configureConcurrency(maxWorkers: number): void;
|
|
33
|
+
export declare function getDefaultMaxWorkers(): number;
|
|
34
|
+
/**
|
|
35
|
+
* Apply `fn` to each item with bounded concurrency; results returned in
|
|
36
|
+
* input order. Fast-path for ≤1 items / maxWorkers=1 (no scheduling
|
|
37
|
+
* overhead).
|
|
38
|
+
*
|
|
39
|
+
* The first rejection rethrows after all in-flight calls have settled,
|
|
40
|
+
* matching the Python implementation's "let siblings finish" contract.
|
|
41
|
+
*/
|
|
42
|
+
export declare function parallelMap<T, R>(fn: (item: T) => Promise<R>, items: Iterable<T>, options?: {
|
|
43
|
+
maxWorkers?: number;
|
|
44
|
+
}): Promise<R[]>;
|
|
45
|
+
//# sourceMappingURL=concurrency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"concurrency.d.ts","sourceRoot":"","sources":["../../src/search/concurrency.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;GAOG;AACH,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,wDAAwD;IACxD,OAAO,CAAC,KAAK,CAAoC;gBAErC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IASzC,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,OAAO,CAAC,MAAM;IASd;;;;;OAKG;IACG,OAAO,CAAC,MAAM,SAAI,EAAE,SAAS,GAAE,MAAM,GAAG,IAAW,GAAG,OAAO,CAAC,OAAO,CAAC;CAuC7E;AAQD,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAG7D;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,CAAC,EAAE,CAAC,EACpC,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EAC3B,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,EAClB,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GACpC,OAAO,CAAC,CAAC,EAAE,CAAC,CAqCd"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async concurrency primitives. 1:1 port of fli/search/_concurrency.py,
|
|
3
|
+
* adapted to JavaScript's single-threaded event loop (no need for a
|
|
4
|
+
* threading.Condition — `setTimeout`/`Promise` are the equivalent).
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Async token-bucket rate limiter.
|
|
8
|
+
*
|
|
9
|
+
* The bucket starts full (`capacity` tokens) and refills continuously at
|
|
10
|
+
* `capacity / period` tokens per second. `acquire()` resolves once a
|
|
11
|
+
* token has been taken; concurrent waiters are released in arrival order
|
|
12
|
+
* via a FIFO promise chain.
|
|
13
|
+
*/
|
|
14
|
+
export class TokenBucketRateLimiter {
|
|
15
|
+
capacityValue;
|
|
16
|
+
refillPerSecond;
|
|
17
|
+
tokens;
|
|
18
|
+
lastRefill;
|
|
19
|
+
/** Serialises waiters so they wake in arrival order. */
|
|
20
|
+
queue = Promise.resolve();
|
|
21
|
+
constructor(calls, period) {
|
|
22
|
+
if (calls <= 0)
|
|
23
|
+
throw new Error("calls must be positive");
|
|
24
|
+
if (period <= 0)
|
|
25
|
+
throw new Error("period must be positive");
|
|
26
|
+
this.capacityValue = calls;
|
|
27
|
+
this.refillPerSecond = calls / period;
|
|
28
|
+
this.tokens = calls;
|
|
29
|
+
this.lastRefill = performance.now() / 1000;
|
|
30
|
+
}
|
|
31
|
+
get capacity() {
|
|
32
|
+
return Math.floor(this.capacityValue);
|
|
33
|
+
}
|
|
34
|
+
refill() {
|
|
35
|
+
const now = performance.now() / 1000;
|
|
36
|
+
const elapsed = now - this.lastRefill;
|
|
37
|
+
if (elapsed > 0) {
|
|
38
|
+
this.tokens = Math.min(this.capacityValue, this.tokens + elapsed * this.refillPerSecond);
|
|
39
|
+
this.lastRefill = now;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Block until `tokens` are available; returns `false` on timeout.
|
|
44
|
+
*
|
|
45
|
+
* @param tokens count to acquire (must be ≤ capacity)
|
|
46
|
+
* @param timeoutMs optional timeout in milliseconds
|
|
47
|
+
*/
|
|
48
|
+
async acquire(tokens = 1, timeoutMs = null) {
|
|
49
|
+
if (tokens <= 0)
|
|
50
|
+
return true;
|
|
51
|
+
if (tokens > this.capacityValue) {
|
|
52
|
+
throw new Error(`tokens=${tokens} exceeds bucket capacity=${this.capacity}`);
|
|
53
|
+
}
|
|
54
|
+
const deadline = timeoutMs == null ? null : performance.now() + timeoutMs;
|
|
55
|
+
// Serialise waiters so they take tokens FIFO.
|
|
56
|
+
let release = () => { };
|
|
57
|
+
const slot = new Promise((res) => {
|
|
58
|
+
release = res;
|
|
59
|
+
});
|
|
60
|
+
const prev = this.queue;
|
|
61
|
+
this.queue = prev.then(() => slot);
|
|
62
|
+
await prev;
|
|
63
|
+
try {
|
|
64
|
+
// eslint-disable-next-line no-constant-condition
|
|
65
|
+
while (true) {
|
|
66
|
+
this.refill();
|
|
67
|
+
if (this.tokens >= tokens) {
|
|
68
|
+
this.tokens -= tokens;
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
const deficit = tokens - this.tokens;
|
|
72
|
+
const waitS = deficit / this.refillPerSecond;
|
|
73
|
+
let waitMs = waitS * 1000;
|
|
74
|
+
if (deadline != null) {
|
|
75
|
+
const remaining = deadline - performance.now();
|
|
76
|
+
if (remaining <= 0)
|
|
77
|
+
return false;
|
|
78
|
+
waitMs = Math.min(waitMs, remaining);
|
|
79
|
+
}
|
|
80
|
+
await new Promise((res) => setTimeout(res, waitMs));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
// Wake the next waiter so they can re-check the bucket.
|
|
85
|
+
release();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// parallelMap — bounded Promise.all
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
let DEFAULT_MAX_WORKERS = 10;
|
|
93
|
+
export function configureConcurrency(maxWorkers) {
|
|
94
|
+
if (maxWorkers <= 0)
|
|
95
|
+
throw new Error("maxWorkers must be positive");
|
|
96
|
+
DEFAULT_MAX_WORKERS = maxWorkers;
|
|
97
|
+
}
|
|
98
|
+
export function getDefaultMaxWorkers() {
|
|
99
|
+
return DEFAULT_MAX_WORKERS;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Apply `fn` to each item with bounded concurrency; results returned in
|
|
103
|
+
* input order. Fast-path for ≤1 items / maxWorkers=1 (no scheduling
|
|
104
|
+
* overhead).
|
|
105
|
+
*
|
|
106
|
+
* The first rejection rethrows after all in-flight calls have settled,
|
|
107
|
+
* matching the Python implementation's "let siblings finish" contract.
|
|
108
|
+
*/
|
|
109
|
+
export async function parallelMap(fn, items, options = {}) {
|
|
110
|
+
const materialised = Array.isArray(items) ? items : [...items];
|
|
111
|
+
const n = materialised.length;
|
|
112
|
+
if (n === 0)
|
|
113
|
+
return [];
|
|
114
|
+
const workers = options.maxWorkers ?? DEFAULT_MAX_WORKERS;
|
|
115
|
+
if (n === 1 || workers === 1) {
|
|
116
|
+
const out = Array.from({ length: n });
|
|
117
|
+
for (let i = 0; i < n; i++) {
|
|
118
|
+
out[i] = await fn(materialised[i]);
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
const results = Array.from({ length: n });
|
|
123
|
+
let firstError = null;
|
|
124
|
+
let nextIndex = 0;
|
|
125
|
+
const runner = async () => {
|
|
126
|
+
while (true) {
|
|
127
|
+
const idx = nextIndex++;
|
|
128
|
+
if (idx >= n)
|
|
129
|
+
return;
|
|
130
|
+
try {
|
|
131
|
+
results[idx] = await fn(materialised[idx]);
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
if (firstError == null)
|
|
135
|
+
firstError = err;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
const workerPromises = [];
|
|
140
|
+
const cap = Math.min(workers, n);
|
|
141
|
+
for (let i = 0; i < cap; i++)
|
|
142
|
+
workerPromises.push(runner());
|
|
143
|
+
await Promise.all(workerPromises);
|
|
144
|
+
if (firstError != null)
|
|
145
|
+
throw firstError;
|
|
146
|
+
return results;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=concurrency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"concurrency.js","sourceRoot":"","sources":["../../src/search/concurrency.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Date-based flight search. Finds the cheapest prices across a date range
|
|
3
|
+
* using Google Flights' GetCalendarGraph RPC.
|
|
4
|
+
*
|
|
5
|
+
* 1:1 port of fli/search/dates.py.
|
|
6
|
+
*/
|
|
7
|
+
import { TripType } from "../models/google-flights/base.ts";
|
|
8
|
+
import { DateSearchFilters } from "../models/google-flights/dates.ts";
|
|
9
|
+
import { type Client } from "./client.ts";
|
|
10
|
+
export interface DatePrice {
|
|
11
|
+
/** Single date for one-way searches, or `[outbound, return]` for round trip. */
|
|
12
|
+
date: [Date] | [Date, Date];
|
|
13
|
+
price: number;
|
|
14
|
+
currency: string | null;
|
|
15
|
+
}
|
|
16
|
+
export interface DateSearchOptions {
|
|
17
|
+
currency?: string | null;
|
|
18
|
+
language?: string | null;
|
|
19
|
+
country?: string | null;
|
|
20
|
+
}
|
|
21
|
+
export declare class SearchDates {
|
|
22
|
+
static readonly BASE_URL = "https://www.google.com/_/FlightsFrontendUi/data/travel.frontend.flights.FlightsFrontendService/GetCalendarGraph";
|
|
23
|
+
static readonly MAX_DAYS_PER_SEARCH = 61;
|
|
24
|
+
private readonly client;
|
|
25
|
+
constructor(client?: Client);
|
|
26
|
+
search(filters: DateSearchFilters, options?: DateSearchOptions): Promise<DatePrice[] | null>;
|
|
27
|
+
private _buildChunkFilters;
|
|
28
|
+
private _searchChunk;
|
|
29
|
+
static _parseDate(item: unknown, tripType: TripType): [Date] | [Date, Date];
|
|
30
|
+
static _parsePrice(item: unknown): number | null;
|
|
31
|
+
static _parseCurrency(item: unknown): string | null;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=dates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dates.d.ts","sourceRoot":"","sources":["../../src/search/dates.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,KAAK,MAAM,EAAa,MAAM,aAAa,CAAC;AASrD,MAAM,WAAW,SAAS;IACxB,gFAAgF;IAChF,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAwCD,qBAAa,WAAW;IACtB,MAAM,CAAC,QAAQ,CAAC,QAAQ,qHAAY;IACpC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,MAAuB;IAE1D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,MAAM,CAAC,EAAE,MAAM;IAIrB,MAAM,CACV,OAAO,EAAE,iBAAiB,EAC1B,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC;IAoB9B,OAAO,CAAC,kBAAkB;YA6BZ,YAAY;IA+B1B,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC;IAQ3E,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI;IAWhD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI;CAUpD"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Date-based flight search. Finds the cheapest prices across a date range
|
|
3
|
+
* using Google Flights' GetCalendarGraph RPC.
|
|
4
|
+
*
|
|
5
|
+
* 1:1 port of fli/search/dates.py.
|
|
6
|
+
*/
|
|
7
|
+
import { extractCurrencyFromPriceToken } from "../core/currency.js";
|
|
8
|
+
import { formatIsoDate, parseIsoDate } from "../core/dates.js";
|
|
9
|
+
import { TripType } from "../models/google-flights/base.js";
|
|
10
|
+
import { DateSearchFilters } from "../models/google-flights/dates.js";
|
|
11
|
+
import { getClient } from "./client.js";
|
|
12
|
+
import { parallelMap } from "./concurrency.js";
|
|
13
|
+
import { withLocaleParams } from "./urls.js";
|
|
14
|
+
import { parseFirstWrbPayload } from "./wire.js";
|
|
15
|
+
const MAX_DAYS_PER_SEARCH = 61;
|
|
16
|
+
const BASE_URL = "https://www.google.com/_/FlightsFrontendUi/data/travel.frontend.flights.FlightsFrontendService/GetCalendarGraph";
|
|
17
|
+
function cloneSegments(filters) {
|
|
18
|
+
return filters.flight_segments.map((s) => {
|
|
19
|
+
const clone = Object.create(Object.getPrototypeOf(s));
|
|
20
|
+
Object.assign(clone, {
|
|
21
|
+
departure_airport: s.departure_airport,
|
|
22
|
+
arrival_airport: s.arrival_airport,
|
|
23
|
+
travel_date: s.travel_date,
|
|
24
|
+
time_restrictions: s.time_restrictions,
|
|
25
|
+
selected_flight: s.selected_flight,
|
|
26
|
+
});
|
|
27
|
+
return clone;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function cloneFilters(filters) {
|
|
31
|
+
const out = Object.create(DateSearchFilters.prototype);
|
|
32
|
+
Object.assign(out, {
|
|
33
|
+
trip_type: filters.trip_type,
|
|
34
|
+
passenger_info: { ...filters.passenger_info },
|
|
35
|
+
flight_segments: cloneSegments(filters),
|
|
36
|
+
stops: filters.stops,
|
|
37
|
+
seat_type: filters.seat_type,
|
|
38
|
+
price_limit: filters.price_limit ? { ...filters.price_limit } : null,
|
|
39
|
+
airlines: filters.airlines ? [...filters.airlines] : null,
|
|
40
|
+
airlines_exclude: filters.airlines_exclude ? [...filters.airlines_exclude] : null,
|
|
41
|
+
alliances: filters.alliances ? [...filters.alliances] : null,
|
|
42
|
+
alliances_exclude: filters.alliances_exclude ? [...filters.alliances_exclude] : null,
|
|
43
|
+
max_duration: filters.max_duration,
|
|
44
|
+
layover_restrictions: filters.layover_restrictions ? { ...filters.layover_restrictions } : null,
|
|
45
|
+
emissions: filters.emissions,
|
|
46
|
+
bags: filters.bags ? { ...filters.bags } : null,
|
|
47
|
+
from_date: filters.from_date,
|
|
48
|
+
to_date: filters.to_date,
|
|
49
|
+
duration: filters.duration,
|
|
50
|
+
});
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
export class SearchDates {
|
|
54
|
+
static BASE_URL = BASE_URL;
|
|
55
|
+
static MAX_DAYS_PER_SEARCH = MAX_DAYS_PER_SEARCH;
|
|
56
|
+
client;
|
|
57
|
+
constructor(client) {
|
|
58
|
+
this.client = client ?? getClient();
|
|
59
|
+
}
|
|
60
|
+
async search(filters, options = {}) {
|
|
61
|
+
const fromDate = parseIsoDate(filters.from_date);
|
|
62
|
+
const toDate = parseIsoDate(filters.to_date);
|
|
63
|
+
const dayMs = 24 * 60 * 60 * 1000;
|
|
64
|
+
const dateRange = Math.floor((toDate.getTime() - fromDate.getTime()) / dayMs) + 1;
|
|
65
|
+
if (dateRange <= MAX_DAYS_PER_SEARCH) {
|
|
66
|
+
return this._searchChunk(filters, options);
|
|
67
|
+
}
|
|
68
|
+
const chunkFilters = this._buildChunkFilters(filters, fromDate, toDate);
|
|
69
|
+
const chunkResults = await parallelMap((cf) => this._searchChunk(cf, options), chunkFilters);
|
|
70
|
+
const allResults = [];
|
|
71
|
+
for (const r of chunkResults) {
|
|
72
|
+
if (r)
|
|
73
|
+
allResults.push(...r);
|
|
74
|
+
}
|
|
75
|
+
return allResults.length > 0 ? allResults : null;
|
|
76
|
+
}
|
|
77
|
+
_buildChunkFilters(filters, fromDate, toDate) {
|
|
78
|
+
const chunks = [];
|
|
79
|
+
let currentFrom = fromDate;
|
|
80
|
+
let chunkIndex = 0;
|
|
81
|
+
const dayMs = 24 * 60 * 60 * 1000;
|
|
82
|
+
while (currentFrom <= toDate) {
|
|
83
|
+
const endMs = currentFrom.getTime() + (MAX_DAYS_PER_SEARCH - 1) * dayMs;
|
|
84
|
+
const currentTo = new Date(Math.min(endMs, toDate.getTime()));
|
|
85
|
+
const cloned = cloneFilters(filters);
|
|
86
|
+
const shiftDays = MAX_DAYS_PER_SEARCH * chunkIndex;
|
|
87
|
+
if (chunkIndex > 0) {
|
|
88
|
+
for (const segment of cloned.flight_segments) {
|
|
89
|
+
const segDate = parseIsoDate(segment.travel_date);
|
|
90
|
+
segment.travel_date = formatIsoDate(new Date(segDate.getTime() + shiftDays * dayMs));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
cloned.from_date = formatIsoDate(currentFrom);
|
|
94
|
+
cloned.to_date = formatIsoDate(currentTo);
|
|
95
|
+
chunks.push(cloned);
|
|
96
|
+
currentFrom = new Date(currentTo.getTime() + dayMs);
|
|
97
|
+
chunkIndex++;
|
|
98
|
+
}
|
|
99
|
+
return chunks;
|
|
100
|
+
}
|
|
101
|
+
async _searchChunk(filters, options) {
|
|
102
|
+
const encoded = filters.encode();
|
|
103
|
+
const url = withLocaleParams(BASE_URL, options.currency ?? null, options.language ?? null, options.country ?? null);
|
|
104
|
+
const response = await this.client.post(url, { body: `f.req=${encoded}` });
|
|
105
|
+
const data = parseFirstWrbPayload(response.text);
|
|
106
|
+
if (data == null || !Array.isArray(data))
|
|
107
|
+
return null;
|
|
108
|
+
const items = data[data.length - 1];
|
|
109
|
+
if (!Array.isArray(items))
|
|
110
|
+
return null;
|
|
111
|
+
const out = [];
|
|
112
|
+
for (const item of items) {
|
|
113
|
+
const price = SearchDates._parsePrice(item);
|
|
114
|
+
if (price == null)
|
|
115
|
+
continue;
|
|
116
|
+
out.push({
|
|
117
|
+
date: SearchDates._parseDate(item, filters.trip_type),
|
|
118
|
+
price,
|
|
119
|
+
currency: SearchDates._parseCurrency(item),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
static _parseDate(item, tripType) {
|
|
125
|
+
if (!Array.isArray(item))
|
|
126
|
+
throw new Error("date item is not an array");
|
|
127
|
+
if (tripType === TripType.ONE_WAY) {
|
|
128
|
+
return [parseIsoDate(item[0])];
|
|
129
|
+
}
|
|
130
|
+
return [parseIsoDate(item[0]), parseIsoDate(item[1])];
|
|
131
|
+
}
|
|
132
|
+
static _parsePrice(item) {
|
|
133
|
+
if (!Array.isArray(item) || item.length <= 2)
|
|
134
|
+
return null;
|
|
135
|
+
const middle = item[2];
|
|
136
|
+
if (!Array.isArray(middle) || middle.length === 0)
|
|
137
|
+
return null;
|
|
138
|
+
const inner = middle[0];
|
|
139
|
+
if (!Array.isArray(inner) || inner.length <= 1)
|
|
140
|
+
return null;
|
|
141
|
+
const raw = inner[1];
|
|
142
|
+
const n = typeof raw === "number" ? raw : Number.parseFloat(raw);
|
|
143
|
+
return Number.isFinite(n) ? n : null;
|
|
144
|
+
}
|
|
145
|
+
static _parseCurrency(item) {
|
|
146
|
+
if (!Array.isArray(item) || item.length <= 2)
|
|
147
|
+
return null;
|
|
148
|
+
const middle = item[2];
|
|
149
|
+
if (!Array.isArray(middle) || middle.length <= 1)
|
|
150
|
+
return null;
|
|
151
|
+
try {
|
|
152
|
+
return extractCurrencyFromPriceToken(middle[1]);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=dates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dates.js","sourceRoot":"","sources":["../../src/search/dates.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure response decoders for Google Flights' RPC payloads.
|
|
3
|
+
* 1:1 port of fli/search/_decoders.py.
|
|
4
|
+
*/
|
|
5
|
+
import { type Airline } from "../models/airline.ts";
|
|
6
|
+
import type { BookingOption, FlightResult } from "../models/google-flights/base.ts";
|
|
7
|
+
declare function parseDateTime(dateArr: unknown, timeArr: unknown): Date;
|
|
8
|
+
declare function safeAirline(code: unknown): Airline | null;
|
|
9
|
+
interface EmissionsBlock {
|
|
10
|
+
this_g: number | null;
|
|
11
|
+
typical_g: number | null;
|
|
12
|
+
delta_pct: number | null;
|
|
13
|
+
tag: string | null;
|
|
14
|
+
}
|
|
15
|
+
declare function parseEmissions(detail: unknown): EmissionsBlock;
|
|
16
|
+
/** Decode a single flight row into a `FlightResult`. */
|
|
17
|
+
export declare function parseFlightRow(row: unknown[]): FlightResult;
|
|
18
|
+
/** Walk a decoded `wrb.fr` chunk and yield every booking-option row. */
|
|
19
|
+
export declare function parseBookingChunk(chunk: unknown): BookingOption[];
|
|
20
|
+
declare function tryParseBookingRow(row: unknown[]): BookingOption | null;
|
|
21
|
+
export { parseDateTime as _parseDateTime, parseEmissions as _parseEmissions, safeAirline as _safeAirline, tryParseBookingRow as _tryParseBookingRow, };
|
|
22
|
+
//# sourceMappingURL=decoders.d.ts.map
|