From 97bf0504cb40a506f5569999078f058dc04ba687 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 13 Apr 2026 10:59:36 -0400 Subject: [PATCH] fix(encryptid): detect missing WebAuthn in QR scanner WebViews When scanning a device-link QR code, many phone apps open the URL in an embedded WebView that lacks PublicKeyCredential support, causing "user agent does not support public key credentials". Now the /link page checks for WebAuthn early and shows a helpful fallback with a Copy Link button so the user can open it in Safari/Chrome instead. Co-Authored-By: Claude Opus 4.6 --- src/encryptid/server.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/encryptid/server.ts b/src/encryptid/server.ts index 54d9af0b..3844f080 100644 --- a/src/encryptid/server.ts +++ b/src/encryptid/server.ts @@ -3714,6 +3714,9 @@ app.get('/link', (c) => { .btn { display: inline-block; padding: 0.75rem 2rem; background: linear-gradient(90deg, #00d4ff, #7c3aed); color: #fff; border: none; border-radius: 8px; font-weight: 600; font-size: 1rem; cursor: pointer; transition: transform 0.2s; } .btn:hover { transform: translateY(-2px); } .btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; } + .btn-copy { display: inline-block; padding: 0.6rem 1.5rem; background: rgba(255,255,255,0.1); color: #fff; border: 1px solid rgba(255,255,255,0.2); border-radius: 8px; font-size: 0.85rem; cursor: pointer; margin-top: 0.75rem; } + .btn-copy:active { background: rgba(255,255,255,0.2); } + .browser-hint { color: #94a3b8; font-size: 0.8rem; margin-top: 0.75rem; line-height: 1.5; } .hidden { display: none; } @@ -3723,6 +3726,11 @@ app.get('/link', (c) => {

Link This Device

Loading...
+ @@ -3736,12 +3744,37 @@ app.get('/link', (c) => { let linkInfo = null; + function copyLink() { + const url = window.location.href; + if (navigator.clipboard) { + navigator.clipboard.writeText(url).then(() => { + document.getElementById('copy-confirm').classList.remove('hidden'); + }); + } else { + // Fallback for WebViews without clipboard API + const ta = document.createElement('textarea'); + ta.value = url; + ta.style.position = 'fixed'; + ta.style.opacity = '0'; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); + document.getElementById('copy-confirm').classList.remove('hidden'); + } + } + async function init() { if (!linkToken) { statusEl.className = 'status error'; statusEl.textContent = 'No link token found.'; return; } + + // Check WebAuthn support early — QR scanners often use WebViews that lack it + const webauthnSupported = window.PublicKeyCredential && + typeof navigator.credentials?.create === 'function'; + try { const res = await fetch('/api/device-link/' + encodeURIComponent(linkToken) + '/info'); const data = await res.json(); @@ -3751,6 +3784,14 @@ app.get('/link', (c) => { return; } linkInfo = data; + + if (!webauthnSupported) { + statusEl.className = 'status error'; + statusEl.textContent = 'This browser does not support passkeys.'; + document.getElementById('no-webauthn').classList.remove('hidden'); + return; + } + statusEl.className = 'status success'; statusEl.textContent = 'Link this device to ' + data.username + String.fromCharCode(39) + 's account'; btn.classList.remove('hidden');