From b18fa6a6eb2a1efdeb383c31c059a663fc0c9e2f Mon Sep 17 00:00:00 2001 From: JeanExtreme002 Date: Mon, 11 May 2026 16:48:41 -0300 Subject: [PATCH 1/2] refactor: replace manual cookie handling with session-based APIClient Introduce APIClient in both packages as the single point of HTTP concerns (session, cookies, TLS fingerprint). FlightRadar24API now delegates all transport details to APIClient and has no knowledge of sessions, curl_cffi or cookie jars. - Python: APIClient wraps curl_cffi Session; exposes request(), request_standalone() for thread-safe calls, get_cookie() and clear_cookies(). APIRequest accepts an optional session parameter. - Node.js: APIClient wraps a Session (cookie jar over undici fetch); same interface minus request_standalone (not needed in single-threaded event loop). - login() resets cookies before attempting auth to guarantee a clean state on re-login or failed login attempts. - logout() uses try/finally to ensure cookies are always cleared even if the logout request throws. --- nodejs/FlightRadar24/api.js | 72 +++++++++++++++---------- nodejs/FlightRadar24/request.js | 94 ++++++++++++++++++++++++++++++++- python/FlightRadar24/api.py | 87 ++++++++++++++++++++---------- python/FlightRadar24/request.py | 56 +++++++++++++++----- 4 files changed, 238 insertions(+), 71 deletions(-) diff --git a/nodejs/FlightRadar24/api.js b/nodejs/FlightRadar24/api.js index dacb73a..09a6738 100644 --- a/nodejs/FlightRadar24/api.js +++ b/nodejs/FlightRadar24/api.js @@ -1,5 +1,5 @@ const Core = require("./core"); -const {request} = require("./request"); +const {APIClient} = require("./request"); const Airport = require("./entities/airport"); const Flight = require("./entities/flight"); const FlightTrackerConfig = require("./flightTrackerConfig"); @@ -41,6 +41,7 @@ class FlightRadar24API { constructor({timeout = 30000, maxWorkers = 8} = {}) { this.__flightTrackerConfig = new FlightTrackerConfig(); this.__loginData = null; + this.__client = new APIClient(); this.timeout = timeout; this.maxWorkers = maxWorkers; } @@ -51,7 +52,7 @@ class FlightRadar24API { * @return {Promise>} */ async getAirlines() { - const {content} = await request(Core.airlinesDataUrl, {headers: Core.htmlHeaders, timeout: this.timeout}); + const {content} = await this.__client.request(Core.airlinesDataUrl, {headers: Core.htmlHeaders, timeout: this.timeout}); return parseAirlinesHtml(content); } @@ -70,7 +71,7 @@ class FlightRadar24API { const notFound = [403, 404]; const firstLogoUrl = Core.airlineLogoUrl(iata, icao); - let {content, statusCode} = await request(firstLogoUrl, { + let {content, statusCode} = await this.__client.request(firstLogoUrl, { headers: Core.imageHeaders, allowedErrorCodes: notFound, timeout: this.timeout, @@ -81,7 +82,7 @@ class FlightRadar24API { } const secondLogoUrl = Core.alternativeAirlineLogoUrl(icao); - ({content, statusCode} = await request(secondLogoUrl, { + ({content, statusCode} = await this.__client.request(secondLogoUrl, { headers: Core.imageHeaders, allowedErrorCodes: notFound, timeout: this.timeout, @@ -112,7 +113,9 @@ class FlightRadar24API { return airport; } - const {content} = await request(Core.airportDataUrl(code), {headers: Core.jsonHeaders, timeout: this.timeout}); + const {content} = await this.__client.request( + Core.airportDataUrl(code), {headers: Core.jsonHeaders, timeout: this.timeout}, + ); const info = content["details"]; if (info === undefined) { @@ -136,11 +139,11 @@ class FlightRadar24API { const params = {"format": "json", "code": code, "limit": flightLimit, "page": page}; - if (this.__loginData !== null) { - params["token"] = this.__loginData["cookies"]["_frPl"]; + if (this.isLoggedIn()) { + params["token"] = this.__client.getCookie("_frPl"); } - const {content, statusCode} = await request(Core.apiAirportDataUrl, { + const {content, statusCode} = await this.__client.request(Core.apiAirportDataUrl, { params, headers: Core.jsonHeaders, allowedErrorCodes: [400], @@ -177,7 +180,9 @@ class FlightRadar24API { * @return {Promise} */ async getAirportDisruptions() { - const {content} = await request(Core.airportDisruptionsUrl, {headers: Core.jsonHeaders, timeout: this.timeout}); + const {content} = await this.__client.request( + Core.airportDisruptionsUrl, {headers: Core.jsonHeaders, timeout: this.timeout}, + ); return content; } @@ -191,7 +196,7 @@ class FlightRadar24API { const airports = []; await mapConcurrent(countries, this.maxWorkers, async (countryName) => { const countryHref = Core.airportsDataUrl + "/" + countryName; - const {content} = await request(countryHref, {headers: Core.htmlHeaders, timeout: this.timeout}); + const {content} = await this.__client.request(countryHref, {headers: Core.htmlHeaders, timeout: this.timeout}); airports.push(...parseAirportsHtml(content, countryHref)); }); return airports; @@ -208,9 +213,8 @@ class FlightRadar24API { } const headers = {...Core.jsonHeaders, "accesstoken": this.getLoginData()["accessToken"]}; - const {content} = await request(Core.bookmarksUrl, { + const {content} = await this.__client.request(Core.bookmarksUrl, { headers, - cookies: this.__loginData["cookies"], timeout: this.timeout, }); @@ -293,7 +297,7 @@ class FlightRadar24API { const headers = {...Core.imageHeaders}; delete headers["origin"]; - const {content, statusCode} = await request(flagUrl, { + const {content, statusCode} = await this.__client.request(flagUrl, { headers, allowedErrorCodes: [403, 404], timeout: this.timeout, @@ -313,7 +317,9 @@ class FlightRadar24API { * @return {Promise} */ async getFlightDetails(flight) { - const {content} = await request(Core.flightDataUrl(flight.id), {headers: Core.jsonHeaders, timeout: this.timeout}); + const {content} = await this.__client.request( + Core.flightDataUrl(flight.id), {headers: Core.jsonHeaders, timeout: this.timeout}, + ); return content; } @@ -330,15 +336,15 @@ class FlightRadar24API { async getFlights(airline = null, bounds = null, registration = null, aircraftType = null, details = false) { const params = {...this.__flightTrackerConfig}; - if (this.__loginData !== null) { - params["enc"] = this.__loginData["cookies"]["_frPl"]; + if (this.isLoggedIn()) { + params["enc"] = this.__client.getCookie("_frPl"); } if (airline !== null) params["airline"] = airline; if (bounds !== null) params["bounds"] = bounds; if (registration !== null) params["reg"] = registration; if (aircraftType !== null) params["type"] = aircraftType; - const {content} = await request(Core.realTimeFlightTrackerDataUrl, { + const {content} = await this.__client.request(Core.realTimeFlightTrackerDataUrl, { params, headers: Core.jsonHeaders, timeout: this.timeout, @@ -393,9 +399,8 @@ class FlightRadar24API { } const headers = {...Core.jsonHeaders, "accesstoken": this.getLoginData()["accessToken"]}; - const {content} = await request(Core.historicalDataUrl(flight.id, fileType, timestamp), { + const {content} = await this.__client.request(Core.historicalDataUrl(flight.id, fileType, timestamp), { headers, - cookies: this.__loginData["cookies"], timeout: this.timeout, }); @@ -420,7 +425,7 @@ class FlightRadar24API { * @return {Promise} */ async getMostTracked() { - const {content} = await request(Core.mostTrackedUrl, {headers: Core.jsonHeaders, timeout: this.timeout}); + const {content} = await this.__client.request(Core.mostTrackedUrl, {headers: Core.jsonHeaders, timeout: this.timeout}); return content; } @@ -430,7 +435,9 @@ class FlightRadar24API { * @return {Promise} */ async getVolcanicEruptions() { - const {content} = await request(Core.volcanicEruptionDataUrl, {headers: Core.jsonHeaders, timeout: this.timeout}); + const {content} = await this.__client.request( + Core.volcanicEruptionDataUrl, {headers: Core.jsonHeaders, timeout: this.timeout}, + ); return content; } @@ -453,7 +460,9 @@ class FlightRadar24API { * @return {Promise} */ async search(query, limit = 50) { - const {content} = await request(Core.searchDataUrl(query, limit), {headers: Core.jsonHeaders, timeout: this.timeout}); + const {content} = await this.__client.request( + Core.searchDataUrl(query, limit), {headers: Core.jsonHeaders, timeout: this.timeout}, + ); const results = content["results"] ?? []; const countDict = content["stats"]?.["count"] ?? {}; @@ -491,7 +500,10 @@ class FlightRadar24API { * @return {Promise} */ async login(user, password) { - const {content, statusCode, cookies} = await request(Core.userLoginUrl, { + this.__loginData = null; + this.__client.clearCookies(); + + const {content, statusCode} = await this.__client.request(Core.userLoginUrl, { headers: Core.jsonHeaders, data: {"email": user, "password": password, "remember": "true", "type": "web"}, timeout: this.timeout, @@ -503,7 +515,7 @@ class FlightRadar24API { ); } - this.__loginData = {"userData": content["userData"], "cookies": cookies}; + this.__loginData = {"userData": content["userData"]}; } /** @@ -516,11 +528,17 @@ class FlightRadar24API { return true; } - const cookies = this.__loginData["cookies"]; this.__loginData = null; - const {statusCode} = await request(Core.userLogoutUrl, {headers: Core.jsonHeaders, cookies, timeout: this.timeout}); - return statusCode >= 200 && statusCode < 300; + try { + const {statusCode} = await this.__client.request( + Core.userLogoutUrl, {headers: Core.jsonHeaders, timeout: this.timeout}, + ); + return statusCode >= 200 && statusCode < 300; + } + finally { + this.__client.clearCookies(); + } } /** diff --git a/nodejs/FlightRadar24/request.js b/nodejs/FlightRadar24/request.js index 271cf85..659dbbf 100644 --- a/nodejs/FlightRadar24/request.js +++ b/nodejs/FlightRadar24/request.js @@ -139,4 +139,96 @@ async function request(url, { return {content, statusCode, cookies: responseCookies}; } -module.exports = {request}; +/** + * HTTP session that automatically manages cookies across requests. + */ +class Session { + /** @constructor */ + constructor() { + this.__cookies = {}; + } + + /** + * Return the value of a stored cookie by name. + * + * @param {string} name + * @return {string|undefined} + */ + getCookie(name) { + return this.__cookies[name]; + } + + /** + * Clear all stored cookies. + */ + clearCookies() { + this.__cookies = {}; + } + + /** + * Make an HTTP request, automatically sending stored cookies and storing + * any cookies returned by the response. + * + * Accepts the same parameters as the module-level {@link request} function. + * + * @param {string} url + * @param {object} [options={}] + * @return {Promise<{content: *, statusCode: number, cookies: object}>} + */ + async request(url, options = {}) { + const {cookies: extraCookies, ...rest} = options; + const merged = {...this.__cookies, ...(extraCookies ?? {})}; + const cookies = Object.keys(merged).length > 0 ? merged : null; + + const result = await request(url, {...rest, cookies}); + + if (result.cookies && Object.keys(result.cookies).length > 0) { + Object.assign(this.__cookies, result.cookies); + } + + return result; + } +} + +/** + * Central HTTP client for the FlightRadar24 package. + * + * Owns the persistent session (cookie jar, TLS fingerprint, future bypass logic) + * so that the rest of the codebase never has to deal with those concerns directly. + */ +class APIClient { + /** @constructor */ + constructor() { + this.__session = new Session(); + } + + /** + * Make a request through the shared session. + * + * @param {string} url + * @param {object} [options={}] + * @return {Promise<{content: *, statusCode: number, cookies: object}>} + */ + async request(url, options = {}) { + return this.__session.request(url, options); + } + + /** + * Return the value of a stored cookie by name. + * + * @param {string} name + * @return {string|undefined} + */ + getCookie(name) { + return this.__session.getCookie(name); + } + + /** + * Clear all cookies from the session. + */ + clearCookies() { + this.__session.clearCookies(); + } +} + +module.exports = {request, Session, APIClient}; diff --git a/python/FlightRadar24/api.py b/python/FlightRadar24/api.py index 8191127..144d21f 100644 --- a/python/FlightRadar24/api.py +++ b/python/FlightRadar24/api.py @@ -12,7 +12,7 @@ from .errors import AirportNotFoundError, LoginError from .flight_tracker_config import FlightTrackerConfig from .parsers import parse_airlines_html, parse_airports_html -from .request import APIRequest +from .request import APIClient class FlightRadar24API: @@ -31,6 +31,7 @@ def __init__(self, user: Optional[str] = None, password: Optional[str] = None, t """ self.__flight_tracker_config = FlightTrackerConfig() self.__login_data: Optional[Dict] = None + self.__client = APIClient() self.timeout: int = timeout self.max_workers: int = max_workers @@ -42,7 +43,7 @@ def get_airlines(self) -> List[Dict]: """ Return a list with all airlines. """ - response = APIRequest(Core.airlines_data_url, headers=Core.html_headers, timeout=self.timeout) + response = self.__client.request(Core.airlines_data_url, headers=Core.html_headers, timeout=self.timeout) return parse_airlines_html(response.get_bytes_content()) def get_airline_logo(self, iata: str, icao: str) -> Optional[Tuple[bytes, str]]: @@ -54,7 +55,10 @@ def get_airline_logo(self, iata: str, icao: str) -> Optional[Tuple[bytes, str]]: first_logo_url = Core.airline_logo_url.format(iata, icao) # Try to get the image by the first URL option. - response = APIRequest(first_logo_url, headers=Core.image_headers, allowed_error_codes=[403, 404], timeout=self.timeout) + response = self.__client.request( + first_logo_url, headers=Core.image_headers, + allowed_error_codes=[403, 404], timeout=self.timeout, + ) status_code = response.get_status_code() if not (400 <= status_code < 500): @@ -63,7 +67,10 @@ def get_airline_logo(self, iata: str, icao: str) -> Optional[Tuple[bytes, str]]: # Get the image by the second airline logo URL. second_logo_url = Core.alternative_airline_logo_url.format(icao) - response = APIRequest(second_logo_url, headers=Core.image_headers, allowed_error_codes=[403, 404], timeout=self.timeout) + response = self.__client.request( + second_logo_url, headers=Core.image_headers, + allowed_error_codes=[403, 404], timeout=self.timeout, + ) status_code = response.get_status_code() if not (400 <= status_code < 500): @@ -86,7 +93,10 @@ def get_airport(self, code: str, *, details: bool = False) -> Airport: airport.set_airport_details(self.get_airport_details(code)) return airport - response = APIRequest(Core.airport_data_url.format(code), headers=Core.json_headers, timeout=self.timeout) + response = self.__client.request( + Core.airport_data_url.format(code), + headers=Core.json_headers, timeout=self.timeout, + ) content = response.get_json_content() if not content or not content.get("details"): @@ -107,8 +117,8 @@ def get_airport_details(self, code: str, flight_limit: int = 100, page: int = 1) request_params: Dict[str, Any] = {"format": "json"} - if self.__login_data is not None: - request_params["token"] = self.__login_data["cookies"]["_frPl"] + if self.is_logged_in(): + request_params["token"] = self.__client.get_cookie("_frPl") # Insert the method parameters into the dictionary for the request. request_params["code"] = code @@ -116,7 +126,7 @@ def get_airport_details(self, code: str, flight_limit: int = 100, page: int = 1) request_params["page"] = page # Request details from the FlightRadar24. - response = APIRequest( + response = self.__client.request( Core.api_airport_data_url, params=request_params, headers=Core.json_headers, @@ -148,7 +158,10 @@ def get_airport_disruptions(self) -> Dict: """ Return airport disruptions. """ - response = APIRequest(Core.airport_disruptions_url, headers=Core.json_headers, timeout=self.timeout) + response = self.__client.request( + Core.airport_disruptions_url, + headers=Core.json_headers, timeout=self.timeout, + ) return response.get_json_content() def get_airports(self, countries: List[Countries]) -> List[Airport]: @@ -159,7 +172,7 @@ def get_airports(self, countries: List[Countries]) -> List[Airport]: """ def _fetch(country): href = Core.airports_data_url + "/" + country.value - response = APIRequest(href, headers=Core.html_headers, timeout=self.timeout) + response = APIClient.request_standalone(href, headers=Core.html_headers, timeout=self.timeout) return parse_airports_html(response.get_bytes_content(), href) with ThreadPoolExecutor(max_workers=self.max_workers) as executor: @@ -176,9 +189,8 @@ def get_bookmarks(self) -> Dict: assert self.__login_data is not None headers = {**Core.json_headers, "accesstoken": self.get_login_data()["accessToken"]} - cookies = self.__login_data["cookies"] - response = APIRequest(Core.bookmarks_url, headers=headers, cookies=cookies, timeout=self.timeout) + response = self.__client.request(Core.bookmarks_url, headers=headers, timeout=self.timeout) return response.get_json_content() def get_bounds(self, zone: Dict[str, float]) -> str: @@ -254,7 +266,10 @@ def get_country_flag(self, country: str) -> Optional[Tuple[bytes, str]]: headers.pop("origin", None) # Does not work for this request. - response = APIRequest(flag_url, headers=headers, allowed_error_codes=[403, 404], timeout=self.timeout) + response = self.__client.request( + flag_url, headers=headers, + allowed_error_codes=[403, 404], timeout=self.timeout, + ) status_code = response.get_status_code() if not (400 <= status_code < 500): @@ -268,7 +283,9 @@ def get_flight_details(self, flight: Flight) -> Dict[Any, Any]: :param flight: A Flight instance """ - response = APIRequest(Core.flight_data_url.format(flight.id), headers=Core.json_headers, timeout=self.timeout) + response = APIClient.request_standalone( + Core.flight_data_url.format(flight.id), headers=Core.json_headers, timeout=self.timeout, + ) return response.get_json_content() def get_flights( @@ -291,8 +308,8 @@ def get_flights( """ request_params = dataclasses.asdict(self.__flight_tracker_config) - if self.__login_data is not None: - request_params["enc"] = self.__login_data["cookies"]["_frPl"] + if self.is_logged_in(): + request_params["enc"] = self.__client.get_cookie("_frPl") # Insert the method parameters into the dictionary for the request. if airline is not None: request_params["airline"] = airline @@ -301,7 +318,7 @@ def get_flights( if aircraft_type is not None: request_params["type"] = aircraft_type # Get all flights from Data Live FlightRadar24. - response = APIRequest( + response = self.__client.request( Core.real_time_flight_tracker_data_url, params=request_params, headers=Core.json_headers, @@ -352,10 +369,10 @@ def get_history_data(self, flight: Flight, file_type: str, timestamp: int) -> st headers = {**Core.json_headers, "accesstoken": self.get_login_data()["accessToken"]} - response = APIRequest( + response = self.__client.request( Core.historical_data_url.format(flight.id, file_type, timestamp), - headers=headers, cookies=self.__login_data["cookies"], - timeout=self.timeout + headers=headers, + timeout=self.timeout, ) return response.get_bytes_content().decode("utf-8") @@ -374,14 +391,17 @@ def get_most_tracked(self) -> Dict: """ Return the most tracked data. """ - response = APIRequest(Core.most_tracked_url, headers=Core.json_headers, timeout=self.timeout) + response = self.__client.request(Core.most_tracked_url, headers=Core.json_headers, timeout=self.timeout) return response.get_json_content() def get_volcanic_eruptions(self) -> Dict: """ Return boundaries of volcanic eruptions and ash clouds impacting aviation. """ - response = APIRequest(Core.volcanic_eruption_data_url, headers=Core.json_headers, timeout=self.timeout) + response = self.__client.request( + Core.volcanic_eruption_data_url, + headers=Core.json_headers, timeout=self.timeout, + ) return response.get_json_content() def get_zones(self) -> Dict[str, Any]: @@ -396,7 +416,10 @@ def search(self, query: str, limit: int = 50) -> Dict: """ Return the search result. """ - response = APIRequest(Core.search_data_url.format(quote(query), limit), headers=Core.json_headers, timeout=self.timeout) + response = self.__client.request( + Core.search_data_url.format(quote(query), limit), + headers=Core.json_headers, timeout=self.timeout, + ) content = response.get_json_content() results = content.get("results", []) stats = content.get("stats", {}) @@ -421,6 +444,9 @@ def login(self, user: str, password: str) -> None: :param user: Your email. :param password: Your password. """ + self.__login_data = None + self.__client.clear_cookies() + data = { "email": user, "password": password, @@ -428,7 +454,10 @@ def login(self, user: str, password: str) -> None: "type": "web" } - response = APIRequest(Core.user_login_url, headers=Core.json_headers, data=data, timeout=self.timeout) + response = self.__client.request( + Core.user_login_url, + headers=Core.json_headers, data=data, timeout=self.timeout, + ) status_code = response.get_status_code() content = response.get_json_content() @@ -437,7 +466,6 @@ def login(self, user: str, password: str) -> None: self.__login_data = { "userData": content["userData"], - "cookies": response.get_cookies(), } def logout(self) -> bool: @@ -449,11 +477,12 @@ def logout(self) -> bool: if self.__login_data is None: return True - cookies = self.__login_data["cookies"] self.__login_data = None - - response = APIRequest(Core.user_logout_url, headers=Core.json_headers, cookies=cookies, timeout=self.timeout) - return 200 <= response.get_status_code() < 300 + try: + response = self.__client.request(Core.user_logout_url, headers=Core.json_headers, timeout=self.timeout) + return 200 <= response.get_status_code() < 300 + finally: + self.__client.clear_cookies() def set_flight_tracker_config( self, diff --git a/python/FlightRadar24/request.py b/python/FlightRadar24/request.py index 67de1a7..948b84b 100644 --- a/python/FlightRadar24/request.py +++ b/python/FlightRadar24/request.py @@ -7,12 +7,42 @@ import brotli from curl_cffi import requests +from curl_cffi.requests import Session from .errors import CloudflareError _IMPERSONATE = "chrome136" +class APIClient: + """ + Central HTTP client for the FlightRadar24 package. + + Owns the persistent session (cookie jar, TLS fingerprint, future bypass logic) + so that the rest of the codebase never has to deal with those concerns directly. + """ + + def __init__(self) -> None: + self.__session: Session = Session(impersonate=_IMPERSONATE) # type: ignore[arg-type] + + def request(self, url: str, **kwargs) -> "APIRequest": + """Make a request through the shared session.""" + return APIRequest(url, session=self.__session, **kwargs) + + @staticmethod + def request_standalone(url: str, **kwargs) -> "APIRequest": + """Make a stateless request with no shared session (safe to call from threads).""" + return APIRequest(url, **kwargs) + + def get_cookie(self, name: str) -> Optional[str]: + """Return the value of a stored cookie by name.""" + return self.__session.cookies.get(name) + + def clear_cookies(self) -> None: + """Clear all cookies from the session.""" + self.__session.cookies.clear() + + class APIRequest: """ Class to make requests to the FlightRadar24. @@ -27,32 +57,36 @@ def __init__( self, url: str, *, + session: Optional[Session] = None, params: Optional[Dict] = None, headers: Optional[Dict] = None, timeout: int = 30, data: Optional[Dict] = None, - cookies: Optional[Dict] = None, allowed_error_codes: Optional[List[int]] = None ): """ Constructor of the APIRequest class. :param url: URL for the request + :param session: session to reuse across requests; handles cookies automatically :param params: params that will be inserted on the URL for the request :param headers: headers for the request :param data: data for the request. If "data" is None, request will be a GET. Otherwise, it will be a POST - :param cookies: cookies for the request :param allowed_error_codes: status codes that should not raise an error """ self.url = url - request_method = requests.get if data is None else requests.post - if params: url += "?" + urlencode(params) - self.__response = request_method( - url, headers=headers, cookies=cookies, data=data, timeout=timeout, - impersonate=_IMPERSONATE # type: ignore[arg-type] - ) + + if session is not None: + request_method = session.get if data is None else session.post + self.__response = request_method(url, headers=headers, data=data, timeout=timeout) + else: + request_method = requests.get if data is None else requests.post + self.__response = request_method( + url, headers=headers, data=data, timeout=timeout, + impersonate=_IMPERSONATE # type: ignore[arg-type] + ) if self.get_status_code() == 520: raise CloudflareError( @@ -104,12 +138,6 @@ def get_bytes_content(self) -> bytes: raise ValueError(f"Expected bytes response from {self.url}, got JSON") return content - def get_cookies(self) -> Dict: - """ - Return the received cookies from the request. - """ - return self.__response.cookies.get_dict() - def get_headers(self) -> Any: """ Return the headers of the response. From 908b5b99f468be83372fbac2e5d32d116aee1047 Mon Sep 17 00:00:00 2001 From: JeanExtreme002 Date: Mon, 11 May 2026 17:05:18 -0300 Subject: [PATCH 2/2] style(nodejs): enforce object-curly-spacing and fix line lengths Add ESLint rule object-curly-spacing: always and apply spacing consistently across all source files. Fix two lines exceeding the 130-char limit in api.js. --- nodejs/.eslintrc.json | 3 +- nodejs/FlightRadar24/api.js | 80 +++++++++++---------- nodejs/FlightRadar24/core.js | 6 +- nodejs/FlightRadar24/entities/entity.js | 2 +- nodejs/FlightRadar24/entities/flight.js | 2 +- nodejs/FlightRadar24/errors.js | 2 +- nodejs/FlightRadar24/flightTrackerConfig.js | 2 +- nodejs/FlightRadar24/index.js | 6 +- nodejs/FlightRadar24/parsers.js | 6 +- nodejs/FlightRadar24/request.js | 16 ++--- nodejs/FlightRadar24/util.js | 2 +- nodejs/FlightRadar24/zones.js | 2 +- nodejs/tests/testApi.js | 24 +++---- nodejs/tests/testSnapshots.js | 4 +- 14 files changed, 80 insertions(+), 77 deletions(-) diff --git a/nodejs/.eslintrc.json b/nodejs/.eslintrc.json index 122e7d1..c8d2bd8 100644 --- a/nodejs/.eslintrc.json +++ b/nodejs/.eslintrc.json @@ -14,6 +14,7 @@ "indent": ["warn", 4], "brace-style": ["warn", "stroustrup"], "valid-jsdoc": ["warn"], - "require-jsdoc": ["warn"] + "require-jsdoc": ["warn"], + "object-curly-spacing": ["warn", "always"] } } diff --git a/nodejs/FlightRadar24/api.js b/nodejs/FlightRadar24/api.js index 09a6738..c60bebb 100644 --- a/nodejs/FlightRadar24/api.js +++ b/nodejs/FlightRadar24/api.js @@ -1,11 +1,11 @@ const Core = require("./core"); -const {APIClient} = require("./request"); +const { APIClient } = require("./request"); const Airport = require("./entities/airport"); const Flight = require("./entities/flight"); const FlightTrackerConfig = require("./flightTrackerConfig"); -const {AirportNotFoundError, LoginError} = require("./errors"); -const {isNumeric, radians, rad2deg} = require("./util"); -const {parseAirlinesHtml, parseAirportsHtml} = require("./parsers"); +const { AirportNotFoundError, LoginError } = require("./errors"); +const { isNumeric, radians, rad2deg } = require("./util"); +const { parseAirlinesHtml, parseAirportsHtml } = require("./parsers"); /** @@ -24,7 +24,7 @@ async function mapConcurrent(items, concurrency, fn) { await fn(items[i++]); } } - await Promise.all(Array.from({length: Math.min(concurrency, items.length)}, worker)); + await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, worker)); } /** @@ -38,7 +38,7 @@ class FlightRadar24API { * @param {number} [options.timeout=30000] - Request timeout in milliseconds * @param {number} [options.maxWorkers=8] - Maximum concurrent requests when fetching flight details */ - constructor({timeout = 30000, maxWorkers = 8} = {}) { + constructor({ timeout = 30000, maxWorkers = 8 } = {}) { this.__flightTrackerConfig = new FlightTrackerConfig(); this.__loginData = null; this.__client = new APIClient(); @@ -52,7 +52,8 @@ class FlightRadar24API { * @return {Promise>} */ async getAirlines() { - const {content} = await this.__client.request(Core.airlinesDataUrl, {headers: Core.htmlHeaders, timeout: this.timeout}); + const { content } = await this.__client.request( + Core.airlinesDataUrl, { headers: Core.htmlHeaders, timeout: this.timeout }); return parseAirlinesHtml(content); } @@ -71,7 +72,7 @@ class FlightRadar24API { const notFound = [403, 404]; const firstLogoUrl = Core.airlineLogoUrl(iata, icao); - let {content, statusCode} = await this.__client.request(firstLogoUrl, { + let { content, statusCode } = await this.__client.request(firstLogoUrl, { headers: Core.imageHeaders, allowedErrorCodes: notFound, timeout: this.timeout, @@ -82,7 +83,7 @@ class FlightRadar24API { } const secondLogoUrl = Core.alternativeAirlineLogoUrl(icao); - ({content, statusCode} = await this.__client.request(secondLogoUrl, { + ({ content, statusCode } = await this.__client.request(secondLogoUrl, { headers: Core.imageHeaders, allowedErrorCodes: notFound, timeout: this.timeout, @@ -113,8 +114,8 @@ class FlightRadar24API { return airport; } - const {content} = await this.__client.request( - Core.airportDataUrl(code), {headers: Core.jsonHeaders, timeout: this.timeout}, + const { content } = await this.__client.request( + Core.airportDataUrl(code), { headers: Core.jsonHeaders, timeout: this.timeout }, ); const info = content["details"]; @@ -137,13 +138,13 @@ class FlightRadar24API { throw new Error("The code '" + code + "' is invalid. It must be the IATA or ICAO of the airport."); } - const params = {"format": "json", "code": code, "limit": flightLimit, "page": page}; + const params = { "format": "json", "code": code, "limit": flightLimit, "page": page }; if (this.isLoggedIn()) { params["token"] = this.__client.getCookie("_frPl"); } - const {content, statusCode} = await this.__client.request(Core.apiAirportDataUrl, { + const { content, statusCode } = await this.__client.request(Core.apiAirportDataUrl, { params, headers: Core.jsonHeaders, allowedErrorCodes: [400], @@ -180,8 +181,8 @@ class FlightRadar24API { * @return {Promise} */ async getAirportDisruptions() { - const {content} = await this.__client.request( - Core.airportDisruptionsUrl, {headers: Core.jsonHeaders, timeout: this.timeout}, + const { content } = await this.__client.request( + Core.airportDisruptionsUrl, { headers: Core.jsonHeaders, timeout: this.timeout }, ); return content; } @@ -196,7 +197,7 @@ class FlightRadar24API { const airports = []; await mapConcurrent(countries, this.maxWorkers, async (countryName) => { const countryHref = Core.airportsDataUrl + "/" + countryName; - const {content} = await this.__client.request(countryHref, {headers: Core.htmlHeaders, timeout: this.timeout}); + const { content } = await this.__client.request(countryHref, { headers: Core.htmlHeaders, timeout: this.timeout }); airports.push(...parseAirportsHtml(content, countryHref)); }); return airports; @@ -212,8 +213,8 @@ class FlightRadar24API { throw new LoginError("You must log in to your account."); } - const headers = {...Core.jsonHeaders, "accesstoken": this.getLoginData()["accessToken"]}; - const {content} = await this.__client.request(Core.bookmarksUrl, { + const headers = { ...Core.jsonHeaders, "accesstoken": this.getLoginData()["accessToken"] }; + const { content } = await this.__client.request(Core.bookmarksUrl, { headers, timeout: this.timeout, }); @@ -294,10 +295,10 @@ class FlightRadar24API { async getCountryFlag(country) { const flagUrl = Core.countryFlagUrl(country.toLowerCase().replaceAll(" ", "-")); - const headers = {...Core.imageHeaders}; + const headers = { ...Core.imageHeaders }; delete headers["origin"]; - const {content, statusCode} = await this.__client.request(flagUrl, { + const { content, statusCode } = await this.__client.request(flagUrl, { headers, allowedErrorCodes: [403, 404], timeout: this.timeout, @@ -317,8 +318,8 @@ class FlightRadar24API { * @return {Promise} */ async getFlightDetails(flight) { - const {content} = await this.__client.request( - Core.flightDataUrl(flight.id), {headers: Core.jsonHeaders, timeout: this.timeout}, + const { content } = await this.__client.request( + Core.flightDataUrl(flight.id), { headers: Core.jsonHeaders, timeout: this.timeout }, ); return content; } @@ -334,7 +335,7 @@ class FlightRadar24API { * @return {Promise>} */ async getFlights(airline = null, bounds = null, registration = null, aircraftType = null, details = false) { - const params = {...this.__flightTrackerConfig}; + const params = { ...this.__flightTrackerConfig }; if (this.isLoggedIn()) { params["enc"] = this.__client.getCookie("_frPl"); @@ -344,7 +345,7 @@ class FlightRadar24API { if (registration !== null) params["reg"] = registration; if (aircraftType !== null) params["type"] = aircraftType; - const {content} = await this.__client.request(Core.realTimeFlightTrackerDataUrl, { + const { content } = await this.__client.request(Core.realTimeFlightTrackerDataUrl, { params, headers: Core.jsonHeaders, timeout: this.timeout, @@ -377,7 +378,7 @@ class FlightRadar24API { * @return {FlightTrackerConfig} */ getFlightTrackerConfig() { - return new FlightTrackerConfig({...this.__flightTrackerConfig}); + return new FlightTrackerConfig({ ...this.__flightTrackerConfig }); } /** @@ -398,8 +399,8 @@ class FlightRadar24API { throw new Error("File type '" + fileType + "' is not supported. Only CSV and KML are supported."); } - const headers = {...Core.jsonHeaders, "accesstoken": this.getLoginData()["accessToken"]}; - const {content} = await this.__client.request(Core.historicalDataUrl(flight.id, fileType, timestamp), { + const headers = { ...Core.jsonHeaders, "accesstoken": this.getLoginData()["accessToken"] }; + const { content } = await this.__client.request(Core.historicalDataUrl(flight.id, fileType, timestamp), { headers, timeout: this.timeout, }); @@ -416,7 +417,7 @@ class FlightRadar24API { if (!this.isLoggedIn()) { throw new LoginError("You must log in to your account."); } - return {...this.__loginData["userData"]}; + return { ...this.__loginData["userData"] }; } /** @@ -425,7 +426,8 @@ class FlightRadar24API { * @return {Promise} */ async getMostTracked() { - const {content} = await this.__client.request(Core.mostTrackedUrl, {headers: Core.jsonHeaders, timeout: this.timeout}); + const { content } = await this.__client.request( + Core.mostTrackedUrl, { headers: Core.jsonHeaders, timeout: this.timeout }); return content; } @@ -435,8 +437,8 @@ class FlightRadar24API { * @return {Promise} */ async getVolcanicEruptions() { - const {content} = await this.__client.request( - Core.volcanicEruptionDataUrl, {headers: Core.jsonHeaders, timeout: this.timeout}, + const { content } = await this.__client.request( + Core.volcanicEruptionDataUrl, { headers: Core.jsonHeaders, timeout: this.timeout }, ); return content; } @@ -447,7 +449,7 @@ class FlightRadar24API { * @return {object} */ getZones() { - const zones = {...Core.staticZones}; + const zones = { ...Core.staticZones }; delete zones.version; return zones; } @@ -460,8 +462,8 @@ class FlightRadar24API { * @return {Promise} */ async search(query, limit = 50) { - const {content} = await this.__client.request( - Core.searchDataUrl(query, limit), {headers: Core.jsonHeaders, timeout: this.timeout}, + const { content } = await this.__client.request( + Core.searchDataUrl(query, limit), { headers: Core.jsonHeaders, timeout: this.timeout }, ); const results = content["results"] ?? []; @@ -503,9 +505,9 @@ class FlightRadar24API { this.__loginData = null; this.__client.clearCookies(); - const {content, statusCode} = await this.__client.request(Core.userLoginUrl, { + const { content, statusCode } = await this.__client.request(Core.userLoginUrl, { headers: Core.jsonHeaders, - data: {"email": user, "password": password, "remember": "true", "type": "web"}, + data: { "email": user, "password": password, "remember": "true", "type": "web" }, timeout: this.timeout, }); @@ -515,7 +517,7 @@ class FlightRadar24API { ); } - this.__loginData = {"userData": content["userData"]}; + this.__loginData = { "userData": content["userData"] }; } /** @@ -531,8 +533,8 @@ class FlightRadar24API { this.__loginData = null; try { - const {statusCode} = await this.__client.request( - Core.userLogoutUrl, {headers: Core.jsonHeaders, timeout: this.timeout}, + const { statusCode } = await this.__client.request( + Core.userLogoutUrl, { headers: Core.jsonHeaders, timeout: this.timeout }, ); return statusCode >= 200 && statusCode < 300; } diff --git a/nodejs/FlightRadar24/core.js b/nodejs/FlightRadar24/core.js index 296a8d7..b9becc6 100644 --- a/nodejs/FlightRadar24/core.js +++ b/nodejs/FlightRadar24/core.js @@ -1,4 +1,4 @@ -const {staticZones} = require("./zones"); +const { staticZones } = require("./zones"); const FR24_BASE = "https://www.flightradar24.com"; const API_FR24_BASE = "https://api.flightradar24.com/common/v1"; @@ -68,8 +68,8 @@ const Core = { staticZones, headers: baseHeaders, - jsonHeaders: {accept: "application/json", ...baseHeaders}, - imageHeaders: {accept: "image/gif, image/jpg, image/jpeg, image/png", ...baseHeaders}, + jsonHeaders: { accept: "application/json", ...baseHeaders }, + imageHeaders: { accept: "image/gif, image/jpg, image/jpeg, image/png", ...baseHeaders }, htmlHeaders: { "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "accept-encoding": "gzip, deflate, br", diff --git a/nodejs/FlightRadar24/entities/entity.js b/nodejs/FlightRadar24/entities/entity.js index 190634e..4524a99 100644 --- a/nodejs/FlightRadar24/entities/entity.js +++ b/nodejs/FlightRadar24/entities/entity.js @@ -1,4 +1,4 @@ -const {radians} = require("../util"); +const { radians } = require("../util"); const DEFAULT_TEXT = "N/A"; diff --git a/nodejs/FlightRadar24/entities/flight.js b/nodejs/FlightRadar24/entities/flight.js index 7f17cc6..61068a5 100644 --- a/nodejs/FlightRadar24/entities/flight.js +++ b/nodejs/FlightRadar24/entities/flight.js @@ -70,7 +70,7 @@ class Flight extends Entity { * @return {boolean} */ checkInfo(info) { - const comparisonFunctions = {"max": Math.max, "min": Math.min}; + const comparisonFunctions = { "max": Math.max, "min": Math.min }; for (let key in info) { if (!Object.prototype.hasOwnProperty.call(info, key)) { diff --git a/nodejs/FlightRadar24/errors.js b/nodejs/FlightRadar24/errors.js index 0989b1d..6d1c237 100644 --- a/nodejs/FlightRadar24/errors.js +++ b/nodejs/FlightRadar24/errors.js @@ -26,4 +26,4 @@ class CloudflareError extends FlightRadarError { /** Thrown when login fails or an authenticated endpoint is accessed without login. */ class LoginError extends FlightRadarError {} -module.exports = {FlightRadarError, AirportNotFoundError, CloudflareError, LoginError}; +module.exports = { FlightRadarError, AirportNotFoundError, CloudflareError, LoginError }; diff --git a/nodejs/FlightRadar24/flightTrackerConfig.js b/nodejs/FlightRadar24/flightTrackerConfig.js index 869266a..cbcb7fb 100644 --- a/nodejs/FlightRadar24/flightTrackerConfig.js +++ b/nodejs/FlightRadar24/flightTrackerConfig.js @@ -1,4 +1,4 @@ -const {isNumeric} = require("./util"); +const { isNumeric } = require("./util"); const proxyHandler = { diff --git a/nodejs/FlightRadar24/index.js b/nodejs/FlightRadar24/index.js index 4de8a5e..4caaf32 100644 --- a/nodejs/FlightRadar24/index.js +++ b/nodejs/FlightRadar24/index.js @@ -9,15 +9,15 @@ * https://www.flightradar24.com/terms-and-conditions */ -const {FlightRadarError, AirportNotFoundError, CloudflareError, LoginError} = require("./errors"); +const { FlightRadarError, AirportNotFoundError, CloudflareError, LoginError } = require("./errors"); const FlightRadar24API = require("./api"); const FlightTrackerConfig = require("./flightTrackerConfig"); const Airport = require("./entities/airport"); const Entity = require("./entities/entity"); const Flight = require("./entities/flight"); -const {Countries} = require("./core"); +const { Countries } = require("./core"); -const {version, author} = require("../package.json"); +const { version, author } = require("../package.json"); module.exports = { FlightRadar24API, diff --git a/nodejs/FlightRadar24/parsers.js b/nodejs/FlightRadar24/parsers.js index dd65c99..90c35dc 100644 --- a/nodejs/FlightRadar24/parsers.js +++ b/nodejs/FlightRadar24/parsers.js @@ -1,4 +1,4 @@ -const {JSDOM} = require("jsdom"); +const { JSDOM } = require("jsdom"); const Airport = require("./entities/airport"); /** @@ -69,7 +69,7 @@ function parseAirlinesHtml(html) { } } - airlines.push({"Name": airlineName, "ICAO": icao, "IATA": iata, "n_aircrafts": nAircrafts}); + airlines.push({ "Name": airlineName, "ICAO": icao, "IATA": iata, "n_aircrafts": nAircrafts }); } return airlines; @@ -152,4 +152,4 @@ function parseAirportsHtml(html, countryHref) { return airports; } -module.exports = {parseAirlinesHtml, parseAirportsHtml}; +module.exports = { parseAirlinesHtml, parseAirportsHtml }; diff --git a/nodejs/FlightRadar24/request.js b/nodejs/FlightRadar24/request.js index 659dbbf..d7aaabe 100644 --- a/nodejs/FlightRadar24/request.js +++ b/nodejs/FlightRadar24/request.js @@ -1,5 +1,5 @@ -const {CloudflareError} = require("./errors"); -const {fetch, Agent} = require("undici"); +const { CloudflareError } = require("./errors"); +const { fetch, Agent } = require("undici"); // Chrome 136 TLS cipher suites to approximate its JA3 fingerprint const CHROME_CIPHERS = [ @@ -75,7 +75,7 @@ async function request(url, { } const method = data === null ? "GET" : "POST"; - const settings = {method, headers: requestHeaders, dispatcher: chromeAgent}; + const settings = { method, headers: requestHeaders, dispatcher: chromeAgent }; if (method === "POST") { const formData = new URLSearchParams(); @@ -136,7 +136,7 @@ async function request(url, { }); } - return {content, statusCode, cookies: responseCookies}; + return { content, statusCode, cookies: responseCookies }; } /** @@ -176,11 +176,11 @@ class Session { * @return {Promise<{content: *, statusCode: number, cookies: object}>} */ async request(url, options = {}) { - const {cookies: extraCookies, ...rest} = options; - const merged = {...this.__cookies, ...(extraCookies ?? {})}; + const { cookies: extraCookies, ...rest } = options; + const merged = { ...this.__cookies, ...(extraCookies ?? {}) }; const cookies = Object.keys(merged).length > 0 ? merged : null; - const result = await request(url, {...rest, cookies}); + const result = await request(url, { ...rest, cookies }); if (result.cookies && Object.keys(result.cookies).length > 0) { Object.assign(this.__cookies, result.cookies); @@ -231,4 +231,4 @@ class APIClient { } } -module.exports = {request, Session, APIClient}; +module.exports = { request, Session, APIClient }; diff --git a/nodejs/FlightRadar24/util.js b/nodejs/FlightRadar24/util.js index 50893ae..d4993f9 100644 --- a/nodejs/FlightRadar24/util.js +++ b/nodejs/FlightRadar24/util.js @@ -24,4 +24,4 @@ const radians = (x) => x * (Math.PI / 180); */ const rad2deg = (x) => x * (180 / Math.PI); -module.exports = {isNumeric, radians, rad2deg}; +module.exports = { isNumeric, radians, rad2deg }; diff --git a/nodejs/FlightRadar24/zones.js b/nodejs/FlightRadar24/zones.js index 35ca6d9..151054b 100644 --- a/nodejs/FlightRadar24/zones.js +++ b/nodejs/FlightRadar24/zones.js @@ -203,4 +203,4 @@ staticZones = { }, }; -module.exports = {staticZones}; +module.exports = { staticZones }; diff --git a/nodejs/tests/testApi.js b/nodejs/tests/testApi.js index f8832f2..0767c8b 100644 --- a/nodejs/tests/testApi.js +++ b/nodejs/tests/testApi.js @@ -1,4 +1,4 @@ -const {FlightRadar24API, Flight, Entity, FlightTrackerConfig, Countries, version} = require(".."); +const { FlightRadar24API, Flight, Entity, FlightTrackerConfig, Countries, version } = require(".."); const expect = require("chai").expect; @@ -188,7 +188,7 @@ describe("Testing FlightRadarAPI version " + version, function() { describe("getBounds()", function() { it("Converts zone dict to coordinate string.", function() { - const zone = {"tl_y": 75.78, "br_y": -75.78, "tl_x": -427.56, "br_x": 427.56}; + const zone = { "tl_y": 75.78, "br_y": -75.78, "tl_x": -427.56, "br_x": 427.56 }; expect(frApi.getBounds(zone)).to.equal("75.78,-75.78,-427.56,427.56"); }); }); @@ -207,13 +207,13 @@ describe("Testing FlightRadarAPI version " + version, function() { describe("setFlightTrackerConfig() — invalid key", function() { it("Throws when an unknown config key is set.", function() { - expect(() => frApi.setFlightTrackerConfig(null, {unknownKey: "1"})).to.throw(); + expect(() => frApi.setFlightTrackerConfig(null, { unknownKey: "1" })).to.throw(); }); }); describe("setFlightTrackerConfig() — invalid value", function() { it("Throws when a non-numeric value is set.", function() { - expect(() => frApi.setFlightTrackerConfig(null, {limit: "not_a_number"})).to.throw(); + expect(() => frApi.setFlightTrackerConfig(null, { limit: "not_a_number" })).to.throw(); }); }); @@ -226,31 +226,31 @@ describe("Testing FlightRadarAPI version " + version, function() { const flight = new Flight("123456789", info); it("Exact match returns true.", function() { - expect(flight.checkInfo({altitude: 35000})).to.be.true; + expect(flight.checkInfo({ altitude: 35000 })).to.be.true; }); it("Min/max range within bounds returns true.", function() { - expect(flight.checkInfo({minAltitude: 30000, maxAltitude: 40000})).to.be.true; + expect(flight.checkInfo({ minAltitude: 30000, maxAltitude: 40000 })).to.be.true; }); it("Exact mismatch returns false.", function() { - expect(flight.checkInfo({altitude: 40000})).to.be.false; + expect(flight.checkInfo({ altitude: 40000 })).to.be.false; }); it("Max exceeded returns false.", function() { - expect(flight.checkInfo({maxAltitude: 30000})).to.be.false; + expect(flight.checkInfo({ maxAltitude: 30000 })).to.be.false; }); it("String field match returns true.", function() { - expect(flight.checkInfo({airlineIcao: "GLO"})).to.be.true; + expect(flight.checkInfo({ airlineIcao: "GLO" })).to.be.true; }); it("String field mismatch returns false.", function() { - expect(flight.checkInfo({airlineIcao: "TAM"})).to.be.false; + expect(flight.checkInfo({ airlineIcao: "TAM" })).to.be.false; }); it("Combined conditions all matching returns true.", function() { - expect(flight.checkInfo({minAltitude: 30000, maxAltitude: 40000, airlineIcao: "GLO"})).to.be.true; + expect(flight.checkInfo({ minAltitude: 30000, maxAltitude: 40000, airlineIcao: "GLO" })).to.be.true; }); }); @@ -334,7 +334,7 @@ describe("Testing FlightRadarAPI version " + version, function() { describe("getHistoryData() — invalid file type", function() { it("Throws for unsupported file type.", async function() { const api = new FlightRadar24API(); - api.__loginData = {userData: {accessToken: "fake"}, cookies: {"_frPl": "fake"}}; + api.__loginData = { userData: { accessToken: "fake" }, cookies: { "_frPl": "fake" } }; const flight = new Flight("123", ["ABC", 0, 0, 0, 0, 0, "0", null, "B738", "PR-X", 0, "GRU", "GIG", "G1", 0, 0, "GLO1", null, "GLO"]); try { await api.getHistoryData(flight, "PDF", 0); diff --git a/nodejs/tests/testSnapshots.js b/nodejs/tests/testSnapshots.js index f86949d..5fefe9e 100644 --- a/nodejs/tests/testSnapshots.js +++ b/nodejs/tests/testSnapshots.js @@ -1,4 +1,4 @@ -const {FlightRadar24API, Countries} = require(".."); +const { FlightRadar24API, Countries } = require(".."); const expect = require("chai").expect; @@ -102,7 +102,7 @@ const AIRPORT_DETAILS_SHAPE = { }, name: null, position: { - country: {name: null}, + country: { name: null }, latitude: null, longitude: null, },