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:
@@ -26,7 +26,8 @@ export const useSetting = <K extends keyof Settings>(
|
||||
key: K
|
||||
): [Settings[K], ReturnType<typeof useSetSetting<K>>] => {
|
||||
const selector = useMemo(() => (s: Settings) => s[key], [key]);
|
||||
const setting = useAtomValue(selectAtom(settingsAtom, selector));
|
||||
const derivedAtom = useMemo(() => selectAtom(settingsAtom, selector), [settingsAtom, selector]);
|
||||
const setting = useAtomValue(derivedAtom);
|
||||
|
||||
const setter = useSetSetting(settingsAtom, key);
|
||||
return [setting, setter];
|
||||
|
||||
@@ -102,17 +102,19 @@ const defaultSettings: Settings = {
|
||||
pttKey: 'Space',
|
||||
};
|
||||
|
||||
export const getSettings = () => {
|
||||
const settings = localStorage.getItem(STORAGE_KEY);
|
||||
if (settings === null) return defaultSettings;
|
||||
return {
|
||||
...defaultSettings,
|
||||
...(JSON.parse(settings) as Settings),
|
||||
};
|
||||
export const getSettings = (): Settings => {
|
||||
try {
|
||||
const settings = localStorage.getItem(STORAGE_KEY);
|
||||
if (settings === null) return defaultSettings;
|
||||
return { ...defaultSettings, ...(JSON.parse(settings) as Settings) };
|
||||
} catch {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
return defaultSettings;
|
||||
}
|
||||
};
|
||||
|
||||
export const setSettings = (settings: Settings) => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
|
||||
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)); } catch { /* quota */ }
|
||||
};
|
||||
|
||||
const baseSettings = atom<Settings>(getSettings());
|
||||
|
||||
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user