@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(primaryDeviceOnline && secondaryDeviceOnline);
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
  });
@@ -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.setSecondaryDeviceConnected = setSecondaryDeviceConnected;
71
- const setupDeviceStatusListeners = _ref => {
107
+ exports.updateCurrentDeviceStatus = updateCurrentDeviceStatus;
108
+ const setupDeviceStatusListeners = _ref2 => {
72
109
  let {
73
110
  firebaseClient,
74
111
  isSecondaryDevice,
75
112
  onPrimaryDeviceStatusChange,
76
- onSecondaryDeviceStatusChange,
77
- allPermissionGranted
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
- update(currentDeviceStatusRef, {
96
- allPermissionGranted
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.126-beta.1",
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": ">=16.8.0",
23
- "react-dom": ">=16.8.0"
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",