diff --git a/resources/js/candidate-proctor.js b/resources/js/candidate-proctor.js
index 67d1aac..a418df7 100644
--- a/resources/js/candidate-proctor.js
+++ b/resources/js/candidate-proctor.js
@@ -343,6 +343,70 @@ export function uploadTabScreenshotBlob(imageData, reason = 'tab_switch') {
.catch(err => console.log('System screenshot fetch error:', err));
}
+export function isEntireScreenShare(track) {
+ if (!track || !track.getSettings) return true;
+ const settings = track.getSettings();
+ const surface = settings.displaySurface;
+
+ if (surface) {
+ const allowedSurfaces = ['monitor', 'screen'];
+ if (!allowedSurfaces.includes(surface)) {
+ return false;
+ }
+ }
+
+ if (window.screen && window.screen.width && settings.width) {
+ if (settings.width < (window.screen.width * 0.65) && settings.height < (window.screen.height * 0.65)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+export function isScreenSharingActive() {
+ return Boolean(screenShareStream && screenShareStream.active && screenShareStream.getVideoTracks().some(t => t.readyState === 'live'));
+}
+
+export function enforceBrowserFullscreen() {
+ if (!isScreenSharingActive() || document.fullscreenElement) return;
+
+ beginNativePrompt();
+ if (document.documentElement.requestFullscreen) {
+ document.documentElement.requestFullscreen()
+ .then(() => {
+ endNativePrompt();
+ armTrailingBuffer(1000);
+ })
+ .catch(() => {
+ endNativePrompt();
+ showFullscreenRequiredModal();
+ });
+ } else {
+ endNativePrompt();
+ showFullscreenRequiredModal();
+ }
+}
+
+export function showFullscreenRequiredModal() {
+ if (!isScreenSharingActive() || document.fullscreenElement) return;
+ if (typeof window.Swal !== 'undefined') {
+ window.Swal.fire({
+ icon: 'warning',
+ title: 'Fullscreen Mode Required',
+ text: 'Your assessment must be conducted in Fullscreen mode while screen sharing. Please click below to enter Fullscreen.',
+ confirmButtonText: 'Enter Fullscreen Mode',
+ confirmButtonColor: '#6366f1',
+ allowOutsideClick: false,
+ allowEscapeKey: false
+ }).then(() => {
+ if (isScreenSharingActive()) {
+ enforceBrowserFullscreen();
+ }
+ });
+ }
+}
+
// --- Screen Share Enforcement & Live Overlay Detector (Category 6) ---
export function startScreenShare() {
if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) {
@@ -361,26 +425,27 @@ export function startScreenShare() {
.then(stream => {
endNativePrompt();
const track = stream.getVideoTracks()[0];
- const settings = track.getSettings ? track.getSettings() : {};
- // Enforce ENTIRE SCREEN share
- if (settings.displaySurface && settings.displaySurface !== 'monitor') {
+ // Enforce ENTIRE SCREEN share across all browsers
+ if (!isEntireScreenShare(track)) {
+ track.stop();
+ screenShareStream = null;
+ logViolation('tab_switch', 'Candidate attempted non-entire screen share (tab or window)');
+
if (typeof window.Swal !== 'undefined') {
window.Swal.fire({
- icon: 'warning',
+ icon: 'error',
title: 'Entire Screen Share Required',
- text: 'You selected a single tab or window. You MUST select "Entire Screen".',
- confirmButtonText: 'Reshare Entire Screen',
- confirmButtonColor: '#6366f1',
+ html: 'You selected a single tab or window.
You MUST select "Entire Screen" (or whole monitor) to continue your interview.',
+ confirmButtonText: 'Reshare Entire Screen Now',
+ confirmButtonColor: '#ef4444',
allowOutsideClick: false,
allowEscapeKey: false
}).then(() => {
- track.stop();
setTimeout(startScreenShare, 300);
});
} else {
alert('Entire Screen share is required. Please select Entire Screen.');
- track.stop();
setTimeout(startScreenShare, 300);
}
return;
@@ -391,21 +456,9 @@ export function startScreenShare() {
vidElem.srcObject = stream;
vidElem.play().catch(() => {});
- const btn = document.getElementById('share-screen-btn');
- if (btn) {
- btn.innerText = '🟢 Entire Screen Sharing Active';
- btn.style.background = '#10b981';
- }
+ updateScreenShareButton(true);
- beginNativePrompt();
- if (document.documentElement.requestFullscreen) {
- document.documentElement.requestFullscreen()
- .catch(() => {})
- .finally(() => {
- endNativePrompt();
- armTrailingBuffer(1000);
- });
- }
+ enforceBrowserFullscreen();
const interviewId = document.body.dataset.interviewId;
if (typeof window.Peer !== 'undefined' && interviewId) {
@@ -417,10 +470,12 @@ export function startScreenShare() {
track.onended = () => {
screenShareStream = null;
- if (btn) {
- btn.innerText = '🖥️ Share Screen with Interviewer';
- btn.style.background = 'linear-gradient(135deg, #6366f1 0%, #0078d4 100%)';
+ updateScreenShareButton(false);
+
+ if (document.fullscreenElement) {
+ try { document.exitFullscreen().catch(() => {}); } catch(e) {}
}
+
logViolation('tab_switch', 'Candidate stopped screen sharing');
};
@@ -430,14 +485,50 @@ export function startScreenShare() {
endNativePrompt();
console.log('Screen sharing cancelled/denied:', err);
screenShareStream = null;
- const btn = document.getElementById('share-screen-btn');
- if (btn) {
- btn.innerText = '🖥️ Share Screen with Interviewer';
- btn.style.background = 'linear-gradient(135deg, #6366f1 0%, #0078d4 100%)';
- }
+ updateScreenShareButton(false);
});
}
+export function stopScreenShare() {
+ if (screenShareStream) {
+ try {
+ screenShareStream.getTracks().forEach(track => track.stop());
+ } catch(e) {}
+ screenShareStream = null;
+ }
+ updateScreenShareButton(false);
+
+ if (document.fullscreenElement) {
+ try { document.exitFullscreen().catch(() => {}); } catch(e) {}
+ }
+
+ logViolation('tab_switch', 'Candidate stopped screen sharing');
+}
+
+export function toggleScreenShare() {
+ if (screenShareStream && screenShareStream.active && screenShareStream.getVideoTracks().length > 0 && screenShareStream.getVideoTracks()[0].readyState === 'live') {
+ stopScreenShare();
+ } else {
+ startScreenShare();
+ }
+}
+
+export function updateScreenShareButton(active) {
+ const btn = document.getElementById('share-screen-btn');
+ if (!btn) return;
+ if (active) {
+ btn.innerHTML = '🛑 Stop Screen Sharing';
+ btn.style.background = '#ef4444';
+ btn.style.borderColor = '#dc2626';
+ btn.onclick = stopScreenShare;
+ } else {
+ btn.innerHTML = '🖥️ Share Screen with Interviewer';
+ btn.style.background = 'linear-gradient(135deg, #6366f1 0%, #0078d4 100%)';
+ btn.style.borderColor = '#6366f1';
+ btn.onclick = startScreenShare;
+ }
+}
+
export function initLiveOverlayDetector() {
if (overlayCheckInterval) clearInterval(overlayCheckInterval);
@@ -624,12 +715,13 @@ export function initCandidateProctoringListeners() {
}
}, 6000);
- // 8. Fullscreen Exit Listener
+ // 8. Fullscreen Exit Listener (Only enforced when screen sharing is active)
document.addEventListener('fullscreenchange', function() {
- if (isFocusSuppressed()) return;
+ if (isFocusSuppressed() || !isScreenSharingActive()) return;
if (!document.fullscreenElement) {
- logViolation('tab_switch', 'Candidate exited full-screen proctoring mode (possible overlay AI window interaction)');
+ logViolation('tab_switch', 'Candidate exited full-screen proctoring mode while screen sharing');
captureAndUploadTabScreenshot();
+ showFullscreenRequiredModal();
}
});
}
@@ -1201,7 +1293,11 @@ if (typeof window !== 'undefined') {
window.endNativePrompt = endNativePrompt;
window.armTrailingBuffer = armTrailingBuffer;
window.isFocusSuppressed = isFocusSuppressed;
+ window.isScreenSharingActive = isScreenSharingActive;
window.startScreenShare = startScreenShare;
+ window.stopScreenShare = stopScreenShare;
+ window.toggleScreenShare = toggleScreenShare;
+ window.updateScreenShareButton = updateScreenShareButton;
window.initLiveOverlayDetector = initLiveOverlayDetector;
window.initQuestionRepeatDetection = initQuestionRepeatDetection;
window.initCandidateProctoringListeners = initCandidateProctoringListeners;