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:
parent
57d4c0ff51
commit
686cebe577
@ -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');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user