Skip to content

feat: support Node.js for @jupyterhub/binderhub-client#2044

Open
agoose77 wants to merge 3 commits intojupyterhub:mainfrom
agoose77:feat-node-support
Open

feat: support Node.js for @jupyterhub/binderhub-client#2044
agoose77 wants to merge 3 commits intojupyterhub:mainfrom
agoose77:feat-node-support

Conversation

@agoose77
Copy link
Copy Markdown
Contributor

This PR switches out @microsoft/fetch-event-source in favour of eventsource. This package is both newer (and maintained) and explicitly supports the Node.js runtime.

I'm consuming a vendored version of these changes in https://github.com/2i2c-org/clinder/tree/main/packages/binderhub-client-next, which is Node.js only.


this.eventIteratorQueue = null;
this.abortSignal = null;
this.stopQueue = null;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention here is that the queue is the application-level interface we care about. The underlying EventSource is just an implementation detail.

Comment on lines +108 to +111
const response = await fetch(input, {
...init,
headers: { ...init.headers, ...headers },
});
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure we pass through the headers

Comment on lines +112 to +123
// Known failures are passed on and handled in onError
if (response.ok) {
return response;
} else if (
response.status >= 400 &&
response.status < 500 &&
response.status !== 429
) {
return response;
}
// Otherwise, throw, triggering a retry
throw new EventStreamRetry();
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK responses, or known-failures, are passed through to EventSource, which dispatches the proper events.

We don't do that for retry-able responses, because EventSource doesn't feature a configurable retry mechanism — it only kicks in for failures during fetching.

throw new EventStreamRetry();
}
},
this.stopQueue = () => queue.stop();
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leak the stop action.

return () => {
es.removeEventListener("message", onMessage);
es.removeEventListener("error", onError);
es.close();
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This closes the event source, from stopQueue().

@rgaiacs
Copy link
Copy Markdown
Contributor

rgaiacs commented Dec 16, 2025

@ agoose77 thanks for the pull request.

Switches out @microsoft/fetch-event-source in favour of eventsource looks great.

Two tests failed. Can you investigate the tests that failed?

@agoose77
Copy link
Copy Markdown
Contributor Author

@rgaiacs yes, at this stage just pushing the code. Plan to get to the tests later today!

@agoose77
Copy link
Copy Markdown
Contributor Author

agoose77 commented Mar 4, 2026

So, I've fixed the tests ... but it is not perfect:

  1. I switched to happy-dom from jsdom for the test environment. jsdom has poor fetch support, and the polyfill we were using does not properly capture important details of the fetch response.
  2. happy-dom throws errors when fetches are aborted by DOM elements, e.g. when the IFrame is disposed. This ends up producing "log after test" warnings that are harmless but noisy.

I can't seem to find an easy way to suppress these hooks or ask Jest to ignore the console messages. Perhaps one can patch the fetch error handling prototype or tweak the happy-dom console.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants