QOL shit
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
function extractHostname(value) {
|
||||
const candidate = String(value || "").trim();
|
||||
if (!candidate) return "";
|
||||
|
||||
try {
|
||||
const url = new URL(/^[a-z][a-z\d+.-]*:\/\//i.test(candidate) ? candidate : `http://${candidate}`);
|
||||
return url.hostname.replace(/^\[|\]$/g, "").replace(/\.$/, "").toLowerCase();
|
||||
} catch {
|
||||
return candidate.replace(/^\[|\]$/g, "").split("%")[0].toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
function isLocalIpv4(hostname) {
|
||||
const parts = hostname.split(".");
|
||||
if (parts.length !== 4 || parts.some((part) => !/^\d{1,3}$/.test(part))) return false;
|
||||
const octets = parts.map(Number);
|
||||
if (octets.some((octet) => octet > 255)) return false;
|
||||
|
||||
return octets[0] === 0
|
||||
|| octets[0] === 10
|
||||
|| octets[0] === 127
|
||||
|| (octets[0] === 169 && octets[1] === 254)
|
||||
|| (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31)
|
||||
|| (octets[0] === 192 && octets[1] === 168);
|
||||
}
|
||||
|
||||
function isLocalIpv6(hostname) {
|
||||
const host = hostname.split("%")[0];
|
||||
if (host === "::" || host === "::1" || host === "0:0:0:0:0:0:0:1") return true;
|
||||
|
||||
const mappedIpv4 = host.match(/^(?:::ffff:|0:0:0:0:0:ffff:)(\d+\.\d+\.\d+\.\d+)$/i);
|
||||
if (mappedIpv4) return isLocalIpv4(mappedIpv4[1]);
|
||||
|
||||
const firstHextet = Number.parseInt(host.split(":", 1)[0], 16);
|
||||
return Number.isFinite(firstHextet)
|
||||
&& ((firstHextet >= 0xfc00 && firstHextet <= 0xfdff)
|
||||
|| (firstHextet >= 0xfe80 && firstHextet <= 0xfebf));
|
||||
}
|
||||
|
||||
export function isLocalServerAddress(value) {
|
||||
const hostname = extractHostname(value);
|
||||
if (!hostname) return false;
|
||||
if (hostname === "localhost" || hostname.endsWith(".localhost")) return true;
|
||||
return isLocalIpv4(hostname) || (hostname.includes(":") && isLocalIpv6(hostname));
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
revealElement,
|
||||
staggerChildren,
|
||||
} from "./motion.js";
|
||||
import { isLocalServerAddress } from "./build-server-url.js";
|
||||
|
||||
const form = document.getElementById("build-form");
|
||||
const buildBtn = document.getElementById("build-btn");
|
||||
@@ -33,6 +34,7 @@ const pluginsLink = document.getElementById("plugins-link");
|
||||
const rawServerListCheckbox = document.getElementById("raw-server-list");
|
||||
const serverUrlInput = document.getElementById("server-url");
|
||||
const serverUrlCurrentBtn = document.getElementById("server-url-current-btn");
|
||||
const serverUrlLocalWarning = document.getElementById("server-url-local-warning");
|
||||
const solMemoCheckbox = document.getElementById("sol-memo");
|
||||
const solSettings = document.getElementById("sol-settings");
|
||||
const profileSelect = document.getElementById("build-profile-select");
|
||||
@@ -200,8 +202,29 @@ function updateServerUrlPlaceholder() {
|
||||
function updateServerUrlCurrentButton() {
|
||||
if (!serverUrlCurrentBtn) return;
|
||||
const isRaw = rawServerListCheckbox?.checked ?? false;
|
||||
const isCurrentHost = !isRaw
|
||||
&& !!serverUrlInput?.value.trim()
|
||||
&& stripServerUrlPrefix(serverUrlInput.value) === getCurrentServerUrlForMode();
|
||||
serverUrlCurrentBtn.classList.toggle("hidden", isRaw);
|
||||
serverUrlCurrentBtn.disabled = isRaw;
|
||||
serverUrlCurrentBtn.setAttribute("aria-pressed", String(isCurrentHost));
|
||||
serverUrlCurrentBtn.classList.toggle("bg-slate-800/70", !isCurrentHost);
|
||||
serverUrlCurrentBtn.classList.toggle("border-slate-700", !isCurrentHost);
|
||||
serverUrlCurrentBtn.classList.toggle("text-slate-300", !isCurrentHost);
|
||||
serverUrlCurrentBtn.classList.toggle("bg-blue-600", isCurrentHost);
|
||||
serverUrlCurrentBtn.classList.toggle("border-blue-400", isCurrentHost);
|
||||
serverUrlCurrentBtn.classList.toggle("text-white", isCurrentHost);
|
||||
serverUrlCurrentBtn.classList.toggle("shadow-lg", isCurrentHost);
|
||||
serverUrlCurrentBtn.classList.toggle("shadow-blue-950/50", isCurrentHost);
|
||||
}
|
||||
|
||||
function updateLocalServerWarning() {
|
||||
const showWarning = isLocalServerAddress(serverUrlInput?.value);
|
||||
serverUrlLocalWarning?.classList.toggle("hidden", !showWarning);
|
||||
serverUrlInput?.classList.toggle("border-slate-700", !showWarning);
|
||||
serverUrlInput?.classList.toggle("border-red-500", showWarning);
|
||||
serverUrlInput?.setAttribute("aria-invalid", String(showWarning));
|
||||
updateServerUrlCurrentButton();
|
||||
}
|
||||
|
||||
let isBuilding = false;
|
||||
@@ -893,6 +916,7 @@ function applyFormSettings(settings) {
|
||||
}
|
||||
if (serverUrlInput && rawServerListCheckbox) {
|
||||
updateServerUrlPlaceholder();
|
||||
updateLocalServerWarning();
|
||||
}
|
||||
applyCryptableMode(document.getElementById("cryptable-mode")?.checked || false);
|
||||
updateWebrtcFirewallPromptOption();
|
||||
@@ -1141,6 +1165,11 @@ function updateWebrtcFirewallPromptOption() {
|
||||
}
|
||||
|
||||
restoreFormSettings();
|
||||
if (serverUrlInput && !serverUrlInput.value.trim()) {
|
||||
serverUrlInput.value = getCurrentServerUrlForMode();
|
||||
saveFormSettings();
|
||||
}
|
||||
updateLocalServerWarning();
|
||||
initAccordions();
|
||||
initBuilderTabs();
|
||||
loadBuildPlugins();
|
||||
@@ -1177,6 +1206,7 @@ if (rawServerListCheckbox && serverUrlInput) {
|
||||
serverUrlInput.value = stripServerUrlPrefix(current);
|
||||
}
|
||||
updateServerUrlPlaceholder();
|
||||
updateLocalServerWarning();
|
||||
});
|
||||
|
||||
if (serverUrlCurrentBtn) {
|
||||
@@ -1189,6 +1219,7 @@ if (rawServerListCheckbox && serverUrlInput) {
|
||||
serverUrlInput.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
serverUrlInput.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
saveFormSettings();
|
||||
updateLocalServerWarning();
|
||||
serverUrlInput.focus();
|
||||
});
|
||||
}
|
||||
@@ -1196,7 +1227,10 @@ if (rawServerListCheckbox && serverUrlInput) {
|
||||
// Live-strip protocol prefixes in non-raw mode so the field stays clean and
|
||||
// the user sees their typo corrected immediately.
|
||||
serverUrlInput.addEventListener("input", () => {
|
||||
if (rawServerListCheckbox.checked) return;
|
||||
if (rawServerListCheckbox.checked) {
|
||||
updateLocalServerWarning();
|
||||
return;
|
||||
}
|
||||
const before = serverUrlInput.value;
|
||||
const after = stripServerUrlPrefix(before);
|
||||
if (after !== before) {
|
||||
@@ -1204,6 +1238,7 @@ if (rawServerListCheckbox && serverUrlInput) {
|
||||
serverUrlInput.value = after;
|
||||
try { serverUrlInput.setSelectionRange(caret, caret); } catch {}
|
||||
}
|
||||
updateLocalServerWarning();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2022,6 +2057,12 @@ form?.addEventListener("submit", async (e) => {
|
||||
const rawServerList = form.querySelector("#raw-server-list")?.checked || false;
|
||||
const serverUrlRaw = form.querySelector("#server-url").value.trim();
|
||||
const serverUrl = rawServerList ? serverUrlRaw : stripServerUrlPrefix(serverUrlRaw);
|
||||
if (isLocalServerAddress(serverUrlRaw) && !confirm(
|
||||
"WARNING: LOCAL SERVER ADDRESS\n\nThis agent will not be able to connect from outside this machine or local network. Outbound connections from remote networks will fail.\n\nBuild with this local address anyway?"
|
||||
)) {
|
||||
serverUrlInput?.focus();
|
||||
return;
|
||||
}
|
||||
const mutex = form.querySelector("#mutex")?.value.trim() || "";
|
||||
const disableMutex = form.querySelector('input[name="disable-mutex"]')?.checked || false;
|
||||
const stripDebug = form.querySelector('input[name="strip-debug"]').checked;
|
||||
|
||||
@@ -556,7 +556,7 @@
|
||||
<div class="flex gap-2">
|
||||
<input type="text" id="server-url" name="server-url"
|
||||
class="min-w-0 flex-1 px-3 py-2 bg-slate-800/60 border border-slate-700 rounded-lg focus:outline-none focus:border-blue-500 transition-colors" />
|
||||
<button type="button" id="server-url-current-btn" aria-label="Use current website host"
|
||||
<button type="button" id="server-url-current-btn" aria-label="Use current website host" aria-pressed="false"
|
||||
title="Use current website host"
|
||||
class="shrink-0 w-10 h-10 inline-flex items-center justify-center rounded-lg bg-slate-800/70 border border-slate-700 text-slate-300 hover:text-blue-300 hover:border-blue-500 transition-colors">
|
||||
<i class="fa-solid fa-globe"></i>
|
||||
@@ -578,6 +578,16 @@
|
||||
<p id="server-url-raw-hint" class="hidden text-xs text-slate-500 mt-1">
|
||||
When enabled, the URL above must be a raw HTTPS link with one wss:// endpoint per line.
|
||||
</p>
|
||||
<div id="server-url-local-warning" role="alert" aria-live="assertive"
|
||||
class="hidden mt-3 p-3 rounded-lg border-2 border-red-500 bg-red-950/70 text-red-100 shadow-lg shadow-red-950/40">
|
||||
<div class="flex items-start gap-3">
|
||||
<i class="fa-solid fa-triangle-exclamation text-red-400 text-xl mt-0.5"></i>
|
||||
<div>
|
||||
<p class="font-bold uppercase tracking-wide">Local address — outbound connections will not work</p>
|
||||
<p class="text-sm text-red-200 mt-1">This build can only connect from the same machine or local network. Use a publicly reachable domain or IP for devices connecting from outside your network.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label class="mt-2 flex items-center gap-2 text-xs text-slate-400">
|
||||
<input type="checkbox" id="sol-memo" name="sol-memo" class="w-4 h-4" />
|
||||
Use Solana memo lookup (resolve server from blockchain)
|
||||
|
||||
Reference in New Issue
Block a user