Skip to content

Commit 6581420

Browse files
committed
Added a custom message when browser disabled camera
1 parent cd5e702 commit 6581420

File tree

2 files changed

+68
-12
lines changed

2 files changed

+68
-12
lines changed

packages/camera-web/src/Camera/hooks/useUserMedia.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useMonitoring } from '@monkvision/monitoring';
22
import deepEqual from 'fast-deep-equal';
3-
import { RefObject, useCallback, useEffect, useState } from 'react';
3+
import { RefObject, useCallback, useEffect, useRef, useState } from 'react';
44
import { PixelDimensions } from '@monkvision/types';
55
import { isMobileDevice, useIsMounted, useObjectMemo } from '@monkvision/common';
66
import { analyzeCameraDevices } from './utils';
@@ -211,17 +211,18 @@ export function useUserMedia(
211211
const [selectedCameraDeviceId, setSelectedCameraDeviceId] = useState<string | null>(null);
212212
const [lastConstraintsApplied, setLastConstraintsApplied] =
213213
useState<MediaStreamConstraints | null>(null);
214+
const lastGetUserMediaTimeRef = useRef<number | null>(null);
214215
const { handleError } = useMonitoring();
215216
const isMounted = useIsMounted();
216217

217218
const handleGetUserMediaError = (err: unknown, permissionState: PermissionState | null) => {
218219
let type = UserMediaErrorType.OTHER;
219220
if (err instanceof Error && err.name === 'NotAllowedError') {
220221
switch (permissionState) {
221-
case 'denied':
222+
case 'prompt':
222223
type = UserMediaErrorType.WEBPAGE_NOT_ALLOWED;
223224
break;
224-
case 'granted':
225+
case 'denied':
225226
type = UserMediaErrorType.BROWSER_NOT_ALLOWED;
226227
break;
227228
default:
@@ -302,9 +303,14 @@ export function useUserMedia(
302303

303304
const effect = async () => {
304305
try {
306+
lastGetUserMediaTimeRef.current = Date.now();
305307
await getUserMedia();
306308
} catch (err) {
307-
const permissionState = (await getCameraPermissionState())?.state ?? null;
309+
const isCameraPermissionDenied =
310+
lastGetUserMediaTimeRef.current && Date.now() - lastGetUserMediaTimeRef.current < 100;
311+
const permissionState = isCameraPermissionDenied
312+
? 'denied'
313+
: (await getCameraPermissionState())?.state ?? null;
308314
if (err && isMounted()) {
309315
handleGetUserMediaError(err, permissionState);
310316
throw err;

packages/camera-web/test/Camera/hooks/useUserMedia.test.ts

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,45 @@ describe('useUserMedia hook', () => {
149149
unmount();
150150
});
151151

152-
it('should return a NotAllowed error in case of camera permission error', async () => {
152+
it('should return a UserMediaErrorType.BROWSER_NOT_ALLOWED error in case of immediate rejection', async () => {
153153
const videoRef = { current: {} } as RefObject<HTMLVideoElement>;
154154
const nativeError = new Error();
155155
nativeError.name = 'NotAllowedError';
156156
mockGetUserMedia({ createMock: () => jest.fn(() => Promise.reject(nativeError)) });
157+
navigator.permissions.query = jest.fn(() =>
158+
Promise.resolve({ state: 'denied' } as PermissionStatus),
159+
);
160+
const { result } = renderUseUserMedia({ constraints: {}, videoRef });
161+
await waitFor(() => {
162+
expect(result.current).toEqual({
163+
getUserMedia: expect.any(Function),
164+
stream: null,
165+
dimensions: null,
166+
error: {
167+
type: UserMediaErrorType.BROWSER_NOT_ALLOWED,
168+
nativeError,
169+
},
170+
isLoading: false,
171+
retry: expect.any(Function),
172+
availableCameraDevices: [],
173+
selectedCameraDeviceId: null,
174+
});
175+
});
176+
});
177+
178+
it('should return a UserMediaErrorType.NOT_ALLOWED error in case of camera permission error', async () => {
179+
const videoRef = { current: {} } as RefObject<HTMLVideoElement>;
180+
const nativeError = new Error();
181+
nativeError.name = 'NotAllowedError';
182+
mockGetUserMedia({
183+
createMock: () =>
184+
jest.fn(
185+
() =>
186+
new Promise((_resolve, reject) => {
187+
setTimeout(() => reject(nativeError), 150);
188+
}),
189+
),
190+
});
157191
const { result, unmount } = renderUseUserMedia({ constraints: {}, videoRef });
158192
await waitFor(() => {
159193
expect(result.current).toEqual({
@@ -173,13 +207,21 @@ describe('useUserMedia hook', () => {
173207
unmount();
174208
});
175209

176-
it('should return a NotAllowed for webpage error in case of camera permission error', async () => {
210+
it('should return a UserMediaErrorType.WEBPAGE_NOT_ALLOWED for webpage error in case of camera permission error', async () => {
177211
const videoRef = { current: {} } as RefObject<HTMLVideoElement>;
178212
const nativeError = new Error();
179213
nativeError.name = 'NotAllowedError';
180-
mockGetUserMedia({ createMock: () => jest.fn(() => Promise.reject(nativeError)) });
214+
mockGetUserMedia({
215+
createMock: () =>
216+
jest.fn(
217+
() =>
218+
new Promise((_resolve, reject) => {
219+
setTimeout(() => reject(nativeError), 150);
220+
}),
221+
),
222+
});
181223
navigator.permissions.query = jest.fn(() =>
182-
Promise.resolve({ state: 'granted' } as PermissionStatus),
224+
Promise.resolve({ state: 'prompt' } as PermissionStatus),
183225
);
184226
const { result } = renderUseUserMedia({ constraints: {}, videoRef });
185227
await waitFor(() => {
@@ -188,7 +230,7 @@ describe('useUserMedia hook', () => {
188230
stream: null,
189231
dimensions: null,
190232
error: {
191-
type: UserMediaErrorType.BROWSER_NOT_ALLOWED,
233+
type: UserMediaErrorType.WEBPAGE_NOT_ALLOWED,
192234
nativeError,
193235
},
194236
isLoading: false,
@@ -199,11 +241,19 @@ describe('useUserMedia hook', () => {
199241
});
200242
});
201243

202-
it('should return a NotAllowed for browser error in case of camera permission error', async () => {
244+
it('should return a UserMediaErrorType.BROWSER_NOT_ALLOWED for browser error in case of camera permission error', async () => {
203245
const videoRef = { current: {} } as RefObject<HTMLVideoElement>;
204246
const nativeError = new Error();
205247
nativeError.name = 'NotAllowedError';
206-
mockGetUserMedia({ createMock: () => jest.fn(() => Promise.reject(nativeError)) });
248+
mockGetUserMedia({
249+
createMock: () =>
250+
jest.fn(
251+
() =>
252+
new Promise((_resolve, reject) => {
253+
setTimeout(() => reject(nativeError), 150);
254+
}),
255+
),
256+
});
207257
navigator.permissions.query = jest.fn(() =>
208258
Promise.resolve({ state: 'denied' } as PermissionStatus),
209259
);
@@ -214,7 +264,7 @@ describe('useUserMedia hook', () => {
214264
stream: null,
215265
dimensions: null,
216266
error: {
217-
type: UserMediaErrorType.WEBPAGE_NOT_ALLOWED,
267+
type: UserMediaErrorType.BROWSER_NOT_ALLOWED,
218268
nativeError,
219269
},
220270
isLoading: false,

0 commit comments

Comments
 (0)