@ -26,13 +26,22 @@ export async function prewarmCaches(
onProgress? : ProgressCallback
onProgress? : ProgressCallback
) : Promise < void > {
) : Promise < void > {
const steps = [
const steps = [
{ name : 'Initializing database...' , weight : 5 } ,
{ name : 'Initializing database...' , weight : 2 } ,
{ name : 'Loading user profile...' , weight : 10 } ,
{ name : 'Archiving old events...' , weight : 8 } ,
{ name : 'Loading user lists...' , weight : 15 } ,
{ name : 'Loading user profile...' , weight : 5 } ,
{ name : 'Loading recent events...' , weight : 20 } ,
{ name : 'Loading user lists...' , weight : 8 } ,
{ name : 'Loading profiles...' , weight : 15 } ,
{ name : 'Loading recent feed events...' , weight : 15 } ,
{ name : 'Loading RSS feeds...' , weight : 10 } ,
{ name : 'Loading more feed events...' , weight : 15 } ,
{ name : 'Finalizing...' , weight : 5 }
{ name : 'Loading discussions and threads...' , weight : 10 } ,
{ name : 'Loading comments...' , weight : 8 } ,
{ name : 'Loading reactions...' , weight : 8 } ,
{ name : 'Loading profiles from events...' , weight : 12 } ,
{ name : 'Loading more profiles...' , weight : 10 } ,
{ name : 'Loading GIF events...' , weight : 8 } ,
{ name : 'Loading emoji events...' , weight : 6 } ,
{ name : 'Loading RSS feeds...' , weight : 8 } ,
{ name : 'Loading user bookmarks...' , weight : 5 } ,
{ name : 'Finalizing cache...' , weight : 5 }
] ;
] ;
let totalProgress = 0 ;
let totalProgress = 0 ;
@ -68,15 +77,34 @@ export async function prewarmCaches(
await getDB ( ) ;
await getDB ( ) ;
updateProgress ( 0 , 100 ) ;
updateProgress ( 0 , 100 ) ;
// Step 2: Load user profile if logged in
// Step 2: Archive old events (free up space before prewarming)
updateProgress ( 1 , 0 ) ;
updateProgress ( 1 , 0 ) ;
try {
const { archiveOldEvents } = await import ( './event-archive.js' ) ;
// Archive events older than 30 days
const ARCHIVE_THRESHOLD = 30 * 24 * 60 * 60 * 1000 ; // 30 days
// Archive in background with progress updates
const archived = await archiveOldEvents ( ARCHIVE_THRESHOLD ) ;
if ( archived > 0 ) {
// Archive completed - events have been compressed and moved
}
} catch ( error ) {
// Archive failed (non-critical, continue)
console . warn ( 'Archive step failed (non-critical):' , error ) ;
}
updateProgress ( 1 , 100 ) ;
// Step 3: Load user profile if logged in
updateProgress ( 2 , 0 ) ;
try {
try {
if ( sessionManager . isLoggedIn ( ) ) {
if ( sessionManager . isLoggedIn ( ) ) {
const pubkey = sessionManager . getSession ( ) ? . pubkey ;
const pubkey = sessionManager . getSession ( ) ? . pubkey ;
if ( pubkey ) {
if ( pubkey ) {
const profileRelays = relayManager . getProfileReadRelays ( ) ;
const profileRelays = relayManager . getProfileReadRelays ( ) ;
// Use a shorter timeout for prewarming to avoid hanging
// Increased timeout for thorough prewarm ing
const prewarmTimeout = Math . min ( config . standardTimeout , 5000 ) ; // Max 5 seconds
const prewarmTimeout = config . standardTimeout ; // Use full timeout
await Promise . race ( [
await Promise . race ( [
nostrClient . fetchEvents (
nostrClient . fetchEvents (
[ { kinds : [ KIND . METADATA ] , authors : [ pubkey ] , limit : 1 } ] ,
[ { kinds : [ KIND . METADATA ] , authors : [ pubkey ] , limit : 1 } ] ,
@ -84,7 +112,7 @@ export async function prewarmCaches(
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
) ,
) ,
new Promise ( ( _ , reject ) = >
new Promise ( ( _ , reject ) = >
setTimeout ( ( ) = > reject ( new Error ( 'Profile fetch timeout' ) ) , prewarmTimeout + 1 000)
setTimeout ( ( ) = > reject ( new Error ( 'Profile fetch timeout' ) ) , prewarmTimeout + 2 000)
)
)
] ) . catch ( ( ) = > {
] ) . catch ( ( ) = > {
// Ignore errors - prewarming is non-critical
// Ignore errors - prewarming is non-critical
@ -108,7 +136,7 @@ export async function prewarmCaches(
. . . relayManager . getFeedReadRelays ( )
. . . relayManager . getFeedReadRelays ( )
] ;
] ;
const uniqueRelays = [ . . . new Set ( relays ) ] ;
const uniqueRelays = [ . . . new Set ( relays ) ] ;
const prewarmTimeout = Math . min ( config . standardTimeout , 5000 ) ; // Max 5 seconds
const prewarmTimeout = config . standardTimeout ; // Use full timeout
await Promise . race ( [
await Promise . race ( [
Promise . all ( [
Promise . all ( [
@ -121,10 +149,30 @@ export async function prewarmCaches(
[ { kinds : [ KIND . FOLLOW_SET ] , authors : [ pubkey ] } ] ,
[ { kinds : [ KIND . FOLLOW_SET ] , authors : [ pubkey ] } ] ,
uniqueRelays ,
uniqueRelays ,
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
) ,
nostrClient . fetchEvents (
[ { kinds : [ KIND . MUTE_LIST ] , authors : [ pubkey ] , limit : 1 } ] ,
uniqueRelays ,
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
) ,
nostrClient . fetchEvents (
[ { kinds : [ KIND . PIN_LIST ] , authors : [ pubkey ] , limit : 1 } ] ,
uniqueRelays ,
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
) ,
nostrClient . fetchEvents (
[ { kinds : [ KIND . RELAY_LIST ] , authors : [ pubkey ] , limit : 1 } ] ,
uniqueRelays ,
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
) ,
nostrClient . fetchEvents (
[ { kinds : [ KIND . INTEREST_LIST ] , authors : [ pubkey ] , limit : 1 } ] ,
uniqueRelays ,
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
)
)
] ) ,
] ) ,
new Promise ( ( _ , reject ) = >
new Promise ( ( _ , reject ) = >
setTimeout ( ( ) = > reject ( new Error ( 'Lists fetch timeout' ) ) , prewarmTimeout + 1000 )
setTimeout ( ( ) = > reject ( new Error ( 'Lists fetch timeout' ) ) , prewarmTimeout + 2 000)
)
)
] ) . catch ( ( ) = > {
] ) . catch ( ( ) = > {
// Ignore errors - prewarming is non-critical
// Ignore errors - prewarming is non-critical
@ -134,22 +182,23 @@ export async function prewarmCaches(
} catch ( error ) {
} catch ( error ) {
// Ignore errors - prewarming is non-critical
// Ignore errors - prewarming is non-critical
}
}
updateProgress ( 2 , 100 ) ;
updateProgress ( 3 , 100 ) ;
// Step 4: Load recent feed events
// Step 5: Load recent feed events (all kinds, first batch)
updateProgress ( 3 , 0 ) ;
updateProgress ( 4 , 0 ) ;
try {
try {
const feedRelays = relayManager . getFeedReadRelays ( ) ;
const feedRelays = relayManager . getFeedReadRelays ( ) ;
const prewarmTimeout = Math . min ( config . standardTimeout , 5000 ) ; // Max 5 seconds
const prewarmTimeout = config . standardTimeout ; // Use full timeout
// Load all feed kinds with more events
await Promise . race ( [
await Promise . race ( [
nostrClient . fetchEvents (
nostrClient . fetchEvents (
[ { kinds : feedKinds.slice ( 0 , 10 ) , limit : 5 0 } ] , // Load first 10 feed kinds, limit 5 0 events
[ { kinds : feedKinds , limit : 10 0 } ] , // Load all feed kinds, limit 10 0 events
feedRelays ,
feedRelays ,
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
) ,
) ,
new Promise ( ( _ , reject ) = >
new Promise ( ( _ , reject ) = >
setTimeout ( ( ) = > reject ( new Error ( 'Feed events fetch timeout' ) ) , prewarmTimeout + 1 000)
setTimeout ( ( ) = > reject ( new Error ( 'Feed events fetch timeout' ) ) , prewarmTimeout + 2 000)
)
)
] ) . catch ( ( ) = > {
] ) . catch ( ( ) = > {
// Ignore errors - prewarming is non-critical
// Ignore errors - prewarming is non-critical
@ -157,42 +206,174 @@ export async function prewarmCaches(
} catch ( error ) {
} catch ( error ) {
// Ignore errors - prewarming is non-critical
// Ignore errors - prewarming is non-critical
}
}
updateProgress ( 3 , 100 ) ;
updateProgress ( 4 , 100 ) ;
// Step 5: Load profiles for recent events (if logged in )
// Step 6: Load more feed events (second batch for better coverage )
updateProgress ( 4 , 0 ) ;
updateProgress ( 5 , 0 ) ;
try {
try {
if ( sessionManager . isLoggedIn ( ) ) {
const feedRelays = relayManager . getFeedReadRelays ( ) ;
// Get some recent events to extract pubkeys
const prewarmTimeout = config . standardTimeout ;
const { getRecentCachedEvents } = await import ( './event-cache.js' ) ;
const recentEvents = await getRecentCachedEvents ( feedKinds , 24 * 60 * 60 * 1000 , 20 ) ;
const pubkeys = [ . . . new Set ( recentEvents . map ( e = > e . pubkey ) ) ] . slice ( 0 , 20 ) ;
if ( pubkeys . length > 0 ) {
// Load another batch with different time range
const profileRelays = relayManager . getProfileReadRelays ( ) ;
await Promise . race ( [
const prewarmTimeout = Math . min ( config . standardTimeout , 5000 ) ; // Max 5 seconds
nostrClient . fetchEvents (
[ { kinds : feedKinds , limit : 100 } ] , // Another 100 events
feedRelays ,
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
) ,
new Promise ( ( _ , reject ) = >
setTimeout ( ( ) = > reject ( new Error ( 'More feed events fetch timeout' ) ) , prewarmTimeout + 2000 )
)
] ) . catch ( ( ) = > {
// Ignore errors - prewarming is non-critical
} ) ;
} catch ( error ) {
// Ignore errors - prewarming is non-critical
}
updateProgress ( 5 , 100 ) ;
// Step 7: Load discussions and threads
updateProgress ( 6 , 0 ) ;
try {
const feedRelays = relayManager . getFeedReadRelays ( ) ;
const prewarmTimeout = config . standardTimeout ;
// Load discussion/thread events
await Promise . race ( [
nostrClient . fetchEvents (
[ { kinds : [ KIND . SHORT_TEXT_NOTE , KIND . LONG_FORM_NOTE ] , limit : 50 } ] , // Load discussions
feedRelays ,
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
) ,
new Promise ( ( _ , reject ) = >
setTimeout ( ( ) = > reject ( new Error ( 'Discussions fetch timeout' ) ) , prewarmTimeout + 2000 )
)
] ) . catch ( ( ) = > {
// Ignore errors - prewarming is non-critical
} ) ;
} catch ( error ) {
// Ignore errors - prewarming is non-critical
}
updateProgress ( 6 , 100 ) ;
// Step 8: Load comments on discussions and threads
updateProgress ( 7 , 0 ) ;
try {
const feedRelays = relayManager . getFeedReadRelays ( ) ;
const prewarmTimeout = config . standardTimeout ;
// Load comments (kind 1111) - these are replies to discussions/threads
await Promise . race ( [
nostrClient . fetchEvents (
[ { kinds : [ KIND . COMMENT ] , limit : 100 } ] ,
feedRelays ,
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
) ,
new Promise ( ( _ , reject ) = >
setTimeout ( ( ) = > reject ( new Error ( 'Comments fetch timeout' ) ) , prewarmTimeout + 2000 )
)
] ) . catch ( ( ) = > {
// Ignore errors - prewarming is non-critical
} ) ;
} catch ( error ) {
// Ignore errors - prewarming is non-critical
}
updateProgress ( 7 , 100 ) ;
// Step 9: Load reactions on events
updateProgress ( 8 , 0 ) ;
try {
const feedRelays = relayManager . getFeedReadRelays ( ) ;
const prewarmTimeout = config . standardTimeout ;
// Load reactions (kind 7) - these are reactions to posts/events
await Promise . race ( [
nostrClient . fetchEvents (
[ { kinds : [ KIND . REACTION ] , limit : 100 } ] ,
feedRelays ,
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
) ,
new Promise ( ( _ , reject ) = >
setTimeout ( ( ) = > reject ( new Error ( 'Reactions fetch timeout' ) ) , prewarmTimeout + 2000 )
)
] ) . catch ( ( ) = > {
// Ignore errors - prewarming is non-critical
} ) ;
} catch ( error ) {
// Ignore errors - prewarming is non-critical
}
updateProgress ( 8 , 100 ) ;
// Step 10: Load profiles for recent events
updateProgress ( 9 , 0 ) ;
try {
// Get recent events to extract pubkeys
const { getRecentCachedEvents } = await import ( './event-cache.js' ) ;
const recentEvents = await getRecentCachedEvents ( feedKinds , 24 * 60 * 60 * 1000 , 100 ) ; // Get more events
const pubkeys = [ . . . new Set ( recentEvents . map ( e = > e . pubkey ) ) ] . slice ( 0 , 50 ) ; // Get up to 50 unique pubkeys
if ( pubkeys . length > 0 ) {
const profileRelays = relayManager . getProfileReadRelays ( ) ;
const prewarmTimeout = config . standardTimeout ;
// Load profiles in batches to avoid overwhelming relays
const batchSize = 20 ;
for ( let i = 0 ; i < pubkeys . length ; i += batchSize ) {
const batch = pubkeys . slice ( i , i + batchSize ) ;
await Promise . race ( [
await Promise . race ( [
nostrClient . fetchEvents (
nostrClient . fetchEvents (
[ { kinds : [ KIND . METADATA ] , authors : pubkeys , limit : 1 } ] ,
[ { kinds : [ KIND . METADATA ] , authors : batch , limit : 1 } ] ,
profileRelays ,
profileRelays ,
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
) ,
) ,
new Promise ( ( _ , reject ) = >
new Promise ( ( _ , reject ) = >
setTimeout ( ( ) = > reject ( new Error ( 'Profiles fetch timeout' ) ) , prewarmTimeout + 1000 )
setTimeout ( ( ) = > reject ( new Error ( 'Profiles fetch timeout' ) ) , prewarmTimeout + 2 000)
)
)
] ) . catch ( ( ) = > {
] ) . catch ( ( ) = > {
// Ignore errors - prewarming is non-critical
// Ignore errors - prewarming is non-critical
} ) ;
} ) ;
// Update progress within this step
const stepProgress = Math . min ( 100 , ( ( i + batchSize ) / pubkeys . length ) * 100 ) ;
updateProgress ( 9 , stepProgress ) ;
}
}
}
}
} catch ( error ) {
} catch ( error ) {
// Ignore errors - prewarming is non-critical
// Ignore errors - prewarming is non-critical
}
}
updateProgress ( 4 , 100 ) ;
updateProgress ( 9 , 100 ) ;
// Step 6: Load RSS feeds (if logged in)
// Step 11: Load more profiles (from discussions and threads)
updateProgress ( 5 , 0 ) ;
updateProgress ( 10 , 0 ) ;
try {
const { getRecentCachedEvents } = await import ( './event-cache.js' ) ;
const discussionEvents = await getRecentCachedEvents ( [ KIND . SHORT_TEXT_NOTE , KIND . LONG_FORM_NOTE ] , 7 * 24 * 60 * 60 * 1000 , 50 ) ;
const pubkeys = [ . . . new Set ( discussionEvents . map ( e = > e . pubkey ) ) ] . slice ( 0 , 30 ) ;
if ( pubkeys . length > 0 ) {
const profileRelays = relayManager . getProfileReadRelays ( ) ;
const prewarmTimeout = config . standardTimeout ;
await Promise . race ( [
nostrClient . fetchEvents (
[ { kinds : [ KIND . METADATA ] , authors : pubkeys , limit : 1 } ] ,
profileRelays ,
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
) ,
new Promise ( ( _ , reject ) = >
setTimeout ( ( ) = > reject ( new Error ( 'More profiles fetch timeout' ) ) , prewarmTimeout + 2000 )
)
] ) . catch ( ( ) = > {
// Ignore errors - prewarming is non-critical
} ) ;
}
} catch ( error ) {
// Ignore errors - prewarming is non-critical
}
updateProgress ( 8 , 100 ) ;
// Step 12: Load RSS feeds (if logged in)
updateProgress ( 11 , 0 ) ;
try {
try {
if ( sessionManager . isLoggedIn ( ) ) {
if ( sessionManager . isLoggedIn ( ) ) {
const pubkey = sessionManager . getSession ( ) ? . pubkey ;
const pubkey = sessionManager . getSession ( ) ? . pubkey ;
@ -203,16 +384,16 @@ export async function prewarmCaches(
. . . relayManager . getFeedReadRelays ( )
. . . relayManager . getFeedReadRelays ( )
] ;
] ;
const uniqueRelays = [ . . . new Set ( relays ) ] ;
const uniqueRelays = [ . . . new Set ( relays ) ] ;
const prewarmTimeout = Math . min ( config . standardTimeout , 5000 ) ; // Max 5 seconds
const prewarmTimeout = config . standardTimeout ;
await Promise . race ( [
await Promise . race ( [
nostrClient . fetchEvents (
nostrClient . fetchEvents (
[ { kinds : [ KIND . RSS_FEED ] , authors : [ pubkey ] , limit : 1 0 } ] ,
[ { kinds : [ KIND . RSS_FEED ] , authors : [ pubkey ] , limit : 5 0 } ] , // Load more RSS feeds
uniqueRelays ,
uniqueRelays ,
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
) ,
) ,
new Promise ( ( _ , reject ) = >
new Promise ( ( _ , reject ) = >
setTimeout ( ( ) = > reject ( new Error ( 'RSS feeds fetch timeout' ) ) , prewarmTimeout + 1 000)
setTimeout ( ( ) = > reject ( new Error ( 'RSS feeds fetch timeout' ) ) , prewarmTimeout + 2 000)
)
)
] ) . catch ( ( ) = > {
] ) . catch ( ( ) = > {
// Ignore errors - prewarming is non-critical
// Ignore errors - prewarming is non-critical
@ -222,13 +403,111 @@ export async function prewarmCaches(
} catch ( error ) {
} catch ( error ) {
// Ignore errors - prewarming is non-critical
// Ignore errors - prewarming is non-critical
}
}
updateProgress ( 5 , 100 ) ;
updateProgress ( 9 , 100 ) ;
// Step 7: Finalize
// Step 11: Load GIF events
updateProgress ( 6 , 0 ) ;
updateProgress ( 10 , 0 ) ;
try {
const { fetchGifs } = await import ( '../nostr/gif-service.js' ) ;
const prewarmTimeout = config . standardTimeout ;
// Fetch GIFs to prewarm the cache (limit 100 for thorough prewarming)
await Promise . race ( [
fetchGifs ( undefined , 100 , true ) , // Force refresh to get latest GIFs
new Promise ( ( _ , reject ) = >
setTimeout ( ( ) = > reject ( new Error ( 'GIF fetch timeout' ) ) , prewarmTimeout + 2000 )
)
] ) . catch ( ( ) = > {
// Ignore errors - prewarming is non-critical
} ) ;
} catch ( error ) {
// Ignore errors - prewarming is non-critical
}
updateProgress ( 11 , 100 ) ;
// Step 13: Load emoji events (emoji sets and packs)
updateProgress ( 12 , 0 ) ;
try {
const feedRelays = relayManager . getFeedReadRelays ( ) ;
const prewarmTimeout = config . standardTimeout ;
// Load emoji sets (kind 10030) and emoji packs (kind 30030)
// If logged in, load user's emojis; otherwise load popular emojis
if ( sessionManager . isLoggedIn ( ) ) {
const pubkey = sessionManager . getSession ( ) ? . pubkey ;
if ( pubkey ) {
// Load user's emoji sets and packs
await Promise . race ( [
nostrClient . fetchEvents (
[ { kinds : [ KIND . EMOJI_SET , KIND . EMOJI_PACK ] , authors : [ pubkey ] , limit : 50 } ] ,
feedRelays ,
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
) ,
new Promise ( ( _ , reject ) = >
setTimeout ( ( ) = > reject ( new Error ( 'Emoji events fetch timeout' ) ) , prewarmTimeout + 2000 )
)
] ) . catch ( ( ) = > {
// Ignore errors - prewarming is non-critical
} ) ;
}
}
// Also load some popular emoji sets/packs (from well-known users or recent)
await Promise . race ( [
nostrClient . fetchEvents (
[ { kinds : [ KIND . EMOJI_SET , KIND . EMOJI_PACK ] , limit : 50 } ] ,
feedRelays ,
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
) ,
new Promise ( ( _ , reject ) = >
setTimeout ( ( ) = > reject ( new Error ( 'Popular emoji events fetch timeout' ) ) , prewarmTimeout + 2000 )
)
] ) . catch ( ( ) = > {
// Ignore errors - prewarming is non-critical
} ) ;
} catch ( error ) {
// Ignore errors - prewarming is non-critical
}
updateProgress ( 13 , 100 ) ;
// Step 15: Load user bookmarks (if logged in)
updateProgress ( 14 , 0 ) ;
try {
if ( sessionManager . isLoggedIn ( ) ) {
const pubkey = sessionManager . getSession ( ) ? . pubkey ;
if ( pubkey ) {
const relays = [
. . . config . defaultRelays ,
. . . config . profileRelays ,
. . . relayManager . getFeedReadRelays ( )
] ;
const uniqueRelays = [ . . . new Set ( relays ) ] ;
const prewarmTimeout = config . standardTimeout ;
await Promise . race ( [
nostrClient . fetchEvents (
[ { kinds : [ KIND . BOOKMARKS ] , authors : [ pubkey ] , limit : 20 } ] ,
uniqueRelays ,
{ useCache : 'cache-first' , cacheResults : true , timeout : prewarmTimeout }
) ,
new Promise ( ( _ , reject ) = >
setTimeout ( ( ) = > reject ( new Error ( 'Bookmarks fetch timeout' ) ) , prewarmTimeout + 2000 )
)
] ) . catch ( ( ) = > {
// Ignore errors - prewarming is non-critical
} ) ;
}
}
} catch ( error ) {
// Ignore errors - prewarming is non-critical
}
updateProgress ( 14 , 100 ) ;
// Step 16: Finalize
updateProgress ( 15 , 0 ) ;
// Small delay to ensure all operations complete
// Small delay to ensure all operations complete
await new Promise ( resolve = > setTimeout ( resolve , 100 ) ) ;
await new Promise ( resolve = > setTimeout ( resolve , 2 00) ) ;
updateProgress ( 6 , 100 ) ;
updateProgress ( 15 , 100 ) ;
} catch ( error ) {
} catch ( error ) {
// Prewarming errors are non-critical - app can still work
// Prewarming errors are non-critical - app can still work