Dependency updates

This commit is contained in:
2025-09-08 22:57:53 -06:00
parent 91c4fe274b
commit b41a43d006
7 changed files with 118 additions and 1168 deletions

View File

@@ -1,290 +1,8 @@
/*
Server Sent Events Extension
============================
This extension adds support for Server Sent Events to htmx. See /www/extensions/sse.md for usage instructions.
*/
(function() {
/** @type {import("../htmx").HtmxInternalApi} */
var api
htmx.defineExtension('sse', {
/**
* Init saves the provided reference to the internal HTMX API.
*
* @param {import("../htmx").HtmxInternalApi} api
* @returns void
*/
init: function(apiRef) {
// store a reference to the internal API.
api = apiRef
// set a function in the public API for creating new EventSource objects
if (htmx.createEventSource == undefined) {
htmx.createEventSource = createEventSource
}
},
getSelectors: function() {
return ['[sse-connect]', '[data-sse-connect]', '[sse-swap]', '[data-sse-swap]']
},
/**
* onEvent handles all events passed to this extension.
*
* @param {string} name
* @param {Event} evt
* @returns void
*/
onEvent: function(name, evt) {
var parent = evt.target || evt.detail.elt
switch (name) {
case 'htmx:beforeCleanupElement':
var internalData = api.getInternalData(parent)
// Try to remove remove an EventSource when elements are removed
var source = internalData.sseEventSource
if (source) {
api.triggerEvent(parent, 'htmx:sseClose', {
source,
type: 'nodeReplaced',
})
internalData.sseEventSource.close()
}
return
// Try to create EventSources when elements are processed
case 'htmx:afterProcessNode':
ensureEventSourceOnElement(parent)
}
}
})
/// ////////////////////////////////////////////
// HELPER FUNCTIONS
/// ////////////////////////////////////////////
/**
* createEventSource is the default method for creating new EventSource objects.
* it is hoisted into htmx.config.createEventSource to be overridden by the user, if needed.
*
* @param {string} url
* @returns EventSource
*/
function createEventSource(url) {
return new EventSource(url, { withCredentials: true })
}
/**
* registerSSE looks for attributes that can contain sse events, right
* now hx-trigger and sse-swap and adds listeners based on these attributes too
* the closest event source
*
* @param {HTMLElement} elt
*/
function registerSSE(elt) {
// Add message handlers for every `sse-swap` attribute
if (api.getAttributeValue(elt, 'sse-swap')) {
// Find closest existing event source
var sourceElement = api.getClosestMatch(elt, hasEventSource)
if (sourceElement == null) {
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
return null // no eventsource in parentage, orphaned element
}
// Set internalData and source
var internalData = api.getInternalData(sourceElement)
var source = internalData.sseEventSource
var sseSwapAttr = api.getAttributeValue(elt, 'sse-swap')
var sseEventNames = sseSwapAttr.split(',')
for (var i = 0; i < sseEventNames.length; i++) {
const sseEventName = sseEventNames[i].trim()
const listener = function(event) {
// If the source is missing then close SSE
if (maybeCloseSSESource(sourceElement)) {
return
}
// If the body no longer contains the element, remove the listener
if (!api.bodyContains(elt)) {
source.removeEventListener(sseEventName, listener)
return
}
// swap the response into the DOM and trigger a notification
if (!api.triggerEvent(elt, 'htmx:sseBeforeMessage', event)) {
return
}
swap(elt, event.data)
api.triggerEvent(elt, 'htmx:sseMessage', event)
}
// Register the new listener
api.getInternalData(elt).sseEventListener = listener
source.addEventListener(sseEventName, listener)
}
}
// Add message handlers for every `hx-trigger="sse:*"` attribute
if (api.getAttributeValue(elt, 'hx-trigger')) {
// Find closest existing event source
var sourceElement = api.getClosestMatch(elt, hasEventSource)
if (sourceElement == null) {
// api.triggerErrorEvent(elt, "htmx:noSSESourceError")
return null // no eventsource in parentage, orphaned element
}
// Set internalData and source
var internalData = api.getInternalData(sourceElement)
var source = internalData.sseEventSource
var triggerSpecs = api.getTriggerSpecs(elt)
triggerSpecs.forEach(function(ts) {
if (ts.trigger.slice(0, 4) !== 'sse:') {
return
}
var listener = function (event) {
if (maybeCloseSSESource(sourceElement)) {
return
}
if (!api.bodyContains(elt)) {
source.removeEventListener(ts.trigger.slice(4), listener)
}
// Trigger events to be handled by the rest of htmx
htmx.trigger(elt, ts.trigger, event)
htmx.trigger(elt, 'htmx:sseMessage', event)
}
// Register the new listener
api.getInternalData(elt).sseEventListener = listener
source.addEventListener(ts.trigger.slice(4), listener)
})
}
}
/**
* ensureEventSourceOnElement creates a new EventSource connection on the provided element.
* If a usable EventSource already exists, then it is returned. If not, then a new EventSource
* is created and stored in the element's internalData.
* @param {HTMLElement} elt
* @param {number} retryCount
* @returns {EventSource | null}
*/
function ensureEventSourceOnElement(elt, retryCount) {
if (elt == null) {
return null
}
// handle extension source creation attribute
if (api.getAttributeValue(elt, 'sse-connect')) {
var sseURL = api.getAttributeValue(elt, 'sse-connect')
if (sseURL == null) {
return
}
ensureEventSource(elt, sseURL, retryCount)
}
registerSSE(elt)
}
function ensureEventSource(elt, url, retryCount) {
var source = htmx.createEventSource(url)
source.onerror = function(err) {
// Log an error event
api.triggerErrorEvent(elt, 'htmx:sseError', { error: err, source })
// If parent no longer exists in the document, then clean up this EventSource
if (maybeCloseSSESource(elt)) {
return
}
// Otherwise, try to reconnect the EventSource
if (source.readyState === EventSource.CLOSED) {
retryCount = retryCount || 0
retryCount = Math.max(Math.min(retryCount * 2, 128), 1)
var timeout = retryCount * 500
window.setTimeout(function() {
ensureEventSourceOnElement(elt, retryCount)
}, timeout)
}
}
source.onopen = function(evt) {
api.triggerEvent(elt, 'htmx:sseOpen', { source })
if (retryCount && retryCount > 0) {
const childrenToFix = elt.querySelectorAll("[sse-swap], [data-sse-swap], [hx-trigger], [data-hx-trigger]")
for (let i = 0; i < childrenToFix.length; i++) {
registerSSE(childrenToFix[i])
}
// We want to increase the reconnection delay for consecutive failed attempts only
retryCount = 0
}
}
api.getInternalData(elt).sseEventSource = source
var closeAttribute = api.getAttributeValue(elt, "sse-close");
if (closeAttribute) {
// close eventsource when this message is received
source.addEventListener(closeAttribute, function() {
api.triggerEvent(elt, 'htmx:sseClose', {
source,
type: 'message',
})
source.close()
});
}
}
/**
* maybeCloseSSESource confirms that the parent element still exists.
* If not, then any associated SSE source is closed and the function returns true.
*
* @param {HTMLElement} elt
* @returns boolean
*/
function maybeCloseSSESource(elt) {
if (!api.bodyContains(elt)) {
var source = api.getInternalData(elt).sseEventSource
if (source != undefined) {
api.triggerEvent(elt, 'htmx:sseClose', {
source,
type: 'nodeMissing',
})
source.close()
// source = null
return true
}
}
return false
}
/**
* @param {HTMLElement} elt
* @param {string} content
*/
function swap(elt, content) {
api.withExtensions(elt, function(extension) {
content = extension.transformResponse(content, null, elt)
})
var swapSpec = api.getSwapSpecification(elt)
var target = api.getTarget(elt)
api.swap(target, content, swapSpec)
}
function hasEventSource(node) {
return api.getInternalData(node).sseEventSource != null
}
})()
/**
* Minified by jsDelivr using Terser v5.37.0.
* Original file: /npm/htmx-ext-sse@2.2.2/sse.js
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
*/
!function(){var e;function t(e){return new EventSource(e,{withCredentials:!0})}function n(t){if(e.getAttributeValue(t,"sse-swap")){if(null==(u=e.getClosestMatch(t,i)))return null;for(var n=e.getInternalData(u).sseEventSource,r=e.getAttributeValue(t,"sse-swap").split(","),o=0;o<r.length;o++){const i=r[o].trim(),c=function(r){s(u)||(e.bodyContains(t)?e.triggerEvent(t,"htmx:sseBeforeMessage",r)&&(a(t,r.data),e.triggerEvent(t,"htmx:sseMessage",r)):n.removeEventListener(i,c))};e.getInternalData(t).sseEventListener=c,n.addEventListener(i,c)}}if(e.getAttributeValue(t,"hx-trigger")){var u;if(null==(u=e.getClosestMatch(t,i)))return null;n=e.getInternalData(u).sseEventSource;e.getTriggerSpecs(t).forEach((function(r){if("sse:"===r.trigger.slice(0,4)){var a=function(i){s(u)||(e.bodyContains(t)||n.removeEventListener(r.trigger.slice(4),a),htmx.trigger(t,r.trigger,i),htmx.trigger(t,"htmx:sseMessage",i))};e.getInternalData(t).sseEventListener=a,n.addEventListener(r.trigger.slice(4),a)}}))}}function r(t,a){if(null==t)return null;if(e.getAttributeValue(t,"sse-connect")){var i=e.getAttributeValue(t,"sse-connect");if(null==i)return;!function(t,a,i){var o=htmx.createEventSource(a);o.onerror=function(n){if(e.triggerErrorEvent(t,"htmx:sseError",{error:n,source:o}),!s(t)&&o.readyState===EventSource.CLOSED){i=i||0;var a=500*(i=Math.max(Math.min(2*i,128),1));window.setTimeout((function(){r(t,i)}),a)}},o.onopen=function(r){if(e.triggerEvent(t,"htmx:sseOpen",{source:o}),i&&i>0){const e=t.querySelectorAll("[sse-swap], [data-sse-swap], [hx-trigger], [data-hx-trigger]");for(let t=0;t<e.length;t++)n(e[t]);i=0}},e.getInternalData(t).sseEventSource=o;var u=e.getAttributeValue(t,"sse-close");u&&o.addEventListener(u,(function(){e.triggerEvent(t,"htmx:sseClose",{source:o,type:"message"}),o.close()}))}(t,i,a)}n(t)}function s(t){if(!e.bodyContains(t)){var n=e.getInternalData(t).sseEventSource;if(null!=n)return e.triggerEvent(t,"htmx:sseClose",{source:n,type:"nodeMissing"}),n.close(),!0}return!1}function a(t,n){e.withExtensions(t,(function(e){n=e.transformResponse(n,null,t)}));var r=e.getSwapSpecification(t),s=e.getTarget(t);e.swap(s,n,r)}function i(t){return null!=e.getInternalData(t).sseEventSource}htmx.defineExtension("sse",{init:function(n){e=n,null==htmx.createEventSource&&(htmx.createEventSource=t)},getSelectors:function(){return["[sse-connect]","[data-sse-connect]","[sse-swap]","[data-sse-swap]"]},onEvent:function(t,n){var s=n.target||n.detail.elt;switch(t){case"htmx:beforeCleanupElement":var a=e.getInternalData(s),i=a.sseEventSource;return void(i&&(e.triggerEvent(s,"htmx:sseClose",{source:i,type:"nodeReplaced"}),a.sseEventSource.close()));case"htmx:afterProcessNode":r(s)}}})}();
//# sourceMappingURL=/sm/97a20e642efcf59a346095b79d9e73120e258a50836a64ba297a1990d62f8cd5.map