Thanks for taking the time to write all that up OnlineProxy!
Genuinely appreciate the review. It actually pushed me to go through the article with a fine comb and fix a bunch of stuff.
I updated the article based on your feedback. It now has a full Known Limitations section covering CSP, JWT expiry, persisted queries, and the single-user constraint. The What I Learned section also calls out schema versioning as the real maintenance headache, which you were spot on about. Credited you where relevant, so people know where those improvements came from.
That said, a couple things you mentioned don't actually apply here, and I want to clear them up so the record is straight.
Cursor invalidation in localStorage recovery. There's no cursor logic in this code at all. Expedia uses offset-based pagination through selPageIndex and selPageCount inside a selections[] array, not cursor tokens. The loadMoreAction returns startingIndex and hasNextPage, and the dedup key is searchId:startingIndex. So there's no cursor to invalidate, and no edge case around one. The localStorage recovery just timestamps the snapshot and drops it after 1 hour, nothing more.
Duck-typing. I wish. The fetch hook checks requestBody.operationName !== "CarSearchV3", which is a hard string match on the operation name. That's the opposite of duck-typing. If Expedia ships CarSearchV4 tomorrow, it breaks. No shape inference happens. The article originally described it as shape matching (which was wrong), and I fixed that too in this revision.
Dedup overhead. It's a Set of string keys tested with .has(). A Set lookup is O(1) average. For a worst case of 2500 items (100 pages x 25 listings, the hard cap), we're talking microseconds per check. Even at a hypothetical 5000+ items, this would never be the bottleneck. The delays between requests (600ms to 7s depending on speed setting) dominate the runtime by many orders of magnitude. I appreciate the engineering rigor, but this one is safe.
GraphQL operation name changes being rare. That one you got right. V3 to V4 would be a big deal and I'd catch it immediately. But your real point about schema field mutations is what I took to heart. If Expedia renames priceSummary.total.price.amount to something else, the CSV export silently produces empty columns. That's now flagged in the article.
So thanks again. Your comment helped make the article more honest about what this tool can and can't do. Even where you swung and missed (cursor invalidation, duck-typing), it made me double check my own assumptions and fix genuine inaccuracies in the writeup. Appreciate it.
That cursor invalidation edge case you're handling in the localStorage recovery -- document that because readers will have no idea it's even a thing without digging into the actual code, be upfront about where this breaks - if you're dealing with persisted queries like Airbnb uses, strict Content Security Policy, or JWTs that expire mid-extraction, you gotta tell people so they can figure out whether interception or headless automation actually fits their situation. Also put some real numbers on what deduplication costs you - you mention sidestepping silent errors, but give us a concrete example of how much overhead you're burning so folks can actually gauge whether this scales for those 5000+ item extracts. The session-borrowing hack is genuinely clever, but it also locks you into being single-user and real-time dependent, which is a pretty significant constraint that deserves to be called out explicitly if someone's thinking about running this in production. I've shipped extractors across 12+ travel sites, and while GraphQL operation name changes like V3 to V4 stuff are rare, schema field mutations happen constantly - your duck-typing approach keeps you safe there, but honestly, flagging schema versioning as the actual maintenance headache would save future maintainers a ton of pain down the road