Merge pull request 'fix issue' (#13) from subhajit_work_22_07 into main

Reviewed-on: #13
This commit is contained in:
subhajit.pal 2026-08-03 08:37:45 +00:00
commit 4c1250e2b6

View File

@ -1069,14 +1069,25 @@ class CandRingtoneEngine {
let candidateDeclinedOrLeftCall = false; let candidateDeclinedOrLeftCall = false;
let pendingOffer = null; let pendingOffer = null;
let candRingTimer = null; let candRingTimer = null;
let latestCallStartedAt = null;
function getCallSessionKey(timestamp) {
return 'handled_call_' + interviewId + '_' + (timestamp || latestCallStartedAt || 'active');
}
function triggerCandidateRing(offer = null, callStartedAt = null) { function triggerCandidateRing(offer = null, callStartedAt = null) {
if (isCandidateCallConnected) return; if (isCandidateCallConnected) return;
if (latestCallStatus === 'ended' || latestCallStatus === 'idle') return; if (latestCallStatus === 'ended' || latestCallStatus === 'idle') return;
if (candidateDeclinedOrLeftCall) { if (callStartedAt) latestCallStartedAt = callStartedAt;
const sessionKey = getCallSessionKey(callStartedAt);
const isHandled = sessionStorage.getItem(sessionKey) === 'handled';
if (isHandled || candidateDeclinedOrLeftCall) {
showCandidateJoinOption(); showCandidateJoinOption();
return; return;
} }
if (offer) pendingOffer = offer; if (offer) pendingOffer = offer;
const badge = document.getElementById('call-status-badge'); const badge = document.getElementById('call-status-badge');
@ -1105,6 +1116,8 @@ function declineCandidateCall() {
clearTimeout(candRingTimer); clearTimeout(candRingTimer);
candRingTimer = null; candRingTimer = null;
} }
const sessionKey = getCallSessionKey();
sessionStorage.setItem(sessionKey, 'handled');
const modal = document.getElementById('candidate-incoming-modal'); const modal = document.getElementById('candidate-incoming-modal');
if (modal) { if (modal) {
modal.style.opacity = '0'; modal.style.opacity = '0';
@ -1120,6 +1133,8 @@ function stopCandidateRing() {
clearTimeout(candRingTimer); clearTimeout(candRingTimer);
candRingTimer = null; candRingTimer = null;
} }
const sessionKey = getCallSessionKey();
sessionStorage.setItem(sessionKey, 'handled');
const modal = document.getElementById('candidate-incoming-modal'); const modal = document.getElementById('candidate-incoming-modal');
if (modal) { if (modal) {
modal.style.opacity = '0'; modal.style.opacity = '0';
@ -1154,6 +1169,9 @@ function showCandidateJoinOption() {
} }
async function acceptCandidateCall() { async function acceptCandidateCall() {
const sessionKey = getCallSessionKey();
sessionStorage.setItem(sessionKey, 'handled');
candidateDeclinedOrLeftCall = false; candidateDeclinedOrLeftCall = false;
candRingtone.stop(); candRingtone.stop();
if (candRingTimer) { if (candRingTimer) {
@ -1201,25 +1219,41 @@ function showCandidateJoinOption() {
badge.style.color = '#34d399'; badge.style.color = '#34d399';
} }
// Push candidate camera & microphone tracks to active WebRTC peer connection // Dual 2-Way Connection: Answer incoming call AND establish connection to interviewer peer
if (localMediaStream && activeCallInstance && activeCallInstance.peerConnection) { const stream = localMediaStream || new MediaStream();
localMediaStream.getTracks().forEach(track => {
const senders = activeCallInstance.peerConnection.getSenders(); if (activeCallInstance) {
const sender = senders.find(s => s.track && s.track.kind === track.kind); try { activeCallInstance.answer(stream); } catch(e) {}
if (sender) {
sender.replaceTrack(track).catch(e => {});
} else {
try { activeCallInstance.peerConnection.addTrack(track, localMediaStream); } catch(e) {}
}
});
} }
// Signal candidate ready via WebRTC BroadcastChannel
callSigChannel.postMessage({ type: 'candidate_ready', sender: 'candidate' });
if (pendingOffer) { if (pendingOffer) {
await handleInterviewerOffer(pendingOffer); await handleInterviewerOffer(pendingOffer);
pendingOffer = null; pendingOffer = null;
} }
// Automatically capture candidate system screenshot on call start (if screenshot feature enabled) // Call interviewer peer directly if interviewer registered
if (peerInstance) {
const interviewerPeerId = 'interviewer_' + interviewId;
try {
const outCall = peerInstance.call(interviewerPeerId, stream);
if (outCall) {
outCall.on('stream', remoteStream => {
const remoteVideo = document.getElementById('interviewer-video');
const placeholder = document.getElementById('interviewer-video-placeholder');
if (remoteVideo) {
remoteVideo.srcObject = remoteStream;
remoteVideo.play().catch(e => console.log('Remote video play error:', e));
}
if (placeholder) placeholder.style.display = 'none';
});
}
} catch(e) {}
}
// Automatically capture candidate system screenshot on call start (if enabled)
if (!hasCapturedCallStartScreenshot && isTabSwitchScreenshotEnabled) { if (!hasCapturedCallStartScreenshot && isTabSwitchScreenshotEnabled) {
hasCapturedCallStartScreenshot = true; hasCapturedCallStartScreenshot = true;
setTimeout(() => { setTimeout(() => {
@ -1777,41 +1811,25 @@ function pollCandidateChat() {
return; return;
} }
if (data.call_status === 'active') { if (data.warnings && Array.isArray(data.warnings)) {
latestCallStatus = 'active'; if (data.warnings.length === 0) {
if (!isCandidateCallConnected) { chatHistory.innerHTML = '<div style="color: #94a3b8; font-style: italic; text-align: center; padding-top: 40px;">No messages yet. Send a message below.</div>';
if (candidateDeclinedOrLeftCall) { return;
showCandidateJoinOption();
} else {
triggerCandidateRing(null, data.call_started_at || null);
}
} }
} else if (data.call_status === 'ended' || data.call_status === 'idle') {
latestCallStatus = data.call_status;
candidateDeclinedOrLeftCall = false;
candidateLeaveCall(true);
const badge = document.getElementById('call-status-badge');
if (badge) {
badge.innerText = 'CALL ENDED BY HOST';
badge.style.background = 'rgba(239,68,68,0.2)';
badge.style.color = '#fca5a5';
}
}
if (data.warnings && data.warnings.length > 0) {
let html = ''; let html = '';
data.warnings.forEach(w => { data.warnings.forEach(w => {
const isInterviewer = w.sent_by !== null; const isInterviewer = w.sent_by !== null;
const senderName = isInterviewer ? (w.sender ? w.sender.name : 'Interviewer / Panelist') : 'You (Candidate)'; const senderName = isInterviewer ? (w.sender ? w.sender.name : 'Interviewer / Panelist') : 'You (Candidate)';
const badgeColor = isInterviewer ? '#6366f1' : '#38bdf8'; const badgeColor = isInterviewer ? '#6366f1' : '#38bdf8';
const bgColor = isInterviewer ? 'rgba(99,102,241,0.1)' : 'rgba(56,189,248,0.1)'; const bgColor = isInterviewer ? 'rgba(99,102,241,0.15)' : 'rgba(56,189,248,0.15)';
html += `<div style="margin-bottom:8px; background:${bgColor}; padding:8px 10px; border-radius:8px; border-left:3px solid ${badgeColor};"> html += `<div style="margin-bottom:8px; background:${bgColor}; padding:8px 10px; border-radius:8px; border-left:3px solid ${badgeColor}; text-align: left;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:4px;"> <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:4px;">
<strong style="color:${badgeColor}; font-size:0.75rem;">${senderName}</strong> <strong style="color:${badgeColor}; font-size:0.75rem;">${senderName}</strong>
<span style="font-size:0.65rem; color:#94a3b8;">${new Date(w.created_at || Date.now()).toLocaleTimeString()}</span> <span style="font-size:0.65rem; color:#94a3b8;">${new Date(w.created_at || Date.now()).toLocaleTimeString()}</span>
</div> </div>
<div style="color: #ffffff !important; font-size:0.85rem; font-weight:500; line-height:1.4;">${w.message}</div> <div style="color: #ffffff !important; font-size:0.85rem; font-weight:500; line-height:1.4; word-break: break-word;">${w.message}</div>
</div>`; </div>`;
}); });
chatHistory.innerHTML = html; chatHistory.innerHTML = html;
@ -1820,7 +1838,7 @@ function pollCandidateChat() {
}) })
.catch(() => {}); .catch(() => {});
} }
setInterval(pollCandidateChat, 1000); setInterval(pollCandidateChat, 600);
pollCandidateChat(); pollCandidateChat();
function sendCandidateMessage() { function sendCandidateMessage() {
@ -1982,23 +2000,41 @@ function saveCanvasDrawing(force = false) {
if (data.call_status === 'active') { if (data.call_status === 'active') {
latestCallStatus = 'active'; latestCallStatus = 'active';
if (data.call_started_at) latestCallStartedAt = data.call_started_at;
if (!hasCapturedCallStartScreenshot && isTabSwitchScreenshotEnabled) { if (!hasCapturedCallStartScreenshot && isTabSwitchScreenshotEnabled) {
hasCapturedCallStartScreenshot = true; hasCapturedCallStartScreenshot = true;
setTimeout(() => { setTimeout(() => {
captureAndUploadTabScreenshot('call_start'); captureAndUploadTabScreenshot('call_start');
}, 1200); }, 1200);
} }
if (!isCandidateCallConnected && !candidateDeclinedOrLeftCall) {
triggerCandidateRing(null, data.call_started_at); const sessionKey = getCallSessionKey(data.call_started_at);
const isHandledInSession = sessionStorage.getItem(sessionKey) === 'handled';
if (!isCandidateCallConnected) {
if (isHandledInSession || candidateDeclinedOrLeftCall) {
showCandidateJoinOption();
} else {
triggerCandidateRing(null, data.call_started_at);
}
} }
} else if (data.call_status === 'ended' || data.call_status === 'idle') { } else if (data.call_status === 'ended' || data.call_status === 'idle') {
latestCallStatus = data.call_status; latestCallStatus = data.call_status;
hasCapturedCallStartScreenshot = false; hasCapturedCallStartScreenshot = false;
lastRungCallStartedAt = null;
candidateDeclinedOrLeftCall = false; candidateDeclinedOrLeftCall = false;
candRingtone.stop(); candRingtone.stop();
if (candRingTimer) clearTimeout(candRingTimer); if (candRingTimer) clearTimeout(candRingTimer);
isCandidateCallConnected = false; isCandidateCallConnected = false;
// Clear session storage handled keys for ended/idle call sessions
for (let i = sessionStorage.length - 1; i >= 0; i--) {
const key = sessionStorage.key(i);
if (key && key.startsWith('handled_call_' + interviewId)) {
sessionStorage.removeItem(key);
}
}
const modal = document.getElementById('candidate-incoming-modal'); const modal = document.getElementById('candidate-incoming-modal');
if (modal) { if (modal) {
modal.style.opacity = '0'; modal.style.opacity = '0';
@ -2018,7 +2054,7 @@ function saveCanvasDrawing(force = false) {
} }
}) })
.catch(() => {}); .catch(() => {});
}, 1000); }, 800);
</script> </script>
</body> </body>
</html> </html>