From e7cdc544d0bb4830a91250b69cb4bf3dc86335e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A5=D0=B0=D0=BB=D0=B8=D0=BC=D0=BE=D0=B2=20=D0=A0=D1=83?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D0=BC?= Date: Tue, 17 Mar 2026 16:33:18 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81=20=D1=81=D0=BA=D1=80?= =?UTF-8?q?=D0=BE=D0=BB=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/components/ChatView.tsx | 11 --------- apps/web/src/pages/AdminPage.tsx | 35 ++++++++++++++++++++++------ 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index c5196c8..1efdcb2 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -115,7 +115,6 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal } const firstUnreadId = activeChat === sessionUnreadRef.current.chatId ? sessionUnreadRef.current.msgId : null; const lastObservedMessageIdRef = useRef(null); - const prevScrollHeightRef = useRef(0); const initialScrollChatId = useRef(null); // Load muted state @@ -209,15 +208,6 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal } }, [activeChat, isLoadingMessages]); - useLayoutEffect(() => { - if (prevScrollHeightRef.current > 0 && messagesContainerRef.current) { - const container = messagesContainerRef.current; - const diff = container.scrollHeight - prevScrollHeightRef.current; - container.scrollTop += diff; - prevScrollHeightRef.current = 0; - } - }, [chatMessages.length]); - // Scroll on new message arrivals useEffect(() => { if (chatMessages.length > 0) { @@ -337,7 +327,6 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal const container = messagesContainerRef.current; if (container && container.scrollTop < 100 && activeChat && hasMoreMessages[activeChat] && !isLoadingMessages) { - prevScrollHeightRef.current = container.scrollHeight; useChatStore.getState().loadMessages(activeChat, false, true); } }; diff --git a/apps/web/src/pages/AdminPage.tsx b/apps/web/src/pages/AdminPage.tsx index f6e27a1..bb90d43 100644 --- a/apps/web/src/pages/AdminPage.tsx +++ b/apps/web/src/pages/AdminPage.tsx @@ -130,6 +130,9 @@ const translations = { klipySuccess: 'Klipy connection successful!', klipyFailed: 'Klipy Test Failed: ', apiRequired: 'API Key is required', + customerIdRequired: 'Customer ID is required', + turnGatherFailed: 'Failed to gather candidates. Check credentials or network.', + turnTimeout: 'Timeout gathering candidates.', }, ru: { loginTitle: 'Вход администратора', @@ -215,6 +218,9 @@ const translations = { klipySuccess: 'Успешное подключение к Klipy!', klipyFailed: 'Ошибка Klipy: ', apiRequired: 'API ключ обязателен', + customerIdRequired: 'Customer ID обязателен', + turnGatherFailed: 'Сбор кандидатов не удался. Проверьте данные или сеть.', + turnTimeout: 'Таймаут при сборе кандидатов.', } }; @@ -333,12 +339,23 @@ export default function AdminPage() { let resolved = false; try { - const pc = new RTCPeerConnection({ - iceServers: [{ - urls: `turn:${config.turnHost}:${config.turnPort}`, + const turnUrl = `turn:${config.turnHost}:${config.turnPort}`; + const stunUrl = `stun:${config.turnHost}:${config.turnPort}`; + + const servers: RTCIceServer[] = [ + { urls: stunUrl } + ]; + + if (config.turnUser) { + servers.push({ + urls: [turnUrl, turnUrl + "?transport=tcp"], username: config.turnUser, - credential: config.turnSecret - }] + credential: config.turnSecret || config.turnUser + }); + } + + const pc = new RTCPeerConnection({ + iceServers: servers }); pc.addTransceiver('audio'); @@ -349,7 +366,7 @@ export default function AdminPage() { const timeout = setTimeout(() => { if (!resolved) { resolved = true; - reject(new Error("Timeout gathering relay candidates")); + reject(new Error(t.turnTimeout || "Timeout")); } }, 10000); @@ -365,7 +382,7 @@ export default function AdminPage() { if (!resolved) { resolved = true; clearTimeout(timeout); - reject(new Error("Failed to gather candidates. Check credentials or network.")); + reject(new Error(t.turnGatherFailed || "Failed to gather candidates.")); } } }; @@ -385,6 +402,10 @@ export default function AdminPage() { showToast(t.apiRequired, 'error'); return; } + if (!config.klipyCustomerId) { + showToast(t.customerIdRequired || "Customer ID required", 'error'); + return; + } setIsTestingKlipy(true); try {