feat(scan): camera on companion + insecure origins — WebView bridge & photo fallback
Demo images / Build & push demo images (push) Successful in 2m55s
Demo images / Build & push demo images (push) Successful in 2m55s
Two gaps kept the wallet QR scanner camera-less outside a desktop browser: - Companion app: the WebView's default WebChromeClient silently denies getUserMedia. Both WebViews (main + in-app overlay) now implement onPermissionRequest — granting video capture only, requesting the app-level CAMERA permission on first use (manifest already declares it). - Plain-http origins (mobile web/PWA on a LAN node): browsers hide navigator.mediaDevices entirely, no permission can bring it back. New "Take photo of QR" fallback uses <input capture=environment> — the native camera needs no secure context — and decodes the shot locally with qr-scanner's scanImage. Live preview still used when available. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
5982fceb7c
commit
2a376ab275
@@ -54,9 +54,23 @@
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
<button @click="startScanning" class="glass-button px-4 py-2 rounded-lg text-sm font-medium">
|
||||
<button v-if="!liveCameraUnavailable" @click="startScanning" class="glass-button px-4 py-2 rounded-lg text-sm font-medium">
|
||||
{{ cameraError ? 'Retry camera' : 'Start camera' }}
|
||||
</button>
|
||||
<!-- Insecure-context fallback: live getUserMedia preview needs
|
||||
HTTPS, but the native camera via a file input does not —
|
||||
snap a photo of the QR and decode it locally. -->
|
||||
<button @click="photoInput?.click()" class="glass-button px-4 py-2 rounded-lg text-sm font-medium">
|
||||
Take photo of QR
|
||||
</button>
|
||||
<input
|
||||
ref="photoInput"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
capture="environment"
|
||||
class="hidden"
|
||||
@change="onPhotoPicked"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -316,6 +330,28 @@ function submitPaste() {
|
||||
handleScanned(text)
|
||||
}
|
||||
|
||||
// --- Photo-capture fallback (works without a secure context) ---
|
||||
// getUserMedia is unreachable over plain http (navigator.mediaDevices is
|
||||
// undefined), but <input capture> opens the native camera in any browser,
|
||||
// PWA or WebView; the shot is decoded locally by qr-scanner's scanImage.
|
||||
const photoInput = ref<HTMLInputElement | null>(null)
|
||||
const liveCameraUnavailable = computed(() => !navigator.mediaDevices?.getUserMedia)
|
||||
|
||||
async function onPhotoPicked(e: Event) {
|
||||
const input = e.target as HTMLInputElement
|
||||
const file = input.files?.[0]
|
||||
if (!file) return
|
||||
try {
|
||||
const result = await QrScanner.scanImage(file, { returnDetailedScanResult: true })
|
||||
handleScanned(result.data)
|
||||
} catch {
|
||||
scanStatusIsError.value = true
|
||||
scanStatus.value = 'No QR code found in that photo — try again, closer and well-lit'
|
||||
} finally {
|
||||
input.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// --- Detection (ported from k484's scanner) ---
|
||||
const rail = ref<Rail>('lightning')
|
||||
const action = ref<Action>('pay-invoice')
|
||||
|
||||
Reference in New Issue
Block a user