@newtonschool/react_proctoring_library 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -0
- package/dist/ProctorApp.js +186 -0
- package/dist/components/FullScreenTestInWebcam.js +49 -0
- package/dist/components/ResponseModal.js +63 -0
- package/dist/index.js +19 -0
- package/dist/utils/GetBrowserDocumentProp.js +31 -0
- package/dist/utils/GetIsDocumentHidden.js +14 -0
- package/dist/utils/TabVisibility.js +32 -0
- package/dist/utils/ValidityChecks.js +19 -0
- package/dist/utils/commonUtilConfigs.js +21 -0
- package/dist/utils/getGlancePercentage.js +43 -0
- package/dist/utils/index.js +85 -0
- package/dist/utils/initialSetup.js +15 -0
- package/dist/utils/localStorageUtil.js +27 -0
- package/dist/utils/useFullScreenStatus.js +45 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Welcome to react_proctoring_library · [](https://github.com/Newton-School/proctoring_react/blob/main/LICENSE) [](https://www.npmjs.com/package/react_proctoring_library) [](https://www.npmjs.com/package/react_proctoring_library) [](https://circleci.com/gh/facebook/react) [](https://reactjs.org/docs/how-to-contribute.html#your-first-pull-request)
|
|
2
|
+
|
|
3
|
+
Hi! **react_proctoring_library** is a user friendly, easy to use and your _one-stop_ [npm library](https://www.npmjs.com/package/react_proctoring_library) which will provide you the ability to proctor all your exams, tests, quizzes, Playgrounds, almost everything you need to proctor with the minimum effort possible. We have used [body-pix](https://www.npmjs.com/package/@tensorflow-models/body-pix) model from [tensorflow](https://www.tensorflow.org/) library to detect if user is looking outside the screen, multiple people visible in camera. We have also implemented tab switching tracker and full screen exit tracker.
|
|
4
|
+
|
|
5
|
+
## Why?
|
|
6
|
+
|
|
7
|
+
- In these times when most of the exams are conducted online, a lot of us want some sort of mechanism which could make their life easy while taking online test and **react_proctoring_library** has done this for you.
|
|
8
|
+
|
|
9
|
+
Future Plans?
|
|
10
|
+
|
|
11
|
+
- Detection of multiple persons via Audio.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
You must have [React](https://reactjs.org/) v16.8 or above installed in your project to use this proctoring library.
|
|
17
|
+
|
|
18
|
+
If you want, you can also [create a new react app](https://reactjs.org/docs/create-a-new-react-app.html) using `npx create-react-app my-new-app` command.
|
|
19
|
+
|
|
20
|
+
Once you have a react app, just type
|
|
21
|
+
`npm install react_proctoring_library` in your terminal and if everything goes fine, you are good to go.
|
|
22
|
+
|
|
23
|
+
## Props
|
|
24
|
+
|
|
25
|
+
| Props | isRequired | ParamsType | Description |
|
|
26
|
+
| ------------------- | ---------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
27
|
+
| `TestComponent` | Yes | JSX Component | The Component which you want to proctor |
|
|
28
|
+
| `testIdentifier` | Yes | String | A unique string to identify every test |
|
|
29
|
+
| `fullScreenMessage` | Optional | String | If you want your test to run only in full screen mode, this message will appear instead of TestComponent whenever User exits Full Screen |
|
|
30
|
+
|
|
31
|
+
## Documentation
|
|
32
|
+
|
|
33
|
+
Currently, **react_proctoring_library** comes with 2 things:
|
|
34
|
+
|
|
35
|
+
- `ProctorApp` - A Component which will help you in proctoring
|
|
36
|
+
- `Object getStatistics(string: testIdentifier)` - A function which will return a data Object with all Statistics
|
|
37
|
+
about breaches.
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
import { ProctorApp, getStatistics } from 'react_proctoring_library';
|
|
41
|
+
function Test(props) {
|
|
42
|
+
return (
|
|
43
|
+
<div>
|
|
44
|
+
<h1>Proctoring Window</h1>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function App() {
|
|
50
|
+
const testIdentifier = 'unique-proctoring-identifier';
|
|
51
|
+
const fullScreenMessage = 'This test can only be completed in Full Screen Mode, do you want to start this test?';
|
|
52
|
+
const getStats = e => {
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
console.log(getStatistics(testIdentifier));
|
|
55
|
+
};
|
|
56
|
+
return (
|
|
57
|
+
<div className="App">
|
|
58
|
+
<ProctorApp TestComponent={Test} testIdentifier={testIdentifier} fullScreenMessage={fullScreenMessage} />
|
|
59
|
+
</div>
|
|
60
|
+
<button onClick={getStats}>Get Statistics</button>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default App;
|
|
65
|
+
```
|
|
66
|
+
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ProctorApp = ProctorApp;
|
|
7
|
+
exports.getStatistics = exports.default = void 0;
|
|
8
|
+
|
|
9
|
+
require("core-js/modules/web.dom-collections.iterator.js");
|
|
10
|
+
|
|
11
|
+
require("core-js/modules/es.promise.js");
|
|
12
|
+
|
|
13
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
14
|
+
|
|
15
|
+
var tf = _interopRequireWildcard(require("@tensorflow/tfjs"));
|
|
16
|
+
|
|
17
|
+
var bodyPix = _interopRequireWildcard(require("@tensorflow-models/body-pix"));
|
|
18
|
+
|
|
19
|
+
var _ResponseModal = _interopRequireDefault(require("./components/ResponseModal"));
|
|
20
|
+
|
|
21
|
+
var _FullScreenTestInWebcam = _interopRequireDefault(require("./components/FullScreenTestInWebcam"));
|
|
22
|
+
|
|
23
|
+
var _index = require("./utils/index");
|
|
24
|
+
|
|
25
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
26
|
+
|
|
27
|
+
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
28
|
+
|
|
29
|
+
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
30
|
+
|
|
31
|
+
const defaultValues = {
|
|
32
|
+
tabSwitchCount: 0,
|
|
33
|
+
isTabSwitched: false,
|
|
34
|
+
userCount: 0,
|
|
35
|
+
isWatching: true,
|
|
36
|
+
canOpenFullScreen: false,
|
|
37
|
+
fullScreenExitCount: 0,
|
|
38
|
+
lookedAwayCount: 0,
|
|
39
|
+
firstTimeFullScreen: true
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function ProctorApp(props) {
|
|
43
|
+
const webcamReference = (0, _react.useRef)(null);
|
|
44
|
+
const canvasReference = (0, _react.useRef)(null);
|
|
45
|
+
let Statistics = (0, _react.useRef)({
|
|
46
|
+
TAB_SWITCH_AWAY: 0,
|
|
47
|
+
FULL_SCREEN_EXIT: 0,
|
|
48
|
+
LOOKED_AWAY: 0,
|
|
49
|
+
CHANGE_IN_NUMBER_OF_PEOPLE_IN_CAMERA: 0
|
|
50
|
+
});
|
|
51
|
+
const fullScreenElement = (0, _react.useRef)(document.documentElement);
|
|
52
|
+
|
|
53
|
+
const tabSwitchingCheck = isTabVisible => {
|
|
54
|
+
if (isTabSwitched !== isTabVisible) {
|
|
55
|
+
if (!isTabVisible) {
|
|
56
|
+
setTabSwitchCount(tabSwitchCount + 1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
setIsTabSwitched(isTabVisible);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const setValues = () => {
|
|
64
|
+
Statistics.current.TAB_SWITCH_AWAY = tabSwitchCount;
|
|
65
|
+
Statistics.current.FULL_SCREEN_EXIT = fullScreenExitCount;
|
|
66
|
+
Statistics.current.LOOKED_AWAY = lookedAwayCount;
|
|
67
|
+
Statistics.current.CHANGE_IN_NUMBER_OF_PEOPLE_IN_CAMERA = Math.max(userCount, Statistics.current.CHANGE_IN_NUMBER_OF_PEOPLE_IN_CAMERA);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const [isTabSwitched, setIsTabSwitched] = (0, _react.useState)(defaultValues.isTabSwitched);
|
|
71
|
+
const [tabSwitchCount, setTabSwitchCount] = (0, _react.useState)(defaultValues.tabSwitchCount);
|
|
72
|
+
const [userCount, setUserCount] = (0, _react.useState)(defaultValues.userCount);
|
|
73
|
+
const [isWatching, setIsWatching] = (0, _react.useState)(defaultValues.isWatching);
|
|
74
|
+
const [lookedAwayCount, setLookedAwayCount] = (0, _react.useState)(defaultValues.lookedAwayCount);
|
|
75
|
+
const [fullScreenExitCount, setFullScreenExitCount] = (0, _react.useState)(defaultValues.fullScreenExitCount);
|
|
76
|
+
const [firstTimeFullScreen, setFirstTimeFullScreen] = (0, _react.useState)(defaultValues.firstTimeFullScreen);
|
|
77
|
+
const [canOpenFullScreen, SetCanOpenFullScreen] = (0, _index.useFullscreenStatus)(fullScreenElement);
|
|
78
|
+
const isTabVisible = (0, _index.usePageVisibility)();
|
|
79
|
+
|
|
80
|
+
const setInitialStates = () => {
|
|
81
|
+
setTabSwitchCount(defaultValues.tabSwitchCount);
|
|
82
|
+
setIsTabSwitched(defaultValues.isTabSwitched);
|
|
83
|
+
setUserCount(defaultValues.userCount);
|
|
84
|
+
setIsWatching(defaultValues.isWatching);
|
|
85
|
+
setLookedAwayCount(defaultValues.lookedAwayCount);
|
|
86
|
+
setFullScreenExitCount(defaultValues.fullScreenExitCount);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const init = () => {
|
|
90
|
+
setInitialStates();
|
|
91
|
+
setValues();
|
|
92
|
+
(0, _index.addOrUpdateStatsToLocalStorage)(props.testIdentifier, Statistics.current);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
(0, _react.useEffect)(() => {
|
|
96
|
+
(0, _index.removeStatsFromLocalStorage)(props.testIdentifier);
|
|
97
|
+
init();
|
|
98
|
+
loadBodyPixModel();
|
|
99
|
+
}, []);
|
|
100
|
+
(0, _react.useEffect)(() => {
|
|
101
|
+
if (!isWatching) {
|
|
102
|
+
setLookedAwayCount(lookedAwayCount => lookedAwayCount + 1);
|
|
103
|
+
}
|
|
104
|
+
}, [isWatching]);
|
|
105
|
+
(0, _react.useEffect)(() => {
|
|
106
|
+
tabSwitchingCheck(isTabVisible);
|
|
107
|
+
}, [isTabVisible]);
|
|
108
|
+
(0, _react.useEffect)(() => {
|
|
109
|
+
if (!canOpenFullScreen && !firstTimeFullScreen) {
|
|
110
|
+
setFullScreenExitCount(fullScreenExitCount => fullScreenExitCount + 1);
|
|
111
|
+
}
|
|
112
|
+
}, [canOpenFullScreen]);
|
|
113
|
+
(0, _react.useEffect)(() => {
|
|
114
|
+
setValues();
|
|
115
|
+
(0, _index.addOrUpdateStatsToLocalStorage)(props.testIdentifier, Statistics.current);
|
|
116
|
+
}, [userCount, tabSwitchCount, fullScreenExitCount, lookedAwayCount, props.testIdentifier, isWatching]);
|
|
117
|
+
|
|
118
|
+
if (props.TestComponent === undefined || props.testIdentifier === undefined) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const multipleUserCount = async (network, video) => {
|
|
123
|
+
try {
|
|
124
|
+
const body = await network.segmentMultiPerson(video, _index.bodypixConfigs);
|
|
125
|
+
setUserCount(body ? body.length : 0);
|
|
126
|
+
|
|
127
|
+
if (!(0, _index.isArrayValid)(body)) {
|
|
128
|
+
setIsWatching(false);
|
|
129
|
+
}
|
|
130
|
+
} catch (err) {
|
|
131
|
+
console.log('Error Trace:', err);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const glanceChecker = async (network, video) => {
|
|
137
|
+
try {
|
|
138
|
+
const partSegmentation = await network.segmentPersonParts(video, _index.bodypixConfigs);
|
|
139
|
+
const currentPercentage = (0, _index.getGlancePercentage)(partSegmentation);
|
|
140
|
+
setIsWatching(currentPercentage >= _index.glancePercentageToPass);
|
|
141
|
+
} catch (err) {
|
|
142
|
+
console.log('Error Trace:', err);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const loadBodyPixModel = async () => {
|
|
148
|
+
const network = await bodyPix.load();
|
|
149
|
+
setInterval(() => {
|
|
150
|
+
if ((0, _index.isWebcamVideoValid)(webcamReference) && fullScreenElement) {
|
|
151
|
+
const video = webcamReference.current.video;
|
|
152
|
+
const videoWidth = webcamReference.current.video.videoWidth;
|
|
153
|
+
const videoHeight = webcamReference.current.video.videoHeight;
|
|
154
|
+
webcamReference.current.video.width = videoWidth;
|
|
155
|
+
webcamReference.current.video.height = videoHeight;
|
|
156
|
+
canvasReference.current.width = videoWidth;
|
|
157
|
+
canvasReference.current.height = videoHeight;
|
|
158
|
+
multipleUserCount(network, video);
|
|
159
|
+
glanceChecker(network, video);
|
|
160
|
+
}
|
|
161
|
+
}, _index.secondsToCheckUserCountAndGlance * 1000);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return /*#__PURE__*/_react.default.createElement("div", {
|
|
165
|
+
className: "ProctorApp"
|
|
166
|
+
}, !canOpenFullScreen ? /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("h1", null, "Test can run only in Full Screen Mode"), /*#__PURE__*/_react.default.createElement(_ResponseModal.default, {
|
|
167
|
+
SetCanOpenFullScreen: SetCanOpenFullScreen,
|
|
168
|
+
setFirstTimeFullScreen: setFirstTimeFullScreen,
|
|
169
|
+
firstTimeFullScreen: firstTimeFullScreen,
|
|
170
|
+
setFullScreenExitCount: setFullScreenExitCount,
|
|
171
|
+
fullScreenMessage: props.fullScreenMessage
|
|
172
|
+
})) : /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_FullScreenTestInWebcam.default, {
|
|
173
|
+
webcamRef: webcamReference,
|
|
174
|
+
canvasRef: canvasReference,
|
|
175
|
+
userCount: userCount,
|
|
176
|
+
isWatching: isWatching
|
|
177
|
+
})));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const getStatistics = testIdentifier => {
|
|
181
|
+
return JSON.parse((0, _index.retriveStatsFromLocalStorage)(testIdentifier));
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
exports.getStatistics = getStatistics;
|
|
185
|
+
var _default = ProctorApp;
|
|
186
|
+
exports.default = _default;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
|
|
8
|
+
var _react = _interopRequireDefault(require("react"));
|
|
9
|
+
|
|
10
|
+
var _reactWebcam = _interopRequireDefault(require("react-webcam"));
|
|
11
|
+
|
|
12
|
+
var _reactBootstrap = require("react-bootstrap");
|
|
13
|
+
|
|
14
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
15
|
+
|
|
16
|
+
const FullScreenTestInWebcam = props => {
|
|
17
|
+
const webcamReference = props.webcamRef,
|
|
18
|
+
canvasReference = props.canvasRef;
|
|
19
|
+
return /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Col, {
|
|
20
|
+
md: 6
|
|
21
|
+
}, /*#__PURE__*/_react.default.createElement(_reactWebcam.default, {
|
|
22
|
+
className: "canvas-video",
|
|
23
|
+
ref: webcamReference
|
|
24
|
+
}), /*#__PURE__*/_react.default.createElement("canvas", {
|
|
25
|
+
className: "canvas-video",
|
|
26
|
+
ref: canvasReference
|
|
27
|
+
})), /*#__PURE__*/_react.default.createElement(_reactBootstrap.Col, {
|
|
28
|
+
md: 6
|
|
29
|
+
}, /*#__PURE__*/_react.default.createElement("canvas", {
|
|
30
|
+
id: "glanceTrackerCanvas",
|
|
31
|
+
style: {
|
|
32
|
+
display: 'none'
|
|
33
|
+
}
|
|
34
|
+
})), /*#__PURE__*/_react.default.createElement("div", {
|
|
35
|
+
style: {
|
|
36
|
+
position: 'absolute',
|
|
37
|
+
marginLeft: 'auto',
|
|
38
|
+
marginRight: 'auto',
|
|
39
|
+
left: 0,
|
|
40
|
+
right: 0,
|
|
41
|
+
top: '35rem',
|
|
42
|
+
textAlign: 'center',
|
|
43
|
+
zindex: 9
|
|
44
|
+
}
|
|
45
|
+
}, "user count: ", props.userCount, /*#__PURE__*/_react.default.createElement("br", null), props.isWatching ? 'You are watching the screen' : 'You are not watching'));
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
var _default = FullScreenTestInWebcam;
|
|
49
|
+
exports.default = _default;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = ResponseModal;
|
|
7
|
+
|
|
8
|
+
require("core-js/modules/web.dom-collections.iterator.js");
|
|
9
|
+
|
|
10
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
11
|
+
|
|
12
|
+
var _reactBootstrap = require("react-bootstrap");
|
|
13
|
+
|
|
14
|
+
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
15
|
+
|
|
16
|
+
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
17
|
+
|
|
18
|
+
function ResponseModal(props) {
|
|
19
|
+
const [show, setShow] = (0, _react.useState)(true);
|
|
20
|
+
|
|
21
|
+
const displayResponseModal = () => setShow(true);
|
|
22
|
+
|
|
23
|
+
const closeResponseModal = () => {
|
|
24
|
+
if (props.firstTimeFullScreen) {
|
|
25
|
+
props.setFirstTimeFullScreen(false);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setShow(false);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const handleNoOption = () => {
|
|
32
|
+
if (props.firstTimeFullScreen) {
|
|
33
|
+
props.setFullScreenExitCount(fullScreenExitCount => fullScreenExitCount + 1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
closeResponseModal();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const openFullscreen = () => {
|
|
40
|
+
closeResponseModal();
|
|
41
|
+
props.SetCanOpenFullScreen();
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
(0, _react.useEffect)(() => {
|
|
45
|
+
if (!props.canOpenFullScreen) {
|
|
46
|
+
displayResponseModal();
|
|
47
|
+
}
|
|
48
|
+
}, [props.canOpenFullScreen]);
|
|
49
|
+
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Modal, {
|
|
50
|
+
show: show,
|
|
51
|
+
onHide: closeResponseModal,
|
|
52
|
+
size: "lg",
|
|
53
|
+
backdrop: "static",
|
|
54
|
+
"aria-labelledby": "contained-modal-title-vcenter",
|
|
55
|
+
centered: true
|
|
56
|
+
}, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Modal.Header, null, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Modal.Title, null, "Full Screen Permission")), /*#__PURE__*/_react.default.createElement(_reactBootstrap.Modal.Body, null, props.fullScreenMessage), /*#__PURE__*/_react.default.createElement(_reactBootstrap.Modal.Footer, null, /*#__PURE__*/_react.default.createElement(_reactBootstrap.Button, {
|
|
57
|
+
variant: "secondary",
|
|
58
|
+
onClick: handleNoOption
|
|
59
|
+
}, "No"), /*#__PURE__*/_react.default.createElement(_reactBootstrap.Button, {
|
|
60
|
+
variant: "primary",
|
|
61
|
+
onClick: openFullscreen
|
|
62
|
+
}, "Yes"))));
|
|
63
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "ProctorApp", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function get() {
|
|
9
|
+
return _ProctorApp.ProctorApp;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(exports, "getStatistics", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function get() {
|
|
15
|
+
return _ProctorApp.getStatistics;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
var _ProctorApp = require("./ProctorApp");
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = getBrowserDocumentProp;
|
|
7
|
+
|
|
8
|
+
function getBrowserDocumentProp(type) {
|
|
9
|
+
let output = '';
|
|
10
|
+
|
|
11
|
+
if (typeof document.hidden !== 'undefined') {
|
|
12
|
+
output = '';
|
|
13
|
+
} else if (typeof document.msHidden !== 'undefined') {
|
|
14
|
+
output = 'ms';
|
|
15
|
+
} else if (typeof document.webkitHidden !== 'undefined') {
|
|
16
|
+
output = 'webkit';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (type === 'Visibility') {
|
|
20
|
+
// Opera 12.10 and Firefox 18 and later support
|
|
21
|
+
output += 'visibilitychange';
|
|
22
|
+
} else if (type === 'hidden') {
|
|
23
|
+
if (typeof document.hidden !== 'undefined') {
|
|
24
|
+
output = 'hidden';
|
|
25
|
+
} else {
|
|
26
|
+
output += 'Hidden';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return output;
|
|
31
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = getIsDocumentHidden;
|
|
7
|
+
|
|
8
|
+
var _GetBrowserDocumentProp = _interopRequireDefault(require("./GetBrowserDocumentProp"));
|
|
9
|
+
|
|
10
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
11
|
+
|
|
12
|
+
function getIsDocumentHidden() {
|
|
13
|
+
return !document[(0, _GetBrowserDocumentProp.default)('hidden')];
|
|
14
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = usePageVisibility;
|
|
7
|
+
|
|
8
|
+
require("core-js/modules/web.dom-collections.iterator.js");
|
|
9
|
+
|
|
10
|
+
var _react = _interopRequireDefault(require("react"));
|
|
11
|
+
|
|
12
|
+
var _GetBrowserDocumentProp = _interopRequireDefault(require("./GetBrowserDocumentProp"));
|
|
13
|
+
|
|
14
|
+
var _GetIsDocumentHidden = _interopRequireDefault(require("./GetIsDocumentHidden"));
|
|
15
|
+
|
|
16
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
17
|
+
|
|
18
|
+
function usePageVisibility() {
|
|
19
|
+
const [isVisible, setIsVisible] = _react.default.useState((0, _GetIsDocumentHidden.default)());
|
|
20
|
+
|
|
21
|
+
const onVisibilityChange = () => setIsVisible((0, _GetIsDocumentHidden.default)());
|
|
22
|
+
|
|
23
|
+
_react.default.useEffect(() => {
|
|
24
|
+
const visibilityChange = (0, _GetBrowserDocumentProp.default)('Visibility');
|
|
25
|
+
document.addEventListener(visibilityChange, onVisibilityChange, false);
|
|
26
|
+
return () => {
|
|
27
|
+
document.removeEventListener(visibilityChange, onVisibilityChange);
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return isVisible;
|
|
32
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.isArrayValid = isArrayValid;
|
|
7
|
+
exports.isWebcamVideoValid = isWebcamVideoValid;
|
|
8
|
+
|
|
9
|
+
function isWebcamVideoValid(webcamReference) {
|
|
10
|
+
return webcamReference !== null && webcamReference !== undefined && typeof webcamReference.current !== 'undefined' && webcamReference.current !== null && webcamReference.current.video.readyState === 4;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function isArrayValid(array) {
|
|
14
|
+
if (!array || !(array instanceof Array) || array.length <= 0) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.secondsToCheckUserCountAndGlance = exports.glancePercentageToPass = exports.bodypixConfigs = void 0;
|
|
7
|
+
const bodypixConfigs = {
|
|
8
|
+
flipHorizontal: false,
|
|
9
|
+
internalResolution: 'medium',
|
|
10
|
+
segmentationThreshold: 0.4,
|
|
11
|
+
maxDetections: 10,
|
|
12
|
+
scoreThreshold: 0.2,
|
|
13
|
+
nmsRadius: 20,
|
|
14
|
+
minKeypointScore: 0.3,
|
|
15
|
+
refineSteps: 10
|
|
16
|
+
};
|
|
17
|
+
exports.bodypixConfigs = bodypixConfigs;
|
|
18
|
+
const glancePercentageToPass = 60;
|
|
19
|
+
exports.glancePercentageToPass = glancePercentageToPass;
|
|
20
|
+
const secondsToCheckUserCountAndGlance = 0.5;
|
|
21
|
+
exports.secondsToCheckUserCountAndGlance = secondsToCheckUserCountAndGlance;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getGlancePercentage = void 0;
|
|
7
|
+
|
|
8
|
+
var _ValidityChecks = require("./ValidityChecks");
|
|
9
|
+
|
|
10
|
+
const getGlancePercentage = partSegmentation => {
|
|
11
|
+
if (!partSegmentation || !(partSegmentation instanceof Object) || !(0, _ValidityChecks.isArrayValid)(partSegmentation.allPoses)) {
|
|
12
|
+
return 0.0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const allPoses = partSegmentation.allPoses;
|
|
16
|
+
let totalScoreLeftEye = 0.0,
|
|
17
|
+
totalScoreRightEye = 0.0,
|
|
18
|
+
totalScoreNose = 0.0,
|
|
19
|
+
totalScoreLeftEar = 0.0,
|
|
20
|
+
totalScoreRightEar = 0.0;
|
|
21
|
+
let noseIndex = 0,
|
|
22
|
+
leftEyeIndex = 1,
|
|
23
|
+
rightEyeIndex = 2,
|
|
24
|
+
leftEarIndex = 3,
|
|
25
|
+
rightEarIndex = 4;
|
|
26
|
+
allPoses.forEach(poseArray => {
|
|
27
|
+
let keypointsArray = poseArray.keypoints;
|
|
28
|
+
totalScoreNose += keypointsArray[noseIndex].score;
|
|
29
|
+
totalScoreLeftEye += keypointsArray[leftEyeIndex].score;
|
|
30
|
+
totalScoreRightEye += keypointsArray[rightEyeIndex].score;
|
|
31
|
+
totalScoreLeftEar += keypointsArray[leftEarIndex].score;
|
|
32
|
+
totalScoreRightEar += keypointsArray[rightEarIndex].score;
|
|
33
|
+
});
|
|
34
|
+
const avgScoreLeftEye = totalScoreLeftEye / allPoses.length,
|
|
35
|
+
avgScoreRightEye = totalScoreRightEye / allPoses.length,
|
|
36
|
+
avgScoreNose = totalScoreNose / allPoses.length,
|
|
37
|
+
avgScoreLeftEar = totalScoreLeftEar / allPoses.length,
|
|
38
|
+
avgScoreRightEar = totalScoreRightEar / allPoses.length;
|
|
39
|
+
const avgPercentage = 100 * (avgScoreLeftEye + avgScoreRightEye + avgScoreNose + avgScoreLeftEar + avgScoreRightEar) / 5;
|
|
40
|
+
return Math.ceil(avgPercentage);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
exports.getGlancePercentage = getGlancePercentage;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "addOrUpdateStatsToLocalStorage", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function get() {
|
|
9
|
+
return _localStorageUtil.addOrUpdateStatsToLocalStorage;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(exports, "bodypixConfigs", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function get() {
|
|
15
|
+
return _commonUtilConfigs.bodypixConfigs;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(exports, "getGlancePercentage", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function get() {
|
|
21
|
+
return _getGlancePercentage.getGlancePercentage;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
Object.defineProperty(exports, "glancePercentageToPass", {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: function get() {
|
|
27
|
+
return _commonUtilConfigs.glancePercentageToPass;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
Object.defineProperty(exports, "isArrayValid", {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
get: function get() {
|
|
33
|
+
return _ValidityChecks.isArrayValid;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
Object.defineProperty(exports, "isWebcamVideoValid", {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
get: function get() {
|
|
39
|
+
return _ValidityChecks.isWebcamVideoValid;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
Object.defineProperty(exports, "removeStatsFromLocalStorage", {
|
|
43
|
+
enumerable: true,
|
|
44
|
+
get: function get() {
|
|
45
|
+
return _localStorageUtil.removeStatsFromLocalStorage;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
Object.defineProperty(exports, "retriveStatsFromLocalStorage", {
|
|
49
|
+
enumerable: true,
|
|
50
|
+
get: function get() {
|
|
51
|
+
return _localStorageUtil.retriveStatsFromLocalStorage;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
Object.defineProperty(exports, "secondsToCheckUserCountAndGlance", {
|
|
55
|
+
enumerable: true,
|
|
56
|
+
get: function get() {
|
|
57
|
+
return _commonUtilConfigs.secondsToCheckUserCountAndGlance;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
Object.defineProperty(exports, "useFullscreenStatus", {
|
|
61
|
+
enumerable: true,
|
|
62
|
+
get: function get() {
|
|
63
|
+
return _useFullScreenStatus.default;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
Object.defineProperty(exports, "usePageVisibility", {
|
|
67
|
+
enumerable: true,
|
|
68
|
+
get: function get() {
|
|
69
|
+
return _TabVisibility.default;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
var _useFullScreenStatus = _interopRequireDefault(require("./useFullScreenStatus"));
|
|
74
|
+
|
|
75
|
+
var _TabVisibility = _interopRequireDefault(require("./TabVisibility"));
|
|
76
|
+
|
|
77
|
+
var _ValidityChecks = require("./ValidityChecks");
|
|
78
|
+
|
|
79
|
+
var _commonUtilConfigs = require("./commonUtilConfigs");
|
|
80
|
+
|
|
81
|
+
var _getGlancePercentage = require("./getGlancePercentage");
|
|
82
|
+
|
|
83
|
+
var _localStorageUtil = require("./localStorageUtil");
|
|
84
|
+
|
|
85
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
const defaultValues = {
|
|
8
|
+
tabSwitchCount: 0,
|
|
9
|
+
isTabSwitched: false,
|
|
10
|
+
userCount: 0,
|
|
11
|
+
isWatching: true,
|
|
12
|
+
canOpenFullScreen: null
|
|
13
|
+
};
|
|
14
|
+
var _default = defaultValues;
|
|
15
|
+
exports.default = _default;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.retriveStatsFromLocalStorage = exports.removeStatsFromLocalStorage = exports.addOrUpdateStatsToLocalStorage = void 0;
|
|
7
|
+
|
|
8
|
+
require("core-js/modules/es.json.stringify.js");
|
|
9
|
+
|
|
10
|
+
const addOrUpdateStatsToLocalStorage = (testIdentifier, data) => {
|
|
11
|
+
const stringifiedData = JSON.stringify(data);
|
|
12
|
+
localStorage.setItem(testIdentifier, stringifiedData);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
exports.addOrUpdateStatsToLocalStorage = addOrUpdateStatsToLocalStorage;
|
|
16
|
+
|
|
17
|
+
const retriveStatsFromLocalStorage = testIdentifier => {
|
|
18
|
+
return localStorage.getItem(testIdentifier);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
exports.retriveStatsFromLocalStorage = retriveStatsFromLocalStorage;
|
|
22
|
+
|
|
23
|
+
const removeStatsFromLocalStorage = testIdentifier => {
|
|
24
|
+
localStorage.removeItem(testIdentifier);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
exports.removeStatsFromLocalStorage = removeStatsFromLocalStorage;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = useFullscreenStatus;
|
|
7
|
+
|
|
8
|
+
require("core-js/modules/web.dom-collections.iterator.js");
|
|
9
|
+
|
|
10
|
+
var _react = _interopRequireDefault(require("react"));
|
|
11
|
+
|
|
12
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
13
|
+
|
|
14
|
+
function useFullscreenStatus(elRef) {
|
|
15
|
+
const [isFullscreen, setIsFullscreen] = _react.default.useState(document[getBrowserFullscreenElementProp()] != null);
|
|
16
|
+
|
|
17
|
+
const setFullscreen = () => {
|
|
18
|
+
if (!elRef || !elRef.current) return;
|
|
19
|
+
elRef.current.requestFullscreen().then(() => {
|
|
20
|
+
setIsFullscreen(document[getBrowserFullscreenElementProp()] != null);
|
|
21
|
+
}).catch(() => {
|
|
22
|
+
setIsFullscreen(false);
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
_react.default.useEffect(() => {
|
|
27
|
+
document.onfullscreenchange = () => setIsFullscreen(document[getBrowserFullscreenElementProp()] != null);
|
|
28
|
+
|
|
29
|
+
return () => document.onfullscreenchange = undefined;
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
return [isFullscreen, setFullscreen];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getBrowserFullscreenElementProp() {
|
|
36
|
+
if (typeof document.fullscreenElement !== 'undefined') {
|
|
37
|
+
return 'fullscreenElement';
|
|
38
|
+
} else if (typeof document.mozFullScreenElement !== 'undefined') {
|
|
39
|
+
return 'mozFullScreenElement';
|
|
40
|
+
} else if (typeof document.msFullscreenElement !== 'undefined') {
|
|
41
|
+
return 'msFullscreenElement';
|
|
42
|
+
} else if (typeof document.webkitFullscreenElement !== 'undefined') {
|
|
43
|
+
return 'webkitFullscreenElement';
|
|
44
|
+
}
|
|
45
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@newtonschool/react_proctoring_library",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Used to proctor online tests",
|
|
5
|
+
"author": "ayushkagrawal",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
""
|
|
14
|
+
],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@tensorflow-models/body-pix": "^2.2.0",
|
|
17
|
+
"@tensorflow/tfjs": "^3.9.0",
|
|
18
|
+
"bootstrap": "^5.1.3",
|
|
19
|
+
"react-bootstrap": "^2.0.4",
|
|
20
|
+
"react-webcam": "^6.0.0"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"react": ">=16.8.0",
|
|
24
|
+
"react-dom": ">=16.8.0"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "babel src/lib --out-dir dist --copy-files",
|
|
28
|
+
"test": "react-scripts test",
|
|
29
|
+
"eject": "react-scripts eject"
|
|
30
|
+
},
|
|
31
|
+
"eslintConfig": {
|
|
32
|
+
"extends": [
|
|
33
|
+
"react-app",
|
|
34
|
+
"react-app/jest"
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"browserslist": {
|
|
38
|
+
"production": [
|
|
39
|
+
">0.2%",
|
|
40
|
+
"not dead",
|
|
41
|
+
"not op_mini all"
|
|
42
|
+
],
|
|
43
|
+
"development": [
|
|
44
|
+
"last 1 chrome version",
|
|
45
|
+
"last 1 firefox version",
|
|
46
|
+
"last 1 safari version"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"react-scripts": ">=5.0.0",
|
|
51
|
+
"@babel/cli": "^7.16.7",
|
|
52
|
+
"@babel/core": "^7.16.7",
|
|
53
|
+
"@babel/preset-env": "^7.16.7"
|
|
54
|
+
}
|
|
55
|
+
}
|