diff --git a/Flights/Services/RouteExplorerClient.swift b/Flights/Services/RouteExplorerClient.swift index 3e1cba7..b42c7fb 100644 --- a/Flights/Services/RouteExplorerClient.swift +++ b/Flights/Services/RouteExplorerClient.swift @@ -109,11 +109,14 @@ actor RouteExplorerClient { let url = baseURL.appendingPathComponent("api/token") var req = URLRequest(url: url) req.httpMethod = "GET" + Self.applyBrowserHeaders(to: &req) req.setValue("application/json", forHTTPHeaderField: "Accept") let (data, response) = try await session.data(for: req) let status = (response as? HTTPURLResponse)?.statusCode ?? -1 - guard status == 200 else { + if status != 200 { + let bodyStr = String(data: data, encoding: .utf8) ?? "" + print("[RouteExplorer] /api/token failed status=\(status) body=\(bodyStr.prefix(300))") throw ClientError.tokenFetchFailed(status: status) } @@ -127,6 +130,21 @@ actor RouteExplorerClient { } } + /// Browser-shaped headers — `/api/token` and `/api/flight-search` are + /// gated by Origin/Referer in production. Without these the server + /// returns 403 even though the endpoints are otherwise anonymous. + private static func applyBrowserHeaders(to request: inout URLRequest) { + request.setValue( + "Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) " + + "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 " + + "Mobile/15E148 Safari/604.1", + forHTTPHeaderField: "User-Agent" + ) + request.setValue("en-US,en;q=0.9", forHTTPHeaderField: "Accept-Language") + request.setValue("https://route-explorer.com/", forHTTPHeaderField: "Referer") + request.setValue("https://route-explorer.com", forHTTPHeaderField: "Origin") + } + // MARK: - Flight search proxy private func callFlightSearch( @@ -144,6 +162,7 @@ actor RouteExplorerClient { var req = URLRequest(url: url) req.httpMethod = "POST" + Self.applyBrowserHeaders(to: &req) req.setValue("application/json", forHTTPHeaderField: "Content-Type") req.setValue("application/json", forHTTPHeaderField: "Accept") req.setValue(token, forHTTPHeaderField: "X-API-Token") @@ -181,6 +200,7 @@ actor RouteExplorerClient { var req = URLRequest(url: url) req.httpMethod = "POST" + Self.applyBrowserHeaders(to: &req) req.setValue("application/json", forHTTPHeaderField: "Content-Type") req.setValue(token, forHTTPHeaderField: "X-API-Token") req.httpBody = bodyData