mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-01 16:58:06 +00:00
fix(web): restore htmx script re-execution timing that Alpine x-data depends on
Removing the custom htmx:afterSwap script-reexecution handler (in a prior
commit, as a "duplicate execution" cleanup) broke every partial whose Alpine
x-data component function is defined by an inline <script> in that same
partial (e.g. wifi.html's wifiSetup()) - confirmed live via browser console:
"Alpine Expression Error: wifiSetup is not defined" on every field in the
WiFi tab.
Root cause: htmx's own native script execution runs during its "settle"
phase (~20ms after swap, per htmx's own defaultSettleDelay), but Alpine's
MutationObserver evaluates x-data on newly-inserted elements synchronously,
right as the swap lands - before settle. So the inline <script> defining
wifiSetup() was still un-run when Alpine tried to call it, and Alpine does
not retry a failed x-data evaluation later once the function does become
defined.
Fix: re-execute swapped <script> tags ourselves on htmx:afterSwap (which
fires synchronously, before settle, beating Alpine's observer), and disable
htmx's own native script re-execution (htmx.config.allowScriptTags = false)
so the same script doesn't also run a second time during settle - restoring
correct timing without reintroducing the original double-execution bug.
Also in this commit:
- fix XSS: unescaped repoUrl in a title attribute in renderSavedRepositories
- replace .includes('github.com') substring checks with real URL hostname
validation (CodeQL: incomplete URL substring sanitization)
This commit is contained in:
@@ -667,7 +667,7 @@ window.handleGitHubPluginInstall = function() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!repoUrl.includes('github.com')) {
|
||||
if (!isGithubUrl(repoUrl)) {
|
||||
if (statusDiv) {
|
||||
statusDiv.innerHTML = '<span class="text-red-600"><i class="fas fa-exclamation-circle mr-1"></i>Please enter a valid GitHub URL</span>';
|
||||
}
|
||||
@@ -4022,9 +4022,9 @@ function renderSavedRepositories(repositories) {
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fas ${repoType === 'registry' ? 'fa-folder-open' : 'fa-code-branch'} text-gray-400 text-xs"></i>
|
||||
<span class="text-sm font-medium text-gray-900 truncate" title="${repoUrl}">${escapeHtml(repoName)}</span>
|
||||
<span class="text-sm font-medium text-gray-900 truncate" title="${escapeAttribute(repoUrl)}">${escapeHtml(repoName)}</span>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 truncate" title="${repoUrl}">${escapeHtml(repoUrl)}</p>
|
||||
<p class="text-xs text-gray-500 truncate" title="${escapeAttribute(repoUrl)}">${escapeHtml(repoUrl)}</p>
|
||||
</div>
|
||||
<button onclick='if(window.removeSavedRepository){window.removeSavedRepository(${escapeJs(repoUrl)})}else{console.error("removeSavedRepository not available")}' class="ml-2 text-red-600 hover:text-red-800 text-xs px-2 py-1" title="Remove repository">
|
||||
<i class="fas fa-trash"></i>
|
||||
@@ -4107,7 +4107,7 @@ function attachInstallButtonHandler() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!repoUrl.includes('github.com')) {
|
||||
if (!isGithubUrl(repoUrl)) {
|
||||
if (pluginStatusDiv) {
|
||||
pluginStatusDiv.innerHTML = '<span class="text-red-600"><i class="fas fa-exclamation-circle mr-1"></i>Please enter a valid GitHub URL</span>';
|
||||
}
|
||||
@@ -4280,7 +4280,7 @@ function setupGitHubInstallHandlers() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!repoUrl.includes('github.com')) {
|
||||
if (!isGithubUrl(repoUrl)) {
|
||||
registryStatusDiv.innerHTML = '<span class="text-red-600"><i class="fas fa-exclamation-circle mr-1"></i>Please enter a valid GitHub URL</span>';
|
||||
return;
|
||||
}
|
||||
@@ -4336,7 +4336,7 @@ function setupGitHubInstallHandlers() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!repoUrl.includes('github.com')) {
|
||||
if (!isGithubUrl(repoUrl)) {
|
||||
showError('Please enter a valid GitHub URL');
|
||||
return;
|
||||
}
|
||||
@@ -4480,6 +4480,19 @@ function showError(message) {
|
||||
}
|
||||
|
||||
|
||||
// Validate that a URL's actual host is github.com (not just a substring
|
||||
// match, which 'evil.com/github.com' or 'github.com.evil.com' would pass).
|
||||
// This is only a UX nicety pointing users at a valid URL - the server does
|
||||
// its own proper hostname validation before actually acting on the URL.
|
||||
function isGithubUrl(url) {
|
||||
try {
|
||||
const hostname = new URL(url).hostname.toLowerCase();
|
||||
return hostname === 'github.com' || hostname === 'www.github.com';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Utility function to escape HTML
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
|
||||
Reference in New Issue
Block a user