fix: enforce strict full screen while screen sharing
This commit is contained in:
parent
e48f193a64
commit
eba3ec96b3
@ -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.<br><br><b>You MUST select "Entire Screen" (or whole monitor)</b> 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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user