@newtonschool/react_proctoring_library 0.0.126-beta.1 → 0.0.127
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.
|
@@ -122,7 +122,8 @@ const ProctorApp = _ref => {
|
|
|
122
122
|
askPermission
|
|
123
123
|
},
|
|
124
124
|
browserDetails: (0, _browserUtils.getBrowserDetails)(),
|
|
125
|
-
permissionErrors: permissionErrors
|
|
125
|
+
permissionErrors: permissionErrors,
|
|
126
|
+
allPermissionGrantedOnce: allPermissionGrantedOnce.current
|
|
126
127
|
}, permissionPassedProps)), !shouldShowProctoredComponent && !customPermissionView && /*#__PURE__*/(0, _jsxRuntime.jsx)(_permissions.Permission, {
|
|
127
128
|
permissions: permissions,
|
|
128
129
|
proctorParams: proctorParams,
|
|
@@ -29,20 +29,27 @@ const SecondaryDevice = _ref => {
|
|
|
29
29
|
const cleanup = (0, _proctoringStatusUtils.setupDeviceStatusListeners)({
|
|
30
30
|
firebaseClient,
|
|
31
31
|
isSecondaryDevice,
|
|
32
|
+
allPermissionGranted,
|
|
32
33
|
onPrimaryDeviceStatusChange: connectionStatus => {
|
|
33
34
|
setPrimaryDeviceOnline(connectionStatus);
|
|
34
35
|
},
|
|
35
36
|
onSecondaryDeviceStatusChange: (connectionStatus, allPermissionGranted) => {
|
|
36
37
|
setSecondaryDeviceOnline(connectionStatus);
|
|
37
38
|
setSecondaryDeviceAllPermissionGranted(allPermissionGranted);
|
|
38
|
-
}
|
|
39
|
-
allPermissionGranted
|
|
39
|
+
}
|
|
40
40
|
});
|
|
41
41
|
return cleanup;
|
|
42
42
|
}, [firebaseClient, isSecondaryDevice, allPermissionGranted]);
|
|
43
|
+
(0, _react.useEffect)(() => {
|
|
44
|
+
(0, _proctoringStatusUtils.updateCurrentDeviceStatus)({
|
|
45
|
+
firebaseClient,
|
|
46
|
+
isSecondaryDevice,
|
|
47
|
+
allPermissionGranted
|
|
48
|
+
});
|
|
49
|
+
}, [firebaseClient, isSecondaryDevice, allPermissionGranted]);
|
|
43
50
|
(0, _react.useEffect)(() => {
|
|
44
51
|
if (isSecondaryDevice) {
|
|
45
|
-
setSecondaryDevicePermission(
|
|
52
|
+
setSecondaryDevicePermission(secondaryDeviceOnline);
|
|
46
53
|
} else {
|
|
47
54
|
setSecondaryDevicePermission(primaryDeviceOnline && secondaryDeviceOnline && secondaryDeviceAllPermissionGranted);
|
|
48
55
|
}
|
|
@@ -4,21 +4,24 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = useFullscreenData;
|
|
7
|
+
require("core-js/modules/es.regexp.exec.js");
|
|
8
|
+
require("core-js/modules/es.regexp.test.js");
|
|
7
9
|
require("core-js/modules/web.dom-collections.iterator.js");
|
|
8
10
|
var _react = require("react");
|
|
9
11
|
var _utils = require("../../utils");
|
|
10
12
|
var _fullScreen = _interopRequireDefault(require("../../utils/fullScreen"));
|
|
11
13
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
14
|
const browserFullscreenElementProp = (0, _utils.getBrowserFullscreenElementProp)();
|
|
15
|
+
const isIOS = typeof window !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent || '') && !window.MSStream;
|
|
13
16
|
function useFullscreenData() {
|
|
14
17
|
const fullScreenElement = (0, _react.useRef)(document.documentElement);
|
|
15
|
-
const [isFullscreen, setIsFullscreen] = (0, _react.useState)(typeof window !== 'undefined' && (document[browserFullscreenElementProp] != null || !!window.NS_TESTING_ENVIRONMENT));
|
|
18
|
+
const [isFullscreen, setIsFullscreen] = (0, _react.useState)(typeof window !== 'undefined' && (isIOS || document[browserFullscreenElementProp] != null || !!window.NS_TESTING_ENVIRONMENT));
|
|
16
19
|
const setFullscreen = (0, _react.useCallback)(() => {
|
|
17
20
|
var _fullScreenElement$cu, _fullScreenElement$cu2, _fullScreenElement$cu3, _fullScreenElement$cu4, _fullScreenElement$cu5, _fullScreenElement$cu6;
|
|
18
21
|
if (!fullScreenElement || !fullScreenElement.current) {
|
|
19
22
|
return;
|
|
20
23
|
}
|
|
21
|
-
if (typeof window !== 'undefined' && window.NS_TESTING_ENVIRONMENT) {
|
|
24
|
+
if (typeof window !== 'undefined' && (window.NS_TESTING_ENVIRONMENT || isIOS)) {
|
|
22
25
|
setIsFullscreen(true);
|
|
23
26
|
return;
|
|
24
27
|
}
|
|
@@ -31,6 +34,9 @@ function useFullscreenData() {
|
|
|
31
34
|
});
|
|
32
35
|
}, []);
|
|
33
36
|
(0, _react.useEffect)(() => {
|
|
37
|
+
if (isIOS) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
34
40
|
const cleanup = _fullScreen.default.addFullScreenListener(() => {
|
|
35
41
|
setIsFullscreen(document[browserFullscreenElementProp] !== null);
|
|
36
42
|
});
|
package/dist/utils/permission.js
CHANGED
|
@@ -18,7 +18,6 @@ const hasAllPermissions = (requiredPermissions, permissions) => {
|
|
|
18
18
|
hasPermission = hasPermission && false;
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
|
-
console.log('::[hasAllPermissions]', status, 'allGranted:', hasPermission);
|
|
22
21
|
return hasPermission;
|
|
23
22
|
};
|
|
24
23
|
exports.hasAllPermissions = hasAllPermissions;
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.setupDeviceStatusListeners = exports.setSecondaryDeviceConnected = exports.setPrimaryDeviceConnected = void 0;
|
|
6
|
+
exports.updateCurrentDeviceStatus = exports.setupDeviceStatusListeners = exports.setSecondaryDeviceConnected = exports.setPrimaryDeviceConnected = void 0;
|
|
7
7
|
var _defaults = require("../constants/defaults");
|
|
8
|
+
const HEARTBEAT_INTERVAL_MS = 5000;
|
|
9
|
+
const OTHER_DEVICE_HEARTBEAT_TIMEOUT_MS = 15000;
|
|
10
|
+
|
|
8
11
|
/**
|
|
9
12
|
* Marks the primary device as connected in Firebase. Call when desktop webcam
|
|
10
13
|
* connects successfully. No-op if firebaseClient is missing required fields.
|
|
@@ -54,6 +57,40 @@ const setSecondaryDeviceConnected = firebaseClient => {
|
|
|
54
57
|
});
|
|
55
58
|
};
|
|
56
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Utility to update the current device's status (connected + permissions + heartbeat)
|
|
62
|
+
* based on whether it's a primary or secondary device.
|
|
63
|
+
*
|
|
64
|
+
* @param {Object} params
|
|
65
|
+
* @param {Object} params.firebaseClient
|
|
66
|
+
* @param {boolean} params.isSecondaryDevice
|
|
67
|
+
* @param {boolean} params.allPermissionGranted
|
|
68
|
+
* @returns {Promise<void>|undefined}
|
|
69
|
+
*/
|
|
70
|
+
exports.setSecondaryDeviceConnected = setSecondaryDeviceConnected;
|
|
71
|
+
const updateCurrentDeviceStatus = _ref => {
|
|
72
|
+
let {
|
|
73
|
+
firebaseClient,
|
|
74
|
+
isSecondaryDevice,
|
|
75
|
+
allPermissionGranted
|
|
76
|
+
} = _ref;
|
|
77
|
+
if (!(firebaseClient !== null && firebaseClient !== void 0 && firebaseClient.userUuid) || !(firebaseClient !== null && firebaseClient !== void 0 && firebaseClient.myFirebaseDB) || !(firebaseClient !== null && firebaseClient !== void 0 && firebaseClient.getRef) || !(firebaseClient !== null && firebaseClient !== void 0 && firebaseClient.updateValue)) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
const {
|
|
81
|
+
updateValue: update,
|
|
82
|
+
getRef: ref
|
|
83
|
+
} = firebaseClient;
|
|
84
|
+
const deviceKey = isSecondaryDevice ? 'secondaryDevice' : 'primaryDevice';
|
|
85
|
+
const deviceStatusRef = ref(firebaseClient.myFirebaseDB, "".concat(_defaults.PROCTORING_STATUS_PATH, "/").concat(firebaseClient.userUuid, "/").concat(deviceKey));
|
|
86
|
+
const payload = {
|
|
87
|
+
allPermissionGranted
|
|
88
|
+
};
|
|
89
|
+
return update(deviceStatusRef, payload).catch(error => {
|
|
90
|
+
console.error('::[proctoringStatusUtils] Failed to update current device status:', error);
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
57
94
|
/**
|
|
58
95
|
* Sets up Firebase device status: marks current device (primary/secondary) as connected,
|
|
59
96
|
* registers onDisconnect, and subscribes to primary and secondary status.
|
|
@@ -67,15 +104,14 @@ const setSecondaryDeviceConnected = firebaseClient => {
|
|
|
67
104
|
* @param {boolean} params.allPermissionGranted
|
|
68
105
|
* @returns {() => void} Cleanup function to unsubscribe, or no-op if setup skipped
|
|
69
106
|
*/
|
|
70
|
-
exports.
|
|
71
|
-
const setupDeviceStatusListeners =
|
|
107
|
+
exports.updateCurrentDeviceStatus = updateCurrentDeviceStatus;
|
|
108
|
+
const setupDeviceStatusListeners = _ref2 => {
|
|
72
109
|
let {
|
|
73
110
|
firebaseClient,
|
|
74
111
|
isSecondaryDevice,
|
|
75
112
|
onPrimaryDeviceStatusChange,
|
|
76
|
-
onSecondaryDeviceStatusChange
|
|
77
|
-
|
|
78
|
-
} = _ref;
|
|
113
|
+
onSecondaryDeviceStatusChange
|
|
114
|
+
} = _ref2;
|
|
79
115
|
const {
|
|
80
116
|
userUuid,
|
|
81
117
|
firestoreAuthStatus,
|
|
@@ -92,12 +128,44 @@ const setupDeviceStatusListeners = _ref => {
|
|
|
92
128
|
const primaryDeviceStatusRef = ref(myFirebaseDB, "".concat(_defaults.PROCTORING_STATUS_PATH, "/").concat(userUuid, "/primaryDevice"));
|
|
93
129
|
const secondaryDeviceStatusRef = ref(myFirebaseDB, "".concat(_defaults.PROCTORING_STATUS_PATH, "/").concat(userUuid, "/secondaryDevice"));
|
|
94
130
|
const currentDeviceStatusRef = isSecondaryDevice ? secondaryDeviceStatusRef : primaryDeviceStatusRef;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
131
|
+
const otherDeviceStatusRef = isSecondaryDevice ? primaryDeviceStatusRef : secondaryDeviceStatusRef;
|
|
132
|
+
|
|
133
|
+
// When this client disconnects (tab close, network loss)
|
|
98
134
|
onDisconnect(currentDeviceStatusRef).update({
|
|
99
135
|
connected: false
|
|
136
|
+
}).catch(err => {
|
|
137
|
+
console.error('::[proctoringStatusUtils] Failed to mark current device disconnected:', err);
|
|
100
138
|
});
|
|
139
|
+
let heartbeatIntervalId = null;
|
|
140
|
+
if (typeof window !== 'undefined' && typeof setInterval === 'function') {
|
|
141
|
+
heartbeatIntervalId = setInterval(() => {
|
|
142
|
+
const now = Date.now();
|
|
143
|
+
|
|
144
|
+
// Send heartbeat for current device if document is not hidden
|
|
145
|
+
if (!document.hidden) {
|
|
146
|
+
update(currentDeviceStatusRef, {
|
|
147
|
+
connected: true,
|
|
148
|
+
lastHeartbeatAt: now
|
|
149
|
+
}).catch(error => {
|
|
150
|
+
console.error('::[proctoringStatusUtils] Failed to send heartbeat for current device:', error);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Check other device's last heartbeat; if > OTHER_DEVICE_HEARTBEAT_TIMEOUT_MS then mark as disconnected.
|
|
155
|
+
onValue(otherDeviceStatusRef, snapshot => {
|
|
156
|
+
const val = snapshot.val();
|
|
157
|
+
const lastBeat = val === null || val === void 0 ? void 0 : val.lastHeartbeatAt;
|
|
158
|
+
const isConnected = Boolean(val === null || val === void 0 ? void 0 : val.connected);
|
|
159
|
+
if (isConnected && typeof lastBeat === 'number' && now - lastBeat > OTHER_DEVICE_HEARTBEAT_TIMEOUT_MS) {
|
|
160
|
+
update(otherDeviceStatusRef, {
|
|
161
|
+
connected: false
|
|
162
|
+
}).catch(otherErr => {
|
|
163
|
+
console.error('::[proctoringStatusUtils] Failed to mark other device disconnected due to stale heartbeat:', otherErr);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
168
|
+
}
|
|
101
169
|
const unsubscribePrimary = onValue(primaryDeviceStatusRef, snapshot => {
|
|
102
170
|
const val = snapshot.val();
|
|
103
171
|
onPrimaryDeviceStatusChange(Boolean(val === null || val === void 0 ? void 0 : val.connected));
|
|
@@ -109,6 +177,9 @@ const setupDeviceStatusListeners = _ref => {
|
|
|
109
177
|
return () => {
|
|
110
178
|
unsubscribePrimary();
|
|
111
179
|
unsubscribeSecondary();
|
|
180
|
+
if (heartbeatIntervalId) {
|
|
181
|
+
clearInterval(heartbeatIntervalId);
|
|
182
|
+
}
|
|
112
183
|
};
|
|
113
184
|
};
|
|
114
185
|
exports.setupDeviceStatusListeners = setupDeviceStatusListeners;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newtonschool/react_proctoring_library",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.127",
|
|
4
4
|
"description": "Used to proctor online tests",
|
|
5
5
|
"author": "ayushkagrawal,shreyachandra,weastel",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"react-webcam": "6.0.0"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
|
-
"react": "
|
|
23
|
-
"react-dom": "
|
|
22
|
+
"react": "18.3.0",
|
|
23
|
+
"react-dom": "18.3.0"
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "babel src --out-dir dist --copy-files",
|