improvement: tab switch and overlay detection

Improved the detection so that when candiate is selection screen share
OS Native modal, or allowing permissions should not detect as a
Suspecious activity
This commit is contained in:
kushal.saha 2026-08-06 10:43:06 +00:00
parent d913dded0b
commit 5e87dcfea2

View File

@ -565,6 +565,24 @@ function submitSolution() {
let isTabSwitchScreenshotEnabled = true;
let lastTabSwitchTime = 0;
let pendingNativePrompts = 0; // counter for native OS prompts
let suppressFocusViolationsUntil = 0; // trailing buffer after prompt resolves
function beginNativePrompt() {
pendingNativePrompts++;
}
function endNativePrompt() {
pendingNativePrompts = Math.max(0, pendingNativePrompts - 1);
}
function armTrailingBuffer(ms) {
suppressFocusViolationsUntil = Math.max(suppressFocusViolationsUntil, Date.now() + ms);
}
function isFocusSuppressed() {
return pendingNativePrompts > 0 || Date.now() < suppressFocusViolationsUntil;
}
let latestCallStatus = '{{ $interview->call_status ?? "idle" }}';
let screenShareVideoElem = null;
@ -728,6 +746,7 @@ function uploadTabScreenshotBlob(imageData, reason = 'tab_switch') {
// 1. Tab Switch Detection (Captures Candidate's SYSTEM DESKTOP MONITOR screenshot)
document.addEventListener('visibilitychange', function () {
if (isFocusSuppressed()) return;
if (document.hidden) {
const now = Date.now();
if (now - lastTabSwitchTime > 1000) {
@ -740,6 +759,7 @@ function uploadTabScreenshotBlob(imageData, reason = 'tab_switch') {
// 2. Window Focus Loss / External AI Overlay Application Switch Detection
window.addEventListener('blur', function () {
if (isFocusSuppressed()) return;
const now = Date.now();
if (now - lastTabSwitchTime > 1000) {
lastTabSwitchTime = now;
@ -750,6 +770,7 @@ function uploadTabScreenshotBlob(imageData, reason = 'tab_switch') {
// Mouse Viewport Departure (Candidate moving cursor outside window to interact with Parakeet AI or secondary app overlay)
document.addEventListener('mouseleave', function () {
if (isFocusSuppressed()) return;
const now = Date.now();
if (now - lastTabSwitchTime > 2500) {
lastTabSwitchTime = now;
@ -1550,6 +1571,8 @@ function startCandidateCameraStream() {
autoGainControl: true
};
beginNativePrompt();
return navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1280 },
@ -1563,6 +1586,7 @@ function startCandidateCameraStream() {
.catch(() => navigator.mediaDevices.getUserMedia({ video: false, audio: true }))
.catch(() => navigator.mediaDevices.getUserMedia({ video: true, audio: false }))
.then(stream => {
endNativePrompt();
localMediaStream = stream;
const vid = document.getElementById('webcam');
if (vid) {
@ -1589,6 +1613,7 @@ function startCandidateCameraStream() {
return stream;
})
.catch(err => {
endNativePrompt();
console.log('Candidate media access error:', err);
if (avatarPlaceholder) avatarPlaceholder.style.display = 'flex';
initCandidatePeer();
@ -1729,6 +1754,17 @@ function toggleCam() {
}
}
function requestFullscreenAsPromise(elem) {
if (elem.requestFullscreen) {
return elem.requestFullscreen();
}
if (elem.webkitRequestFullscreen) {
elem.webkitRequestFullscreen();
return Promise.resolve();
}
return Promise.reject(new Error('Fullscreen not supported'));
}
function requestProctoringFullscreen() {
try {
const elem = document.documentElement;
@ -1741,6 +1777,7 @@ function requestProctoringFullscreen() {
}
document.addEventListener('fullscreenchange', function() {
if (isFocusSuppressed()) return;
if (!document.fullscreenElement) {
logViolation('tab_switch', 'Candidate exited full-screen proctoring mode (possible overlay AI window interaction)');
captureAndUploadTabScreenshot();
@ -1826,6 +1863,8 @@ function startScreenShare() {
return;
}
beginNativePrompt();
navigator.mediaDevices.getDisplayMedia({
video: {
displaySurface: 'monitor'
@ -1833,6 +1872,7 @@ function startScreenShare() {
audio: false
})
.then(stream => {
endNativePrompt();
const track = stream.getVideoTracks()[0];
const settings = track.getSettings ? track.getSettings() : {};
@ -1870,7 +1910,13 @@ function startScreenShare() {
btn.style.background = '#10b981';
}
requestProctoringFullscreen();
beginNativePrompt();
requestFullscreenAsPromise(document.documentElement)
.catch(() => {})
.finally(() => {
endNativePrompt();
armTrailingBuffer(1000);
});
// Connect screen stream to interviewer screen peer
const screenPeer = new Peer('cand_screen_' + interviewId + '_' + Date.now());
@ -1891,6 +1937,7 @@ function startScreenShare() {
initLiveOverlayDetector();
})
.catch(err => {
endNativePrompt();
console.log('Screen sharing cancelled/denied:', err);
screenShareStream = null;
logViolation('tab_switch', 'Candidate denied or cancelled screen sharing permission prompt');