class

Hwaro::Services::LiveReloadInjectHandler

Inherits HTTP::Handler < Reference < Object

Constants

LIVE_RELOAD_SCRIPT = "<script>\n(function() {\n var reconnectDelay = 1000;\n var maxDelay = 30000;\n var OVERLAY_ID = '__hwaro_build_error__';\n function showError(message) {\n var existing = document.getElementById(OVERLAY_ID);\n if (existing) existing.remove();\n var overlay = document.createElement('div');\n overlay.id = OVERLAY_ID;\n overlay.style.cssText = 'position:fixed;inset:0;z-index:2147483647;background:#fef3c7;color:#78350f;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:14px;line-height:1.55;padding:32px 40px;overflow:auto;box-sizing:border-box;';\n var title = document.createElement('div');\n title.textContent = 'Build failed';\n title.style.cssText = 'font-size:20px;font-weight:600;margin-bottom:16px;color:#92400e;';\n var body = document.createElement('pre');\n body.textContent = message || 'Unknown error';\n body.style.cssText = 'white-space:pre-wrap;margin:0;font-family:inherit;';\n var hint = document.createElement('div');\n hint.textContent = 'hwaro will clear this overlay on the next successful build.';\n hint.style.cssText = 'margin-top:24px;font-size:12px;color:#a16207;';\n overlay.appendChild(title);\n overlay.appendChild(body);\n overlay.appendChild(hint);\n document.body.appendChild(overlay);\n }\n function clearError() {\n var existing = document.getElementById(OVERLAY_ID);\n if (existing) existing.remove();\n }\n function swapLink(link, href) {\n var pending = link.__hwaroNext;\n if (pending) {\n pending.onload = pending.onerror = null;\n pending.remove();\n }\n var next = link.cloneNode();\n next.removeAttribute('integrity');\n next.href = href;\n next.__hwaroPending = true;\n next.onload = function() {\n next.__hwaroPending = false;\n link.remove();\n };\n next.onerror = function() { location.reload(); };\n link.__hwaroNext = next;\n link.parentNode.insertBefore(next, link.nextSibling);\n }\n function swapCss(paths) {\n var links = document.querySelectorAll('link[rel~=\"stylesheet\"][href]');\n var swapped = 0;\n for (var i = 0; i < links.length; i++) {\n if (links[i].__hwaroPending) continue;\n var url = new URL(links[i].href, location.href);\n if (url.origin !== location.origin) continue;\n var path = url.pathname;\n try { path = decodeURIComponent(path); } catch (e) {}\n var match = paths.length === 0;\n for (var j = 0; j < paths.length && !match; j++) {\n match = path.slice(-paths[j].length) === paths[j];\n }\n if (!match) continue;\n url.searchParams.set('__hwaro', Date.now());\n swapLink(links[i], url.href);\n swapped++;\n }\n if (swapped === 0) location.reload();\n }\n function connect() {\n var protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';\n var ws = new WebSocket(protocol + '//' + location.host + '/__hwaro_livereload');\n ws.onopen = function() {\n reconnectDelay = 1000;\n ws.send('hello:css');\n };\n ws.onmessage = function(event) {\n var data = event.data;\n if (data === 'reload') {\n clearError();\n location.reload();\n } else if (data === 'clear-error') {\n clearError();\n } else if (typeof data === 'string' && data.indexOf('css:') === 0) {\n clearError();\n var paths = [];\n try { paths = JSON.parse(data.slice('css:'.length)).paths || []; } catch (e) {}\n swapCss(paths);\n } else if (typeof data === 'string' && data.indexOf('error:') === 0) {\n try {\n var payload = JSON.parse(data.slice('error:'.length));\n showError(payload && payload.message);\n } catch (e) {\n showError(data.slice('error:'.length));\n }\n }\n };\n ws.onclose = function() {\n setTimeout(function() {\n reconnectDelay = Math.min(reconnectDelay * 2, maxDelay);\n connect();\n }, reconnectDelay);\n };\n }\n connect();\n})();\n</script>"

Client-side script bundle: WebSocket reconnect loop + a full-screen amber overlay rendered on error:<json> messages. We render the overlay client-side (not server-side) because a whole-build failure produces no new HTML to inject into. The overlay clears on the next reload or clear-error message. css:<json> swaps the matching stylesheets in place (see LiveReloadHandler#notify_css); the script opts in on connect. A swap still loading when the next css: arrives is retired and re-cloned from the original link, so overlapping saves never leave duplicates; a clone that fails to load falls back to a full reload.

Constructors

new(public_dir : String)
Source

Instance methods

call(context)
Source
inject_script(html : String) : String
Source