@@ -70,6 +70,7 @@ export class PageReader {
7070 this . coverPageIndex = 0 ; // Index of the cover page (default 0)
7171 this . prefetchCache = new Map ( ) ; // Cache for prefetched images
7272 this . workerInitialized = false ; // Media worker status
73+ this . _displayBlobUrls = [ ] ; // Blob URLs for currently displayed pages (for cleanup)
7374
7475 // Initialize managers
7576 this . fullscreenManager = new FullscreenManager ( {
@@ -284,31 +285,24 @@ export class PageReader {
284285 async loadSinglePage ( index ) {
285286 const contentDiv = document . getElementById ( 'page-content' ) ;
286287 const imageUrl = this . getEndpoint ( `page/${ index } ` ) ;
287- const img = new Image ( ) ;
288288
289- return new Promise ( ( resolve , reject ) => {
290- img . onload = ( ) => {
291- contentDiv . innerHTML = `
292- <div class="page-image-container ${ this . fitMode } ">
293- <img src="${ imageUrl } " alt="Page ${ index + 1 } " class="page-image" id="page-image" />
294- </div>
295- ` ;
289+ // Fetch with auth header, convert to blob URL
290+ const response = await APIClient . fetch ( imageUrl ) ;
291+ const blob = await response . blob ( ) ;
292+ const objectUrl = URL . createObjectURL ( blob ) ;
293+ this . _revokePreviousDisplayBlobs ( ) ;
294+ this . _displayBlobUrls = [ objectUrl ] ;
296295
297- this . applyZoom ( this . zoomLevel ) ;
298- contentDiv . scrollTop = 0 ;
299- this . updateURL ( index ) ;
300- this . loading = false ;
301- resolve ( ) ;
302- } ;
303-
304- img . onerror = ( ) => {
305- contentDiv . innerHTML = `<div class="error">Failed to load page image</div>` ;
306- this . loading = false ;
307- reject ( new Error ( 'Failed to load image' ) ) ;
308- } ;
309-
310- img . src = imageUrl ;
311- } ) ;
296+ contentDiv . innerHTML = `
297+ <div class="page-image-container ${ this . fitMode } ">
298+ <img src="${ objectUrl } " alt="Page ${ index + 1 } " class="page-image" id="page-image" />
299+ </div>
300+ ` ;
301+
302+ this . applyZoom ( this . zoomLevel ) ;
303+ contentDiv . scrollTop = 0 ;
304+ this . updateURL ( index ) ;
305+ this . loading = false ;
312306 }
313307
314308 /**
@@ -320,41 +314,28 @@ export class PageReader {
320314 const imageUrl1 = this . getEndpoint ( `page/${ index } ` ) ;
321315 const imageUrl2 = this . getEndpoint ( `page/${ index + 1 } ` ) ;
322316
323- const img1 = new Image ( ) ;
324- const img2 = new Image ( ) ;
325-
326- return new Promise ( ( resolve , reject ) => {
327- let loaded = 0 ;
328- const checkBothLoaded = ( ) => {
329- loaded ++ ;
330- if ( loaded === 2 ) {
331- contentDiv . innerHTML = `
332- <div class="page-spread-container ${ this . fitMode } ">
333- <img src="${ imageUrl1 } " alt="Page ${ index + 1 } " class="spread-image" />
334- <img src="${ imageUrl2 } " alt="Page ${ index + 2 } " class="spread-image" />
335- </div>
336- ` ;
337-
338- this . applyZoom ( this . zoomLevel ) ;
339- contentDiv . scrollTop = 0 ;
340- this . updateURL ( index ) ;
341- this . loading = false ;
342- resolve ( ) ;
343- }
344- } ;
345-
346- img1 . onload = checkBothLoaded ;
347- img2 . onload = checkBothLoaded ;
317+ // Fetch both pages with auth headers in parallel
318+ const [ res1 , res2 ] = await Promise . all ( [
319+ APIClient . fetch ( imageUrl1 ) ,
320+ APIClient . fetch ( imageUrl2 ) ,
321+ ] ) ;
322+ const [ blob1 , blob2 ] = await Promise . all ( [ res1 . blob ( ) , res2 . blob ( ) ] ) ;
323+ const objectUrl1 = URL . createObjectURL ( blob1 ) ;
324+ const objectUrl2 = URL . createObjectURL ( blob2 ) ;
325+ this . _revokePreviousDisplayBlobs ( ) ;
326+ this . _displayBlobUrls = [ objectUrl1 , objectUrl2 ] ;
327+
328+ contentDiv . innerHTML = `
329+ <div class="page-spread-container ${ this . fitMode } ">
330+ <img src="${ objectUrl1 } " alt="Page ${ index + 1 } " class="spread-image" />
331+ <img src="${ objectUrl2 } " alt="Page ${ index + 2 } " class="spread-image" />
332+ </div>
333+ ` ;
348334
349- img1 . onerror = img2 . onerror = ( ) => {
350- contentDiv . innerHTML = `<div class="error">Failed to load page images</div>` ;
351- this . loading = false ;
352- reject ( new Error ( 'Failed to load images' ) ) ;
353- } ;
354-
355- img1 . src = imageUrl1 ;
356- img2 . src = imageUrl2 ;
357- } ) ;
335+ this . applyZoom ( this . zoomLevel ) ;
336+ contentDiv . scrollTop = 0 ;
337+ this . updateURL ( index ) ;
338+ this . loading = false ;
358339 }
359340
360341 /**
@@ -418,6 +399,20 @@ export class PageReader {
418399 }
419400 }
420401
402+ /**
403+ * Prefetch a single page image
404+ * @param {number } index - Page index to prefetch
405+ */
406+ /**
407+ * Revoke blob URLs for previously displayed pages to avoid memory leaks.
408+ */
409+ _revokePreviousDisplayBlobs ( ) {
410+ for ( const url of this . _displayBlobUrls ) {
411+ URL . revokeObjectURL ( url ) ;
412+ }
413+ this . _displayBlobUrls = [ ] ;
414+ }
415+
421416 /**
422417 * Prefetch a single page image
423418 * @param {number } index - Page index to prefetch
@@ -453,19 +448,29 @@ export class PageReader {
453448 console . warn ( `Failed to prefetch page ${ index + 1 } via worker:` , err ) ;
454449 } ) ;
455450 } else {
456- const img = new Image ( ) ;
457- img . onload = ( ) => {
458- this . prefetchCache . set ( index , img ) ;
459- if ( this . prefetchCache . size > MAX_PREFETCH_CACHE_SIZE ) {
460- const oldestKey = this . prefetchCache . keys ( ) . next ( ) . value ;
461- this . prefetchCache . delete ( oldestKey ) ;
462- }
463- console . log ( `Prefetched page ${ index + 1 } ` ) ;
464- } ;
465- img . onerror = ( ) => {
466- console . warn ( `Failed to prefetch page ${ index + 1 } ` ) ;
467- } ;
468- img . src = imageUrl ;
451+ // Fetch with auth header, convert to blob URL
452+ APIClient . fetch ( imageUrl )
453+ . then ( ( response ) => response . blob ( ) )
454+ . then ( ( blob ) => {
455+ const objectUrl = URL . createObjectURL ( blob ) ;
456+ const img = new Image ( ) ;
457+ img . src = objectUrl ;
458+ img . onload = ( ) => {
459+ this . prefetchCache . set ( index , img ) ;
460+ if ( this . prefetchCache . size > MAX_PREFETCH_CACHE_SIZE ) {
461+ const oldestKey = this . prefetchCache . keys ( ) . next ( ) . value ;
462+ const oldestImg = this . prefetchCache . get ( oldestKey ) ;
463+ if ( oldestImg ?. src ?. startsWith ( 'blob:' ) ) {
464+ URL . revokeObjectURL ( oldestImg . src ) ;
465+ }
466+ this . prefetchCache . delete ( oldestKey ) ;
467+ }
468+ console . log ( `Prefetched page ${ index + 1 } ` ) ;
469+ } ;
470+ } )
471+ . catch ( ( err ) => {
472+ console . warn ( `Failed to prefetch page ${ index + 1 } :` , err ) ;
473+ } ) ;
469474 }
470475 }
471476
@@ -762,6 +767,7 @@ export class PageReader {
762767 }
763768 }
764769 this . prefetchCache . clear ( ) ;
770+ this . _revokePreviousDisplayBlobs ( ) ;
765771 }
766772
767773 /**
0 commit comments