Spaces:
Runtime error
Runtime error
Fix call signaling glare and duplicate sockets (v2.6.5)
Browse files- client.js +43 -10
- index.html +1 -1
- package.json +1 -1
client.js
CHANGED
|
@@ -24,6 +24,7 @@ let remoteCallPlaybackRetryBound = false;
|
|
| 24 |
let remoteCallPlaybackStarted = false;
|
| 25 |
let isStartingCallSession = false;
|
| 26 |
let pendingSignalOffer = null;
|
|
|
|
| 27 |
let hasUserInteractedWithPage = false;
|
| 28 |
let activeRoomSubscription = null;
|
| 29 |
let roomMessages = JSON.parse(localStorage.getItem('roomMessages') || '{}');
|
|
@@ -548,6 +549,16 @@ function switchAccount() {
|
|
| 548 |
|
| 549 |
|
| 550 |
function connectSocket() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 551 |
socket = io(SERVER_URL, {
|
| 552 |
reconnection: true,
|
| 553 |
reconnectionDelay: 1000,
|
|
@@ -947,6 +958,22 @@ function connectSocket() {
|
|
| 947 |
|
| 948 |
try {
|
| 949 |
if (signal.type === 'offer') {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 950 |
if (!activeCallState) {
|
| 951 |
activeCallState = { callId: payload?.callId || null, peerId: fromId, direction: 'incoming' };
|
| 952 |
}
|
|
@@ -6356,16 +6383,21 @@ async function startCallSession(peerName, peerId, isCaller) {
|
|
| 6356 |
createPeerConnection(peerId);
|
| 6357 |
|
| 6358 |
if (isCaller && peerConnection) {
|
| 6359 |
-
|
| 6360 |
-
|
| 6361 |
-
|
| 6362 |
-
|
| 6363 |
-
|
| 6364 |
-
|
| 6365 |
-
|
| 6366 |
-
|
| 6367 |
-
|
| 6368 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6369 |
}
|
| 6370 |
|
| 6371 |
// Handle an offer that arrived while this session was being set up
|
|
@@ -6514,6 +6546,7 @@ function cleanupCallSession(keepDialogOpen = false) {
|
|
| 6514 |
lastCallWarningAt = 0;
|
| 6515 |
isStartingCallSession = false;
|
| 6516 |
pendingSignalOffer = null;
|
|
|
|
| 6517 |
|
| 6518 |
if (localCallStream) {
|
| 6519 |
localCallStream.getTracks().forEach((track) => track.stop());
|
|
|
|
| 24 |
let remoteCallPlaybackStarted = false;
|
| 25 |
let isStartingCallSession = false;
|
| 26 |
let pendingSignalOffer = null;
|
| 27 |
+
let makingOffer = false;
|
| 28 |
let hasUserInteractedWithPage = false;
|
| 29 |
let activeRoomSubscription = null;
|
| 30 |
let roomMessages = JSON.parse(localStorage.getItem('roomMessages') || '{}');
|
|
|
|
| 549 |
|
| 550 |
|
| 551 |
function connectSocket() {
|
| 552 |
+
// Prevent multiple active socket connections/listeners that can duplicate signaling events.
|
| 553 |
+
if (socket) {
|
| 554 |
+
try {
|
| 555 |
+
socket.removeAllListeners();
|
| 556 |
+
socket.disconnect();
|
| 557 |
+
} catch (socketCleanupError) {
|
| 558 |
+
console.warn('Previous socket cleanup failed:', socketCleanupError);
|
| 559 |
+
}
|
| 560 |
+
}
|
| 561 |
+
|
| 562 |
socket = io(SERVER_URL, {
|
| 563 |
reconnection: true,
|
| 564 |
reconnectionDelay: 1000,
|
|
|
|
| 958 |
|
| 959 |
try {
|
| 960 |
if (signal.type === 'offer') {
|
| 961 |
+
const isPolitePeer = String(currentUser?.id || '') > String(fromId || '');
|
| 962 |
+
const hasPeer = !!peerConnection;
|
| 963 |
+
const offerCollision = hasPeer && (makingOffer || peerConnection.signalingState !== 'stable');
|
| 964 |
+
|
| 965 |
+
if (offerCollision) {
|
| 966 |
+
if (!isPolitePeer) {
|
| 967 |
+
console.warn('Ignoring offer collision (impolite peer)');
|
| 968 |
+
return;
|
| 969 |
+
}
|
| 970 |
+
|
| 971 |
+
// Polite peer: rollback local offer so remote offer can be accepted.
|
| 972 |
+
if (peerConnection.signalingState === 'have-local-offer') {
|
| 973 |
+
await peerConnection.setLocalDescription({ type: 'rollback' });
|
| 974 |
+
}
|
| 975 |
+
}
|
| 976 |
+
|
| 977 |
if (!activeCallState) {
|
| 978 |
activeCallState = { callId: payload?.callId || null, peerId: fromId, direction: 'incoming' };
|
| 979 |
}
|
|
|
|
| 6383 |
createPeerConnection(peerId);
|
| 6384 |
|
| 6385 |
if (isCaller && peerConnection) {
|
| 6386 |
+
makingOffer = true;
|
| 6387 |
+
try {
|
| 6388 |
+
const offer = await peerConnection.createOffer({
|
| 6389 |
+
offerToReceiveAudio: true,
|
| 6390 |
+
offerToReceiveVideo: true
|
| 6391 |
+
});
|
| 6392 |
+
await peerConnection.setLocalDescription(offer);
|
| 6393 |
+
socket.emit('video signal', {
|
| 6394 |
+
toId: peerId,
|
| 6395 |
+
callId: activeCallState?.callId,
|
| 6396 |
+
signal: { type: 'offer', offer }
|
| 6397 |
+
});
|
| 6398 |
+
} finally {
|
| 6399 |
+
makingOffer = false;
|
| 6400 |
+
}
|
| 6401 |
}
|
| 6402 |
|
| 6403 |
// Handle an offer that arrived while this session was being set up
|
|
|
|
| 6546 |
lastCallWarningAt = 0;
|
| 6547 |
isStartingCallSession = false;
|
| 6548 |
pendingSignalOffer = null;
|
| 6549 |
+
makingOffer = false;
|
| 6550 |
|
| 6551 |
if (localCallStream) {
|
| 6552 |
localCallStream.getTracks().forEach((track) => track.stop());
|
index.html
CHANGED
|
@@ -3048,7 +3048,7 @@
|
|
| 3048 |
<div class="chat-header-title" id="chatTitle">💬 Select a friend to chat</div>
|
| 3049 |
</div>
|
| 3050 |
<div class="chat-header-actions">
|
| 3051 |
-
<span class="build-badge mobile-essential" id="buildVersionBadge" title="Frontend build version">v2.6.
|
| 3052 |
<button class="header-btn mobile-essential" onclick="toggleArchiveChat()" title="Archive chat">
|
| 3053 |
<i class="fas fa-archive"></i>
|
| 3054 |
</button>
|
|
|
|
| 3048 |
<div class="chat-header-title" id="chatTitle">💬 Select a friend to chat</div>
|
| 3049 |
</div>
|
| 3050 |
<div class="chat-header-actions">
|
| 3051 |
+
<span class="build-badge mobile-essential" id="buildVersionBadge" title="Frontend build version">v2.6.5</span>
|
| 3052 |
<button class="header-btn mobile-essential" onclick="toggleArchiveChat()" title="Archive chat">
|
| 3053 |
<i class="fas fa-archive"></i>
|
| 3054 |
</button>
|
package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"name": "discord-like-app",
|
| 3 |
-
"version": "2.6.
|
| 4 |
"main": "server.js",
|
| 5 |
"scripts": {
|
| 6 |
"dev": "nodemon server.js",
|
|
|
|
| 1 |
{
|
| 2 |
"name": "discord-like-app",
|
| 3 |
+
"version": "2.6.5",
|
| 4 |
"main": "server.js",
|
| 5 |
"scripts": {
|
| 6 |
"dev": "nodemon server.js",
|