From 785f313b292ad26d37f85f44b489b6e86d490296 Mon Sep 17 00:00:00 2001 From: Frederic Cabassut Date: Sun, 8 Mar 2026 21:34:50 +0100 Subject: [PATCH] refactor(sync): namespace IndexedDB by userId Each user gets their own IDB database (keepnotes-{userId}), preventing data leaks between users on the same device. Add initDb(userId) called on mount and destroyDb() for logout cleanup. Co-Authored-By: Claude Opus 4.6 --- src/lib/sync/idb.ts | 34 ++++++++++++++++++++++++++++++--- src/routes/(app)/+layout.svelte | 4 ++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/lib/sync/idb.ts b/src/lib/sync/idb.ts index 5d93a6f..973fef9 100644 --- a/src/lib/sync/idb.ts +++ b/src/lib/sync/idb.ts @@ -1,7 +1,7 @@ -import { openDB, type IDBPDatabase } from 'idb'; +import { openDB, deleteDB, type IDBPDatabase } from 'idb'; import type { Note } from '$lib/types/index.js'; -const DB_NAME = 'crumbs'; +const DB_PREFIX = 'crumbs'; const DB_VERSION = 2; export interface PendingAttachment { @@ -51,10 +51,23 @@ export interface SyncQueueItem { } let dbPromise: Promise> | null = null; +let currentUserId: number | null = null; + +/** + * Initialize the IDB for a specific user. Must be called before any + * other IDB operation (typically after login when userId is known). + */ +export function initDb(userId: number): void { + if (currentUserId !== userId) { + dbPromise = null; + currentUserId = userId; + } +} export function getDb(): Promise> { if (!dbPromise) { - dbPromise = openDB(DB_NAME, DB_VERSION, { + const dbName = currentUserId ? `${DB_PREFIX}-${currentUserId}` : DB_PREFIX; + dbPromise = openDB(dbName, DB_VERSION, { upgrade(db) { // Notes store if (!db.objectStoreNames.contains('notes')) { @@ -87,6 +100,21 @@ export function getDb(): Promise> { return dbPromise; } +/** + * Delete the current user's IDB (call on logout). + */ +export async function destroyDb(): Promise { + if (dbPromise) { + const db = await dbPromise; + db.close(); + dbPromise = null; + } + if (currentUserId) { + await deleteDB(`${DB_PREFIX}-${currentUserId}`); + currentUserId = null; + } +} + // Notes operations export async function getAllNotes(): Promise { const db = await getDb(); diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 6599f93..9132570 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -4,6 +4,7 @@ import Toast from '$lib/components/Layout/Toast.svelte'; import { loadNotes } from '$lib/stores/notes.js'; import { startSync, stopSync } from '$lib/sync/client.js'; + import { initDb } from '$lib/sync/idb.js'; import { onMount, onDestroy } from 'svelte'; let { data, children } = $props(); @@ -11,6 +12,9 @@ onMount(() => { sidebarOpen = window.matchMedia('(min-width: 1024px)').matches; + if (data.user) { + initDb(data.user.id); + } loadNotes(); startSync(); });