Skip to content

feat: added camera rotation in add photo sheet.#1094

Open
Sahil2004 wants to merge 1 commit into
mainfrom
feat/eidWalletBothCamera
Open

feat: added camera rotation in add photo sheet.#1094
Sahil2004 wants to merge 1 commit into
mainfrom
feat/eidWalletBothCamera

Conversation

@Sahil2004

@Sahil2004 Sahil2004 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Description of change

Added camera rotation in add photo sheet and added back camera caching so that it doesn't take huge time to switch to multiple cameras and find the actual camera.

Issue Number

Closes #1093

Type of change

  • New (a change which implements a new feature)

How the change has been tested

Manually.

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Added the ability to switch between front- and rear-facing cameras while capturing photos.
    • Improved camera selection to prioritize the primary rear camera when available.
    • Added more reliable camera startup and switching behavior.
  • Bug Fixes

    • Prevented stale camera streams from remaining active when closing or changing capture modes.

@Sahil2004 Sahil2004 self-assigned this Jul 23, 2026
@Sahil2004
Sahil2004 requested a review from coodos as a code owner July 23, 2026 07:46
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The photo capture component now supports switching between front and rear cameras. It identifies and caches the primary rear camera, coordinates asynchronous stream changes, safely stops obsolete streams, and adds an in-capture camera switch button.

Changes

Photo capture camera switching

Layer / File(s) Summary
Camera stream selection and lifecycle
infrastructure/eid-wallet/src/routes/(app)/personal/components/AddPhotoSheet.svelte
Capture state tracks front/rear selection, resolves rear camera devices, prevents stale asynchronous requests from binding streams, and stops replaced or closed streams.
In-capture camera switch control
infrastructure/eid-wallet/src/routes/(app)/personal/components/AddPhotoSheet.svelte
Capture mode adds a camera-rotation button that toggles the active camera.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CaptureMode
  participant AddPhotoSheet
  participant MediaDevices
  participant VideoPreview
  CaptureMode->>AddPhotoSheet: flipCamera()
  AddPhotoSheet->>MediaDevices: enumerateDevices() and getUserMedia()
  MediaDevices-->>AddPhotoSheet: selected camera stream
  AddPhotoSheet->>VideoPreview: bind stream
  AddPhotoSheet->>MediaDevices: stop superseded stream tracks
Loading

Suggested reviewers: coodos

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The change adds camera switching, but the summary doesn't confirm the front-camera default or iOS/Android behavior. Verify the initial camera on mobile is front-facing and test the switch flow on iOS, Android, and desktop.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: adding camera switching in the add photo sheet.
Description check ✅ Passed All required template sections are present, including issue number, change type, testing, and checklist.
Out of Scope Changes check ✅ Passed The changes stay focused on the photo sheet camera flow and don't show unrelated modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/eidWalletBothCamera

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
infrastructure/eid-wallet/src/routes/(app)/personal/components/AddPhotoSheet.svelte (2)

124-128: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Cached-device lookup has no fallback on stale deviceId.

If backDeviceId becomes stale (device disconnected/re-enumerated), this getUserMedia throws and is caught only in startStream's generic handler, which always shows the permission dialog — misleading the user even though the real cause is a missing device, not a denied permission. Consider catching this specific failure, clearing backDeviceId, and re-running the full probe as a fallback.

