SMP join button: robust clipboard copy with fallback for insecure contexts

This commit is contained in:
Martin Dvořák 2026-09-24 10:52:16 +00:00
commit 9695452974

View file

@ -360,7 +360,7 @@ function SmpApp() {
motd: null, motd: null,
icon: null, icon: null,
}); });
const [copied, setCopied] = useState(false); const [copyState, setCopyState] = useState<"idle" | "ok" | "fail">("idle");
useEffect(() => { useEffect(() => {
let alive = true; let alive = true;
@ -389,13 +389,36 @@ function SmpApp() {
const pending = info.status === "pending"; const pending = info.status === "pending";
const copyAddress = async () => { const copyAddress = async () => {
let ok = false;
// The async Clipboard API only exists in secure contexts (https / localhost)
// and can still reject (no focus, denied permission).
try { try {
await navigator.clipboard.writeText(address); if (navigator.clipboard?.writeText) {
setCopied(true); await navigator.clipboard.writeText(address);
window.setTimeout(() => setCopied(false), 1600); ok = true;
}
} catch { } catch {
/* clipboard unavailable - the address is shown below anyway */ ok = false;
} }
// Fallback that works in insecure contexts (e.g. LAN over http).
if (!ok) {
try {
const ta = document.createElement("textarea");
ta.value = address;
ta.setAttribute("readonly", "");
ta.style.position = "fixed";
ta.style.top = "-1000px";
ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select();
ok = document.execCommand("copy");
document.body.removeChild(ta);
} catch {
ok = false;
}
}
setCopyState(ok ? "ok" : "fail");
window.setTimeout(() => setCopyState("idle"), 1800);
}; };
return ( return (
@ -445,7 +468,7 @@ function SmpApp() {
<div className="smp-actions"> <div className="smp-actions">
<button type="button" className="smp-btn smp-btn-green" onClick={copyAddress}> <button type="button" className="smp-btn smp-btn-green" onClick={copyAddress}>
{copied ? "Address Copied!" : "Join Server"} {copyState === "ok" ? "Address Copied!" : copyState === "fail" ? "Copy failed" : "Join Server"}
</button> </button>
<a <a
className="smp-btn" className="smp-btn"
@ -458,9 +481,11 @@ function SmpApp() {
</div> </div>
<p className="smp-hint"> <p className="smp-hint">
{copied {copyState === "ok"
? "Paste it in Minecraft → Multiplayer → Add Server." ? "Paste it in Minecraft → Multiplayer → Add Server."
: "Join Server copies the address. Add it in Minecraft → Multiplayer → Add Server."} : copyState === "fail"
? `Copy manually: ${address}`
: "Join Server copies the address. Add it in Minecraft → Multiplayer → Add Server."}
</p> </p>
</div> </div>
); );