Skip to content
Merged
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
16 changes: 8 additions & 8 deletions src/editors/jetbrains/htmx.web-types.json
Original file line number Diff line number Diff line change
Expand Up @@ -536,14 +536,14 @@
"doc-url": "https://four.htmx.org/reference/events/htmx-after-viewTransition"
},
{
"name": "before:sse:connection",
"name": "sse:before:connection",
"description": "Fires before an SSE connection attempt. `detail.connection` can be modified; set `cancelled` or cancel the event to stop connecting.",
"doc-url": "https://four.htmx.org/extensions/hx-sse#htmxbeforesseconnection"
"doc-url": "https://four.htmx.org/extensions/hx-sse#htmxssebeforeconnection"
},
{
"name": "after:sse:connection",
"name": "sse:after:connection",
"description": "Fires after a successful SSE connection or reconnection. `detail.connection` describes the connection.",
"doc-url": "https://four.htmx.org/extensions/hx-sse#htmxaftersseconnection"
"doc-url": "https://four.htmx.org/extensions/hx-sse#htmxsseafterconnection"
},
{
"name": "sse:close",
Expand All @@ -556,14 +556,14 @@
"doc-url": "https://four.htmx.org/extensions/hx-sse#htmxsseerror"
},
{
"name": "before:sse:message",
"name": "sse:before:message",
"description": "Fires before an SSE message is processed. `detail.message` contains `data`, `event`, `id`, and `cancelled`.",
"doc-url": "https://four.htmx.org/extensions/hx-sse#htmxbeforessemessage"
"doc-url": "https://four.htmx.org/extensions/hx-sse#htmxssebeforemessage"
},
{
"name": "after:sse:message",
"name": "sse:after:message",
"description": "Fires after an SSE message is processed. `detail.message` contains `data`, `event`, and `id`.",
"doc-url": "https://four.htmx.org/extensions/hx-sse#htmxafterssemessage"
"doc-url": "https://four.htmx.org/extensions/hx-sse#htmxsseaftermessage"
},
{
"name": "before:ws:connection",
Expand Down
72 changes: 39 additions & 33 deletions src/ext/hx-sse.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
(() => {
let api;
let warnedLegacyAttributes = new Set();

// ========================================
// HELPERS
// ========================================

function getConfig(ctx) {
let isConnect = api.attributeValue(ctx.sourceElement, 'hx-sse:connect') != null;
let defaults = {
reconnect: isConnect,
function getConfig(element) {
let hasHxSseConnect = api.attributeValue(element, 'hx-sse:connect') != null;
let hxConfig = api.HCON.parse(api.attributeValue(element, 'hx-config')).sse || {};

return {
reconnect: hasHxSseConnect,
reconnectDelay: 500,
reconnectMaxDelay: 60000,
reconnectMaxAttempts: Infinity,
reconnectJitter: 0.3,
pauseOnBackground: isConnect
pauseOnBackground: hasHxSseConnect,
...htmx.config.sse,
...hxConfig
};
let global = htmx.config.sse || {};
// hx-config="sse.reconnect:true sse.reconnectDelay:50ms" is parsed by
// core's __mergeConfig into ctx.request.sse during createRequestContext
let perElement = ctx.request.sse || {};
return {...defaults, ...global, ...perElement};
}

function clearLastEventIdHeader(headers) {
Expand Down Expand Up @@ -111,7 +111,7 @@
// with the saved request context (no full pipeline re-run).
async function handleSSEResponse(ctx) {
let element = ctx.sourceElement;
let config = getConfig(ctx);
let config = getConfig(element);
let reconnectRequested = false;

let connection = {
Expand Down Expand Up @@ -153,13 +153,13 @@
}

connection.cancelled = false;
if (!api.triggerHtmxEvent(element, 'htmx:before:sse:connection', {connection}) || connection.cancelled) {
if (!api.triggerHtmxEvent(element, 'htmx:sse:before:connection', {connection}) || connection.cancelled) {
cleanup(element, 'cancelled');
return;
}

connection.status = ctx.response.status;
api.triggerHtmxEvent(element, 'htmx:after:sse:connection', {connection});
api.triggerHtmxEvent(element, 'htmx:sse:after:connection', {connection});

let currentResponse = ctx.response.raw;

Expand Down Expand Up @@ -192,7 +192,7 @@
}

connection.cancelled = false;
if (!api.triggerHtmxEvent(element, 'htmx:before:sse:connection', {connection}) || connection.cancelled) break;
if (!api.triggerHtmxEvent(element, 'htmx:sse:before:connection', {connection}) || connection.cancelled) break;

await new Promise(r => {
connection.delayCanceller = r;
Expand Down Expand Up @@ -231,7 +231,7 @@
}

connection.status = currentResponse.status;
api.triggerHtmxEvent(element, 'htmx:after:sse:connection', {connection});
api.triggerHtmxEvent(element, 'htmx:sse:after:connection', {connection});
connection.attempt = 0;
}

Expand All @@ -254,14 +254,14 @@
let detail = {
message: {data: msg.data, event: msg.event, id: msg.id, cancelled: false}
};
if (!api.triggerHtmxEvent(element, 'htmx:before:sse:message', detail) || detail.message.cancelled) continue;
if (!api.triggerHtmxEvent(element, 'htmx:sse:before:message', detail) || detail.message.cancelled) continue;

if (msg.retry != null) config.reconnectDelay = msg.retry;

if (detail.message.event) {
htmx.trigger(element, detail.message.event, {data: detail.message.data, id: detail.message.id});
delete detail.message.cancelled;
api.triggerHtmxEvent(element, 'htmx:after:sse:message', detail);
api.triggerHtmxEvent(element, 'htmx:sse:after:message', detail);

// hx-sse:close="eventname" — close connection on matching event
let closeEvent = api.attributeValue(element, 'hx-sse:close');
Expand All @@ -277,7 +277,7 @@
if (!ctx.swap.includes('swapEmpty')) ctx.swap += ' swapEmpty:false';
await htmx.swap(ctx);
delete detail.message.cancelled;
api.triggerHtmxEvent(element, 'htmx:after:sse:message', detail);
api.triggerHtmxEvent(element, 'htmx:sse:after:message', detail);
}
} catch (e) {
if (!connection.abortController?.signal?.aborted) {
Expand Down Expand Up @@ -334,17 +334,15 @@
// ========================================

function checkLegacyAttributes(element) {
if (element.hasAttribute('sse-connect')) {
console.warn('htmx: [hx-sse] legacy attribute sse-connect is deprecated; use hx-sse:connect instead');
for (let attribute of ['sse-connect', 'sse-close', 'sse-swap']) {
if (!element.hasAttribute(attribute) || warnedLegacyAttributes.has(attribute)) continue;

let url = element.getAttribute('sse-connect');
let attr = (htmx.config.prefix || 'hx-') + 'sse' + (htmx.config.metaCharacter || ':') + 'connect';
if (!element.hasAttribute(attr)) {
element.setAttribute(attr, url);
if (attribute === 'sse-swap') {
console.warn('htmx: [hx-sse] sse-swap is removed in htmx 4. Unnamed SSE messages are swapped automatically. Named events are dispatched as DOM events.');
} else {
console.warn(`htmx: [hx-sse] legacy attribute ${attribute} is deprecated; use hx-sse:${attribute.slice(4)} instead`);
}
}
if (element.hasAttribute('sse-swap')) {
console.warn('htmx: [hx-sse] sse-swap is removed in htmx 4. Unnamed SSE messages are swapped automatically. Named events are dispatched as DOM events.');
warnedLegacyAttributes.add(attribute);
}
}

Expand Down Expand Up @@ -377,16 +375,24 @@
},

htmx_after_process: (element) => {
checkLegacyAttributes(element);
processElement(element);
let mc = htmx.config.metaCharacter || ':';
let processSSEElement = (element) => {
checkLegacyAttributes(element);
for (let name of ['connect', 'close']) {
let legacyAttr = `sse-${name}`;
if (!element.hasAttribute(legacyAttr)) continue;

let attr = (htmx.config.prefix || 'hx-') + 'sse' + mc + name;
if (!element.hasAttribute(attr)) element.setAttribute(attr, element.getAttribute(legacyAttr));
}
processElement(element);
};

processSSEElement(element);
let sseAttr = CSS.escape('hx-sse' + mc + 'connect');
let sseSelector = `[${sseAttr}]`;
if (htmx.config.prefix) sseSelector += `,[${CSS.escape(htmx.config.prefix + 'sse' + mc + 'connect')}]`;
element.querySelectorAll(`${sseSelector},[sse-connect]`).forEach((el) => {
checkLegacyAttributes(el);
processElement(el);
});
element.querySelectorAll(`${sseSelector},[sse-connect],[sse-close],[sse-swap]`).forEach(processSSEElement);
},

htmx_before_cleanup: (element) => {
Expand Down
7 changes: 4 additions & 3 deletions src/scripts/upgrade-check.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@
}

SSE_EVENT_RENAMES = {
"htmx:sseOpen": "htmx:after:sse:connection",
"htmx:sseOpen": "htmx:sse:after:connection",
"htmx:sseError": "htmx:sse:error",
"htmx:sseBeforeMessage": "htmx:before:sse:message",
"htmx:sseMessage": "htmx:after:sse:message",
"htmx:sseBeforeMessage": "htmx:sse:before:message",
"htmx:sseMessage": "htmx:sse:after:message",
"htmx:sseClose": "htmx:sse:close",
}

Expand All @@ -102,6 +102,7 @@
# Extension attribute renames
EXT_ATTR_RENAMES = {
"sse-connect": "rename to hx-sse:connect",
"sse-close": "rename to hx-sse:close",
"sse-swap": "removed — SSE now integrates with standard htmx request pipeline",
"ws-connect": "rename to hx-ws:connect",
"ws-send": "rename to hx-ws:send",
Expand Down
Loading
Loading