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>
|
<script setup>
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { useCredentialsStore } from '../../stores/credentials.js'
|
import { useCredentialsStore } from '../../stores/credentials.js'
|
||||||
|
import { copyText } from '../../utils/clipboard.js'
|
||||||
|
|
||||||
const store = useCredentialsStore()
|
const store = useCredentialsStore()
|
||||||
const showRotateConfirm = ref(false)
|
const showRotateConfirm = ref(false)
|
||||||
@@ -37,7 +38,7 @@ onMounted(() => store.loadKey())
|
|||||||
async function copyKey() {
|
async function copyKey() {
|
||||||
if (!store.extensionKey) return
|
if (!store.extensionKey) return
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(store.extensionKey)
|
await copyText(store.extensionKey)
|
||||||
globalThis.window?.__fcToast?.({ text: 'Copied', type: 'success' })
|
globalThis.window?.__fcToast?.({ text: 'Copied', type: 'success' })
|
||||||
} catch {
|
} catch {
|
||||||
globalThis.window?.__fcToast?.({ text: 'Copy failed', type: 'error' })
|
globalThis.window?.__fcToast?.({ text: 'Copy failed', type: 'error' })
|
||||||
|
|||||||
@@ -110,6 +110,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useApi } from '../../composables/useApi.js'
|
import { useApi } from '../../composables/useApi.js'
|
||||||
|
import { copyText } from '../../utils/clipboard.js'
|
||||||
|
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
@@ -172,7 +173,7 @@ async function rotateKey() {
|
|||||||
|
|
||||||
async function copy(text, label) {
|
async function copy(text, label) {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(text)
|
await copyText(text)
|
||||||
window.__fcToast?.({ text: `${label} copied.`, type: 'success' })
|
window.__fcToast?.({ text: `${label} copied.`, type: 'success' })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
window.__fcToast?.({ text: `Copy failed: ${e.message}`, type: 'error' })
|
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