Skip to content

Commit 3a6c5db

Browse files
author
Illia Obukhau
authored
[WC-1337]: Fix barcode-scanner (#35)
2 parents 3a7ee77 + 2c28d48 commit 3a6c5db

File tree

6 files changed

+53
-100
lines changed

6 files changed

+53
-100
lines changed

packages/pluggableWidgets/barcode-scanner-web/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66

77
## [Unreleased]
88

9+
### Fixed
10+
11+
- We fixed an issue where the camera kept capturing after the widget was closed.
12+
913
## [2.2.1] - 2022-07-27
1014

1115
### Fixed

packages/pluggableWidgets/barcode-scanner-web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "barcode-scanner-web",
33
"widgetName": "BarcodeScanner",
4-
"version": "2.2.1",
4+
"version": "2.2.2",
55
"description": "Displays a barcode scanner",
66
"copyright": "© Mendix Technology BV 2022. All rights reserved.",
77
"license": "Apache-2.0",

packages/pluggableWidgets/barcode-scanner-web/src/components/__tests__/BarcodeScanner.spec.tsx

Lines changed: 9 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import "@testing-library/jest-dom";
2-
import { render, waitFor } from "@testing-library/react";
2+
import { act, render, waitFor, screen } from "@testing-library/react";
33
import { createElement } from "react";
44
import { Dimensions } from "@mendix/pluggable-widgets-commons";
55
import { NotFoundException } from "@zxing/library/cjs";
@@ -66,51 +66,14 @@ describe("Barcode scanner", () => {
6666
describe("shows an appropriate error to the user", () => {
6767
it("in the form of text when a generic error occurs", async () => {
6868
useReaderMock.mockImplementationOnce((args: any) => {
69-
setTimeout(() => args.onError(new Error("this is unexpected error")), 100);
69+
setTimeout(() => args.onError(new Error("some error message")), 100);
7070
});
7171
mockGetUserMedia(jest.fn());
7272

73-
const { container } = render(<BarcodeScanner class="" showMask {...dimensions} />);
74-
75-
await waitFor(() =>
76-
expect(container).toHaveTextContent(
77-
"Error in barcode scanner: an unexpected error occurred while retrieving the camera media stream."
78-
)
79-
);
80-
});
81-
82-
it("in the form of text when no device was found", async () => {
83-
useReaderMock.mockImplementationOnce((args: any) => {
84-
setTimeout(() => {
85-
const error = new Error("This is an error message");
86-
error.name = "NotFoundError";
87-
args.onError(error);
88-
}, 100);
89-
});
90-
91-
mockGetUserMedia(jest.fn());
92-
93-
const { container } = render(<BarcodeScanner class="" showMask {...dimensions} />);
94-
95-
await waitFor(() =>
96-
expect(container).toHaveTextContent("Error in barcode scanner: no camera media devices were found.")
97-
);
98-
});
99-
100-
it("not in the form of text since that is handled by the design when the users denies access to the camera", async () => {
101-
useReaderMock.mockImplementationOnce((args: any) => {
102-
setTimeout(() => {
103-
const error = new Error("This is an error message");
104-
error.name = "NotAllowedError";
105-
args.onError(error);
106-
}, 100);
73+
await act(async () => {
74+
render(<BarcodeScanner class="" showMask {...dimensions} />);
10775
});
108-
109-
mockGetUserMedia(jest.fn());
110-
111-
const { container } = render(<BarcodeScanner class="" showMask {...dimensions} />);
112-
113-
await waitFor(() => expect(container).not.toHaveTextContent(/Error in barcode scanner:/));
76+
await waitFor(() => expect(screen.getByText(/some error message/i)).toBeVisible());
11477
});
11578

11679
it("in the form of text when the code scanner unexpectedly fails", async () => {
@@ -122,13 +85,10 @@ describe("Barcode scanner", () => {
12285

12386
mockGetUserMedia(jest.fn());
12487

125-
const { container } = render(<BarcodeScanner class="" showMask {...dimensions} />);
126-
127-
await waitFor(() =>
128-
expect(container).toHaveTextContent(
129-
"Error in barcode scanner: an unexpected error occurred while detecting a barcode in the camera media stream."
130-
)
131-
);
88+
await act(async () => {
89+
render(<BarcodeScanner class="" showMask {...dimensions} />);
90+
});
91+
await waitFor(() => expect(screen.getByText(/Unable to decode from stream/i)).toBeVisible());
13292
});
13393
});
13494
});

packages/pluggableWidgets/barcode-scanner-web/src/hooks/useCustomErrorMessage.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,15 @@
1-
import { Exception } from "@zxing/library/cjs";
21
import { useState, useCallback } from "react";
32

43
type ErrorCb<E extends Error = Error> = (error: E) => void;
54

65
type UseCustomErrorMessageHook = () => [string | null, ErrorCb];
76

87
function getErrorMessage<E extends Error>(error: E): string | null {
9-
// Error comes from `zxing`
10-
if (error instanceof Exception) {
11-
return "Error in barcode scanner: an unexpected error occurred while detecting a barcode in the camera media stream.";
8+
if (error instanceof Error) {
9+
return error.message;
1210
}
1311

14-
if (error.name === "NotFoundError") {
15-
return "Error in barcode scanner: no camera media devices were found.";
16-
}
17-
18-
// Ignore error if user restricted camera access
19-
if (error.name === "NotAllowedError") {
20-
return null;
21-
}
22-
23-
// Anything else will be a DOMException that we show as media stream error
24-
return "Error in barcode scanner: an unexpected error occurred while retrieving the camera media stream.";
12+
return "Unexpected error in barcode scanner.";
2513
}
2614

2715
export const useCustomErrorMessage: UseCustomErrorMessageHook = () => {
Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useEffect, useRef, RefObject } from "react";
2-
import { BarcodeFormat, BrowserMultiFormatReader, DecodeHintType } from "@zxing/library/cjs";
2+
import { BarcodeFormat, BrowserMultiFormatReader, DecodeHintType, NotFoundException } from "@zxing/library/cjs";
33
import { useEventCallback } from "@mendix/pluggable-widgets-commons";
44

55
const hints = new Map();
@@ -30,47 +30,48 @@ export const useReader: UseReaderHook = args => {
3030
const onSuccess = useEventCallback(args.onSuccess);
3131
const onError = useEventCallback(args.onError);
3232

33-
function setup(): (() => void) | void {
34-
if (!videoRef.current) {
35-
return;
36-
}
37-
let isCanceled = false;
38-
let reader: BrowserMultiFormatReader | null = null;
39-
const elt = videoRef.current;
33+
useEffect(() => {
34+
let stopped = false;
35+
const reader = new BrowserMultiFormatReader(hints, 2000);
4036

41-
const cleanup = (): void => {
42-
if (!isCanceled && reader) {
43-
reader.reset();
44-
reader = null;
45-
isCanceled = true;
46-
}
37+
const stop = (): void => {
38+
stopped = true;
39+
reader.stopAsyncDecode();
40+
reader.reset();
4741
};
4842

49-
const decodeFromReader = (): void => {
50-
const fn = async (): Promise<void> => {
51-
reader = new BrowserMultiFormatReader(hints, 2000);
52-
try {
53-
const result = await reader.decodeOnceFromConstraints(mediaStreamConstraints, elt);
54-
onSuccess(result.getText());
55-
} catch (e) {
56-
console.log(e.message);
57-
if (!isCanceled && onError) {
58-
onError(e);
43+
const start = async (): Promise<void> => {
44+
let stream;
45+
try {
46+
stream = await navigator.mediaDevices.getUserMedia(mediaStreamConstraints);
47+
if (!stopped && videoRef.current) {
48+
const result = await reader.decodeOnceFromStream(stream, videoRef.current);
49+
if (!stopped) {
50+
onSuccess(result.getText());
51+
}
52+
}
53+
} catch (error) {
54+
// Suppress not found error if widget is closed normally (eg. leaving page);
55+
const isNotFound = stopped && error instanceof NotFoundException;
56+
if (!isNotFound) {
57+
if (error instanceof Error) {
58+
console.error(error.message);
59+
}
60+
if (onError) {
61+
onError(error);
5962
}
60-
} finally {
61-
cleanup();
6263
}
63-
};
64-
fn();
64+
} finally {
65+
stop();
66+
stream?.getVideoTracks().forEach(track => track.stop());
67+
}
6568
};
6669

67-
decodeFromReader();
68-
69-
return cleanup;
70-
}
70+
start();
7171

72-
// eslint-disable-next-line react-hooks/exhaustive-deps
73-
useEffect(setup, []);
72+
return stop;
73+
// eslint-disable-next-line react-hooks/exhaustive-deps
74+
}, []);
7475

7576
return videoRef;
7677
};

packages/pluggableWidgets/barcode-scanner-web/src/package.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8" ?>
22
<package xmlns="http://www.mendix.com/package/1.0/">
3-
<clientModule name="Barcode Scanner" version="2.2.1" xmlns="http://www.mendix.com/clientModule/1.0/">
3+
<clientModule name="Barcode Scanner" version="2.2.2" xmlns="http://www.mendix.com/clientModule/1.0/">
44
<widgetFiles>
55
<widgetFile path="BarcodeScanner.xml" />
66
</widgetFiles>

0 commit comments

Comments
 (0)