diff --git a/packages/web/public/sounds/reconnect.mp3 b/packages/web/public/sounds/reconnect.mp3 deleted file mode 100644 index 2ab7491d..00000000 Binary files a/packages/web/public/sounds/reconnect.mp3 and /dev/null differ diff --git a/packages/web/src/components/voice/SoundController.tsx b/packages/web/src/components/voice/SoundController.tsx index a3374314..d41414a1 100644 --- a/packages/web/src/components/voice/SoundController.tsx +++ b/packages/web/src/components/voice/SoundController.tsx @@ -1,27 +1,15 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef } from 'react'; import { useVoiceStore } from '../../stores/voiceStore'; import { useChatStore } from '../../stores/chatStore'; import { useAuthStore } from '../../stores/authStore'; -import { getHomeWsConnected } from '../../hooks/useWebSocket'; import { AudioManager } from '../../audio/AudioManager'; export function SoundController() { const audioManager = AudioManager.getInstance(); const currentUser = useAuthStore((s) => s.user); - const [isWsConnected, setIsWsConnected] = useState(false); - - // Poll home WS connection status without managing lifecycle - useEffect(() => { - const interval = setInterval(() => { - setIsWsConnected(getHomeWsConnected()); - }, 500); - return () => clearInterval(interval); - }, []); // Refs to track previous states const isInitialMount = useRef(true); - const prevIsWsConnected = useRef(false); - const wsDisconnectedAt = useRef(0); const prevIsMuted = useRef(useVoiceStore.getState().isMuted); const prevIsDeafened = useRef(useVoiceStore.getState().isDeafened); const prevIsCameraOn = useRef(useVoiceStore.getState().isCameraOn); @@ -33,28 +21,10 @@ export function SoundController() { const incomingCallLoop = useRef(null); const outgoingCallLoop = useRef(null); - // WebSocket Reconnect Sound — suppress during active voice and brief blips (<3s) - useEffect(() => { - if (isInitialMount.current) return; - if (!isWsConnected && prevIsWsConnected.current) { - // Record when we lost connection - wsDisconnectedAt.current = Date.now(); - } - if (isWsConnected && !prevIsWsConnected.current) { - const isInActiveVoice = useVoiceStore.getState().isLiveKitConnected; - const downtime = wsDisconnectedAt.current > 0 ? Date.now() - wsDisconnectedAt.current : Infinity; - if (!isInActiveVoice && downtime > 3000) { - audioManager.playSound('reconnect'); - } - } - prevIsWsConnected.current = isWsConnected; - }, [isWsConnected, audioManager]); - useEffect(() => { // Set initial mount flag to false after first run const timer = setTimeout(() => { isInitialMount.current = false; - prevIsWsConnected.current = isWsConnected; }, 1000); // 1. Listen to Voice State Changes diff --git a/packages/web/src/hooks/useWebSocket.ts b/packages/web/src/hooks/useWebSocket.ts index 85453bfc..4b21d9c7 100644 --- a/packages/web/src/hooks/useWebSocket.ts +++ b/packages/web/src/hooks/useWebSocket.ts @@ -223,9 +223,14 @@ function handleEvent(origin: string, event: ServerEvent): void { removeMessage(event.messageId, event.channelId); break; - case 'typing': - setTyping(event.channelId, event.userId, event.username); + case 'typing': { + let typingUsername = event.username as string; + if (!isHome && typingUsername && !typingUsername.includes('@')) { + try { typingUsername = `${typingUsername}@${new URL(origin).host}`; } catch {} + } + setTyping(event.channelId, event.userId, typingUsername); break; + } case 'presence_update': updateMemberPresence(event.userId, event.status); @@ -300,9 +305,14 @@ function handleEvent(origin: string, event: ServerEvent): void { removeMessage(event.messageId, event.dmChannelId); break; - case 'dm_typing': - setTyping(event.dmChannelId, event.userId, event.username); + case 'dm_typing': { + let dmTypingUsername = event.username as string; + if (!isHome && dmTypingUsername && !dmTypingUsername.includes('@')) { + try { dmTypingUsername = `${dmTypingUsername}@${new URL(origin).host}`; } catch {} + } + setTyping(event.dmChannelId, event.userId, dmTypingUsername); break; + } // ─── Reactions (all origins) ──────────────────────────────────────────── diff --git a/packages/web/src/utils/assetUrls.ts b/packages/web/src/utils/assetUrls.ts index 456c3fc8..8e2a2d7d 100644 --- a/packages/web/src/utils/assetUrls.ts +++ b/packages/web/src/utils/assetUrls.ts @@ -15,9 +15,24 @@ export function resolveAssetUrl(filename: string | null | undefined, origin: str * Mutates in-place for efficiency (called on arrays of members/messages). */ export function normalizeUserAssets(user: T, origin: string): T { - if (origin && user.avatar) { + if (!origin) return user; + if (user.avatar) { user.avatar = resolveAssetUrl(user.avatar, origin) ?? user.avatar; } + // Qualify users local to the remote instance with their origin domain. + // These users have no homeInstance (they're native there), so the client + // can't distinguish them from its own local users without this step. + const u = user as Record; + if (typeof u.username === 'string' && typeof u.id === 'string' && !u.homeInstance) { + try { + const host = new URL(origin).host; + u.homeInstance = host; + if (!u.homeUserId) u.homeUserId = u.id; + if (!(u.username as string).includes('@')) { + u.username = `${u.username}@${host}`; + } + } catch {} + } return user; }