fix: resolve LiveKit voice/video stability issues
- Guard Disconnected/ConnectionStateChanged event handlers against stale rooms: old room events no longer nuke the new room's state (root cause of buttons failing, mute getting stuck, DUPLICATE_IDENTITY cascades) - Reset media state (isMuted/isCameraOn/isScreenSharing) on connect to prevent desync after reconnects - Add voiceStates to WS ready payload so users see who's in voice on page load - Wire VoiceControls buttons to check getActiveRoom() before SDK calls - Guard ChannelSidebar against re-joining the same voice channel - Switch LIVEKIT_URL to wss://nova.ddns.net/livekit for HTTPS secure context (required for getUserMedia in Safari)
This commit is contained in:
@@ -8,13 +8,14 @@ export const useChatStore = create((set, get) => ({
|
||||
typingUsers: new Map(),
|
||||
hasMore: new Map(),
|
||||
isLoading: false,
|
||||
loadError: null,
|
||||
replyTo: null,
|
||||
setCurrentChannel: (channelId) => set({ currentChannelId: channelId }),
|
||||
setReplyTo: (message) => set({ replyTo: message }),
|
||||
loadMessages: async (channelId) => {
|
||||
if (get().messages.has(channelId))
|
||||
return;
|
||||
set({ isLoading: true });
|
||||
set({ isLoading: true, loadError: null });
|
||||
try {
|
||||
const isDm = useUIStore.getState().showDms;
|
||||
const messages = isDm
|
||||
@@ -25,11 +26,11 @@ export const useChatStore = create((set, get) => ({
|
||||
newMessages.set(channelId, messages);
|
||||
const newHasMore = new Map(state.hasMore);
|
||||
newHasMore.set(channelId, messages.length >= 50);
|
||||
return { messages: newMessages, hasMore: newHasMore, isLoading: false };
|
||||
return { messages: newMessages, hasMore: newHasMore, isLoading: false, loadError: null };
|
||||
});
|
||||
}
|
||||
catch {
|
||||
set({ isLoading: false });
|
||||
catch (err) {
|
||||
set({ isLoading: false, loadError: err.message || 'Failed to load messages' });
|
||||
}
|
||||
},
|
||||
loadMoreMessages: async (channelId) => {
|
||||
|
||||
@@ -75,6 +75,15 @@ export const useServerStore = create((set, get) => ({
|
||||
return { servers: [...state.servers, server] };
|
||||
});
|
||||
},
|
||||
joinByCode: async (inviteCode) => {
|
||||
const server = await api.servers.joinByCode(inviteCode);
|
||||
set((state) => {
|
||||
if (state.servers.find(s => s.id === server.id))
|
||||
return state;
|
||||
return { servers: [...state.servers, server] };
|
||||
});
|
||||
return server;
|
||||
},
|
||||
generateInvite: async (serverId) => {
|
||||
const result = await api.servers.invite(serverId);
|
||||
return result.inviteCode;
|
||||
|
||||
@@ -50,6 +50,20 @@ export const useSocialStore = create((set, get) => ({
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
cancelFriendRequest: async (id) => {
|
||||
set({ isLoading: true, error: null });
|
||||
try {
|
||||
await api.social.cancelRequest(id);
|
||||
set((state) => ({
|
||||
requests: state.requests.filter(r => r.id !== id),
|
||||
isLoading: false,
|
||||
}));
|
||||
}
|
||||
catch (err) {
|
||||
set({ error: err.message, isLoading: false });
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
removeFriend: async (id) => {
|
||||
set({ isLoading: true, error: null });
|
||||
try {
|
||||
@@ -73,4 +87,19 @@ export const useSocialStore = create((set, get) => ({
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// Called from WS handler when another user sends you a friend request
|
||||
addIncomingRequest: (request) => {
|
||||
set((state) => {
|
||||
if (state.requests.find(r => r.id === request.id))
|
||||
return state;
|
||||
return { requests: [...state.requests, request] };
|
||||
});
|
||||
},
|
||||
// Called from WS handler when someone accepts your friend request
|
||||
addFriendFromAccepted: (friend, requestId) => {
|
||||
set((state) => ({
|
||||
friends: state.friends.find(f => f.id === friend.id) ? state.friends : [...state.friends, friend],
|
||||
requests: state.requests.filter(r => r.id !== requestId),
|
||||
}));
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -7,6 +7,8 @@ export const useVoiceStore = create((set, get) => ({
|
||||
isCameraOn: false,
|
||||
isScreenSharing: false,
|
||||
participants: [],
|
||||
connectionError: null,
|
||||
isLiveKitConnected: false,
|
||||
setVoiceUsers: (channelId, userIds) => {
|
||||
set((state) => {
|
||||
const newMap = new Map(state.voiceUsers);
|
||||
@@ -34,6 +36,8 @@ export const useVoiceStore = create((set, get) => ({
|
||||
},
|
||||
setCurrentVoiceChannel: (channelId) => set({ currentVoiceChannelId: channelId }),
|
||||
setParticipants: (participants) => set({ participants }),
|
||||
setConnectionError: (error) => set({ connectionError: error }),
|
||||
setIsLiveKitConnected: (connected) => set({ isLiveKitConnected: connected }),
|
||||
toggleMic: () => set((state) => ({ isMuted: !state.isMuted })),
|
||||
toggleDeafen: () => set((state) => ({ isDeafened: !state.isDeafened })),
|
||||
toggleCamera: () => set((state) => ({ isCameraOn: !state.isCameraOn })),
|
||||
@@ -46,5 +50,8 @@ export const useVoiceStore = create((set, get) => ({
|
||||
isDeafened: false,
|
||||
isCameraOn: false,
|
||||
isScreenSharing: false,
|
||||
participants: [],
|
||||
connectionError: null,
|
||||
isLiveKitConnected: false,
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -9,11 +9,15 @@ interface VoiceState {
|
||||
isCameraOn: boolean;
|
||||
isScreenSharing: boolean;
|
||||
participants: ParticipantInfo[];
|
||||
connectionError: string | null;
|
||||
isLiveKitConnected: boolean;
|
||||
setVoiceUsers: (channelId: string, userIds: string[]) => void;
|
||||
addVoiceUser: (channelId: string, userId: string) => void;
|
||||
removeVoiceUser: (channelId: string, userId: string) => void;
|
||||
setCurrentVoiceChannel: (channelId: string | null) => void;
|
||||
setParticipants: (participants: ParticipantInfo[]) => void;
|
||||
setConnectionError: (error: string | null) => void;
|
||||
setIsLiveKitConnected: (connected: boolean) => void;
|
||||
toggleMic: () => void;
|
||||
toggleCamera: () => void;
|
||||
toggleScreenShare: () => void;
|
||||
@@ -30,6 +34,8 @@ export const useVoiceStore = create<VoiceState>((set, get) => ({
|
||||
isCameraOn: false,
|
||||
isScreenSharing: false,
|
||||
participants: [],
|
||||
connectionError: null,
|
||||
isLiveKitConnected: false,
|
||||
|
||||
setVoiceUsers: (channelId, userIds) => {
|
||||
set((state) => {
|
||||
@@ -62,6 +68,8 @@ export const useVoiceStore = create<VoiceState>((set, get) => ({
|
||||
setCurrentVoiceChannel: (channelId) => set({ currentVoiceChannelId: channelId }),
|
||||
|
||||
setParticipants: (participants) => set({ participants }),
|
||||
setConnectionError: (error) => set({ connectionError: error }),
|
||||
setIsLiveKitConnected: (connected) => set({ isLiveKitConnected: connected }),
|
||||
|
||||
toggleMic: () => set((state) => ({ isMuted: !state.isMuted })),
|
||||
toggleDeafen: () => set((state) => ({ isDeafened: !state.isDeafened })),
|
||||
@@ -77,5 +85,8 @@ export const useVoiceStore = create<VoiceState>((set, get) => ({
|
||||
isDeafened: false,
|
||||
isCameraOn: false,
|
||||
isScreenSharing: false,
|
||||
participants: [],
|
||||
connectionError: null,
|
||||
isLiveKitConnected: false,
|
||||
}),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user