Skip to content

Commit a704fcb

Browse files
authored
Merge pull request #34 from richardr1126/apple-webkit
Fix Apple WebKit (iOS) PDF Upload
2 parents 28a3804 + 136fdb9 commit a704fcb

File tree

8 files changed

+56
-68
lines changed

8 files changed

+56
-68
lines changed

.github/workflows/playwright.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55
pull_request:
66
branches: [ main, master ]
77
jobs:
8-
test:
8+
e2e-testing:
99
timeout-minutes: 60
1010
runs-on: ubuntu-latest
1111
steps:

src/components/PDFViewer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export function PDFViewer({ zoomLevel }: PDFViewerProps) {
3636
clearHighlights,
3737
handleTextClick,
3838
onDocumentLoadSuccess,
39-
currDocURL,
39+
currDocData,
4040
currDocPages,
4141
currDocText,
4242
currDocPage,
@@ -163,7 +163,7 @@ export function PDFViewer({ zoomLevel }: PDFViewerProps) {
163163
<Document
164164
loading={<DocumentSkeleton />}
165165
noData={<DocumentSkeleton />}
166-
file={currDocURL}
166+
file={currDocData}
167167
onLoadSuccess={(pdf) => {
168168
onDocumentLoadSuccess(pdf);
169169
}}

src/contexts/PDFContext.tsx

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import { useTTS } from '@/contexts/TTSContext';
2929
import { useConfig } from '@/contexts/ConfigContext';
3030
import {
3131
extractTextFromPDF,
32-
convertPDFDataToURL,
3332
highlightPattern,
3433
clearHighlights,
3534
handleTextClick,
@@ -43,7 +42,7 @@ import { combineAudioChunks } from '@/utils/audio';
4342
*/
4443
interface PDFContextType {
4544
// Current document state
46-
currDocURL: string | undefined;
45+
currDocData: ArrayBuffer | undefined;
4746
currDocName: string | undefined;
4847
currDocPages: number | undefined;
4948
currDocPage: number;
@@ -99,7 +98,7 @@ export function PDFProvider({ children }: { children: ReactNode }) {
9998
} = useConfig();
10099

101100
// Current document state
102-
const [currDocURL, setCurrDocURL] = useState<string>();
101+
const [currDocData, setCurrDocData] = useState<ArrayBuffer>();
103102
const [currDocName, setCurrDocName] = useState<string>();
104103
const [currDocText, setCurrDocText] = useState<string>();
105104
const [pdfDocument, setPdfDocument] = useState<PDFDocumentProxy>();
@@ -147,14 +146,14 @@ export function PDFProvider({ children }: { children: ReactNode }) {
147146
* Triggers text extraction and processing when either the document URL or page changes
148147
*/
149148
useEffect(() => {
150-
if (currDocURL) {
149+
if (currDocData) {
151150
loadCurrDocText();
152151
}
153-
}, [currDocPage, currDocURL, loadCurrDocText]);
152+
}, [currDocPage, currDocData, loadCurrDocText]);
154153

155154
/**
156155
* Sets the current document based on its ID
157-
* Retrieves document from IndexedDB and converts it to a viewable URL
156+
* Retrieves document from IndexedDB
158157
*
159158
* @param {string} id - The unique identifier of the document to set
160159
* @returns {Promise<void>}
@@ -163,12 +162,11 @@ export function PDFProvider({ children }: { children: ReactNode }) {
163162
try {
164163
const doc = await indexedDBService.getDocument(id);
165164
if (doc) {
166-
const url = await convertPDFDataToURL(doc.data);
167165
setCurrDocName(doc.name);
168-
setCurrDocURL(url);
166+
setCurrDocData(doc.data);
169167
}
170168
} catch (error) {
171-
console.error('Failed to get document URL:', error);
169+
console.error('Failed to get document:', error);
172170
}
173171
}, []);
174172

@@ -178,7 +176,7 @@ export function PDFProvider({ children }: { children: ReactNode }) {
178176
*/
179177
const clearCurrDoc = useCallback(() => {
180178
setCurrDocName(undefined);
181-
setCurrDocURL(undefined);
179+
setCurrDocData(undefined);
182180
setCurrDocText(undefined);
183181
setCurrDocPages(undefined);
184182
setPdfDocument(undefined);
@@ -306,7 +304,7 @@ export function PDFProvider({ children }: { children: ReactNode }) {
306304
() => ({
307305
onDocumentLoadSuccess,
308306
setCurrentDocument,
309-
currDocURL,
307+
currDocData,
310308
currDocName,
311309
currDocPages,
312310
currDocPage,
@@ -322,7 +320,7 @@ export function PDFProvider({ children }: { children: ReactNode }) {
322320
[
323321
onDocumentLoadSuccess,
324322
setCurrentDocument,
325-
currDocURL,
323+
currDocData,
326324
currDocName,
327325
currDocPages,
328326
currDocPage,

src/hooks/pdf/usePDFDocuments.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,15 @@ export function usePDFDocuments() {
3939
*/
4040
const addDocument = useCallback(async (file: File): Promise<string> => {
4141
const id = uuidv4();
42+
const arrayBuffer = await file.arrayBuffer();
43+
4244
const newDoc: PDFDocument = {
4345
id,
4446
type: 'pdf',
4547
name: file.name,
4648
size: file.size,
4749
lastModified: file.lastModified,
48-
data: new Blob([file], { type: file.type }),
50+
data: arrayBuffer,
4951
};
5052

5153
try {

src/types/documents.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export interface BaseDocument {
1111

1212
export interface PDFDocument extends BaseDocument {
1313
type: 'pdf';
14-
data: Blob;
14+
data: ArrayBuffer;
1515
}
1616

1717
export interface EPUBDocument extends BaseDocument {

src/utils/indexedDB.ts

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class IndexedDBService {
4343
request.onupgradeneeded = (event) => {
4444
console.log('Upgrading IndexedDB schema...');
4545
const db = (event.target as IDBOpenDBRequest).result;
46-
46+
4747
if (!db.objectStoreNames.contains(PDF_STORE_NAME)) {
4848
console.log('Creating PDF documents store...');
4949
db.createObjectStore(PDF_STORE_NAME, { keyPath: 'id' });
@@ -75,7 +75,12 @@ class IndexedDBService {
7575
console.log('Adding document to IndexedDB:', document.name);
7676
const transaction = this.db!.transaction([PDF_STORE_NAME], 'readwrite');
7777
const store = transaction.objectStore(PDF_STORE_NAME);
78-
const request = store.put(document);
78+
79+
// Create a structured clone of the document to ensure proper storage
80+
const request = store.put({
81+
...document,
82+
data: document.data // Store ArrayBuffer directly
83+
});
7984

8085
request.onerror = (event) => {
8186
const error = (event.target as IDBRequest).error;
@@ -165,7 +170,7 @@ class IndexedDBService {
165170
// Create two transactions - one for document deletion, one for location cleanup
166171
const docTransaction = this.db!.transaction([PDF_STORE_NAME], 'readwrite');
167172
const configTransaction = this.db!.transaction([CONFIG_STORE_NAME], 'readwrite');
168-
173+
169174
const docStore = docTransaction.objectStore(PDF_STORE_NAME);
170175
const configStore = configTransaction.objectStore(CONFIG_STORE_NAME);
171176

@@ -221,7 +226,7 @@ class IndexedDBService {
221226

222227
const transaction = this.db!.transaction([EPUB_STORE_NAME], 'readwrite');
223228
const store = transaction.objectStore(EPUB_STORE_NAME);
224-
229+
225230
// Create a structured clone of the document to ensure proper storage
226231
const request = store.put({
227232
...document,
@@ -315,7 +320,7 @@ class IndexedDBService {
315320
// Create two transactions - one for document deletion, one for location cleanup
316321
const docTransaction = this.db!.transaction([EPUB_STORE_NAME], 'readwrite');
317322
const configTransaction = this.db!.transaction([CONFIG_STORE_NAME], 'readwrite');
318-
323+
319324
const docStore = docTransaction.objectStore(EPUB_STORE_NAME);
320325
const configStore = configTransaction.objectStore(CONFIG_STORE_NAME);
321326

@@ -477,17 +482,15 @@ class IndexedDBService {
477482
async syncToServer(): Promise<{ lastSync: number }> {
478483
const pdfDocs = await this.getAllDocuments();
479484
const epubDocs = await this.getAllEPUBDocuments();
480-
485+
481486
const documents = [];
482-
483-
// Process PDF documents - store the raw PDF data
487+
488+
// Process PDF documents - convert ArrayBuffer to array for JSON serialization
484489
for (const doc of pdfDocs) {
485-
const arrayBuffer = await doc.data.arrayBuffer();
486-
const uint8Array = new Uint8Array(arrayBuffer);
487490
documents.push({
488491
...doc,
489492
type: 'pdf',
490-
data: Array.from(uint8Array) // Convert to regular array for JSON serialization
493+
data: Array.from(new Uint8Array(doc.data))
491494
});
492495
}
493496

@@ -496,7 +499,7 @@ class IndexedDBService {
496499
documents.push({
497500
...doc,
498501
type: 'epub',
499-
data: Array.from(new Uint8Array(doc.data)) // Convert to regular array for JSON serialization
502+
data: Array.from(new Uint8Array(doc.data))
500503
});
501504
}
502505

@@ -520,31 +523,26 @@ class IndexedDBService {
520523
}
521524

522525
const { documents } = await response.json();
523-
526+
524527
// Process each document
525528
for (const doc of documents) {
529+
// Convert the numeric array back to ArrayBuffer
530+
const uint8Array = new Uint8Array(doc.data);
531+
const documentData = {
532+
id: doc.id,
533+
type: doc.type,
534+
name: doc.name,
535+
size: doc.size,
536+
lastModified: doc.lastModified,
537+
data: uint8Array.buffer
538+
};
539+
526540
if (doc.type === 'pdf') {
527-
// Create a Blob from the raw binary data
528-
const blob = new Blob([new Uint8Array(doc.data)], { type: 'application/pdf' });
529-
await this.addDocument({
530-
id: doc.id,
531-
type: doc.type,
532-
name: doc.name,
533-
size: doc.size,
534-
lastModified: doc.lastModified,
535-
data: blob
536-
});
541+
await this.addDocument(documentData);
537542
} else if (doc.type === 'epub') {
538-
// Convert the numeric array back to ArrayBuffer for EPUB
539-
const uint8Array = new Uint8Array(doc.data);
540-
await this.addEPUBDocument({
541-
id: doc.id,
542-
type: doc.type,
543-
name: doc.name,
544-
size: doc.size,
545-
lastModified: doc.lastModified,
546-
data: uint8Array.buffer
547-
});
543+
await this.addEPUBDocument(documentData);
544+
} else {
545+
console.warn(`Unknown document type: ${doc.type}`);
548546
}
549547
}
550548

src/utils/pdf.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,6 @@ interface TextMatch {
1515
lengthDiff: number;
1616
}
1717

18-
// URL Conversion functions
19-
export function convertPDFDataToURL(pdfData: Blob): Promise<string> {
20-
return new Promise((resolve, reject) => {
21-
const reader = new FileReader();
22-
reader.onload = () => resolve(reader.result as string);
23-
reader.onerror = () => reject(reader.error);
24-
reader.readAsDataURL(pdfData);
25-
});
26-
}
27-
2818
// Text Processing functions
2919
export async function extractTextFromPDF(
3020
pdf: PDFDocumentProxy,

tests/e2e.spec.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ async function uploadFile(page: Page, filePath: string) {
1111
test.describe('Document Upload and Display', () => {
1212
test.beforeEach(async ({ page }) => {
1313
// Navigate to the home page before each test
14-
await page.goto('http://localhost:3003');
14+
await page.goto('/');
1515

1616
// Click the "done" button to dismiss the welcome message
1717
await page.getByText('Done').click();
@@ -23,19 +23,19 @@ test.describe('Document Upload and Display', () => {
2323
await uploadFile(page, './public/sample.pdf');
2424

2525
// Verify upload success
26-
await expect(page.getByText('sample.pdf')).toBeVisible();
26+
await expect(page.getByText('sample.pdf')).toBeVisible({ timeout: 10000 });
2727
});
2828

2929
test('should display a PDF document', async ({ page }) => {
3030
// Upload the file
3131
await uploadFile(page, './public/sample.pdf');
3232

3333
// Click on the uploaded document
34-
await page.getByText('sample.pdf').click();
34+
await page.getByText('sample.pdf').click({ timeout: 10000 });
3535

3636
// Verify PDF viewer is displayed
37-
await expect(page.locator('.react-pdf__Document')).toBeVisible();
38-
await expect(page.locator('.react-pdf__Page')).toBeVisible();
37+
await expect(page.locator('.react-pdf__Document')).toBeVisible({ timeout: 10000 });
38+
await expect(page.locator('.react-pdf__Page')).toBeVisible({ timeout: 10000 });
3939
});
4040
});
4141

@@ -45,18 +45,18 @@ test.describe('Document Upload and Display', () => {
4545
await uploadFile(page, './public/sample.epub');
4646

4747
// Verify upload success
48-
await expect(page.getByText('sample.epub')).toBeVisible();
48+
await expect(page.getByText('sample.epub')).toBeVisible({ timeout: 10000 });
4949
});
5050

5151
test('should display an EPUB document', async ({ page }) => {
5252
// Upload the file
5353
await uploadFile(page, './public/sample.epub');
5454

5555
// Click on the uploaded document
56-
await page.getByText('sample.epub').click();
56+
await page.getByText('sample.epub').click({ timeout: 10000 });
5757

5858
// Verify EPUB viewer is displayed
59-
await expect(page.locator('.epub-container')).toBeVisible();
59+
await expect(page.locator('.epub-container')).toBeVisible({ timeout: 10000 });
6060
});
6161
});
6262
});

0 commit comments

Comments
 (0)