From 5e87dcfea2c02e6afee81bdd1991bbb6a848e353 Mon Sep 17 00:00:00 2001 From: "kushal.saha" Date: Thu, 6 Aug 2026 10:43:06 +0000 Subject: [PATCH] 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 --- .../views/interview/candidate_room.blade.php | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/resources/views/interview/candidate_room.blade.php b/resources/views/interview/candidate_room.blade.php index 8ed5696..585b877 100644 --- a/resources/views/interview/candidate_room.blade.php +++ b/resources/views/interview/candidate_room.blade.php @@ -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');