feat(07-03): implement localStorage utilities for lastReadAt

- Add saveLastReadAt: saves timestamp with merge support
- Add loadLastReadAt: loads all timestamps with parse error handling
- Add clearLastReadAt: clears specific channel or all channels
- All functions handle localStorage errors gracefully (quota, parse errors)
- Storage key: 'jk_chat_lastReadAt'
- All 8 tests passing (GREEN phase)

Part of Phase 7 Plan 3 (WebSocket Integration & Selectors)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Nuwan 2026-01-27 12:44:52 +05:30
parent f2bd7c6501
commit 33deac659b
1 changed files with 71 additions and 0 deletions

View File

@ -0,0 +1,71 @@
/**
* localStorage utilities for chat lastReadAt persistence
*
* Stores lastReadAt timestamps for each chat channel to track unread status
* across browser sessions. Data is stored as JSON object keyed by channel ID.
*/
const STORAGE_KEY = 'jk_chat_lastReadAt';
/**
* Save lastReadAt timestamp for a channel
*
* Merges with existing data to preserve timestamps for other channels.
* Handles localStorage quota errors gracefully.
*
* @param {string} channel - Channel ID (session ID, lesson ID, or 'global')
* @param {string} timestamp - ISO timestamp (e.g., '2026-01-26T12:00:00Z')
*/
export const saveLastReadAt = (channel, timestamp) => {
try {
const existing = loadLastReadAt();
existing[channel] = timestamp;
localStorage.setItem(STORAGE_KEY, JSON.stringify(existing));
} catch (error) {
console.error('Failed to save lastReadAt:', error);
}
};
/**
* Load all lastReadAt timestamps from localStorage
*
* Returns empty object if no data exists or if JSON parsing fails.
*
* @returns {Object} Map of channel IDs to ISO timestamp strings
*/
export const loadLastReadAt = () => {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (!stored) return {};
return JSON.parse(stored);
} catch (error) {
console.error('Failed to load lastReadAt:', error);
return {};
}
};
/**
* Clear lastReadAt timestamp for a channel, or all channels
*
* If channel is specified, removes only that channel's timestamp.
* If no channel is specified, removes entire storage key.
*
* @param {string} [channel] - Channel ID to clear, or undefined to clear all
*/
export const clearLastReadAt = (channel) => {
try {
if (channel) {
const existing = loadLastReadAt();
delete existing[channel];
if (Object.keys(existing).length === 0) {
localStorage.removeItem(STORAGE_KEY);
} else {
localStorage.setItem(STORAGE_KEY, JSON.stringify(existing));
}
} else {
localStorage.removeItem(STORAGE_KEY);
}
} catch (error) {
console.error('Failed to clear lastReadAt:', error);
}
};