♻️ Proposed fix
     if (backDeviceId) {
-        return navigator.mediaDevices.getUserMedia({
-            video: { deviceId: { exact: backDeviceId } },
-        });
+        try {
+            return await navigator.mediaDevices.getUserMedia({
+                video: { deviceId: { exact: backDeviceId } },
+            });
+        } catch {
+            // Cached device is no longer valid — clear and re-probe below.
+            backDeviceId = undefined;
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@infrastructure/eid-wallet/src/routes/`(app)/personal/components/AddPhotoSheet.svelte
around lines 124 - 128, Update the cached-device branch in the media-probing
function around backDeviceId to catch getUserMedia failures caused by the stale
device, clear backDeviceId, and rerun the full device probe. Ensure the fallback
result is handled by the existing startStream flow so stale-device errors do not
reach the generic permission-denied handler.

158-172: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Probing loop doesn't check for supersession.

Once started, the torch-probe loop runs to completion (or exhaustion) even if the sheet is closed or the user flips again mid-probe. It doesn't consult streamToken/isOpen between iterations, so each candidate device is still opened and momentarily activates the hardware/camera indicator after the request that started it is no longer relevant. Adding an early-exit check would release the camera sooner in that case.

♻️ Proposed fix
     for (const cam of candidates) {
+        if (facing !== "environment") break; // supersession would be checked via a passed-in token
         try {
             const probe = await navigator.mediaDevices.getUserMedia({
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@infrastructure/eid-wallet/src/routes/`(app)/personal/components/AddPhotoSheet.svelte
around lines 158 - 172, Update the camera probing loop in the torch-probe
function to check the current streamToken and isOpen state before opening each
candidate and stop probing immediately when the request is superseded or the
sheet is closed. Preserve existing cleanup for probes that were opened and the
current behavior for valid active requests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@infrastructure/eid-wallet/src/routes/`(app)/personal/components/AddPhotoSheet.svelte:
- Around line 52-60: Update AddPhotoSheet.svelte so facingMode defaults to the
front camera on mobile, using the environment camera only as the desktop
fallback, and revise its comment accordingly; at chooseCamera, remove the
unconditional environment reset so the user’s selected camera persists across
opens. Apply these changes at the facingMode declaration (lines 52-60) and
chooseCamera (lines 212-224).
- Around line 102-184: Update openCameraStream so iOS/WKWebView rear-camera
selection does not depend on getCapabilities().torch or the multi-camera probing
heuristic. Detect the iOS environment and use an iOS-safe strategy, such as the
established native camera/torch integration, or bypass torch probing and select
the appropriate rear camera deterministically; preserve the existing torch-based
selection for supported non-iOS platforms.

---

Nitpick comments:
In
`@infrastructure/eid-wallet/src/routes/`(app)/personal/components/AddPhotoSheet.svelte:
- Around line 124-128: Update the cached-device branch in the media-probing
function around backDeviceId to catch getUserMedia failures caused by the stale
device, clear backDeviceId, and rerun the full device probe. Ensure the fallback
result is handled by the existing startStream flow so stale-device errors do not
reach the generic permission-denied handler.
- Around line 158-172: Update the camera probing loop in the torch-probe
function to check the current streamToken and isOpen state before opening each
candidate and stop probing immediately when the request is superseded or the
sheet is closed. Preserve existing cleanup for probes that were opened and the
current behavior for valid active requests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7366f2e5-2e83-4d40-9c5d-1e43ae02b848

📥 Commits

Reviewing files that changed from the base of the PR and between ca47058 and deb05cb.

📒 Files selected for processing (1)
  • infrastructure/eid-wallet/src/routes/(app)/personal/components/AddPhotoSheet.svelte

Comment on lines +52 to +60
// Which camera to use. The back camera is the default; users can flip to
// the front (selfie) camera from the viewfinder. On desktop "environment"
// is advisory and resolves to the only available camera anyway.
let facingMode = $state<"user" | "environment">("environment");
// Bumped on every (re)start so a slow getUserMedia belonging to a
// superseded request (e.g. a rapid flip) can detect it lost the race
// and release its stream instead of binding a stale one.
let streamToken = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Default camera and preference persistence contradict the linked issue's requirements. The linked issue states the front camera should default on mobile and the selected camera should be maintained throughout the capture flow, but this implementation defaults to and re-forces the back camera on every open.

  • infrastructure/eid-wallet/src/routes/(app)/personal/components/AddPhotoSheet.svelte#L52-L60: change the initial facingMode default (and the doc comment) so front is the default on mobile, reserving "environment" as the fallback for desktop where "user"-facing has no meaning.
  • infrastructure/eid-wallet/src/routes/(app)/personal/components/AddPhotoSheet.svelte#L212-L224: drop the unconditional facingMode = "environment" reset in chooseCamera() so a user's prior front/back selection carries over between opens, per the "maintained throughout the capture flow" requirement.
📍 Affects 1 file
  • infrastructure/eid-wallet/src/routes/(app)/personal/components/AddPhotoSheet.svelte#L52-L60 (this comment)
  • infrastructure/eid-wallet/src/routes/(app)/personal/components/AddPhotoSheet.svelte#L212-L224
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@infrastructure/eid-wallet/src/routes/`(app)/personal/components/AddPhotoSheet.svelte
around lines 52 - 60, Update AddPhotoSheet.svelte so facingMode defaults to the
front camera on mobile, using the environment camera only as the desktop
fallback, and revise its comment accordingly; at chooseCamera, remove the
unconditional environment reset so the user’s selected camera persists across
opens. Apply these changes at the facingMode declaration (lines 52-60) and
chooseCamera (lines 212-224).

Comment on lines +102 to +184
const BACK_LABEL = /back|rear|environment/i;
const FRONT_LABEL = /front|face|user|selfie/i;

// The main rear sensor, resolved once and cached — flipping back to it then
// costs a single getUserMedia instead of re-probing every camera.
let backDeviceId: string | undefined;

// Open a working stream for the requested facing.
// - Front: a plain facingMode constraint is reliable and fast.
// - Back: the default rear device is often a secondary sensor (ultrawide /
// depth) that streams a black frame, so we locate the *main* rear sensor
// by its torch capability (the technique the KYC scanner uses) and cache
// it. The probe stream for the winning camera is reused, so the main
// camera is opened only once.
async function openCameraStream(
facing: "user" | "environment",
): Promise<MediaStream> {
if (facing === "user") {
return navigator.mediaDevices.getUserMedia({
video: { facingMode: "user" },
});
}
if (backDeviceId) {
return navigator.mediaDevices.getUserMedia({
video: { deviceId: { exact: backDeviceId } },
});
}

let cams = (await navigator.mediaDevices.enumerateDevices()).filter(
(d) => d.kind === "videoinput",
);
// Labels stay empty until the session has been granted a getUserMedia;
// prime with a throwaway stream so we can tell rear cameras from front.
if (cams.length && cams.every((d) => !d.label)) {
try {
const primer = await navigator.mediaDevices.getUserMedia({
video: true,
});
for (const t of primer.getTracks()) t.stop();
cams = (await navigator.mediaDevices.enumerateDevices()).filter(
(d) => d.kind === "videoinput",
);
} catch {
// Ignore — fall through to whatever labels we have.
}
}

// Candidate rear cameras: those labelled back, or (labels hidden)
// everything that isn't explicitly a front camera.
const labelledBack = cams.filter((d) => BACK_LABEL.test(d.label));
const candidates = labelledBack.length
? labelledBack
: cams.filter((d) => !FRONT_LABEL.test(d.label));

// The main rear sensor is the one exposing a torch. Probe each candidate
// and reuse the winner's stream rather than reopening it.
for (const cam of candidates) {
try {
const probe = await navigator.mediaDevices.getUserMedia({
video: { deviceId: { exact: cam.deviceId } },
});
const caps = probe.getVideoTracks()[0]?.getCapabilities?.();
if (caps && "torch" in caps) {
backDeviceId = cam.deviceId;
return probe;
}
for (const t of probe.getTracks()) t.stop();
} catch {
// Skip cameras we can't open.
}
}

// No torch found (desktop, or a webview that hides capabilities): fall
// back to the last rear candidate — conventionally the main camera — or a
// plain facingMode constraint if we couldn't enumerate anything.
const fallback = candidates.at(-1) ?? cams.at(-1);
backDeviceId = fallback?.deviceId;
return navigator.mediaDevices.getUserMedia({
video: fallback
? { deviceId: { exact: fallback.deviceId } }
: { facingMode: "environment" },
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file context =="
sed -n '80,195p' "infrastructure/eid-wallet/src/routes/(app)/personal/components/AddPhotoSheet.svelte" 2>/dev/null || true

echo
echo "== PR/objective references to platforms =="
rg -n -i "iOS|Apple|iPhone|iPad|Android|desktop|rear|torch|getCapabilities|facingMode" . \
  -g '!node_modules' -g '!dist' -g '!build' -g '!coverage' | head -200

echo
echo "== nearby usages of openCameraStream or AddPhotoSheet =="
rg -n "openCameraStream|AddPhotoSheet|enable.*camera|camera" "infrastructure/eid-wallet/src/routes/(app)/personal" 2>/dev/null || true

Repository: MetaState-Prototype-Project/prototype

Length of output: 26058


🌐 Web query:

Apple Developer Forums Safari WKWebView getCapabilities torch capability unsupported iOS

💡 Result:

Support for the 'torch' capability in WKWebView on iOS has been an evolving feature in WebKit [1][2]. While standard Web Media Capture APIs often report the 'torch' capability as unsupported or missing when accessed via getCapabilities() in older iOS versions or standard WKWebView implementations, recent updates have introduced support within the WebKit engine [1]. Key points regarding the current status: 1. Official WebKit Support: Support for the 'torch' capability was added to WebKit (the engine powering WKWebView) to expose it in MediaTrackCapabilities and MediaTrackSettings [1]. This allows web applications to potentially detect and control the torch if the underlying hardware and implementation permit [1]. 2. Limitations and Variability: Even with engine-level support, developers often find that the 'torch' capability is not consistently available or functional across all iOS devices, browser versions, or specific WKWebView configurations [3][4]. In many practical scenarios, especially for barcode scanning or camera-intensive web apps, getCapabilities() may still return an object that lacks the torch property or reports it as unsupported [3][5]. 3. Native Alternatives: Due to the historical inconsistency of the web-based Torch API on iOS, many developers use native integration to control the flashlight [6]. For hybrid apps (using frameworks like Capacitor, Cordova, or custom WKWebView wrappers), it is common practice to implement a native bridge that accesses the camera's torch functionality directly through Swift or Objective-C, bypassing the limitations of the web view's MediaStreamTrack API [5][6][2]. 4. Recent Updates: Apple continues to refine WKWebView and Safari's media capabilities [7]. For instance, recent release notes (e.g., Safari 18.4) have included fixes related to getUserMedia video track constraints and the reporting of torch settings [7]. If you are encountering issues, ensure your environment is updated, though native workarounds remain the most reliable method for production-grade torch control on iOS [5][6]. If you are developing a web application, it is recommended to feature-detect the 'torch' capability dynamically using track.getCapabilities().torch and provide a fallback or inform the user if it is unavailable [8][9].

Citations:


🌐 Web query:

Tauri 2 iOS WKWebView navigator.mediaDevices getUserMedia Torch enabled MediaTrackCapabilities torch

💡 Result:

In Tauri 2 iOS applications, you cannot reliably control the device torch directly through the browser-based navigator.mediaDevices API within the WKWebView [1]. While modern versions of WebKit have added experimental support for MediaTrackCapabilities including 'torch' [2][3], relying on this within a WKWebView container is often inconsistent or unsupported depending on the iOS version and specific platform implementation [1][4]. To achieve reliable torch control in a Tauri 2 iOS app, the recommended approach is to use a native plugin that interfaces directly with the iOS AVFoundation framework [5][6]. Key points for implementing torch functionality: 1. Native Plugin Requirement: Use a dedicated Tauri plugin such as tauri-plugin-torch [5][6]. These plugins bridge the Rust backend to the native iOS Swift code, providing a stable API to toggle the flashlight independently of the webview's media constraints [7][6]. 2. Limitations of Web APIs: Even if navigator.mediaDevices is available in your WKWebView, the torch constraint is frequently ignored or restricted by the iOS system for security and privacy reasons, making it unreliable for production applications [1][4]. 3. Permissions: Ensure your Info.plist includes the necessary privacy keys (e.g., NSCameraUsageDescription) to allow the app to access the camera hardware, which is required for torch control [8][1]. 4. Implementation: With a native plugin, you can expose methods to your frontend (e.g., toggle(), check()) that execute native commands, bypassing the limitations of the webview's media stream environment [5][6]. For camera preview functionality (if needed alongside the torch), you may also consider community plugins like tauri-plugin-nativecamera, which provide native-backed interfaces for camera operations on mobile platforms [9].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Tauri deps =="
sed -n '1,90p' infrastructure/eid-wallet/src-tauri/Cargo.toml
jq '.build, .bundle, .plugins, .identifier, .productName' infrastructure/eid-wallet/src-tauri/tauri.conf.json 2>/dev/null | head -220

echo
echo "== mobile capability =="
cat -n infrastructure/eid-wallet/src-tauri/capabilities/mobile.json

echo
echo "== lockfile relevant Tauri versions (concise) =="
rg -n "'(`@tauri-apps/`|Tauri|tauri)" pnpm-lock.yaml | head -160

Repository: MetaState-Prototype-Project/prototype

Length of output: 5545


🌐 Web query:

WebKit Safari MediaTrackCapabilities torch supported iOS version

💡 Result:

Support for the 'torch' constraint in MediaTrackCapabilities was added to WebKit in September 2023 [1]. While specific official Apple documentation detailing the exact release version is sparse, developer reports and technical tracking indicate that the 'torch' constraint became functional in Safari on iOS starting with iOS 17.4 [2]. Before this implementation, the 'torch' constraint was ignored on iOS Safari [3]. Even following its introduction, some users have reported ongoing issues with the consistency of the 'torch' setting, such as instances where getSettings may return stale or unexpected values in later versions, including iOS 18 [4]. To check if it is supported on a specific device or browser instance, developers should inspect the MediaStreamTrack.getCapabilities object to see if 'torch' is present in the returned list of supported constraints [5].

Citations:


Avoid relying on torch detection for iOS rear-camera selection.

getCapabilities() returns torch support inconsistently on iOS/WKWebView, so this path can fall back to the last rear candidate on devices where the heuristic should identify the main rear sensor. Test the fallback on iOS, or use an iOS-specific strategy such as a native camera/torch plugin or disabling the torch-based multi_probe path for that platform.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@infrastructure/eid-wallet/src/routes/`(app)/personal/components/AddPhotoSheet.svelte
around lines 102 - 184, Update openCameraStream so iOS/WKWebView rear-camera
selection does not depend on getCapabilities().torch or the multi-camera probing
heuristic. Detect the iOS environment and use an iOS-safe strategy, such as the
established native camera/torch integration, or bypass torch probing and select
the appropriate rear camera deterministically; preserve the existing torch-based
selection for supported non-iOS platforms.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support both front and back cameras when adding a photo mark (eID selfie)

1 participant