From b9e42d543c11c8be0dfb2b382454a1140223fc97 Mon Sep 17 00:00:00 2001 From: saopig1 Date: Fri, 17 Apr 2026 00:26:05 +0800 Subject: [PATCH] fix(web): make bot-link copy work over plain HTTP navigator.clipboard requires a secure context (HTTPS or localhost). For users hosting the bot on a remote IP and accessing via http://:3000, clipboard.writeText is undefined and the copy silently fails. Add a hidden-textarea + execCommand('copy') fallback, with a final prompt() fallback so the user can always grab the URL. Co-Authored-By: Claude Opus 4.6 (1M context) --- web/src/components/Navbar.vue | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/web/src/components/Navbar.vue b/web/src/components/Navbar.vue index 10d5eb7..d6b577b 100644 --- a/web/src/components/Navbar.vue +++ b/web/src/components/Navbar.vue @@ -77,11 +77,33 @@ function selectBot(id: string) { dropdownOpen.value = false; } -function copyBotLink(id: string) { +async function copyBotLink(id: string) { const url = `${window.location.origin}/bot/${id}`; - navigator.clipboard.writeText(url).then(() => { + // navigator.clipboard requires a secure context (HTTPS or localhost). + // Fall back to a hidden textarea + execCommand('copy') for plain HTTP + // access (e.g. when the bot is hosted on a remote IP). + try { + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(url); + } else { + const ta = document.createElement('textarea'); + ta.value = url; + ta.style.position = 'fixed'; + ta.style.opacity = '0'; + ta.style.pointerEvents = 'none'; + document.body.appendChild(ta); + ta.focus(); + ta.select(); + const ok = document.execCommand('copy'); + document.body.removeChild(ta); + if (!ok) throw new Error('execCommand copy returned false'); + } dropdownOpen.value = false; - }); + } catch (err) { + // Last-resort: prompt the user with the URL so they can copy manually + console.error('Failed to copy bot link', err); + window.prompt('复制以下链接:', url); + } } async function togglePower(bot: { id: string; connected: boolean; name: string }) {