The initial implementation is actually correct, but my JS wasn't resilient enough to handle server errors. Requests were being fired between cassette reloads, and were returning non-success HTTP statuses.
Updating the JS to handle erroneous requests and continuing polling solved this issue, and in the end, is the how the JS should have been behaving anyway.