function parsePeers(peerMap) { if (!peerMap) return [] return Object.keys(peerMap).map(function (key) { var p = peerMap[key] return { hostname: p.HostName || key, ip: (p.TailscaleIPs && p.TailscaleIPs.length) ? p.TailscaleIPs[0] : "", online: p.Online || false, exitNode: p.ExitNodeOption || false } }) } function makeExitNodeCommand(hostname) { return ["tailscale", "set", "--exit-node=" + hostname] } function findActiveExitNode(peerMap) { if (!peerMap) return "" for (const key in peerMap) { const p = peerMap[key] if (p.ExitNode) { return p.HostName || key } } return "" } var safeClipboardCmds = ["dms cl copy", "wl-copy", "clipmanctl copy", "xclip -selection clipboard", "xsel --clipboard --input", "none"] function validateClipboardCmd(cmd) { return typeof cmd === "string" && safeClipboardCmds.includes(cmd) } function buildCopyCommand(text, clipboardCmd) { if (!validateClipboardCmd(clipboardCmd)) return null var escaped = text.replace(/'/g, "'\\''") return ["sh", "-c", "printf '%s' '" + escaped + "' | " + clipboardCmd] } function parseStatusResult(jsonText) { try { const data = JSON.parse(jsonText) return { isConnected: data.BackendState === "Running", tailscaleIP: (data.Self && data.Self.TailscaleIPs && data.Self.TailscaleIPs[0]) || "", currentExitNode: findActiveExitNode(data.Peer || {}), peers: parsePeers(data.Peer || {}) } } catch (e) { return { isConnected: false, tailscaleIP: "", currentExitNode: "", peers: [] } } } function buildToggleCommand(isConnected) { return isConnected ? ["tailscale", "down"] : ["tailscale", "up"] } function parseClipboardDetection(stdout) { var trimmed = typeof stdout === "string" ? stdout.trim() : "" return validateClipboardCmd(trimmed) ? trimmed : "none" } function errorMessage(cmd) { var messages = { "up": "Failed to connect to Tailscale", "connect": "Failed to connect to Tailscale", "down": "Failed to disconnect from Tailscale", "disconnect": "Failed to disconnect from Tailscale", "set": "Failed to set exit node", "status": "Failed to read Tailscale status" } return messages[cmd] || "Tailscale command failed" } // CommonJS export for Node.js tests (ignored by QML) if (typeof module !== "undefined" && module.exports) { module.exports = { parsePeers, makeExitNodeCommand, findActiveExitNode, errorMessage, validateClipboardCmd, buildCopyCommand, parseClipboardDetection, buildToggleCommand, parseStatusResult } }