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 13 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
2 changes: 1 addition & 1 deletion public/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@
"by_name": "by <strong>{{by}}</strong>",
"camera": "Camera",
"camera_bed_link_success": "Camera linked to bed successfully.",
"camera_permission_denied": "Camera Permission denied",
"camera_permission_denied": "Camera Permission Denied",
"camera_was_linked_to_bed": "This camera was linked to this bed",
"cancel": "Cancel",
"cancel_appointment": "Cancel Appointment",
Expand Down
32 changes: 32 additions & 0 deletions src/Utils/cameraPermissionHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useCallback, useRef } from "react";
import { useTranslation } from "react-i18next";
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 toastShownRef = useRef(false);
const { t } = useTranslation();

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

await navigator.mediaDevices.getUserMedia(constraints);
return true;
} catch (_error) {
if (!toastShownRef.current) {
toastShownRef.current = true;
toast.warning(t("camera_permission_denied"));
}
return false;
}
},
[],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Include necessary dependencies in the useCallback dependency array

The useCallback dependency array is empty, but the function uses t from useTranslation which might change when the app language changes.

- [],
+ [t],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[],
);
[t],
);


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

Confirm this utility is used consistently across the codebase

Based on PR objectives and past reviews, ensure this centralized function is applied in all areas where camera functionality is utilized, particularly in CameraCaptureDialog.tsx.


🏁 Script executed:

#!/bin/bash
echo "Finding places still using direct camera access that should use the centralized handler:"
rg -g "*.{ts,tsx}" -A 3 -B 3 "navigator\.mediaDevices\.getUserMedia" | grep -v "cameraPermissionHandler"

echo -e "\nPlaces already using the centralized handler:"
rg -g "*.{ts,tsx}" -A 1 "handleCameraPermission"

Length of output: 2244


Ensure consistent use of the centralized camera permission utility

Our search confirms that while components like AvatarEditModal.tsx and parts of CameraCaptureDialog.tsx are using the handleCameraPermission function, there remain instances with direct calls to navigator.mediaDevices.getUserMedia. Specifically:

  • In src/components/Files/CameraCaptureDialog.tsx, there's a direct call alongside the centralized handler usage.
  • In src/Utils/useVoiceRecorder.ts, the direct use of navigator.mediaDevices.getUserMedia persists.

Please update these sections to uniformly leverage handleCameraPermission to guarantee consistent handling of camera permissions across the codebase.

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