Files
Warp/apps/web/src/components/QrScanner.vue
T
tajniak81andClaude Opus 5.5 69ce927b6d
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / Complement (federation off) (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s
CI / Admin panel (push) Canceled after 0s
CI / Web client (push) Canceled after 0s
Web client: E2EE on by default, trust shields, identity verification, QR
- New rooms (public too) and direct messages are end-to-end encrypted by
  default.
- Per-message trust: "Not verified" for sessions you haven't verified,
  and what is wrong for unsigned sessions, mismatched keys or plain text
  in an encrypted room (src/matrix/trust.ts).
- Verify a person's identity in a room you share (your DM first), and
  QR-code verification both ways: show ours, scan theirs with the camera
  (qrcode-generator, jsqr).
- Warnings when someone's identity changes, with acknowledge, verify
  again or withdraw verification.
- Encrypted thumbnails for large images.
- A tab-only session encrypts its crypto store with a key kept in
  sessionStorage; stores unused for two weeks are deleted.
- Reset encryption fixed: new secret storage first, so the new
  cross-signing keys survive a stale /keys/query answer; status checks
  no longer query our own keys mid-setup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 16:56:28 +02:00

74 lines
2.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import jsQR from 'jsqr'
import { onBeforeUnmount, onMounted, ref } from 'vue'
import { WBanner, WSpinner } from '@warp/ui'
// The camera, looking for a QR code. Frames are read on a timer, decoded
// here in the page, and the camera is switched off as soon as a code is
// found or the scanner goes away.
const emit = defineEmits<{ found: [data: Uint8ClampedArray] }>()
const video = ref<HTMLVideoElement>()
const error = ref('')
const starting = ref(true)
let stream: MediaStream | null = null
let timer = 0
const canvas = document.createElement('canvas')
function stop() {
window.clearInterval(timer)
stream?.getTracks().forEach((t) => t.stop())
stream = null
}
function scan() {
const v = video.value
if (!v || v.readyState < 2 || !v.videoWidth) return
// Scaled down: decoding is the slow part, and codes are large on screen.
const scale = Math.min(1, 640 / v.videoWidth)
canvas.width = Math.round(v.videoWidth * scale)
canvas.height = Math.round(v.videoHeight * scale)
const g = canvas.getContext('2d', { willReadFrequently: true })
if (!g) return
g.drawImage(v, 0, 0, canvas.width, canvas.height)
const img = g.getImageData(0, 0, canvas.width, canvas.height)
const code = jsQR(img.data, img.width, img.height, { inversionAttempts: 'dontInvert' })
if (code?.binaryData.length) {
stop()
emit('found', Uint8ClampedArray.from(code.binaryData))
}
}
onMounted(async () => {
try {
if (!navigator.mediaDevices?.getUserMedia) throw new Error('This browser can’t use a camera here.')
stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' }, audio: false })
if (!video.value) return stop()
video.value.srcObject = stream
await video.value.play()
timer = window.setInterval(scan, 250)
} catch (e) {
error.value =
e instanceof DOMException && e.name === 'NotAllowedError'
? 'Camera access was turned down. Allow it in the browser’s site settings, or compare emoji instead.'
: e instanceof Error
? e.message
: 'Can’t start the camera.'
} finally {
starting.value = false
}
})
onBeforeUnmount(stop)
</script>
<template>
<div class="flex flex-col items-center gap-3">
<WBanner v-if="error" tone="warning" class="w-full">{{ error }}</WBanner>
<div v-else class="relative aspect-square w-full max-w-[280px] overflow-hidden rounded-md bg-surface-sunken">
<video ref="video" class="h-full w-full object-cover" muted playsinline />
<span v-if="starting" class="absolute inset-0 flex items-center justify-center"><WSpinner /></span>
</div>
</div>
</template>