From 1788079f2ab3113fc1f542265c3217a46d4bd416 Mon Sep 17 00:00:00 2001 From: Alexander Kolotov Date: Thu, 9 Apr 2026 09:09:19 -0600 Subject: [PATCH] fix: support clipboard copy on non-secure origins --- src/frontend/app.js | 55 ++++++++++++++++++++++++++++--------- src/frontend/detail.js | 7 +---- src/frontend/leaderboard.js | 3 +- 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/frontend/app.js b/src/frontend/app.js index 6fd757a..7c061f3 100644 --- a/src/frontend/app.js +++ b/src/frontend/app.js @@ -122,6 +122,43 @@ function showToast(msg) { setTimeout(() => el.classList.remove('show'), 2500); } +function fallbackCopyText(text) { + try { + var ta = document.createElement('textarea'); + ta.value = text; + ta.setAttribute('readonly', ''); + ta.style.position = 'fixed'; + ta.style.top = '-9999px'; + ta.style.left = '-9999px'; + document.body.appendChild(ta); + ta.focus(); + ta.select(); + var ok = document.execCommand('copy'); + document.body.removeChild(ta); + return ok; + } catch (e) { + return false; + } +} + +function copyText(text, successMsg) { + var done = function() { + showToast(successMsg || ('Copied: ' + text)); + return true; + }; + var fail = function() { + if (fallbackCopyText(text)) return done(); + prompt('Copy this command:', text); + showToast(window.isSecureContext ? 'Clipboard copy failed' : 'Clipboard unavailable on non-secure origin'); + return false; + }; + + if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') { + return navigator.clipboard.writeText(text).then(done).catch(fail); + } + return Promise.resolve(fail()); +} + function formatBytes(bytes) { if (!bytes || bytes < 1024) return (bytes || 0) + ' B'; if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'; @@ -1722,7 +1759,7 @@ function installAgent(agent) { var overlay = document.getElementById('confirmOverlay'); document.getElementById('confirmTitle').textContent = 'Install ' + info.name; - var html = '' + escHtml(info.cmd) + ''; + var html = '' + escHtml(info.cmd) + ''; if (info.alt) { html += 'or: ' + escHtml(info.alt) + '
'; } @@ -1732,9 +1769,7 @@ function installAgent(agent) { document.getElementById('confirmAction').textContent = 'Copy Install Command'; document.getElementById('confirmAction').className = 'launch-btn btn-primary'; document.getElementById('confirmAction').onclick = function() { - navigator.clipboard.writeText(info.cmd).then(function() { - showToast('Copied: ' + info.cmd); - }); + copyText(info.cmd, 'Copied: ' + info.cmd); closeConfirm(); }; if (overlay) overlay.style.display = 'flex'; @@ -1756,9 +1791,7 @@ function showExportDialog() { document.getElementById('confirmAction').textContent = 'Copy Export Command'; document.getElementById('confirmAction').className = 'launch-btn btn-primary'; document.getElementById('confirmAction').onclick = function() { - navigator.clipboard.writeText('codedash export').then(function() { - showToast('Copied: codedash export'); - }); + copyText('codedash export', 'Copied: codedash export'); closeConfirm(); }; if (overlay) overlay.style.display = 'flex'; @@ -1789,9 +1822,7 @@ async function checkForUpdates() { badge.classList.add('update-available'); badge.title = 'Click to copy update command'; badge.onclick = function() { - navigator.clipboard.writeText('npm i -g codedash-app@latest').then(function() { - showToast('Copied: npm i -g codedash-app@latest'); - }); + copyText('npm i -g codedash-app@latest', 'Copied: npm i -g codedash-app@latest'); }; } var banner = document.getElementById('updateBanner'); @@ -1807,9 +1838,7 @@ async function checkForUpdates() { function copyUpdate() { var cmd = 'codedash update && codedash restart'; - navigator.clipboard.writeText(cmd).then(function() { - showToast('Copied: ' + cmd + ' (run in terminal)'); - }); + copyText(cmd, 'Copied: ' + cmd + ' (run in terminal)'); } function dismissUpdate() { diff --git a/src/frontend/detail.js b/src/frontend/detail.js index 1f71257..fd69d23 100644 --- a/src/frontend/detail.js +++ b/src/frontend/detail.js @@ -284,12 +284,7 @@ function copyResume(sessionId, tool) { } else { cmd = 'claude --resume ' + sessionId; } - navigator.clipboard.writeText(cmd).then(function() { - showToast('Copied: ' + cmd); - }).catch(function() { - // Fallback - prompt('Copy this command:', cmd); - }); + copyText(cmd, 'Copied: ' + cmd); } function exportMd(sessionId, project) { diff --git a/src/frontend/leaderboard.js b/src/frontend/leaderboard.js index b8efda5..cba0673 100644 --- a/src/frontend/leaderboard.js +++ b/src/frontend/leaderboard.js @@ -138,8 +138,7 @@ async function githubConnect() { document.getElementById('githubAuthCode').textContent = data.user_code; document.getElementById('githubAuthLink').href = data.verification_uri; - // Copy code to clipboard - try { navigator.clipboard.writeText(data.user_code); } catch {} + copyText(data.user_code, 'Copied GitHub code'); // Poll for token var interval = (data.interval || 5) * 1000;