Bug fixes, security hardening, and performance improvements

- BUG-16: Fixed pagination deadlock (fetching flag stuck on error path)
- BUG-17: Fixed absoluteIndex===0 falsy check skipping unread jump
- BUG-19: Fixed mEvt.getRoomId()! non-null assertion crash
- BUG-20: Wrapped getSettings()/setSettings() in try/catch for corrupt localStorage
- SEC: Replaced randomStr() Math.random() with crypto.getRandomValues() CSPRNG
- SEC: Fixed afterLoginRedirectPath open redirect validation
- SEC: Narrowed OSM iframe sandbox to scripts-only (removed allow-same-origin)
- Perf-2: Memoized selectAtom in useSetting (prevented new atom ref per render)
- Perf-4: Fixed typingMembers setTimeout leak (tracked timers per user/room)
- Perf-8: Memoized getChatBg() result in RoomView (not inline in JSX)
- Perf-12: Replaced body.class * font-family with body.class (inherited)
- Perf-15: Memoized typingNames array chain in RoomViewTyping
- Perf-9: Added blob URL cleanup useEffect in AudioContent
- BUG: Fixed forEach(async) -> Promise.all in useCommands join handler
- BUG: Fixed useCompositionEndTracking missing dependency array
- A11y: Fixed spoiler button aria-pressed + keyboard handler
- A11y: Added aria-label to Send message button
- Build: Set sourcemap:false, removed netlify.toml from copyFiles

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lotus Bot
2026-05-20 21:11:38 -04:00
parent 443d8407fc
commit ff7c2ed941
16 changed files with 94 additions and 67 deletions
+27 -20
View File
@@ -89,27 +89,34 @@ export const roomIdToTypingMembersAtom = atom<
// remove typing receipt after some timeout
// to prevent stuck typing members
setTimeout(() => {
const { roomId, userId } = action;
const timeout = timeoutReceipt(
get(baseRoomIdToTypingMembersAtom),
roomId,
userId,
TYPING_TIMEOUT_MS
);
if (timeout) {
set(
baseRoomIdToTypingMembersAtom,
produce(get(baseRoomIdToTypingMembersAtom), (draft) =>
deleteTypingMember(draft, {
type: 'DELETE',
roomId,
userId,
})
)
const timerKey = `${action.roomId}:${action.userId}`;
const existingTimer = typingTimers.get(timerKey);
if (existingTimer !== undefined) clearTimeout(existingTimer);
typingTimers.set(
timerKey,
setTimeout(() => {
typingTimers.delete(timerKey);
const { roomId, userId } = action;
const timeout = timeoutReceipt(
get(baseRoomIdToTypingMembersAtom),
roomId,
userId,
TYPING_TIMEOUT_MS
);
}
}, TYPING_TIMEOUT_MS);
if (timeout) {
set(
baseRoomIdToTypingMembersAtom,
produce(get(baseRoomIdToTypingMembersAtom), (draft) =>
deleteTypingMember(draft, {
type: 'DELETE',
roomId,
userId,
})
)
);
}
}, TYPING_TIMEOUT_MS)
);
}
if (