Repository navigation
Add example/nuts-client.js, a replay-aware EventSource helper (#146) - #157
Merged
Merged
Conversation
A dependency-free ES module that applications copy in. It keeps the cursor of every event that carries one (the connected handshake, messages, and the reset that sets it to 0) in sessionStorage or a storage of your choice, resumes from it after a reload, and opens a new EventSource from it when one gives up for good. Listeners go by event name, which pairs with event_type; raw passes data through for payload_format raw; the EventSource option takes an implementation that can send headers. The demo pages use it. make test-js runs its tests with Node.js's built-in test runner, in CI too, and checks that example_docker keeps the same copy. The README documents it, and the README and website snippets keep their cursor in sessionStorage, one per tab. The source-build demo's start.sh names the page's address, /example/, and /example redirects there so the page's relative import resolves; the image demo pulls the latest image before starting. Closes #146 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
example/nuts-client.jsis a small, dependency-free ES module that wrapsEventSourcefor replay-aware pages (#146). Applications copy it in.What it does:
one: the
connectedhandshake (so a page reloaded before its firstmessage keeps its start position), messages, and the
resetevent thatsets it to
0when the stream was recreated or restored.sessionStorageby default, and areload resumes after the last event the tab received.
localStorageorany other storage can be passed.
sessionStorageis the default, notlocalStorageas the issue suggested, becauselocalStorageis sharedby a site's tabs: a reloaded tab would resume where another tab is, and
miss what arrived in between. Storage that is disabled or full leaves the
cursor in memory.
EventSourcegives up for good (NUTS answers a4xx, or a proxy in front of it fails), the helper opens a new one fromthe latest cursor after
restartDelay(10 s).pairs with
event_type(Optional SSE event types taken from the topic or a message header #142).rawpasses message data through forpayload_format raw(Optional raw payload mode: the NATS payload as the SSE data, without the JSON envelope #143).onStatereportsconnecting,open,retrying,restartingandclosed.The demo pages in
example/andexample_docker/use it;example_dockerkeeps a copy, as it does of the page itself, and
make test-jschecks thetwo copies are the same. The README's new "Replay-aware JavaScript helper"
section documents it, and a shorter one goes on the website. The README and
website snippets now keep their cursor in
sessionStorage, and thewebsite's snippet also stores the
connectedandresetIDs, as theREADME's already did.
The issue's open questions:
event: resetwith{"reason": …}data andid: 0.fetchtransport. Browsers cannot send headers onEventSource, and NUTS already takes subscriber JWTs from a cookie(
subscriber_jwt_cookie). Elsewhere theEventSourceoption accepts animplementation that sends headers. The README shows the
eventsourcepackage's
fetchoption; checked below against a local SSE server.node:testwith a fakeEventSource, run in CI, plus anend-to-end run against the example stack.
Tests
example/nuts-client.test.mjs, 8 tests on Node.js's built-in runner (nopackages; passes on Node.js 20, 22 and 24). They cover URL building and
encoding, the default key, the cursor from each kind of event, events
without listeners,
raw, restarting after a fatal close with the latestcursor,
restartDelay, closing, the default storage, and storage thatthrows. CI runs them with
make test-json Node.js 22, which alsocompares the two copies. The new step uses
actions/setup-node@v7,the current major;
website.ymlstill pins v4.five alive: two gaps (a stored cursor that needs encoding, the listener's
event argument), now tested; three equivalent (null-storage checks that
the
tryalready covered, clearing a timer that already fired), nowsimplified away.
node --experimental-eventsourceagainst the examplestacks:
example/, built from this branch. A subscription closed and reopenedwith the same storage resumed from
?last-id=993. Against a controlsubscription that stayed open, it missed nothing and received nothing
twice (57 messages). After
nats stream rmandnats stream add, theresetarrived within 5 s and stored cursor0. EventSource thenreplayed the new stream from its start (ids 1, 3, 5, …), and the cursor
followed it.
/example/serves the page, and the module astext/javascript; the page's module script passesnode --check.example_docker/withidcttech/nuts:latest(f6cc5da): the same runpasses, the reset within 1 s. A stale local
latest(v0.3.2, olderthan the reset event) failed it at first, hence the pull in
start.sh.eventsourcepackage (5.1.2) and the helper,a local SSE server received
Authorizationon every request. EventSource'sown reconnects sent
Last-Event-ID, and after a401the helperrestarted with
?last-id=set to the latest cursor.🤖 Generated with Claude Code