Skip to content

Commit 6b0b252

Browse files
committed
fix: fetch PDF/comic pages with auth header to fix missing authentication error
1 parent dbaff6f commit 6b0b252

1 file changed

Lines changed: 76 additions & 70 deletions

File tree

static/js/readers/page-reader.js

Lines changed: 76 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)