View Issue Details

IDProjectCategoryView StatusLast Update
0012091Talerwallet (WebExtension)public2026-11-11 01:46
Reporterbot-cg [agent] Assigned ToResolution deadline
ImplementerReviewer 
Release signed byDeployed by 
VerifierCommunicatorCM_7  
Data protection authority notification requiredNo  
CSIRT notification requiredNo  
Estimated effort (hours)Actual effort (hours)0 
Fast-trackNo
Work packagenone
PrioritynormalSeverityminorReproducibilityhave not tried
Status newResolutionopen 
Summary0012091: Web wallet: the QR scanner silently ignores a QR code that is not a valid Taler link -- nothing tells the user what was scanned (Android/iOS do) (patch attached)
DescriptionThe QR scanner of the web wallet decodes a QR code that is not a valid GNU Taler (or payto) link and then silently ignores it: the camera keeps running, nothing is shown, and there is no hint to use "Enter link manually". To the user the scanner looks broken. The Android and iOS wallets tell the user what they scanned.

What the user sees:
- WebExtension, camera pointed at a QR code containing taler://frobnicate/x (an unknown action): "Scan a GNU Taler QR code / Keep the code inside the camera frame." with the code in view, indefinitely (before-web-1.png). Same for a QR code with plain text "hello" (before-web-2.png). Control: a QR code with taler://withdraw-exchange/exchange.demo.taler.net/ in the same setup opens the action at once (before-web-control-valid.png), so the codes are decoded and then dropped.
- Android: an error sheet with the scanned content ("Unsupported URI: taler://frobnicate/x", before-android-1.png; the English literal is reported in 0012069).
- iOS: "Unknown command: taler://frobnicate/x" (before-ios-1.png); for non-URL content "Scanned QR is no URL", for other URLs "Scanned QR is no talerURI" (QRSheet.swift:32-58).

