diff --git a/app/components/chat/Chat.client.tsx b/app/components/chat/Chat.client.tsx index a5d2ca786..008971819 100644 --- a/app/components/chat/Chat.client.tsx +++ b/app/components/chat/Chat.client.tsx @@ -54,6 +54,8 @@ export function Chat() { position="bottom-right" pauseOnFocusLoss transition={toastAnimation} + hideProgressBar + autoClose={false} /> ) : null; diff --git a/app/entry.client.tsx b/app/entry.client.tsx index 62917e70d..36a3d6023 100644 --- a/app/entry.client.tsx +++ b/app/entry.client.tsx @@ -1,7 +1,134 @@ import { RemixBrowser } from '@remix-run/react'; import { startTransition } from 'react'; import { hydrateRoot } from 'react-dom/client'; +import { createScopedLogger } from '~/utils/logger'; -startTransition(() => { - hydrateRoot(document.getElementById('root')!, ); +const logger = createScopedLogger('Client'); + +function isChrome(): boolean { + return /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor); +} + +// Initialize IndexedDB before hydration +async function initIndexedDB() { + if (typeof window === 'undefined' || !window.indexedDB) { + logger.debug('IndexedDB not available'); + window.__BOLT_PERSISTENCE_AVAILABLE__ = false; + return false; + } + + return new Promise((resolve) => { + try { + // For Chrome, we need to be more careful with initialization + if (isChrome()) { + // First, try to open a test database + const testRequest = window.indexedDB.open('test', 1); + testRequest.onerror = () => { + logger.error('Test database failed'); + window.__BOLT_PERSISTENCE_AVAILABLE__ = false; + resolve(false); + }; + + testRequest.onsuccess = () => { + // Close and delete test database + const testDb = testRequest.result; + testDb.close(); + const deleteRequest = window.indexedDB.deleteDatabase('test'); + + deleteRequest.onsuccess = () => { + // Now try to open the actual database + const request = window.indexedDB.open('boltHistory', 1); + + request.onerror = () => { + logger.error('Failed to open database'); + window.__BOLT_PERSISTENCE_AVAILABLE__ = false; + resolve(false); + }; + + request.onupgradeneeded = (event) => { + const db = (event.target as IDBOpenDBRequest).result; + if (!db.objectStoreNames.contains('chats')) { + const store = db.createObjectStore('chats', { keyPath: 'id' }); + store.createIndex('id', 'id', { unique: true }); + store.createIndex('urlId', 'urlId', { unique: true }); + } + }; + + request.onsuccess = (event) => { + const db = (event.target as IDBOpenDBRequest).result; + + // Test if we can actually use the database + try { + const transaction = db.transaction(['chats'], 'readonly'); + transaction.oncomplete = () => { + logger.debug('Database test successful'); + window.__BOLT_PERSISTENCE_AVAILABLE__ = true; + resolve(true); + }; + transaction.onerror = () => { + logger.error('Database test failed'); + window.__BOLT_PERSISTENCE_AVAILABLE__ = false; + resolve(false); + }; + } catch (error) { + logger.error('Error testing database:', error); + window.__BOLT_PERSISTENCE_AVAILABLE__ = false; + resolve(false); + } + }; + }; + + deleteRequest.onerror = () => { + logger.error('Failed to delete test database'); + window.__BOLT_PERSISTENCE_AVAILABLE__ = false; + resolve(false); + }; + }; + } else { + // For other browsers, use the standard approach + const request = window.indexedDB.open('boltHistory', 1); + request.onerror = () => { + logger.error('Failed to open database'); + window.__BOLT_PERSISTENCE_AVAILABLE__ = false; + resolve(false); + }; + + request.onupgradeneeded = (event) => { + const db = (event.target as IDBOpenDBRequest).result; + if (!db.objectStoreNames.contains('chats')) { + const store = db.createObjectStore('chats', { keyPath: 'id' }); + store.createIndex('id', 'id', { unique: true }); + store.createIndex('urlId', 'urlId', { unique: true }); + } + }; + + request.onsuccess = () => { + logger.debug('Database initialized'); + window.__BOLT_PERSISTENCE_AVAILABLE__ = true; + resolve(true); + }; + } + } catch (error) { + logger.error('Error initializing database:', error); + window.__BOLT_PERSISTENCE_AVAILABLE__ = false; + resolve(false); + } + }); +} + +// Set initial persistence state +window.__BOLT_PERSISTENCE_AVAILABLE__ = false; + +// Initialize IndexedDB before hydrating the app +initIndexedDB().then(() => { + startTransition(() => { + hydrateRoot(document.getElementById('root')!, ); + }); }); + +// Add type declaration +declare global { + interface Window { + __BOLT_PERSISTENCE_AVAILABLE__: boolean; + } +} diff --git a/app/lib/persistence/db.ts b/app/lib/persistence/db.ts index 1199ff7b8..bfbb6fcd9 100644 --- a/app/lib/persistence/db.ts +++ b/app/lib/persistence/db.ts @@ -37,62 +37,78 @@ export async function openDatabase(): Promise { return; } - const request = indexedDB.open('boltHistory', 1); + // Test if we can actually open IndexedDB + const testRequest = window.indexedDB.open('test'); + testRequest.onerror = () => { + logger.error('IndexedDB test failed'); + dbInitAttempted = true; + dbInitializing = false; + resolve(undefined); + }; - request.onupgradeneeded = (event: IDBVersionChangeEvent) => { - const db = (event.target as IDBOpenDBRequest).result; - logger.debug('Upgrading database'); + testRequest.onsuccess = () => { + // Close and delete test database + const db = testRequest.result; + db.close(); + window.indexedDB.deleteDatabase('test'); - if (!db.objectStoreNames.contains('chats')) { - const store = db.createObjectStore('chats', { keyPath: 'id' }); - store.createIndex('id', 'id', { unique: true }); - store.createIndex('urlId', 'urlId', { unique: true }); - logger.debug('Created chats store'); - } - }; + // Now open the actual database + const request = window.indexedDB.open('boltHistory', 1); - request.onsuccess = (event: Event) => { - const db = (event.target as IDBOpenDBRequest).result; - logger.debug('Successfully opened database'); - - // Test if we can actually use the database - try { - const transaction = db.transaction(['chats'], 'readonly'); - transaction.oncomplete = () => { - logger.debug('Database test successful'); - dbInitAttempted = true; - dbInitializing = false; - resolve(db); - }; - transaction.onerror = () => { - logger.error('Database test failed'); + request.onupgradeneeded = (event: IDBVersionChangeEvent) => { + const db = (event.target as IDBOpenDBRequest).result; + logger.debug('Upgrading database'); + + if (!db.objectStoreNames.contains('chats')) { + const store = db.createObjectStore('chats', { keyPath: 'id' }); + store.createIndex('id', 'id', { unique: true }); + store.createIndex('urlId', 'urlId', { unique: true }); + logger.debug('Created chats store'); + } + }; + + request.onsuccess = (event: Event) => { + const db = (event.target as IDBOpenDBRequest).result; + logger.debug('Successfully opened database'); + + // Test if we can actually use the database + try { + const transaction = db.transaction(['chats'], 'readonly'); + transaction.oncomplete = () => { + logger.debug('Database test successful'); + dbInitAttempted = true; + dbInitializing = false; + resolve(db); + }; + transaction.onerror = () => { + logger.error('Database test failed'); + dbInitAttempted = true; + dbInitializing = false; + resolve(undefined); + }; + } catch (error) { + logger.error('Error testing database:', error); dbInitAttempted = true; dbInitializing = false; resolve(undefined); - }; - } catch (error) { - logger.error('Error testing database:', error); + } + }; + + request.onerror = (event: Event) => { + const error = (event.target as IDBOpenDBRequest).error; + logger.error('Failed to open database:', error?.message || 'Unknown error'); dbInitAttempted = true; dbInitializing = false; resolve(undefined); - } - }; + }; - request.onerror = (event: Event) => { - const error = (event.target as IDBOpenDBRequest).error; - logger.error('Failed to open database:', error?.message || 'Unknown error'); - dbInitAttempted = true; - dbInitializing = false; - resolve(undefined); - }; - - request.onblocked = () => { - logger.error('Database blocked'); - dbInitAttempted = true; - dbInitializing = false; - resolve(undefined); + request.onblocked = () => { + logger.error('Database blocked'); + dbInitAttempted = true; + dbInitializing = false; + resolve(undefined); + }; }; - } catch (error) { logger.error('Error initializing database:', error); dbInitAttempted = true; diff --git a/app/lib/persistence/useChatHistory.ts b/app/lib/persistence/useChatHistory.ts index eddf17509..b3ac5e6cf 100644 --- a/app/lib/persistence/useChatHistory.ts +++ b/app/lib/persistence/useChatHistory.ts @@ -31,9 +31,23 @@ async function initializeDb() { dbInitializing = true; try { + // Check if we're in a browser environment + if (typeof window === 'undefined') { + logger.debug('Not in browser environment'); + return undefined; + } + + // Check if persistence is available + if (!window.__BOLT_PERSISTENCE_AVAILABLE__) { + logger.debug('Persistence not available'); + return undefined; + } + db = await openDatabase(); - dbInitialized = true; - logger.debug('Database initialized successfully'); + if (db) { + dbInitialized = true; + logger.debug('Database initialized successfully'); + } } catch (error) { logger.error('Failed to initialize database:', error); } finally { @@ -54,23 +68,39 @@ export function useChatHistory() { useEffect(() => { const init = async () => { try { + // Always try to initialize the database const database = await initializeDb(); + + // If we have a mixedId but no database, navigate home silently + if (mixedId && !database) { + navigate('/', { replace: true }); + setReady(true); + return; + } + // If we have both mixedId and database, try to load messages if (mixedId && database) { - const storedMessages = await getMessages(database, mixedId); - if (storedMessages && storedMessages.messages.length > 0) { - setInitialMessages(storedMessages.messages); - setUrlId(storedMessages.urlId); - description.set(storedMessages.description); - chatId.set(storedMessages.id); - } else { + try { + const storedMessages = await getMessages(database, mixedId); + if (storedMessages && storedMessages.messages.length > 0) { + setInitialMessages(storedMessages.messages); + setUrlId(storedMessages.urlId); + description.set(storedMessages.description); + chatId.set(storedMessages.id); + } else { + navigate('/', { replace: true }); + } + } catch (error) { + logger.error('Failed to load messages:', error); navigate('/', { replace: true }); } } + + setReady(true); } catch (error) { logger.error('Failed to initialize:', error); + setReady(true); } - setReady(true); }; init(); @@ -119,3 +149,10 @@ function navigateChat(nextId: string) { url.pathname = `/chat/${nextId}`; window.history.replaceState({}, '', url); } + +// Add type declaration +declare global { + interface Window { + __BOLT_PERSISTENCE_AVAILABLE__: boolean; + } +} diff --git a/app/root.tsx b/app/root.tsx index 31eb387e0..c653d358c 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -50,6 +50,85 @@ const inlineThemeCode = stripIndents` document.querySelector('html')?.setAttribute('data-theme', theme); } + + // Initialize IndexedDB early + if (typeof window !== 'undefined' && window.indexedDB) { + window.__BOLT_PERSISTENCE_AVAILABLE__ = false; + + // Check if we're in Chrome + const isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor); + + if (isChrome) { + // For Chrome, we need to be more careful with initialization + const testRequest = window.indexedDB.open('test', 1); + testRequest.onerror = () => { + window.__BOLT_PERSISTENCE_AVAILABLE__ = false; + }; + + testRequest.onsuccess = () => { + // Close and delete test database + const testDb = testRequest.result; + testDb.close(); + const deleteRequest = window.indexedDB.deleteDatabase('test'); + + deleteRequest.onsuccess = () => { + // Now try to open the actual database + const request = window.indexedDB.open('boltHistory', 1); + + request.onupgradeneeded = (event) => { + const db = event.target.result; + if (!db.objectStoreNames.contains('chats')) { + const store = db.createObjectStore('chats', { keyPath: 'id' }); + store.createIndex('id', 'id', { unique: true }); + store.createIndex('urlId', 'urlId', { unique: true }); + } + }; + + request.onsuccess = (event) => { + const db = event.target.result; + + // Test if we can actually use the database + try { + const transaction = db.transaction(['chats'], 'readonly'); + transaction.oncomplete = () => { + window.__BOLT_PERSISTENCE_AVAILABLE__ = true; + }; + transaction.onerror = () => { + window.__BOLT_PERSISTENCE_AVAILABLE__ = false; + }; + } catch (error) { + window.__BOLT_PERSISTENCE_AVAILABLE__ = false; + } + }; + + request.onerror = () => { + window.__BOLT_PERSISTENCE_AVAILABLE__ = false; + }; + }; + + deleteRequest.onerror = () => { + window.__BOLT_PERSISTENCE_AVAILABLE__ = false; + }; + }; + } else { + // For other browsers, use the standard approach + const request = window.indexedDB.open('boltHistory', 1); + request.onupgradeneeded = (event) => { + const db = event.target.result; + if (!db.objectStoreNames.contains('chats')) { + const store = db.createObjectStore('chats', { keyPath: 'id' }); + store.createIndex('id', 'id', { unique: true }); + store.createIndex('urlId', 'urlId', { unique: true }); + } + }; + request.onsuccess = () => { + window.__BOLT_PERSISTENCE_AVAILABLE__ = true; + }; + request.onerror = () => { + window.__BOLT_PERSISTENCE_AVAILABLE__ = false; + }; + } + } `; export const Head = createHead(() => ( @@ -81,3 +160,10 @@ export function Layout({ children }: { children: React.ReactNode }) { export default function App() { return ; } + +// Add type declaration +declare global { + interface Window { + __BOLT_PERSISTENCE_AVAILABLE__: boolean; + } +}