🆙 Bug fixed in localstorage

This commit is contained in:
duckietm
2026-06-04 13:50:40 +02:00
parent 59ed27b727
commit 9982c96b63
3 changed files with 129 additions and 26 deletions
+5 -2
View File
@@ -12,11 +12,14 @@ const MESSENGER_HISTORY_MAX = 1000;
let CHAT_HISTORY_COUNTER: number = 0;
let MESSENGER_HISTORY_COUNTER: number = 0;
const slimChatEntriesForStorage = (entries: IChatEntry[]): IChatEntry[] =>
entries.map(entry => entry.imageUrl ? { ...entry, imageUrl: undefined } : entry);
const useChatHistoryState = () =>
{
const [ chatHistory, setChatHistory ] = useLocalStorage<IChatEntry[]>('chatHistory', []);
const [ chatHistory, setChatHistory ] = useLocalStorage<IChatEntry[]>('chatHistory', [], { toStorage: slimChatEntriesForStorage });
const [ roomHistory, setRoomHistory ] = useLocalStorage<IRoomHistoryEntry[]>('roomHistory', []);
const [ messengerHistory, setMessengerHistory ] = useLocalStorage<IChatEntry[]>('messengerHistory', []);
const [ messengerHistory, setMessengerHistory ] = useLocalStorage<IChatEntry[]>('messengerHistory', [], { toStorage: slimChatEntriesForStorage });
const [ needsRoomInsert, setNeedsRoomInsert ] = useLocalStorage('needsRoomInsert', false);
const addChatEntry = (entry: IChatEntry) =>
+19 -21
View File
@@ -23,11 +23,6 @@ const useChatWidgetState = () =>
const { addChatEntry, updateChatEntry } = useChatHistory();
const { settings, translateIncoming, consumeOutgoingTranslation } = useTranslation();
const isDisposed = useRef(false);
// Reactive: re-renders if the session-data snapshot flips (e.g.
// reconnect under a different user id). Safe to call here —
// useChatWidget is NOT wrapped in useBetween (see export below),
// so the real React dispatcher is in scope and
// useSyncExternalStore installs correctly.
const ownUserId = (useUserDataSnapshot().userId || -1);
const applyTranslationToBubble = useCallback((chatMessage: ChatBubbleMessage, originalText: string, translatedText: string, detectedLanguage: string, targetLanguage: string) =>
@@ -230,22 +225,25 @@ const useChatWidgetState = () =>
return newValue;
});
const chatEntryId = addChatEntry({
id: -1,
webId: userData.webID,
entityId: userData.roomIndex,
name: username,
imageUrl,
style: styleId,
chatType: chatType,
entityType: userData.type,
message: formattedText,
timestamp: ChatHistoryCurrentDate(),
type: ChatEntryType.TYPE_CHAT,
roomId: roomSession.roomId,
color,
...(outgoingTranslation ? buildTranslatedEntryPatch(outgoingTranslation.originalText, outgoingTranslation.translatedText, outgoingTranslation.detectedLanguage, outgoingTranslation.targetLanguage) : {})
});
const chatEntryId = (userType === RoomObjectType.USER)
? addChatEntry({
id: -1,
webId: userData.webID,
entityId: userData.roomIndex,
name: username,
imageUrl,
style: styleId,
chatType: chatType,
entityType: userData.type,
message: formattedText,
timestamp: ChatHistoryCurrentDate(),
type: ChatEntryType.TYPE_CHAT,
roomId: roomSession.roomId,
color,
...(outgoingTranslation ? buildTranslatedEntryPatch(outgoingTranslation.originalText, outgoingTranslation.translatedText, outgoingTranslation.detectedLanguage, outgoingTranslation.targetLanguage) : {})
})
: -1;
if(!settings.enabled || outgoingTranslation || !isTranslatableChatType || !text.trim().length) return;
+105 -3
View File
@@ -1,10 +1,36 @@
import { NitroLogger } from '@nitrots/nitro-renderer';
import { Dispatch, SetStateAction, useState } from 'react';
import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react';
import { GetLocalStorage, SetLocalStorage } from '../api';
const userId = new URLSearchParams(window.location.search).get('userid') || 0;
const useLocalStorageState = <T>(key: string, initialValue: T): [ T, Dispatch<SetStateAction<T>>] =>
const STORAGE_WRITE_DEBOUNCE_MS = 250;
const QUOTA_TRIM_FACTOR = 0.5; // on quota error, keep the newest 50%.
const MIN_RETAINED_ENTRIES = 50;
const isQuotaError = (error: unknown): boolean =>
{
if(!error || typeof error !== 'object') return false;
const name = (error as { name?: string }).name;
if(name === 'QuotaExceededError') return true;
if(name === 'NS_ERROR_DOM_QUOTA_REACHED') return true;
return false;
};
const trimArrayForQuota = <T>(value: T): T =>
{
if(!Array.isArray(value)) return value;
if(value.length <= MIN_RETAINED_ENTRIES) return [] as unknown as T;
const keep = Math.max(MIN_RETAINED_ENTRIES, Math.floor(value.length * QUOTA_TRIM_FACTOR));
return value.slice(value.length - keep) as unknown as T;
};
interface UseLocalStorageOptions<T>
{
toStorage?: (value: T) => unknown;
}
const useLocalStorageState = <T>(key: string, initialValue: T, options: UseLocalStorageOptions<T> = {}): [ T, Dispatch<SetStateAction<T>>] =>
{
key = userId ? `${ key }.${ userId }` : key;
@@ -22,6 +48,82 @@ const useLocalStorageState = <T>(key: string, initialValue: T): [ T, Dispatch<Se
}
});
const pendingWriteRef = useRef<T | null>(null);
const writeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const optionsRef = useRef(options);
optionsRef.current = options;
const flushWrite = (value: T) =>
{
if(typeof window === 'undefined') return;
const project = optionsRef.current.toStorage;
const projected = project ? project(value) : value;
try
{
SetLocalStorage(key, projected);
return;
}
catch(error)
{
if(!isQuotaError(error))
{
NitroLogger.error(error);
return;
}
}
try
{
const trimmed = trimArrayForQuota(projected as T);
SetLocalStorage(key, trimmed);
NitroLogger.warn(`[useLocalStorage] quota exceeded for ${ key }, trimmed payload`);
}
catch(retryError)
{
NitroLogger.error(retryError);
try { window.localStorage.removeItem(key); } catch(_) { }
}
};
const scheduleWrite = (value: T) =>
{
pendingWriteRef.current = value;
if(writeTimerRef.current) clearTimeout(writeTimerRef.current);
writeTimerRef.current = setTimeout(() =>
{
writeTimerRef.current = null;
if(pendingWriteRef.current !== null)
{
flushWrite(pendingWriteRef.current);
pendingWriteRef.current = null;
}
}, STORAGE_WRITE_DEBOUNCE_MS);
};
useEffect(() =>
{
const flushOnLeave = () =>
{
if(pendingWriteRef.current === null) return;
if(writeTimerRef.current) clearTimeout(writeTimerRef.current);
writeTimerRef.current = null;
flushWrite(pendingWriteRef.current);
pendingWriteRef.current = null;
};
window.addEventListener('pagehide', flushOnLeave);
window.addEventListener('beforeunload', flushOnLeave);
return () =>
{
window.removeEventListener('pagehide', flushOnLeave);
window.removeEventListener('beforeunload', flushOnLeave);
};
}, []);
const setValue = (value: T) =>
{
try
@@ -30,7 +132,7 @@ const useLocalStorageState = <T>(key: string, initialValue: T): [ T, Dispatch<Se
setStoredValue(valueToStore);
if(typeof window !== 'undefined') SetLocalStorage(key, valueToStore);
scheduleWrite(valueToStore);
}
catch(error)