Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/ext/hx-sse.js
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,10 @@
let releaseRequest;
ctx.extensionPromise = new Promise(resolve => releaseRequest = resolve);
handleSSEResponse(ctx, releaseRequest).catch(e => {
// a throw before the stream loop skips the finally, so release
// the request here and tear the connection down
releaseRequest();
cleanup(element);
// an aborted stream is a normal end, not an error
if (e.name !== 'AbortError') {
api.triggerHtmxEvent(element, 'htmx:sse:error', {error: e, url: ctx.request.action});
Expand Down
24 changes: 24 additions & 0 deletions test/tests/ext/hx-sse.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,30 @@ describe('hx-sse SSE extension', function() {
stream.close();
});

it('releases the request when setup throws', async function() {
mockStreamResponse('/setup-throw');
createProcessedHTML('<button hx-get="/setup-throw" hx-swap="innerHTML" hx-indicator="#spin">Go</button><div id="spin" class="htmx-indicator">Loading</div>');

// a throw before the stream loop skips the finally that normally releases
let original = Object.getOwnPropertyDescriptor(htmx.config, 'sse');
Object.defineProperty(htmx.config, 'sse', {
configurable: true,
get() { throw new Error('config blew up') }
});

try {
find('button').click();
await htmx.timeout(50);
assert.isFalse(find('#spin').classList.contains('htmx-request'),
'Indicator must hide when setup throws');
assert.isUndefined(find('button')._htmx?.sse,
'Connection must not stay registered when setup throws');
} finally {
delete htmx.config.sse;
if (original) Object.defineProperty(htmx.config, 'sse', original);
}
});

it('continuous stream reconnects with exponential backoff', async function() {
const stream = mockStreamResponse('/reconnect');
createProcessedHTML('<button hx-get="/reconnect" hx-config="sse.reconnect:true sse.reconnectDelay:50ms sse.reconnectMaxAttempts:3 sse.reconnectJitter:0" hx-swap="innerHTML">Connect</button>');
Expand Down
Loading