💻
CODE SNIPPET — July 17, 2026LocalStorage with TTL, Serialization & Cross-Tab Sync (JavaScript)A robust
Store class wrapping
localStorage with expiration, auto-JSON, and real-time sync across tabs. Zero dependencies.
class Store {
constructor(prefix = 'app', fallback = {}) {
this.prefix = prefix;
this._fallback = fallback;
this._listeners = new Map();
// Cross-tab sync
window.addEventListener('storage', e => {
if (e.key?.startsWith(this.prefix)) {
window.dispatchEvent(new CustomEvent('store-change', {
detail: { key: e.key, value: e.newValue }
}));
}
});
}
set(key, value, ttlMs = null) {
const entry = JSON.stringify({
v: value,
t: Date.now(),
e: ttlMs ? Date.now() + ttlMs : null
});
try { localStorage.setItem(`${this.prefix}:${key}`, entry); }
catch { Object.assign(this._fallback, { [key]: value }); }
}
get(key, defaultValue = undefined) {
const raw = localStorage.getItem(`${this.prefix}:${key}`)
?? JSON.stringify({ v: this._fallback[key] });
const entry = JSON.parse(raw);
if (entry.e && Date.now() > entry.e) return defaultValue;
return entry.v ?? defaultValue;
}
on(key, fn) { this._listeners.set(key, fn); }
clearExpired() {
Object.keys(localStorage).filter(k => k.startsWith(this.prefix))
.forEach(k => {
const e = JSON.parse(localStorage.getItem(k));
if (e.e && Date.now() > e.e) localStorage.removeItem(k);
});
}
}
// Usage:
const store = new Store('myapp');
store.set('theme', 'dark', 86400000); // 24hr TTL
const theme = store.get('theme', 'light'); // default: light
Pro Tip: The
storage event only fires on
other tabs. The
CustomEvent bridge handles same-page listeners. Safari private browsing falls back to in-memory storage gracefully.