diff --git a/app/javascript/packages/document-capture/components/acuant-capture.tsx b/app/javascript/packages/document-capture/components/acuant-capture.tsx index 45eadb56963..f0a71ac37be 100644 --- a/app/javascript/packages/document-capture/components/acuant-capture.tsx +++ b/app/javascript/packages/document-capture/components/acuant-capture.tsx @@ -13,6 +13,7 @@ import type { FocusTrap } from 'focus-trap'; import type { FullScreenRefHandle } from '@18f/identity-components'; import { useDidUpdateEffect } from '@18f/identity-react-hooks'; import { useI18n } from '@18f/identity-react-i18n'; +import { removeUnloadProtection } from '@18f/identity-url'; import AcuantCamera, { AcuantDocumentType } from './acuant-camera'; import type { AcuantCaptureFailureError, @@ -338,6 +339,8 @@ function AcuantCapture( const { failedCaptureAttempts, onFailedCaptureAttempt, + failedCameraPermissionAttempts, + onFailedCameraPermissionAttempt, onResetFailedCaptureAttempts, failedSubmissionAttempts, forceNativeCamera, @@ -561,6 +564,14 @@ function AcuantCapture( setAcuantFailureCookie(null); onCameraAccessDeclined(); + + // Due to a bug with Safari on iOS we force the page to refresh on the third + // time a user denies permissions. + onFailedCameraPermissionAttempt(); + if (failedCameraPermissionAttempts > 2) { + removeUnloadProtection(); + window.location.reload(); + } } else if (code === SEQUENCE_BREAK_CODE) { setOwnErrorMessage( `${t('doc_auth.errors.upload_error')} ${t('errors.messages.try_again') diff --git a/app/javascript/packages/document-capture/context/failed-capture-attempts.tsx b/app/javascript/packages/document-capture/context/failed-capture-attempts.tsx index 56dc3a74815..030f479b8b2 100644 --- a/app/javascript/packages/document-capture/context/failed-capture-attempts.tsx +++ b/app/javascript/packages/document-capture/context/failed-capture-attempts.tsx @@ -27,12 +27,24 @@ interface FailedCaptureAttemptsContextInterface { */ failedSubmissionAttempts: number; + /** + * There's a bug with Safari on iOS where if you deny camera permissions + * three times the prompt stops appearing. To avoid this we keep track + * and force a full page reload on the third time. + */ + failedCameraPermissionAttempts: number; + /** * Callback when submission attempt fails. * Used to increment the failedSubmissionAttempts */ onFailedSubmissionAttempt: (failedImageFingerprints: UploadedImageFingerprints) => void; + /** + * A wrapper around incrementFailedCameraPermissionAttempts + */ + onFailedCameraPermissionAttempt: () => void; + /** * The maximum number of failed Acuant capture attempts * before use of the native camera option is triggered @@ -79,8 +91,10 @@ const DEFAULT_LAST_ATTEMPT_METADATA: CaptureAttemptMetadata = { const FailedCaptureAttemptsContext = createContext({ failedCaptureAttempts: 0, failedSubmissionAttempts: 0, + failedCameraPermissionAttempts: 0, onFailedCaptureAttempt: () => {}, onFailedSubmissionAttempt: () => {}, + onFailedCameraPermissionAttempt: () => {}, onResetFailedCaptureAttempts: () => {}, maxCaptureAttemptsBeforeNativeCamera: Infinity, maxSubmissionAttemptsBeforeNativeCamera: Infinity, @@ -110,6 +124,7 @@ function FailedCaptureAttemptsContextProvider({ const [failedCaptureAttempts, incrementFailedCaptureAttempts, onResetFailedCaptureAttempts] = useCounter(); const [failedSubmissionAttempts, incrementFailedSubmissionAttempts] = useCounter(); + const [failedCameraPermissionAttempts, incrementFailedCameraPermissionAttempts] = useCounter(); const [failedSubmissionImageFingerprints, setFailedSubmissionImageFingerprints] = useState(failedFingerprints); @@ -124,6 +139,10 @@ function FailedCaptureAttemptsContextProvider({ setFailedSubmissionImageFingerprints(failedOnes); } + function onFailedCameraPermissionAttempt() { + incrementFailedCameraPermissionAttempts(); + } + const forceNativeCamera = failedCaptureAttempts >= maxCaptureAttemptsBeforeNativeCamera || failedSubmissionAttempts >= maxSubmissionAttemptsBeforeNativeCamera; @@ -136,6 +155,8 @@ function FailedCaptureAttemptsContextProvider({ onResetFailedCaptureAttempts, failedSubmissionAttempts, onFailedSubmissionAttempt, + failedCameraPermissionAttempts, + onFailedCameraPermissionAttempt, maxCaptureAttemptsBeforeNativeCamera, maxSubmissionAttemptsBeforeNativeCamera, lastAttemptMetadata, diff --git a/spec/javascript/packages/document-capture/context/failed-capture-attempts-spec.jsx b/spec/javascript/packages/document-capture/context/failed-capture-attempts-spec.jsx index 801d217bf14..7f6f94e1ddc 100644 --- a/spec/javascript/packages/document-capture/context/failed-capture-attempts-spec.jsx +++ b/spec/javascript/packages/document-capture/context/failed-capture-attempts-spec.jsx @@ -22,6 +22,8 @@ describe('document-capture/context/failed-capture-attempts', () => { 'onFailedSubmissionAttempt', 'onResetFailedCaptureAttempts', 'maxCaptureAttemptsBeforeNativeCamera', + 'onFailedCameraPermissionAttempt', + 'failedCameraPermissionAttempts', 'maxSubmissionAttemptsBeforeNativeCamera', 'lastAttemptMetadata', 'failedSubmissionImageFingerprints',