Commit Graph

3 Commits

Author SHA1 Message Date
Trey t
c9992e2d11 Add JSX reverse-engineering notes
Capture the findings from the JSXWebViewFetcher rewrite session:

- The Navitaire /api/nsk/v4/availability/search/simple endpoint that
  actually contains the flight load data, including the request body
  shape and the nested response structure (journeysAvailableByMarket →
  fares joined with faresAvailable for price/class).
- How the anonymous auth token lands in sessionStorage
  (navitaire.digital.token) and how to use it as the Authorization
  header on a direct fetch() from inside the page context.
- The jsx.com SPA one-way form structure (trip-type mat-select,
  station pickers, custom two-month range picker with DONE button,
  Find Flights submit), and the selectors / strategies that work for
  each one.
- The Osano cookie banner gotcha — its role="dialog" fools calendar
  detection, so the banner node must be force-removed after accepting.
- The datepicker quirks: JSX uses a custom picker (not mat-calendar),
  renders in two phases (shell then cells), and day cells carry
  aria-labels in the format "Saturday, April 11, 2026" with a weekday
  prefix — so exact-match "April 11, 2026" fails but loose
  month+year+day-word-boundary matching works.
- The central finding: WKWebView's synthetic DOM events have
  isTrusted=false, so JSX's datepicker never commits its day-cell
  selection into the Angular FormControl, Angular's search() sees the
  form as invalid, and no POST fires. Playwright doesn't hit this
  because CDP's Input.dispatchMouseEvent produces trusted events.
- The Akamai surface: external HTTP clients are blocked, Playwright's
  own launch() is blocked on POST /search/simple (ERR_HTTP2_PROTOCOL
  _ERROR), connectOverCDP to a plain Chrome works, and WKWebView's
  same-origin fetch() from inside a loaded jsx.com page works.
- The working approach (direct POST from page context using the
  sessionStorage token) and why it sidesteps both the trusted-events
  and the Akamai problems.
- The network interceptor pattern that distinguishes "Angular never
  fired the POST" from "Angular fired it but the network rejected it"
  — critical for diagnosing the trusted-events trap.
- Code pointers to the Swift runtime (JSXWebViewFetcher.swift),
  the iOS call site (AirlineLoadService.fetchJSXLoad), and the
  Playwright reference (scripts/jsx_playwright_search.mjs).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 10:54:56 -05:00
Trey t
77c59ce2c2 Rewrite JSX flow with per-step verification + direct API call
Full rewrite of Flights/Services/JSXWebViewFetcher.swift implementing a
19-step WKWebView flow that drives the jsx.com one-way search UI, then
calls POST /api/nsk/v4/availability/search/simple directly via fetch()
from within the page context using the anonymous auth token read from
sessionStorage["navitaire.digital.token"].

Why the direct call instead of clicking Find Flights: WKWebView's
synthetic MouseEvents have isTrusted=false, and JSX's custom datepicker
commits its day-cell selection into the Angular FormControl only on
trusted user gestures. The result is that the date input displays
"Sat, Apr 11" but the underlying FormControl stays null, so Angular's
search() sees form.invalid === true and silently returns without
firing a request. Playwright sidesteps this because CDP's
Input.dispatchMouseEvent produces trusted events; WKWebView has no
equivalent. The fix is to drive the UI steps (for page warm-up and
smoke testing) but then call the API directly — the same-origin fetch
inherits the browser's cookies and TLS fingerprint so Akamai sees it
as legitimate traffic, same as the lowfare/estimate GET that already
works through the page.

Every step has an action and one or more post-condition verifications.
On failure the runner dumps the action's returned data fields, page
state (URL, selector counts, form error markers), and both the last
initiated AND last completed api.jsx.com calls so network-level blocks
and form-validation bails can be distinguished.

New return type JSXSearchResult exposes every unique flight from the
search/simple response as [JSXFlight] with per-class load breakdowns
(classOfService, productClass, availableCount, fareTotal, revenueTotal)
so callers can see all flights, not just one.

Flights/Services/AirlineLoadService.swift: fetchJSXLoad now consumes
the [JSXFlight] array, logs every returned flight, and picks the
requested flight by digit-match. Deleted 495 lines of dead JSX helpers
(_fetchJSXLoad_oldMultiStep, parseJSXResponse, findJSXJourneys,
extractJSXFlightNumber, extractJSXAvailableSeats,
collectJSXAvailableCounts, parseJSXLowFareEstimate, normalizeFlightNumber).

scripts/jsx_playwright_search.mjs: standalone Playwright reference
implementation of the same flow. Launches real Chrome with --remote-
debugging-port and attaches via chromium.connectOverCDP() — this
bypasses Akamai's fingerprint check on Playwright's own launch and
produced the UI-flow steps and per-flight extractor logic that the
Swift rewrite mirrors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 10:11:29 -05:00
Trey t
3790792040 Initial commit: Flights iOS app
Flight search app built on FlightConnections.com API data.
Features: airport search with autocomplete, browse by country/state/map,
flight schedules by route and date, multi-airline support with per-airline
schedule loading. Includes 4,561-airport GPS database for map browsing.
Adaptive light/dark mode UI inspired by Flighty.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 15:01:07 -05:00