Now that we've confirmed the direct in-page fetch() POST to
/api/nsk/v4/availability/search/simple works end-to-end on real iOS
devices (and is the only thing that does — simulator is blocked at
the transport layer by Akamai per-endpoint fingerprinting), delete
the dead simulator-era attempts that were kept around as hopeful
fallbacks:
- Delete nativePOSTSearchSimple and all the URLSession+cookie-replay
plumbing. URLSession can't reach /search/simple from iOS Simulator
either (TLS fingerprint same as WKWebView), and on real device the
in-page fetch already works so the URLSession path is never useful.
- Delete the ~150 lines of SPA state-harvest JavaScript that walked
__ngContext__ to find the parsed availability payload inside
Angular services as an attempt-2 fallback. The state-harvest was a
proxy for "maybe the POST went through but our interceptor
swallowed the response" — that theory is dead now that we know the
POST itself is what's blocked in the simulator.
- Delete the capturedBody instance property that only nativePOST
wrote to.
Step 17 is now exactly what it claims to be: read the sessionStorage
token, fire a single direct fetch() POST from the page context, return
the body on success. ~400 lines removed from JSXWebViewFetcher.swift
(2148 -> 1748).
Step 18's low-fare fallback stays as graceful degradation when the
POST fails (which happens on iOS Simulator). The fallback cabin is now
labeled "Route day-total (fallback)" instead of "Route (day total)"
so the UI clearly distinguishes a per-flight seat count from a route
estimate.
JSX_NOTES.md corrected: removed the inaccurate claim that WKWebView
POSTs to /search/simple just work. The anti-bot-surface table now
separates iOS Simulator (fails) from real iOS device (works) with
the specific error modes for each. TL;DR adds a visible caveat at
the top that the working path requires a real device; develop with
the low-fare fallback in the simulator.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>