Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added centralized function to handle camera permission error #10989

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/Utils/cameraPermissionHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useCallback, useState } from "react";
import { toast } from "sonner";

export const handleCameraPermission = () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

convert to hook

const [toastShown, setToastShown] = useState(false);

const requestPermission = useCallback(
async (device: "camera", cameraFacingMode: string = "user") => {
try {
setToastShown(false);
const constraints =
device === "camera"
? { video: { facingMode: cameraFacingMode } }
: { audio: true };

await navigator.mediaDevices.getUserMedia(constraints);
return true;
} catch (_error) {
if (!toastShown) {
setToastShown(true);
toast.warning(`${device} permission denied`);
}
return false; // Permission denied
}
},
[toastShown],
);

return { requestPermission };
};
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify this utility is used consistently across the codebase.

Based on PR objectives, this centralized function should be applied in other areas of the codebase where camera functionality is utilized.


🏁 Script executed:

#!/bin/bash
# Search for any direct calls to navigator.mediaDevices.getUserMedia in the codebase
# that could be replaced with this centralized handler

echo "Finding potential locations for applying centralized camera permission handling:"
rg -A 3 -B 3 "navigator\.mediaDevices\.getUserMedia" --type tsx --type ts | grep -v "cameraPermissionHandler"

echo -e "\nPlaces where the camera permission handler is already used:"
rg -A 1 "handleCameraPermission" --type tsx --type ts

Length of output: 525


Action: Re-run the Search for Camera Permission Usages

It appears the previous search command failed due to the unrecognized file type “tsx.” To verify if the centralized camera permission utility is applied consistently across the codebase, please re-run the updated search using a glob pattern for both .ts and .tsx files.

Once you have the output from this script, please verify that:

  • There are no remaining direct calls to navigator.mediaDevices.getUserMedia that bypass the centralized handler.
  • The handleCameraPermission function is being used wherever camera functionality is expected.

🏁 Script executed:

#!/bin/bash
echo "Finding potential locations for applying centralized camera permission handling in .ts and .tsx files:"
rg -g "*.{ts,tsx}" -A 3 -B 3 "navigator\.mediaDevices\.getUserMedia" | grep -v "cameraPermissionHandler"

echo -e "\nFinding usage of handleCameraPermission in .ts and .tsx files:"
rg -g "*.{ts,tsx}" -A 1 "handleCameraPermission"

Length of output: 2322


Action Required: Ensure Consistent Usage of Centralized Camera Permission Handler

It has been detected that while the centralized handler is correctly used in parts of the codebase (e.g., in AvatarEditModal.tsx), there are still direct calls to navigator.mediaDevices.getUserMedia in at least one location. For example, in src/components/Files/CameraCaptureDialog.tsx, a direct call is made instead of utilizing the centralized handleCameraPermission utility. Please update the camera access logic in this file (and any other similar cases) so that all camera-related getUserMedia calls are funneled through the centralized handler.

  • src/components/Files/CameraCaptureDialog.tsx
    • Replace the direct call to navigator.mediaDevices.getUserMedia({ video: { facingMode: cameraFacingMode } }) with an invocation of the handler’s requestPermission function.

Review and apply this refactoring consistently across the codebase wherever camera functionality is implemented.

🧰 Tools
🪛 ESLint

[error] 10-10: Delete ·

(prettier/prettier)

14 changes: 11 additions & 3 deletions src/components/Common/AvatarEditModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {

import useDragAndDrop from "@/hooks/useDragAndDrop";

import { handleCameraPermission } from "@/Utils/cameraPermissionHandler";

interface Props {
title: string;
open: boolean;
Expand Down Expand Up @@ -77,6 +79,7 @@ const AvatarEditModal = ({
);
const { t } = useTranslation();
const [isDragging, setIsDragging] = useState(false);
const { requestPermission } = handleCameraPermission();

const handleSwitchCamera = useCallback(() => {
setConstraint(
Expand Down Expand Up @@ -391,9 +394,14 @@ const AvatarEditModal = ({
width={1280}
ref={webRef}
videoConstraints={constraint}
onUserMediaError={(_e) => {
setIsCameraOpen(false);
toast.warning(t("camera_permission_denied"));
onUserMediaError={async () => {
const hasPermission = await requestPermission(
"camera",
"user",
);
if (!hasPermission) {
setIsCameraOpen(false);
}
}}
/>
</>
Expand Down
30 changes: 21 additions & 9 deletions src/components/Files/CameraCaptureDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {

import useBreakpoints from "@/hooks/useBreakpoints";

import { handleCameraPermission } from "@/Utils/cameraPermissionHandler";

export interface CameraCaptureDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
Expand All @@ -26,6 +28,8 @@ export interface CameraCaptureDialogProps {
export default function CameraCaptureDialog(props: CameraCaptureDialogProps) {
const { open, onOpenChange, onCapture, onResetCapture, setPreview } = props;
const isLaptopScreen = useBreakpoints({ lg: true, default: false });
const { requestPermission } = handleCameraPermission();
const [stream, setStream] = useState<MediaStream | null>(null);

const [cameraFacingMode, setCameraFacingMode] = useState(
isLaptopScreen ? "user" : "environment",
Expand All @@ -41,17 +45,25 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) {

useEffect(() => {
if (!open) return;
let stream: MediaStream | null = null;

navigator.mediaDevices
.getUserMedia({ video: { facingMode: cameraFacingMode } })
.then((mediaStream) => {
stream = mediaStream;
})
.catch(() => {
toast.warning(t("camera_permission_denied"));
const getCameraStream = async () => {
const hasPermission = await requestPermission("camera", cameraFacingMode);
if (!hasPermission) {
onOpenChange(false);
});
return;
}

try {
const mediaStream = await navigator.mediaDevices.getUserMedia({
Copy link
Contributor

Choose a reason for hiding this comment

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

you can use the hook, it can return mediaStream and hasPermission.

video: { facingMode: cameraFacingMode },
});
setStream(mediaStream);
} catch (error) {
console.error("Error accessing camera:", error);
}
};

getCameraStream();

return () => {
if (stream) {
Expand Down