Somewhere to point the other device's camera
The dialog draws the QR code beside the emoji rather than instead of them. Both are offered because both are needed: a code cannot be made without cross-signing, and when there is no code the emoji are the only way through. Asked for once the flow is past "requested" - a code can only be made after both sides have agreed what they are verifying - and keyed on the verification so a new one fetches its own picture rather than showing the last session's. Drawn as an image from a data URI rather than injected as markup. The SVG is the daemon's own drawing of a QR grid and carries nothing from anywhere else, so injecting it would have been safe; not making the exception is the habit worth keeping. White behind it on both themes. A scanner reads contrast, and a code drawn dark-on-dark is a code nothing can read. Closes #163. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
+1
-1
Submodule nobilis updated: d06a449e92...2929ff7bde
@@ -3392,6 +3392,26 @@ img.smilie {
|
||||
background: var(--primary-hover-light);
|
||||
}
|
||||
|
||||
/* The QR code, where the homeserver and the other device can support one.
|
||||
White behind it always, on both themes: a scanner reads contrast, and a
|
||||
code drawn dark-on-dark is a code nothing can read. */
|
||||
.verification-qr {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
padding-top: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.verification-qr-code {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
display: block;
|
||||
background: #fff;
|
||||
padding: var(--spacing-xs);
|
||||
border-radius: var(--corner-radius-small);
|
||||
}
|
||||
|
||||
.sas-emoji {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Icon } from './Icon'
|
||||
import { useChat, useStore } from '../state/hooks'
|
||||
|
||||
@@ -14,8 +15,43 @@ import { useChat, useStore } from '../state/hooks'
|
||||
* else's is a person you are vouching for.
|
||||
*/
|
||||
export function VerificationDialog(): JSX.Element | null {
|
||||
const active = useChat((s) => s.matrixVerification)
|
||||
if (!active) return null
|
||||
|
||||
// Keyed on the verification so its QR code is fetched afresh for a new one,
|
||||
// rather than a picture of the last session being shown against this one.
|
||||
return <VerificationBody key={active.verificationId} />
|
||||
}
|
||||
|
||||
function VerificationBody(): JSX.Element | null {
|
||||
const store = useStore()
|
||||
const active = useChat((s) => s.matrixVerification)
|
||||
const [qr, setQr] = useState<string | null>(null)
|
||||
|
||||
// Asked for once the flow is past "requested" - a code can only be made
|
||||
// after both sides have agreed what they are verifying. Answering null is
|
||||
// the ordinary case rather than a failure: a QR code proves an identity the
|
||||
// other side can already check, so without cross-signing there is nothing
|
||||
// to encode and emoji stay the way through.
|
||||
useEffect(() => {
|
||||
if (!active || active.emoji) return
|
||||
let dropped = false
|
||||
void window.moho
|
||||
.rpc<{ svg: string | null }>('matrixVerificationQrCode', {
|
||||
accountId: active.accountId,
|
||||
verificationId: active.verificationId
|
||||
})
|
||||
.then((r) => {
|
||||
if (!dropped) setQr(r.svg)
|
||||
})
|
||||
.catch(() => {
|
||||
/* No code to show is not an error worth a toast; emoji still work. */
|
||||
})
|
||||
return () => {
|
||||
dropped = true
|
||||
}
|
||||
}, [active?.verificationId, active?.state, active?.emoji])
|
||||
|
||||
if (!active) return null
|
||||
|
||||
const who = active.otherUser || active.fromUser || ''
|
||||
@@ -71,6 +107,26 @@ export function VerificationDialog(): JSX.Element | null {
|
||||
? 'Waiting for you to accept.'
|
||||
: `Verification ${active.state || 'starting'}…`}
|
||||
</div>
|
||||
|
||||
{/* Scanning is the way most people verify, and until now moho had
|
||||
nothing to be scanned - Element would offer its camera and this
|
||||
side could only answer with emoji. Shown rather than scanned:
|
||||
on a desktop the device holding the camera is the other one. */}
|
||||
{qr && (
|
||||
<div className="verification-qr">
|
||||
<div className="small muted">Or scan this from your other device.</div>
|
||||
{/* As an image rather than injected markup. The SVG is the
|
||||
daemon's own drawing of a QR grid and carries nothing from
|
||||
anywhere else, but a data URI reaches the same picture
|
||||
without this page ever parsing markup it was handed - and
|
||||
the one habit worth keeping is not making that exception. */}
|
||||
<img
|
||||
className="verification-qr-code"
|
||||
alt="Verification QR code"
|
||||
src={`data:image/svg+xml;utf8,${encodeURIComponent(qr)}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="button-row">
|
||||
{active.state === 'requested' && (
|
||||
<button type="button" className="button" onClick={() => rpc('respondMatrixVerification', { accept: true })}>
|
||||
|
||||
Reference in New Issue
Block a user