fix(ui-copy): copyText utility with execCommand fallback — navigator.clipboard is gated by Secure Context (HTTPS-only) and is undefined on plain-HTTP self-hosted deployments. Apply to ExtensionKeyBar + BrowserExtensionCard.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useCredentialsStore } from '../../stores/credentials.js'
|
||||
import { copyText } from '../../utils/clipboard.js'
|
||||
|
||||
const store = useCredentialsStore()
|
||||
const showRotateConfirm = ref(false)
|
||||
@@ -37,7 +38,7 @@ onMounted(() => store.loadKey())
|
||||
async function copyKey() {
|
||||
if (!store.extensionKey) return
|
||||
try {
|
||||
await navigator.clipboard.writeText(store.extensionKey)
|
||||
await copyText(store.extensionKey)
|
||||
globalThis.window?.__fcToast?.({ text: 'Copied', type: 'success' })
|
||||
} catch {
|
||||
globalThis.window?.__fcToast?.({ text: 'Copy failed', type: 'error' })
|
||||
|
||||
@@ -110,6 +110,7 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useApi } from '../../composables/useApi.js'
|
||||
import { copyText } from '../../utils/clipboard.js'
|
||||
|
||||
const api = useApi()
|
||||
|
||||
@@ -172,7 +173,7 @@ async function rotateKey() {
|
||||
|
||||
async function copy(text, label) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
await copyText(text)
|
||||
window.__fcToast?.({ text: `${label} copied.`, type: 'success' })
|
||||
} catch (e) {
|
||||
window.__fcToast?.({ text: `Copy failed: ${e.message}`, type: 'error' })
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// Clipboard write that works on plain-HTTP self-hosted deployments.
|
||||
//
|
||||
// navigator.clipboard is gated by the browser's Secure Context restriction
|
||||
// (HTTPS or localhost only). FabledCurator runs over plain HTTP per the
|
||||
// homelab posture, so the modern API is undefined in production. We fall
|
||||
// back to the legacy execCommand('copy') path via a temporary off-screen
|
||||
// textarea — wide browser support, no HTTPS requirement.
|
||||
|
||||
export async function copyText (text) {
|
||||
const str = text == null ? '' : String(text)
|
||||
if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(str)
|
||||
return
|
||||
} catch {
|
||||
// Fall through to the legacy path. Some browsers throw even when
|
||||
// the API exists (permission denied, focus loss, etc.).
|
||||
}
|
||||
}
|
||||
const ta = document.createElement('textarea')
|
||||
ta.value = str
|
||||
ta.setAttribute('readonly', '')
|
||||
ta.style.position = 'fixed'
|
||||
ta.style.top = '0'
|
||||
ta.style.left = '0'
|
||||
ta.style.opacity = '0'
|
||||
ta.style.pointerEvents = 'none'
|
||||
document.body.appendChild(ta)
|
||||
ta.select()
|
||||
ta.setSelectionRange(0, str.length)
|
||||
let ok = false
|
||||
try { ok = document.execCommand('copy') } catch { ok = false }
|
||||
document.body.removeChild(ta)
|
||||
if (!ok) throw new Error('clipboard copy not supported')
|
||||
}
|
||||
Reference in New Issue
Block a user