export const DEFAULT_SELF_SIGNED_MSG = "This service uses a self-signed certificate. Expect a browser warning; proceed only if you trust this device."; export function isValidLink(str) { if (!str) return true; // empty is allowed try { const u = new URL(str); return u.protocol === "http:" || u.protocol === "https:"; } catch (e) { return false; } } export function normalizePath(path) { if (!path) return ""; const trimmed = path.trim(); if (!trimmed) return ""; if (/\s/.test(trimmed)) return null; // no spaces or tabs in paths if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return null; return trimmed.startsWith("/") ? trimmed : `/${trimmed}`; } export function validateServiceFields({ name, port, path, notice, notice_link }, fail) { const err = (m) => { fail?.(m); return false; }; if (!name || name.trim().length < 2) return err("Name must be at least 2 characters."); if (name.length > 48) return err("Name is too long (max 48 chars)."); if (!Number.isInteger(port) || port < 1 || port > 65535) return err("Port must be 1-65535."); if (path === null) return err("Path must be relative (e.g. /admin) or blank."); if (path.length > 200) return err("Path is too long (max 200 chars)."); if (notice && notice.length > 180) return err("Notice text too long (max 180 chars)."); if (notice_link && notice_link.length > 200) return err("Notice link too long (max 200 chars)."); if (!isValidLink(notice_link)) return err("Enter a valid URL (http/https) or leave blank."); return true; }