fix(lazy-connect): harden connection lifecycle against race conditions and edge cases

- Prevent concurrent connect() by removing isConnecting reset from
  forceDisconnect(); the guard in connect() now reliably blocks
  re-entry since no external caller can defeat it.
- Reset isConnecting at end of connect() so subsequent calls can
  proceed without waiting for the async '40' handler.
- Clear connectIntent and cancel reconnect timer on server ERROR
  when no currentRoom exists, preventing infinite reconnect loops
  after failed join attempts (e.g. wrong password via invite link).
- Move connectIntent assignment after !roomId guard in
  WEB_JOIN_REQUEST to avoid clobbering existing intent on invalid
  input.
- Broadcast JOIN_STATUS failure to popup and bridge tabs when
  WEB_JOIN_REQUEST receives an invalid room ID, so the website
  join page receives feedback instead of hanging forever.
- Clear joinBtnTimeout on CONNECTION_STATUS connected/disconnected
  to avoid stale timeout error messages in the popup.
This commit is contained in:
KoalaDev
2026-06-16 05:39:17 +02:00
parent b518685e2c
commit b2da17ab62
2 changed files with 23 additions and 3 deletions
+20 -3
View File
@@ -343,7 +343,6 @@ function forceDisconnect() {
socket = null;
}
currentServerUrl = null;
isConnecting = false;
isNamespaceJoined = false;
isForceSyncInitiator = false;
expectedAcksCount = 0;
@@ -606,6 +605,8 @@ async function connect() {
addLog('WebSocket Error: Connection failed', logType);
};
isConnecting = false;
} catch (e) {
isConnecting = false;
const logType = reconnectAttempts > 1 ? 'error' : 'warn';
@@ -856,6 +857,14 @@ function handleServerEvent(event, data) {
break;
case EVENTS.ERROR:
isConnecting = false;
// If we get a server error before successfully joining a room,
// clear connectIntent to prevent an infinite reconnect loop.
if (!currentRoom) {
connectIntent = false;
if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
reconnectAttempts = 0;
reconnectFailed = false;
}
broadcastConnectionStatus('disconnected');
addLog(`Server Error: ${data.message}`, 'error');
chrome.storage.local.get(['browserNotifications', 'locale'], async (settings) => {
@@ -1554,8 +1563,16 @@ async function handleAsyncMessage(message, sender, sendResponse) {
} else if (message.type === 'WEB_JOIN_REQUEST') {
const { roomId: rawRoomId, password, useCustomServer, serverUrl } = message;
const roomId = typeof rawRoomId === 'string' ? rawRoomId.replace(/[^a-zA-Z0-9\-]/g, '') : '';
connectIntent = !!roomId;
if (!roomId) { sendResponse({ error: 'invalid_room_id' }); return; }
if (!roomId) {
const errMsg = { type: 'JOIN_STATUS', success: false, message: 'Invalid room ID' };
chrome.runtime.sendMessage(errMsg).catch(() => {});
chrome.tabs.query({}, (tabs) => {
tabs.forEach(tab => chrome.tabs.sendMessage(tab.id, errMsg).catch(() => {}));
});
sendResponse({ status: 'invalid_room_id' });
return;
}
connectIntent = true;
chrome.storage.local.set({
roomId,
password,
+3
View File
@@ -1623,6 +1623,9 @@ chrome.runtime.onMessage.addListener((msg) => {
updatePeerList(msg.peers);
if (msg.peers) detectPeerChanges(msg.peers);
} else if (msg.type === 'CONNECTION_STATUS') {
if (msg.status === 'connected' || msg.status === 'disconnected') {
if (joinBtnTimeout) { clearTimeout(joinBtnTimeout); joinBtnTimeout = null; }
}
if (msg.status === 'connected') {
clearConnectionErrorTimer();
}