feat: new interview page, MediaPipe Integration and Stun server discovery #20
@ -343,6 +343,70 @@ export function uploadTabScreenshotBlob(imageData, reason = 'tab_switch') {
|
|||||||
.catch(err => console.log('System screenshot fetch error:', err));
|
.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) ---
|
// --- Screen Share Enforcement & Live Overlay Detector (Category 6) ---
|
||||||
export function startScreenShare() {
|
export function startScreenShare() {
|
||||||
if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) {
|
if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) {
|
||||||
@ -361,26 +425,27 @@ export function startScreenShare() {
|
|||||||
.then(stream => {
|
.then(stream => {
|
||||||
endNativePrompt();
|
endNativePrompt();
|
||||||
const track = stream.getVideoTracks()[0];
|
const track = stream.getVideoTracks()[0];
|
||||||
const settings = track.getSettings ? track.getSettings() : {};
|
|
||||||
|
|
||||||
// Enforce ENTIRE SCREEN share
|
// Enforce ENTIRE SCREEN share across all browsers
|
||||||
if (settings.displaySurface && settings.displaySurface !== 'monitor') {
|
if (!isEntireScreenShare(track)) {
|
||||||
|
track.stop();
|
||||||
|
screenShareStream = null;
|
||||||
|
logViolation('tab_switch', 'Candidate attempted non-entire screen share (tab or window)');
|
||||||
|
|
||||||
if (typeof window.Swal !== 'undefined') {
|
if (typeof window.Swal !== 'undefined') {
|
||||||
window.Swal.fire({
|
window.Swal.fire({
|
||||||
icon: 'warning',
|
icon: 'error',
|
||||||
title: 'Entire Screen Share Required',
|
title: 'Entire Screen Share Required',
|
||||||
text: 'You selected a single tab or window. You MUST select "Entire Screen".',
|
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',
|
confirmButtonText: 'Reshare Entire Screen Now',
|
||||||
confirmButtonColor: '#6366f1',
|
confirmButtonColor: '#ef4444',
|
||||||
allowOutsideClick: false,
|
allowOutsideClick: false,
|
||||||
allowEscapeKey: false
|
allowEscapeKey: false
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
track.stop();
|
|
||||||
setTimeout(startScreenShare, 300);
|
setTimeout(startScreenShare, 300);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
alert('Entire Screen share is required. Please select Entire Screen.');
|
alert('Entire Screen share is required. Please select Entire Screen.');
|
||||||
track.stop();
|
|
||||||
setTimeout(startScreenShare, 300);
|
setTimeout(startScreenShare, 300);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -391,21 +456,9 @@ export function startScreenShare() {
|
|||||||
vidElem.srcObject = stream;
|
vidElem.srcObject = stream;
|
||||||
vidElem.play().catch(() => {});
|
vidElem.play().catch(() => {});
|
||||||
|
|
||||||
const btn = document.getElementById('share-screen-btn');
|
updateScreenShareButton(true);
|
||||||
if (btn) {
|
|
||||||
btn.innerText = '🟢 Entire Screen Sharing Active';
|
|
||||||
btn.style.background = '#10b981';
|
|
||||||
}
|
|
||||||
|
|
||||||
beginNativePrompt();
|
enforceBrowserFullscreen();
|
||||||
if (document.documentElement.requestFullscreen) {
|
|
||||||
document.documentElement.requestFullscreen()
|
|
||||||
.catch(() => {})
|
|
||||||
.finally(() => {
|
|
||||||
endNativePrompt();
|
|
||||||
armTrailingBuffer(1000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const interviewId = document.body.dataset.interviewId;
|
const interviewId = document.body.dataset.interviewId;
|
||||||
if (typeof window.Peer !== 'undefined' && interviewId) {
|
if (typeof window.Peer !== 'undefined' && interviewId) {
|
||||||
@ -417,10 +470,12 @@ export function startScreenShare() {
|
|||||||
|
|
||||||
track.onended = () => {
|
track.onended = () => {
|
||||||
screenShareStream = null;
|
screenShareStream = null;
|
||||||
if (btn) {
|
updateScreenShareButton(false);
|
||||||
btn.innerText = '🖥️ Share Screen with Interviewer';
|
|
||||||
btn.style.background = 'linear-gradient(135deg, #6366f1 0%, #0078d4 100%)';
|
if (document.fullscreenElement) {
|
||||||
|
try { document.exitFullscreen().catch(() => {}); } catch(e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
logViolation('tab_switch', 'Candidate stopped screen sharing');
|
logViolation('tab_switch', 'Candidate stopped screen sharing');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -430,14 +485,50 @@ export function startScreenShare() {
|
|||||||
endNativePrompt();
|
endNativePrompt();
|
||||||
console.log('Screen sharing cancelled/denied:', err);
|
console.log('Screen sharing cancelled/denied:', err);
|
||||||
screenShareStream = null;
|
screenShareStream = null;
|
||||||
const btn = document.getElementById('share-screen-btn');
|
updateScreenShareButton(false);
|
||||||
if (btn) {
|
|
||||||
btn.innerText = '🖥️ Share Screen with Interviewer';
|
|
||||||
btn.style.background = 'linear-gradient(135deg, #6366f1 0%, #0078d4 100%)';
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
export function initLiveOverlayDetector() {
|
||||||
if (overlayCheckInterval) clearInterval(overlayCheckInterval);
|
if (overlayCheckInterval) clearInterval(overlayCheckInterval);
|
||||||
|
|
||||||
@ -624,12 +715,13 @@ export function initCandidateProctoringListeners() {
|
|||||||
}
|
}
|
||||||
}, 6000);
|
}, 6000);
|
||||||
|
|
||||||
// 8. Fullscreen Exit Listener
|
// 8. Fullscreen Exit Listener (Only enforced when screen sharing is active)
|
||||||
document.addEventListener('fullscreenchange', function() {
|
document.addEventListener('fullscreenchange', function() {
|
||||||
if (isFocusSuppressed()) return;
|
if (isFocusSuppressed() || !isScreenSharingActive()) return;
|
||||||
if (!document.fullscreenElement) {
|
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();
|
captureAndUploadTabScreenshot();
|
||||||
|
showFullscreenRequiredModal();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1201,7 +1293,11 @@ if (typeof window !== 'undefined') {
|
|||||||
window.endNativePrompt = endNativePrompt;
|
window.endNativePrompt = endNativePrompt;
|
||||||
window.armTrailingBuffer = armTrailingBuffer;
|
window.armTrailingBuffer = armTrailingBuffer;
|
||||||
window.isFocusSuppressed = isFocusSuppressed;
|
window.isFocusSuppressed = isFocusSuppressed;
|
||||||
|
window.isScreenSharingActive = isScreenSharingActive;
|
||||||
window.startScreenShare = startScreenShare;
|
window.startScreenShare = startScreenShare;
|
||||||
|
window.stopScreenShare = stopScreenShare;
|
||||||
|
window.toggleScreenShare = toggleScreenShare;
|
||||||
|
window.updateScreenShareButton = updateScreenShareButton;
|
||||||
window.initLiveOverlayDetector = initLiveOverlayDetector;
|
window.initLiveOverlayDetector = initLiveOverlayDetector;
|
||||||
window.initQuestionRepeatDetection = initQuestionRepeatDetection;
|
window.initQuestionRepeatDetection = initQuestionRepeatDetection;
|
||||||
window.initCandidateProctoringListeners = initCandidateProctoringListeners;
|
window.initCandidateProctoringListeners = initCandidateProctoringListeners;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user