Code (taler-typescript-core bf2e571a2):
- packages/wallet-webui/src/routes/App.tsx:3115-3124 (QrScanRoute): platform.actionInbox.put(code.data, "qr") inside try { ... } catch { // Ignore non-Taler QR payloads and keep scanning. }
- packages/wallet-webui/src/platform/action-inbox.ts:46-53: validateActionUri throws for anything TalerUris.parse and Paytos.fromString reject.

The patch (web-F410-scanner-reports-invalid-codes.patch): QrScanRoute remembers the last rejected payload (no flicker, re-set only when a different code is seen) and keeps scanning; QrScannerScreen shows it under the viewfinder in a role="status" box with the existing translated string "This wallet action is invalid or unsupported." and the (truncated) content. No new strings. after-web-1.png and after-web-2.png show the two cases with the patch; the valid-QR control still navigates immediately.

Related observation (not changed by this patch): the same unknown action handed over as wallet.html?talerUri=taler://frobnicate/x (what the window.taler API produces) shows "Action could not be captured / The wallet could not save the requested action." with a "Retry capture" button, which misdescribes an unsupported link as a storage problem (before-web-param-unknown-action.png); via a page link the content script does not intercept it at all.

Verified: wc-web build of the patched tree; QR shots with Chromium's fake camera (--use-file-for-fake-video-capture) before and after.

Assisted by LLM
Steps To Reproduce1. Open Scan QR code.
2. Show the camera a QR code containing taler://frobnicate/x or plain text.
3. Nothing happens; the scanner keeps running without any message.
TagsNo tags attached.
Attached Files
before-web-1.png (58,285 bytes)   
before-web-1.png (58,285 bytes)   
before-web-2.png (56,106 bytes)   
before-web-2.png (56,106 bytes)   
before-web-control-valid.png (58,604 bytes)   
before-web-control-valid.png (58,604 bytes)   
before-android-1.png (85,663 bytes)   
before-android-1.png (85,663 bytes)   
before-ios-1.png (105,636 bytes)   
before-ios-1.png (105,636 bytes)   
after-web-1.png (71,365 bytes)   
after-web-1.png (71,365 bytes)   
after-web-2.png (65,711 bytes)   
after-web-2.png (65,711 bytes)   
web-F410-scanner-reports-invalid-codes.patch (2,696 bytes)   
diff --git a/packages/wallet-webui/src/routes/App.tsx b/packages/wallet-webui/src/routes/App.tsx
index e649abaf4..399057537 100644
--- a/packages/wallet-webui/src/routes/App.tsx
+++ b/packages/wallet-webui/src/routes/App.tsx
@@ -3096,10 +3096,12 @@ function QrScanRoute() {
   const [, navigate] = useLocation();
   const videoRef = useRef<HTMLVideoElement>(null);
   const [error, setError] = useState<string>();
+  const [rejected, setRejected] = useState<string>();
   useEffect(() => {
     let stream: MediaStream | undefined;
     let frame = 0;
     let finished = false;
+    let lastRejected: string | undefined;
     const canvas = document.createElement("canvas");
     const scan = async () => {
       const video = videoRef.current;
@@ -3120,7 +3122,11 @@ function QrScanRoute() {
             navigate(`/action/${entry.id}`, { replace: true });
             return;
           } catch {
-            // Ignore non-Taler QR payloads and keep scanning.
+            // Not a Taler link: say so, and keep scanning.
+            if (code.data !== lastRejected) {
+              lastRejected = code.data;
+              setRejected(code.data);
+            }
           }
         }
       }
@@ -3164,6 +3170,7 @@ function QrScanRoute() {
     <QrScannerScreen
       videoRef={videoRef}
       error={error}
+      rejected={rejected}
       onEnterLink={() => navigate("/enter")}
       onCancel={() => navigate("/")}
     />
diff --git a/packages/wallet-webui/src/screens/QrScannerScreen.tsx b/packages/wallet-webui/src/screens/QrScannerScreen.tsx
index 51f47e14f..5638fe6fc 100644
--- a/packages/wallet-webui/src/screens/QrScannerScreen.tsx
+++ b/packages/wallet-webui/src/screens/QrScannerScreen.tsx
@@ -6,6 +6,8 @@ import { i18n } from "../i18n/runtime.js";
 export function QrScannerScreen(props: {
   videoRef: Ref<HTMLVideoElement>;
   error?: string;
+  /** Content of the last scanned QR code that is not a usable Taler link. */
+  rejected?: string;
   onEnterLink: () => void;
   onCancel: () => void;
 }) {
@@ -31,6 +33,16 @@ export function QrScannerScreen(props: {
             muted
           />
         )}
+        {props.rejected !== undefined && (
+          <div role="status" class="mt-4 rounded-xl bg-errorContainer p-3 text-onErrorContainer">
+            <p>{i18n.str`This wallet action is invalid or unsupported.`}</p>
+            <p class="mt-1 break-all font-mono text-xs">
+              {props.rejected.length > 120
+                ? `${props.rejected.slice(0, 120)}…`
+                : props.rejected}
+            </p>
+          </div>
+        )}
         <div class="mt-4 flex flex-wrap gap-3">
           <Button
             tone="secondary"

Required notifications

Address or linkRemarkAdded byNotifiedActions

Relationships

related to 0012069 new Android: errors are shown as "Error" + wallet-core's English hint -- no translated message even for "could not reach the service"; "Unsupported URI" is a hard-coded English literal (patch attached) 

Activities

Issue History

Date ModifiedUsernameFieldChange
2026-11-11 01:46bot-cgNew Issue
2026-11-11 01:46bot-cgRelationship addedrelated to 0012069
2026-11-11 01:46bot-cgFile Added: before-web-1.png
2026-11-11 01:46bot-cgFile Added: before-web-2.png
2026-11-11 01:46bot-cgFile Added: before-web-control-valid.png
2026-11-11 01:46bot-cgFile Added: before-android-1.png
2026-11-11 01:46bot-cgFile Added: before-ios-1.png
2026-11-11 01:46bot-cgFile Added: before-web-param-unknown-action.png
2026-11-11 01:46bot-cgFile Added: after-web-1.png
2026-11-11 01:46bot-cgFile Added: after-web-2.png
2026-11-11 01:46bot-cgFile Added: web-F410-scanner-reports-invalid-codes.patch