From 9e7d8a8d37103e35896b21f3cd065aa96166f1ec Mon Sep 17 00:00:00 2001 From: Shahanur Islam Shagor Date: Wed, 19 Aug 2026 11:09:12 +0300 Subject: [PATCH 01/17] Reuse warm Redis connection for realtime paths --- backend/server/helpers/socketAdapter.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/backend/server/helpers/socketAdapter.js b/backend/server/helpers/socketAdapter.js index eb7b7149..67948f66 100644 --- a/backend/server/helpers/socketAdapter.js +++ b/backend/server/helpers/socketAdapter.js @@ -44,6 +44,15 @@ const configureSocketAdapter = async (io = global.io) => { return adapterPromise; }; +const getSocketRedisCommandClient = async () => { + if (!isRedisConfigured()) return null; + if (redisClients?.pubClient?.isReady) return redisClients.pubClient; + if (!adapterPromise) return null; + + const clients = await adapterPromise; + return clients?.pubClient?.isReady ? clients.pubClient : null; +}; + const closeSocketAdapter = async () => { const clients = redisClients; redisClients = null; @@ -58,6 +67,7 @@ const closeSocketAdapter = async () => { module.exports = { configureSocketAdapter, + getSocketRedisCommandClient, closeSocketAdapter, isRedisConfigured, }; From 2e9d8753c5bef4033a0a010ad617b8dbdc3ab88c Mon Sep 17 00:00:00 2001 From: Shahanur Islam Shagor Date: Wed, 19 Aug 2026 11:09:41 +0300 Subject: [PATCH 02/17] Remove first-message Redis cold start --- backend/server/helpers/chatAbuse.js | 31 +++++++++++------------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/backend/server/helpers/chatAbuse.js b/backend/server/helpers/chatAbuse.js index e0f581a4..34d14d54 100644 --- a/backend/server/helpers/chatAbuse.js +++ b/backend/server/helpers/chatAbuse.js @@ -1,9 +1,11 @@ const crypto = require('crypto'); -const { createClient } = require('redis'); const logger = require('./logger'); +const { + getSocketRedisCommandClient, + isRedisConfigured, +} = require('./socketAdapter'); const localWindows = new Map(); -let redisPromise = null; const MAX_MESSAGES = Math.max(5, Number(process.env.CHAT_RATE_LIMIT_MESSAGES || 30)); const WINDOW_SECONDS = Math.max(5, Number(process.env.CHAT_RATE_LIMIT_WINDOW_SEC || 10)); @@ -11,23 +13,14 @@ const MAX_DUPLICATES = Math.max(2, Number(process.env.CHAT_DUPLICATE_LIMIT || 6) const DUPLICATE_WINDOW_SECONDS = Math.max(10, Number(process.env.CHAT_DUPLICATE_WINDOW_SEC || 30)); const getRedis = async () => { - const url = String(process.env.REDIS_URL || '').trim(); - if (!url) return null; - if (!redisPromise) { - redisPromise = (async () => { - const client = createClient({ url }); - client.on('error', (error) => { - logger.warn('CHAT_ABUSE_REDIS_ERROR', { message: error.message }); - }); - await client.connect(); - return client; - })().catch((error) => { - redisPromise = null; - logger.warn('CHAT_ABUSE_REDIS_CONNECT_FAILED', { message: error.message }); - return null; - }); - } - return redisPromise; + if (!isRedisConfigured()) return null; + const client = await getSocketRedisCommandClient(); + if (client) return client; + + logger.warn('CHAT_ABUSE_REDIS_NOT_READY', { + message: 'Shared Redis runtime is not ready; using local rate limits for this request', + }); + return null; }; const createRateError = (message, code) => { From cf0f35e0320280950877cb0d714ef9b4ed80eed7 Mon Sep 17 00:00:00 2001 From: Shahanur Islam Shagor Date: Wed, 19 Aug 2026 11:10:12 +0300 Subject: [PATCH 03/17] Reuse warm Redis for call state --- backend/server/helpers/callState.js | 31 ++++++++++------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/backend/server/helpers/callState.js b/backend/server/helpers/callState.js index 4dbcd821..48811828 100644 --- a/backend/server/helpers/callState.js +++ b/backend/server/helpers/callState.js @@ -1,5 +1,8 @@ -const { createClient } = require('redis'); const logger = require('./logger'); +const { + getSocketRedisCommandClient, + isRedisConfigured, +} = require('./socketAdapter'); const DEFAULT_TTL_SEC = 24 * 60 * 60; const MIN_TTL_SEC = 5 * 60; @@ -12,10 +15,8 @@ const clampTtl = (value) => { }; const stateTtlSec = clampTtl(process.env.CALL_STATE_TTL_SEC); -const redisUrl = String(process.env.REDIS_URL || '').trim(); const production = String(process.env.NODE_ENV || '').toLowerCase() === 'production'; -let redisPromise = null; let warnedFallback = false; const memory = new Map(); @@ -60,7 +61,7 @@ const memoryDelete = (key) => { }; const getRedis = async () => { - if (!redisUrl) { + if (!isRedisConfigured()) { if (production) { const error = new Error( 'REDIS_URL is required for durable production call state' @@ -77,24 +78,12 @@ const getRedis = async () => { return null; } - if (redisPromise) return redisPromise; + const client = await getSocketRedisCommandClient(); + if (client) return client; - redisPromise = (async () => { - const client = createClient({ url: redisUrl }); - client.on('error', (error) => { - logger.error('CALL_STATE_REDIS_ERROR', { message: error.message }); - }); - await client.connect(); - logger.info('CALL_STATE_REDIS_READY', { - ttlSec: stateTtlSec, - }); - return client; - })().catch((error) => { - redisPromise = null; - throw error; - }); - - return redisPromise; + const error = new Error('Shared Redis runtime is not ready for call state'); + error.code = 'CALL_STATE_REDIS_NOT_READY'; + throw error; }; const encodeState = (state, ttlSec = stateTtlSec) => ({ From 9532f2b9e1dcb07af32a269537f80e19c4ba92ad Mon Sep 17 00:00:00 2001 From: Shahanur Islam Shagor Date: Wed, 19 Aug 2026 11:10:25 +0300 Subject: [PATCH 04/17] Prewarm realtime dependencies at startup --- backend/server/bootstrap.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/backend/server/bootstrap.js b/backend/server/bootstrap.js index b55bbc42..3d7fc6e8 100644 --- a/backend/server/bootstrap.js +++ b/backend/server/bootstrap.js @@ -3,6 +3,7 @@ const { startScheduledMessageWorker, } = require('./helpers/scheduledMessages'); const { configureSocketAdapter } = require('./helpers/socketAdapter'); +const { getCallConfig } = require('./helpers/callConfig'); const ensureAvatarDefaults = require('./helpers/ensureAvatarDefaults'); const ensureChatIndexes = require('./helpers/chatIndexes'); const logger = require('./helpers/logger'); @@ -14,13 +15,17 @@ const bootstrap = ({ startScheduledWorker = true } = {}) => { if (!bootstrapPromise) { bootstrapPromise = (async () => { await connectDb(); - await ensureChatIndexes(); - await ensureAvatarDefaults(); - await configureSocketAdapter(global.io); + await Promise.all([ + ensureChatIndexes(), + ensureAvatarDefaults(), + configureSocketAdapter(global.io), + getCallConfig(), + ]); logger.info('RUNTIME_READY', { vercel: process.env.VERCEL === '1', redis: Boolean(process.env.REDIS_URL), + realtimeWarm: true, }); return true; From bbfe9c9f499631a3612e2f96036141c542b7849e Mon Sep 17 00:00:00 2001 From: Shahanur Islam Shagor Date: Wed, 19 Aug 2026 11:10:39 +0300 Subject: [PATCH 05/17] Connect realtime transport earlier and reconnect faster --- frontend/client/helpers/socket.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/frontend/client/helpers/socket.js b/frontend/client/helpers/socket.js index b8a7899b..88f09cb9 100644 --- a/frontend/client/helpers/socket.js +++ b/frontend/client/helpers/socket.js @@ -1,16 +1,26 @@ import { io } from 'socket.io-client'; import config from '../config'; +const readToken = () => { + try { + return localStorage.getItem('token') || ''; + } catch (error0) { + return ''; + } +}; + const socket = io(config.socketUrl, { path: '/socket.io', transports: ['websocket'], - autoConnect: false, + autoConnect: Boolean(readToken()), reconnection: true, - reconnectionAttempts: 10, - reconnectionDelay: 1000, - reconnectionDelayMax: 5000, + reconnectionAttempts: Infinity, + reconnectionDelay: 250, + reconnectionDelayMax: 1500, + randomizationFactor: 0.25, + timeout: 8000, auth(callback) { - callback({ token: localStorage.getItem('token') || '' }); + callback({ token: readToken() }); }, }); From d7355893c61f71f6d1904b2193f7c59fc584f0be Mon Sep 17 00:00:00 2001 From: Shahanur Islam Shagor Date: Wed, 19 Aug 2026 11:11:11 +0300 Subject: [PATCH 06/17] Shorten message send critical path --- backend/server/helpers/chatReliability.js | 65 +++++++++++++---------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/backend/server/helpers/chatReliability.js b/backend/server/helpers/chatReliability.js index 85537d33..80aedefb 100644 --- a/backend/server/helpers/chatReliability.js +++ b/backend/server/helpers/chatReliability.js @@ -91,15 +91,34 @@ const wrapReliableChatInsert = (socket) => { return; } - await assertChatSendAllowed({ userId: socket.userId, text: args.text || '' }); - const sequence = await nextSequence(args.roomId); - const mentions = await resolveMentions({ + // Mention lookup can run while the warm Redis abuse guard executes. + const mentionsPromise = resolveMentions({ text: args.text || '', roomId: args.roomId, roomType: args.roomType, senderId: socket.userId, }); + await assertChatSendAllowed({ userId: socket.userId, text: args.text || '' }); + const [sequence, mentions] = await Promise.all([ + nextSequence(args.roomId), + mentionsPromise, + ]); + + const topicId = args.topicId || null; + const e2eeEnvelope = args.e2eeEnvelope && typeof args.e2eeEnvelope === 'object' + ? args.e2eeEnvelope + : null; + const transcript = String(args.transcript || '').slice(0, 8000); + + // Persist reliability metadata in the original create instead of doing a + // second database update after the message has already been written. + args.sequence = sequence; + args.mentionUserIds = mentions.mentionedUserIds; + args.topicId = topicId; + args.e2eeEnvelope = e2eeEnvelope; + args.transcript = transcript; + await original(args); const created = await findCreatedChat({ @@ -119,19 +138,21 @@ const wrapReliableChatInsert = (socket) => { const inbox = await InboxModel.findOne({ where: { roomId: args.roomId } }); const ownerIds = asArray(toPlain(inbox)?.ownersId || args.ownersId); - const topicId = args.topicId || null; - const e2eeEnvelope = args.e2eeEnvelope && typeof args.e2eeEnvelope === 'object' - ? args.e2eeEnvelope - : null; - const transcript = String(args.transcript || '').slice(0, 8000); - await created.update({ - clientMessageId: args.clientMessageId, - sequence, - mentionUserIds: mentions.mentionedUserIds, - topicId, - e2eeEnvelope, - transcript, + // Ack as soon as the durable chat and room ownership are confirmed. + // Message-request and mention side effects should not hold the sender UI. + emitMeta({ + socket, + chat: toPlain(created), + meta: { + clientMessageId: args.clientMessageId, + sequence, + mentionUserIds: mentions.mentionedUserIds, + topicId, + e2eeEnvelope, + transcript, + ownerIds, + }, }); if (args.roomType === 'private' && ownerIds.length === 2) { @@ -163,20 +184,6 @@ const wrapReliableChatInsert = (socket) => { }); }); } - - emitMeta({ - socket, - chat: toPlain(created), - meta: { - clientMessageId: args.clientMessageId, - sequence, - mentionUserIds: mentions.mentionedUserIds, - topicId, - e2eeEnvelope, - transcript, - ownerIds, - }, - }); } catch (error0) { logger.warn('CHAT_RELIABILITY_REJECTED', { userId: socket.userId, From 6a658587fb13c7036192319c0924f7f2a2b09213 Mon Sep 17 00:00:00 2001 From: Shahanur Islam Shagor Date: Wed, 19 Aug 2026 11:11:39 +0300 Subject: [PATCH 07/17] Add low-latency call start guard and catch-up --- backend/server/socket/events/callRealtime.js | 94 ++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 backend/server/socket/events/callRealtime.js diff --git a/backend/server/socket/events/callRealtime.js b/backend/server/socket/events/callRealtime.js new file mode 100644 index 00000000..c493de9c --- /dev/null +++ b/backend/server/socket/events/callRealtime.js @@ -0,0 +1,94 @@ +const { getActiveCallByRoom } = require('../../helpers/callState'); +const logger = require('../../helpers/logger'); + +const START_DEDUPE_MS = 15000; + +const attachCallRealtimeEvents = (socket) => { + if (!socket || socket.__syncchatCallRealtime) return; + socket.__syncchatCallRealtime = true; + + const listeners = socket.listeners('call/start'); + if (listeners.length) { + const original = listeners[0]; + const recentStarts = new Map(); + socket.removeAllListeners('call/start'); + + socket.on('call/start', async (rawArgs = {}) => { + const args = rawArgs && typeof rawArgs === 'object' ? rawArgs : {}; + const fromUserId = socket.userId || args.fromUserId; + const roomId = String(args.roomId || ''); + const mediaType = args.mediaType === 'video' ? 'video' : 'audio'; + if (!roomId || !fromUserId) return; + + const key = `${roomId}:${fromUserId}:${mediaType}`; + const now = Date.now(); + const previous = Number(recentStarts.get(key) || 0); + if (previous && now - previous < START_DEDUPE_MS) { + const current = await getActiveCallByRoom(roomId).catch(() => null); + if (current?.initiatorId === fromUserId) { + socket.emit('call/started', { + callId: current.callId, + roomId: current.roomId, + roomType: current.roomType, + mediaType: current.mediaType, + }); + } + return; + } + + recentStarts.set(key, now); + for (const [entryKey, timestamp] of recentStarts.entries()) { + if (now - timestamp >= START_DEDUPE_MS) recentStarts.delete(entryKey); + } + + await original({ + ...args, + fromUserId, + mediaType, + }); + }); + + listeners.slice(1).forEach((listener) => socket.on('call/start', listener)); + } + + // When an outgoing call is signalled before getUserMedia finishes, a very + // fast recipient can join first. Once the initiator's media is ready and it + // joins, replay already-joined recipients so WebRTC offer creation cannot be + // missed because of that race. + socket.on('call/join', async ({ callId, roomId, userId, mediaType }) => { + const joiningUserId = socket.userId || userId; + if ((!callId && !roomId) || !joiningUserId) return; + + try { + // Let the primary calling handler process the same join event first. + await new Promise((resolve) => setTimeout(resolve, 0)); + const state = roomId + ? await getActiveCallByRoom(roomId) + : null; + if (!state || state.initiatorId !== joiningUserId) return; + + const joinedRecipients = (Array.isArray(state.joinedUserIds) + ? state.joinedUserIds + : [] + ).filter((id) => id && id !== joiningUserId); + + joinedRecipients.forEach((joinedUserId) => { + socket.emit('call/user-joined', { + callId: state.callId, + roomId: state.roomId, + userId: joinedUserId, + mediaType: mediaType === 'video' ? 'video' : state.mediaType, + catchUp: true, + }); + }); + } catch (error0) { + logger.warn('CALL_REALTIME_CATCHUP_ERROR', { + roomId: roomId || null, + userId: joiningUserId, + message: error0.message, + }); + } + }); +}; + +module.exports = attachCallRealtimeEvents; From d59fe497f113bc690c80f73cec60c6a31c45ad8a Mon Sep 17 00:00:00 2001 From: Shahanur Islam Shagor Date: Wed, 19 Aug 2026 11:11:48 +0300 Subject: [PATCH 08/17] Attach low-latency call coordination --- backend/server/socket/events/room.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/server/socket/events/room.js b/backend/server/socket/events/room.js index aa952957..e4ef2dc6 100644 --- a/backend/server/socket/events/room.js +++ b/backend/server/socket/events/room.js @@ -1,5 +1,6 @@ const { io } = global; const attachCallingEvents = require('./calling'); +const attachCallRealtimeEvents = require('./callRealtime'); const attachBackgroundCallingEvents = require('./backgroundCalling'); const attachGroupModerationEvents = require('./groupModeration'); @@ -11,6 +12,7 @@ module.exports = (socket) => { }); attachCallingEvents(socket); + attachCallRealtimeEvents(socket); attachBackgroundCallingEvents(socket); attachGroupModerationEvents(socket); }; From 83fd69d6ed363bea6f62c420053f4b3b5de7bd5e Mon Sep 17 00:00:00 2001 From: Shahanur Islam Shagor Date: Wed, 19 Aug 2026 11:12:18 +0300 Subject: [PATCH 09/17] Add instant optimistic message delivery layer --- frontend/client/helpers/realtimeDelivery.js | 82 +++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 frontend/client/helpers/realtimeDelivery.js diff --git a/frontend/client/helpers/realtimeDelivery.js b/frontend/client/helpers/realtimeDelivery.js new file mode 100644 index 00000000..bce3ea90 --- /dev/null +++ b/frontend/client/helpers/realtimeDelivery.js @@ -0,0 +1,82 @@ +import { v4 as uuidv4 } from 'uuid'; +import socket from './socket'; +import store from '../redux/store'; + +let installed = false; + +const emitWindowEvent = (name, detail) => { + window.dispatchEvent(new CustomEvent(name, { detail })); +}; + +const createClientMessageId = () => + window.crypto?.randomUUID?.() || uuidv4(); + +const buildOptimisticMessage = (source) => { + const master = store.getState()?.user?.master || null; + const now = new Date().toISOString(); + const clientMessageId = source.clientMessageId; + + return { + ...source, + _id: `pending:${clientMessageId}`, + clientMessageId, + userId: master?._id || source.userId || '', + text: String(source.text || ''), + profile: master + ? { + userId: master._id, + fullname: master.fullname || '', + username: master.username || '', + avatar: master.avatar || 'assets/images/default-avatar.png', + } + : null, + file: source.file || null, + deletedBy: [], + delivered: false, + readed: false, + reactions: {}, + pending: true, + sendFailed: false, + createdAt: now, + updatedAt: now, + }; +}; + +const installRealtimeDelivery = () => { + if (installed) return; + installed = true; + + // chatTransportV2 is installed first. Wrap its reliability-aware emit so we + // can show a local message immediately while preserving its durable outbox. + const reliableEmit = socket.emit.bind(socket); + // eslint-disable-next-line no-param-reassign + socket.emit = (event, ...args) => { + if (event !== 'chat/insert') return reliableEmit(event, ...args); + + const source = + args[0] && typeof args[0] === 'object' ? { ...args[0] } : {}; + source.clientMessageId = + String(source.clientMessageId || '').trim() || createClientMessageId(); + + if (String(source.text || '').length > 0 || source.file) { + emitWindowEvent( + 'syncchat:optimistic-message', + buildOptimisticMessage(source) + ); + } + + return reliableEmit(event, source, ...args.slice(1)); + }; + + socket.on('chat/insert', (chat) => { + if (!chat?._id || !chat?.roomId) return; + emitWindowEvent('syncchat:message-confirmed', chat); + }); + + socket.on('chat/ack', (payload = {}) => { + if (payload.accepted !== false || !payload.clientMessageId) return; + emitWindowEvent('syncchat:optimistic-message-failed', payload); + }); +}; + +export default installRealtimeDelivery; From 55f089d777963ef42c2fc2b862f4d3fdf499ac7b Mon Sep 17 00:00:00 2001 From: Shahanur Islam Shagor Date: Wed, 19 Aug 2026 11:13:00 +0300 Subject: [PATCH 10/17] Render sent messages immediately and reconcile delivery --- frontend/client/containers/chat/room.jsx | 135 ++++++++++++++++++++++- 1 file changed, 130 insertions(+), 5 deletions(-) diff --git a/frontend/client/containers/chat/room.jsx b/frontend/client/containers/chat/room.jsx index c3fd9fcf..aa8083e9 100644 --- a/frontend/client/containers/chat/room.jsx +++ b/frontend/client/containers/chat/room.jsx @@ -49,6 +49,27 @@ function Room() { } }; + const mergePendingRows = (serverRows, currentRows) => { + const rows = Array.isArray(serverRows) ? serverRows : []; + const pending = (currentRows || []).filter( + (item) => item?.pending || item?.sendFailed + ); + if (!pending.length) return rows; + + const serverIds = new Set(rows.map((item) => item?._id).filter(Boolean)); + const serverClientIds = new Set( + rows.map((item) => item?.clientMessageId).filter(Boolean) + ); + return [ + ...rows, + ...pending.filter( + (item) => + !serverIds.has(item?._id) && + !serverClientIds.has(item?.clientMessageId) + ), + ]; + }; + const handleGetChats = async (signal) => { try { const { data } = await axios.get(`/chats/${chatRoom.data.roomId}`, { @@ -57,11 +78,11 @@ function Room() { }); if (data.payload.length > 0) { - setChats(data.payload); + setChats((prev) => mergePendingRows(data.payload, prev)); const callback = (mutationlist, observer) => { const monitor = document.querySelector('#monitor'); - monitor.scrollTop = monitor.scrollHeight; + if (monitor) monitor.scrollTop = monitor.scrollHeight; setLoaded(true); @@ -71,14 +92,16 @@ function Room() { const observer = new MutationObserver(callback); const elem = document.querySelector('#monitor-content'); - observer.observe(elem, { childList: true }); + if (elem) observer.observe(elem, { childList: true }); + else setLoaded(true); return; } + setChats((prev) => mergePendingRows([], prev)); setLoaded(true); } catch (error0) { - console.error(error0.response.data.message); + console.error(error0?.response?.data?.message || error0.message); } }; @@ -149,7 +172,9 @@ function Room() { params: { skip: 0, limit: control.limit }, signal: abortCtrl.signal, }); - setChats(Array.isArray(data?.payload) ? data.payload : []); + setChats((prev) => + mergePendingRows(Array.isArray(data?.payload) ? data.payload : [], prev) + ); } catch (error0) { console.error(error0?.response?.data?.message || error0.message); } finally { @@ -166,6 +191,106 @@ function Room() { }; }, [chatRoom?.data?.roomId, control.limit]); + useEffect(() => { + const roomId = chatRoom?.data?.roomId; + if (!roomId) return undefined; + + const appendWithLimit = (list, payload) => { + const current = Array.isArray(list) ? list : []; + if (current.length >= control.limit) { + return [...current.slice(1), payload]; + } + return [...current, payload]; + }; + + const onOptimistic = (event) => { + const payload = event?.detail; + if (!payload?.clientMessageId || payload.roomId !== roomId) return; + + setChats((prev) => { + const list = Array.isArray(prev) ? prev : []; + if ( + list.some( + (item) => item?.clientMessageId === payload.clientMessageId + ) + ) { + return list; + } + return appendWithLimit(list, payload); + }); + }; + + const onConfirmed = (event) => { + const payload = event?.detail; + if (!payload?._id || payload.roomId !== roomId) return; + + setChats((prev) => { + const list = Array.isArray(prev) ? prev : []; + const index = list.findIndex( + (item) => + item?._id === payload._id || + (payload.clientMessageId && + item?.clientMessageId === payload.clientMessageId) + ); + + if (index < 0) { + return appendWithLimit(list, { + ...payload, + pending: false, + sendFailed: false, + }); + } + + const current = list[index]; + const preserveLocalEncryptedText = + current?.pending && + payload?.e2eeEnvelope && + payload?.text === 'Encrypted message'; + const next = [...list]; + next[index] = { + ...current, + ...payload, + text: preserveLocalEncryptedText ? current.text : payload.text, + pending: false, + sendFailed: false, + }; + return next; + }); + }; + + const onFailed = (event) => { + const payload = event?.detail || {}; + if (payload.roomId && payload.roomId !== roomId) return; + if (!payload.clientMessageId) return; + + setChats((prev) => + (prev || []).map((item) => + item?.clientMessageId === payload.clientMessageId + ? { + ...item, + pending: false, + sendFailed: true, + sendError: + payload.message || payload.code || payload.reason || 'Send failed', + } + : item + ) + ); + }; + + window.addEventListener('syncchat:optimistic-message', onOptimistic); + window.addEventListener('syncchat:message-confirmed', onConfirmed); + window.addEventListener('syncchat:optimistic-message-failed', onFailed); + window.addEventListener('syncchat:outbox-failed', onFailed); + + return () => { + window.removeEventListener('syncchat:optimistic-message', onOptimistic); + window.removeEventListener('syncchat:message-confirmed', onConfirmed); + window.removeEventListener('syncchat:optimistic-message-failed', onFailed); + window.removeEventListener('syncchat:outbox-failed', onFailed); + }; + }, [chatRoom?.data?.roomId, control.limit]); + return (
Date: Wed, 19 Aug 2026 11:13:35 +0300 Subject: [PATCH 11/17] Start call signaling before media initialization --- .../components/calling/globalCallLayer.jsx | 63 ++++++++++++++++++- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/frontend/client/components/calling/globalCallLayer.jsx b/frontend/client/components/calling/globalCallLayer.jsx index 321f08fb..511572fd 100644 --- a/frontend/client/components/calling/globalCallLayer.jsx +++ b/frontend/client/components/calling/globalCallLayer.jsx @@ -115,6 +115,65 @@ function GlobalCallLayer() { }; }, [dispatch, master?._id, callPanel]); + // Start server-side call reservation/ringing as soon as the outgoing panel + // opens. Camera/microphone acquisition can then happen in parallel instead + // of blocking the recipient notification path. + useEffect(() => { + if ( + !master?._id || + callPanel?.mode !== 'outgoing' || + !callPanel?.roomId + ) { + return; + } + + socket.emit('call/start', { + roomId: callPanel.roomId, + roomType: callPanel.roomType === 'group' ? 'group' : 'private', + fromUserId: master._id, + mediaType: callPanel.mediaType === 'video' ? 'video' : 'audio', + fromName: callPanel.fromName || master.fullname || '', + fromUsername: callPanel.fromUsername || master.username || '', + recipientsId: Array.isArray(callPanel.recipientsId) + ? callPanel.recipientsId + : [], + }); + }, [ + master?._id, + callPanel?.mode, + callPanel?.roomId, + callPanel?.roomType, + callPanel?.mediaType, + ]); + + useEffect(() => { + const onStarted = (payload = {}) => { + if ( + callPanel?.mode !== 'outgoing' || + !payload.callId || + payload.roomId !== callPanel.roomId || + callPanel.callId === payload.callId + ) { + return; + } + + dispatch( + setModal({ + target: 'callPanel', + data: { + ...callPanel, + callId: payload.callId, + roomType: payload.roomType || callPanel.roomType, + mediaType: payload.mediaType || callPanel.mediaType, + }, + }) + ); + }; + + socket.on('call/started', onStarted); + return () => socket.off('call/started', onStarted); + }, [dispatch, callPanel]); + useEffect(() => { if (!('serviceWorker' in navigator)) return undefined; @@ -125,9 +184,7 @@ function GlobalCallLayer() { }; navigator.serviceWorker.addEventListener('message', onMessage); - return () => { - navigator.serviceWorker.removeEventListener('message', onMessage); - }; + return () => navigator.serviceWorker.removeEventListener('message', onMessage); }, [dispatch, master?._id, callPanel]); useEffect(() => { From 851aa82a0c5dda81b3e578819f9cdb0816ae9b4d Mon Sep 17 00:00:00 2001 From: Shahanur Islam Shagor Date: Wed, 19 Aug 2026 11:13:46 +0300 Subject: [PATCH 12/17] Install realtime delivery acceleration --- frontend/client/index.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/client/index.jsx b/frontend/client/index.jsx index 563c7c1a..39511382 100644 --- a/frontend/client/index.jsx +++ b/frontend/client/index.jsx @@ -9,6 +9,7 @@ import installProfileAvatarSync from './helpers/profileAvatarSync'; import installChatLockSync from './helpers/chatLockSync'; import installChatDeletionSync from './helpers/chatDeletionSync'; import installChatTransportV2 from './helpers/chatTransportV2'; +import installRealtimeDelivery from './helpers/realtimeDelivery'; import installChatDraftV2 from './helpers/chatDraftV2'; import installTopicFilterV2 from './helpers/topicFilterV2'; import installChatHttpReliability from './helpers/chatHttpReliability'; @@ -33,6 +34,7 @@ installChatLockSync(); installChatDeletionSync(); installChatHttpReliability(); installChatTransportV2(); +installRealtimeDelivery(); installTopicFilterV2(); installChatDraftV2(); installMentionAutocompleteV2(); From 5a507dc4a97e598f756e274d798b2559ce56a5e5 Mon Sep 17 00:00:00 2001 From: Shahanur Islam Shagor Date: Wed, 19 Aug 2026 11:14:23 +0300 Subject: [PATCH 13/17] Harden duplicate outgoing call protection --- backend/server/socket/events/callRealtime.js | 35 ++++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/backend/server/socket/events/callRealtime.js b/backend/server/socket/events/callRealtime.js index c493de9c..b4a88591 100644 --- a/backend/server/socket/events/callRealtime.js +++ b/backend/server/socket/events/callRealtime.js @@ -1,7 +1,17 @@ const { getActiveCallByRoom } = require('../../helpers/callState'); const logger = require('../../helpers/logger'); -const START_DEDUPE_MS = 15000; +const INFLIGHT_DEDUPE_MS = 2000; +const RECENT_START_TTL_MS = 2 * 60 * 1000; + +const emitExistingStarted = (socket, current) => { + socket.emit('call/started', { + callId: current.callId, + roomId: current.roomId, + roomType: current.roomType, + mediaType: current.mediaType, + }); +}; const attachCallRealtimeEvents = (socket) => { if (!socket || socket.__syncchatCallRealtime) return; @@ -23,22 +33,25 @@ const attachCallRealtimeEvents = (socket) => { const key = `${roomId}:${fromUserId}:${mediaType}`; const now = Date.now(); const previous = Number(recentStarts.get(key) || 0); - if (previous && now - previous < START_DEDUPE_MS) { + + if (previous) { const current = await getActiveCallByRoom(roomId).catch(() => null); - if (current?.initiatorId === fromUserId) { - socket.emit('call/started', { - callId: current.callId, - roomId: current.roomId, - roomType: current.roomType, - mediaType: current.mediaType, - }); + if ( + current?.initiatorId === fromUserId && + current?.mediaType === mediaType + ) { + emitExistingStarted(socket, current); + return; } - return; + + // Only suppress a duplicate while the first call/start handler is + // still reserving state. If the first attempt failed, allow a retry. + if (now - previous < INFLIGHT_DEDUPE_MS) return; } recentStarts.set(key, now); for (const [entryKey, timestamp] of recentStarts.entries()) { - if (now - timestamp >= START_DEDUPE_MS) recentStarts.delete(entryKey); + if (now - timestamp >= RECENT_START_TTL_MS) recentStarts.delete(entryKey); } await original({ From 976a02b52cff517c626ef20ac7b6cdb0d294e514 Mon Sep 17 00:00:00 2001 From: Shahanur Islam Shagor Date: Wed, 19 Aug 2026 11:16:08 +0300 Subject: [PATCH 14/17] Keep chat send checks parallel and rejection-safe --- backend/server/helpers/chatReliability.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/backend/server/helpers/chatReliability.js b/backend/server/helpers/chatReliability.js index 80aedefb..f04b28f8 100644 --- a/backend/server/helpers/chatReliability.js +++ b/backend/server/helpers/chatReliability.js @@ -91,18 +91,17 @@ const wrapReliableChatInsert = (socket) => { return; } - // Mention lookup can run while the warm Redis abuse guard executes. - const mentionsPromise = resolveMentions({ - text: args.text || '', - roomId: args.roomId, - roomType: args.roomType, - senderId: socket.userId, - }); - + // The warm Redis abuse guard must pass before any optional lookup starts. + // Sequence allocation and mention resolution can then run concurrently. await assertChatSendAllowed({ userId: socket.userId, text: args.text || '' }); const [sequence, mentions] = await Promise.all([ nextSequence(args.roomId), - mentionsPromise, + resolveMentions({ + text: args.text || '', + roomId: args.roomId, + roomType: args.roomType, + senderId: socket.userId, + }), ]); const topicId = args.topicId || null; From 9ee063e2aae80f5a5155a66a2aec6838f3d34606 Mon Sep 17 00:00:00 2001 From: Shahanur Islam Shagor Date: Wed, 19 Aug 2026 11:16:26 +0300 Subject: [PATCH 15/17] Validate realtime call coordination in backend build --- backend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index 27772897..4d0dbe5e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -6,7 +6,7 @@ "scripts": { "predev": "node scripts/free-ports.js 8080", "dev": "nodemon --watch server --watch .env --ignore logs --ignore uploads server", - "build": "node --check server/index.js && node --check server/server.js && node --check server/bootstrap.js && node --check server/config.js && node --check server/db/sequelize.js && node --check server/db/connect.js && node --check server/db/models/inbox.js && node --check server/db/models/chat.js && node --check server/db/models/chatRoomCounter.js && node --check server/db/models/messageReceipt.js && node --check server/db/models/chatDraft.js && node --check server/db/models/messageRequest.js && node --check server/db/models/chatTopic.js && node --check server/db/models/e2eeDeviceKey.js && node --check server/db/models/resumableUpload.js && node --check server/db/models/chatAiConfig.js && node --check server/db/models/adminSocialAuthConfig.js && node --check server/db/models/profile.js && node --check server/db/models/group.js && node --check server/db/models/channel.js && node --check server/db/models/adminStorageConfig.js && node --check server/db/models/adminCallConfig.js && node --check server/db/models/callHistory.js && node --check server/db/models/profilePhoto.js && node --check server/db/models/nativePushDevice.js && node --check server/helpers/storageConfig.js && node --check server/helpers/callConfig.js && node --check server/helpers/callHistory.js && node --check server/helpers/callState.js && node --check server/helpers/avatarDefaults.js && node --check server/helpers/ensureAvatarDefaults.js && node --check server/helpers/ensureProfile.js && node --check server/helpers/privacy.js && node --check server/helpers/profilePhotos.js && node --check server/helpers/livekit.js && node --check server/helpers/nativePushConfig.js && node --check server/helpers/nativePush.js && node --check server/helpers/storage.js && node --check server/helpers/videoPipeline.js && node --check server/helpers/accountArchive.js && node --check server/helpers/accountExport.js && node --check server/helpers/logger.js && node --check server/helpers/chatAbuse.js && node --check server/helpers/chatMentions.js && node --check server/helpers/messageRequests.js && node --check server/helpers/chatReliability.js && node --check server/helpers/chatAiConfig.js && node --check server/helpers/socialAuthConfig.js && node --check server/helpers/e2eeKeyDirectory.js && node --check server/helpers/chatMaintenance.js && node --check server/middleware/chatSendIdempotency.js && node --check server/controllers/avatar.js && node --check server/controllers/profile.js && node --check server/controllers/chatUpload.js && node --check server/controllers/chatSecurity.js && node --check server/controllers/chatDeletion.js && node --check server/controllers/chatV2.js && node --check server/controllers/chatAiAdmin.js && node --check server/controllers/chatSuggestions.js && node --check server/controllers/chatResumableUpload.js && node --check server/controllers/socialAuth.js && node --check server/controllers/socialAuthAdmin.js && node --check server/controllers/storageAdmin.js && node --check server/controllers/callingAdmin.js && node --check server/controllers/callingPushAdmin.js && node --check server/controllers/callingConfig.js && node --check server/controllers/nativePush.js && node --check server/controllers/accountStorage.js && node --check server/routes/profile.js && node --check server/routes/inbox.js && node --check server/routes/chat.js && node --check server/routes/chatV2.js && node --check server/routes/chatAiAdmin.js && node --check server/routes/socialAuthAdmin.js && node --check server/routes/storageAdmin.js && node --check server/routes/callingAdmin.js && node --check server/routes/callingPushAdmin.js && node --check server/routes/callingConfig.js && node --check server/routes/setting.js && node --check server/helpers/socketAdapter.js && node --check server/socket/auth.js && node --check server/socket/index.js && node --check server/socket/events/chatV2.js && node --check server/socket/events/calling.js && node --check server/socket/events/backgroundCalling.js && node --check server/socket/events/groupModeration.js && node --check server/socket/events/room.js && node --check server/routes/cron.js && node --check api/index.js", + "build": "node --check server/index.js && node --check server/server.js && node --check server/bootstrap.js && node --check server/config.js && node --check server/db/sequelize.js && node --check server/db/connect.js && node --check server/db/models/inbox.js && node --check server/db/models/chat.js && node --check server/db/models/chatRoomCounter.js && node --check server/db/models/messageReceipt.js && node --check server/db/models/chatDraft.js && node --check server/db/models/messageRequest.js && node --check server/db/models/chatTopic.js && node --check server/db/models/e2eeDeviceKey.js && node --check server/db/models/resumableUpload.js && node --check server/db/models/chatAiConfig.js && node --check server/db/models/adminSocialAuthConfig.js && node --check server/db/models/profile.js && node --check server/db/models/group.js && node --check server/db/models/channel.js && node --check server/db/models/adminStorageConfig.js && node --check server/db/models/adminCallConfig.js && node --check server/db/models/callHistory.js && node --check server/db/models/profilePhoto.js && node --check server/db/models/nativePushDevice.js && node --check server/helpers/storageConfig.js && node --check server/helpers/callConfig.js && node --check server/helpers/callHistory.js && node --check server/helpers/callState.js && node --check server/helpers/avatarDefaults.js && node --check server/helpers/ensureAvatarDefaults.js && node --check server/helpers/ensureProfile.js && node --check server/helpers/privacy.js && node --check server/helpers/profilePhotos.js && node --check server/helpers/livekit.js && node --check server/helpers/nativePushConfig.js && node --check server/helpers/nativePush.js && node --check server/helpers/storage.js && node --check server/helpers/videoPipeline.js && node --check server/helpers/accountArchive.js && node --check server/helpers/accountExport.js && node --check server/helpers/logger.js && node --check server/helpers/chatAbuse.js && node --check server/helpers/chatMentions.js && node --check server/helpers/messageRequests.js && node --check server/helpers/chatReliability.js && node --check server/helpers/chatAiConfig.js && node --check server/helpers/socialAuthConfig.js && node --check server/helpers/e2eeKeyDirectory.js && node --check server/helpers/chatMaintenance.js && node --check server/middleware/chatSendIdempotency.js && node --check server/controllers/avatar.js && node --check server/controllers/profile.js && node --check server/controllers/chatUpload.js && node --check server/controllers/chatSecurity.js && node --check server/controllers/chatDeletion.js && node --check server/controllers/chatV2.js && node --check server/controllers/chatAiAdmin.js && node --check server/controllers/chatSuggestions.js && node --check server/controllers/chatResumableUpload.js && node --check server/controllers/socialAuth.js && node --check server/controllers/socialAuthAdmin.js && node --check server/controllers/storageAdmin.js && node --check server/controllers/callingAdmin.js && node --check server/controllers/callingPushAdmin.js && node --check server/controllers/callingConfig.js && node --check server/controllers/nativePush.js && node --check server/controllers/accountStorage.js && node --check server/routes/profile.js && node --check server/routes/inbox.js && node --check server/routes/chat.js && node --check server/routes/chatV2.js && node --check server/routes/chatAiAdmin.js && node --check server/routes/socialAuthAdmin.js && node --check server/routes/storageAdmin.js && node --check server/routes/callingAdmin.js && node --check server/routes/callingPushAdmin.js && node --check server/routes/callingConfig.js && node --check server/routes/setting.js && node --check server/helpers/socketAdapter.js && node --check server/socket/auth.js && node --check server/socket/index.js && node --check server/socket/events/chatV2.js && node --check server/socket/events/calling.js && node --check server/socket/events/callRealtime.js && node --check server/socket/events/backgroundCalling.js && node --check server/socket/events/groupModeration.js && node --check server/socket/events/room.js && node --check server/routes/cron.js && node --check api/index.js", "start": "node server" }, "dependencies": { From 98211a36961568890773e92ee52fb78ef74d93b7 Mon Sep 17 00:00:00 2001 From: Shahanur Islam Shagor Date: Wed, 19 Aug 2026 11:16:56 +0300 Subject: [PATCH 16/17] Coalesce concurrent calling config requests --- frontend/client/helpers/callingConfig.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/client/helpers/callingConfig.js b/frontend/client/helpers/callingConfig.js index fc8a821c..ee1a1aaa 100644 --- a/frontend/client/helpers/callingConfig.js +++ b/frontend/client/helpers/callingConfig.js @@ -70,7 +70,10 @@ export const clearCallingConfigCache = () => { export const getCallingConfig = async ({ force = false } = {}) => { const now = Date.now(); if (!force && cache && now - cacheAt < CACHE_TTL_MS) return cache; - if (!force && pending) return pending; + // A force refresh can bypass a completed cache, but it should still reuse an + // in-flight request so the call gateway/runtime never duplicate the same HTTP + // round trip during first-call initialization. + if (pending) return pending; pending = axios .get('/calling/config', { From a832d325813c75a9e4224bfec358bdd6ae46c045 Mon Sep 17 00:00:00 2001 From: Shahanur Islam Shagor Date: Wed, 19 Aug 2026 11:17:06 +0300 Subject: [PATCH 17/17] Prewarm client calling configuration --- frontend/client/index.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/client/index.jsx b/frontend/client/index.jsx index 39511382..fa4fac9b 100644 --- a/frontend/client/index.jsx +++ b/frontend/client/index.jsx @@ -14,6 +14,7 @@ import installChatDraftV2 from './helpers/chatDraftV2'; import installTopicFilterV2 from './helpers/topicFilterV2'; import installChatHttpReliability from './helpers/chatHttpReliability'; import installMentionAutocompleteV2 from './helpers/mentionAutocompleteV2'; +import { getCallingConfig } from './helpers/callingConfig'; import './styles/chatToolsTheme.css'; import { registerServiceWorker } from './pwa/registerSW'; @@ -38,6 +39,7 @@ installRealtimeDelivery(); installTopicFilterV2(); installChatDraftV2(); installMentionAutocompleteV2(); +getCallingConfig().catch(() => {}); registerServiceWorker(); requestNotificationPermission();