@ankhorage/expo-runtime 3.1.1 → 3.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +2 -1
- package/dist/barcodeScanner.d.ts +2 -0
- package/dist/barcodeScanner.d.ts.map +1 -0
- package/dist/barcodeScanner.js +2 -0
- package/dist/barcodeScanner.js.map +1 -0
- package/dist/createExpoRuntimeRegistry.d.ts.map +1 -1
- package/dist/createExpoRuntimeRegistry.js +5 -0
- package/dist/createExpoRuntimeRegistry.js.map +1 -1
- package/dist/expoRuntimePlanningMetadata.d.ts +9 -1
- package/dist/expoRuntimePlanningMetadata.d.ts.map +1 -1
- package/dist/expoRuntimePlanningMetadata.js +8 -0
- package/dist/expoRuntimePlanningMetadata.js.map +1 -1
- package/dist/generatedSources.d.ts +1 -0
- package/dist/generatedSources.d.ts.map +1 -1
- package/dist/generatedSources.js +9 -1
- package/dist/generatedSources.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/planning.d.ts +1 -1
- package/dist/planning.d.ts.map +1 -1
- package/dist/planning.js +1 -1
- package/dist/planning.js.map +1 -1
- package/dist/reader/ExpoReaderSurfaceAdapter.d.ts +4 -0
- package/dist/reader/ExpoReaderSurfaceAdapter.d.ts.map +1 -0
- package/dist/reader/ExpoReaderSurfaceAdapter.js +102 -0
- package/dist/reader/ExpoReaderSurfaceAdapter.js.map +1 -0
- package/dist/reader/ReaderDocumentError.d.ts +6 -0
- package/dist/reader/ReaderDocumentError.d.ts.map +1 -0
- package/dist/reader/ReaderDocumentError.js +9 -0
- package/dist/reader/ReaderDocumentError.js.map +1 -0
- package/dist/reader/ReaderViewport.d.ts +4 -0
- package/dist/reader/ReaderViewport.d.ts.map +1 -0
- package/dist/reader/ReaderViewport.js +212 -0
- package/dist/reader/ReaderViewport.js.map +1 -0
- package/dist/reader/drivers/EpubReaderDriver.d.ts +20 -0
- package/dist/reader/drivers/EpubReaderDriver.d.ts.map +1 -0
- package/dist/reader/drivers/EpubReaderDriver.js +259 -0
- package/dist/reader/drivers/EpubReaderDriver.js.map +1 -0
- package/dist/reader/drivers/PdfReaderDriver.d.ts +18 -0
- package/dist/reader/drivers/PdfReaderDriver.d.ts.map +1 -0
- package/dist/reader/drivers/PdfReaderDriver.js +198 -0
- package/dist/reader/drivers/PdfReaderDriver.js.map +1 -0
- package/dist/reader/epub/EpubArchive.d.ts +11 -0
- package/dist/reader/epub/EpubArchive.d.ts.map +1 -0
- package/dist/reader/epub/EpubArchive.js +65 -0
- package/dist/reader/epub/EpubArchive.js.map +1 -0
- package/dist/reader/epub/EpubArchiveFetcher.d.ts +11 -0
- package/dist/reader/epub/EpubArchiveFetcher.d.ts.map +1 -0
- package/dist/reader/epub/EpubArchiveFetcher.js +44 -0
- package/dist/reader/epub/EpubArchiveFetcher.js.map +1 -0
- package/dist/reader/epub/mediaTypes.d.ts +3 -0
- package/dist/reader/epub/mediaTypes.d.ts.map +1 -0
- package/dist/reader/epub/mediaTypes.js +26 -0
- package/dist/reader/epub/mediaTypes.js.map +1 -0
- package/dist/reader/epub/parseEpubPackage.d.ts +22 -0
- package/dist/reader/epub/parseEpubPackage.d.ts.map +1 -0
- package/dist/reader/epub/parseEpubPackage.js +98 -0
- package/dist/reader/epub/parseEpubPackage.js.map +1 -0
- package/dist/reader/epub/path.d.ts +4 -0
- package/dist/reader/epub/path.d.ts.map +1 -0
- package/dist/reader/epub/path.js +28 -0
- package/dist/reader/epub/path.js.map +1 -0
- package/dist/reader/epub/sanitizeEpubDocument.d.ts +3 -0
- package/dist/reader/epub/sanitizeEpubDocument.d.ts.map +1 -0
- package/dist/reader/epub/sanitizeEpubDocument.js +90 -0
- package/dist/reader/epub/sanitizeEpubDocument.js.map +1 -0
- package/dist/reader/index.d.ts +2 -0
- package/dist/reader/index.d.ts.map +1 -0
- package/dist/reader/index.js +2 -0
- package/dist/reader/index.js.map +1 -0
- package/dist/reader/loadReaderSourceAsync.d.ts +2 -0
- package/dist/reader/loadReaderSourceAsync.d.ts.map +1 -0
- package/dist/reader/loadReaderSourceAsync.js +49 -0
- package/dist/reader/loadReaderSourceAsync.js.map +1 -0
- package/dist/reader/readerViewportPresentation.d.ts +4 -0
- package/dist/reader/readerViewportPresentation.d.ts.map +1 -0
- package/dist/reader/readerViewportPresentation.js +26 -0
- package/dist/reader/readerViewportPresentation.js.map +1 -0
- package/dist/reader/types.d.ts +57 -0
- package/dist/reader/types.d.ts.map +1 -0
- package/dist/reader/types.js +2 -0
- package/dist/reader/types.js.map +1 -0
- package/dist/reader/validateEpubArchiveEntriesAsync.d.ts +3 -0
- package/dist/reader/validateEpubArchiveEntriesAsync.d.ts.map +1 -0
- package/dist/reader/validateEpubArchiveEntriesAsync.js +48 -0
- package/dist/reader/validateEpubArchiveEntriesAsync.js.map +1 -0
- package/package.json +25 -4
- package/src/adapterEntrypoints.test.ts +48 -0
- package/src/barcodeScanner.ts +1 -0
- package/src/createExpoRuntimeRegistry.test.tsx +9 -1
- package/src/createExpoRuntimeRegistry.tsx +7 -1
- package/src/expoRuntimePlanningMetadata.ts +9 -1
- package/src/generatedSources.test.ts +11 -2
- package/src/generatedSources.ts +11 -1
- package/src/index.ts +5 -1
- package/src/planning.ts +4 -1
- package/src/reader/ExpoReaderSurfaceAdapter.tsx +170 -0
- package/src/reader/ReaderDocumentError.ts +11 -0
- package/src/reader/ReaderViewport.tsx +319 -0
- package/src/reader/drivers/EpubReaderDriver.ts +309 -0
- package/src/reader/drivers/PdfReaderDriver.ts +253 -0
- package/src/reader/epub/EpubArchive.ts +83 -0
- package/src/reader/epub/EpubArchiveFetcher.ts +58 -0
- package/src/reader/epub/mediaTypes.ts +16 -0
- package/src/reader/epub/parseEpubPackage.test.ts +116 -0
- package/src/reader/epub/parseEpubPackage.ts +152 -0
- package/src/reader/epub/path.test.ts +22 -0
- package/src/reader/epub/path.ts +26 -0
- package/src/reader/epub/sanitizeEpubDocument.ts +121 -0
- package/src/reader/index.ts +1 -0
- package/src/reader/loadReaderSourceAsync.test.ts +52 -0
- package/src/reader/loadReaderSourceAsync.ts +59 -0
- package/src/reader/readerViewportPresentation.ts +28 -0
- package/src/reader/rendererPackages.test.ts +3 -1
- package/src/reader/types.ts +61 -0
- package/src/reader/validateEpubArchiveEntriesAsync.test.ts +142 -0
- package/src/reader/validateEpubArchiveEntriesAsync.ts +73 -0
- package/src/resolveExpoRuntimePlan.test.ts +36 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.js","sourceRoot":"","sources":["../../../src/reader/epub/path.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,OAAO,IAAI,kBAAkB,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAChF,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG;YAAE,SAAS;QAChD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,IAAI,EAAE,CAAC,CAAC;YACzF,QAAQ,CAAC,GAAG,EAAE,CAAC;YACf,SAAS;QACX,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,QAAgB,EAAE,SAAiB;IACpE,MAAM,cAAc,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1E,IAAI,cAAc,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,oBAAoB,CAAC,cAAc,CAAC,CAAC;IAChF,MAAM,aAAa,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;QAC1C,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClD,CAAC,CAAC,EAAE,CAAC;IACP,OAAO,oBAAoB,CAAC,GAAG,aAAa,GAAG,cAAc,EAAE,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,SAAiB;IACnD,OAAO,mDAAmD,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC;AACpF,CAAC","sourcesContent":["export function normalizeArchivePath(path: string): string {\n const segments: string[] = [];\n for (const segment of decodeURIComponent(path).replaceAll('\\\\', '/').split('/')) {\n if (segment === '' || segment === '.') continue;\n if (segment === '..') {\n if (segments.length === 0) throw new Error(`EPUB resource escapes the archive: ${path}`);\n segments.pop();\n continue;\n }\n segments.push(segment);\n }\n return segments.join('/');\n}\n\nexport function resolveArchivePath(basePath: string, reference: string): string {\n const cleanReference = reference.split('#', 1)[0]?.split('?', 1)[0] ?? '';\n if (cleanReference.startsWith('/')) return normalizeArchivePath(cleanReference);\n const baseDirectory = basePath.includes('/')\n ? basePath.slice(0, basePath.lastIndexOf('/') + 1)\n : '';\n return normalizeArchivePath(`${baseDirectory}${cleanReference}`);\n}\n\nexport function isExternalReference(reference: string): boolean {\n return /^(?:https?:|mailto:|tel:|data:|javascript:|\\/\\/)/i.test(reference.trim());\n}\n"]}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function sanitizeEpubDocument(source: string, resourcePath: string, resolveResourceUrl: (path: string) => string | undefined): string;
|
|
2
|
+
export declare function sanitizeEpubStylesheet(source: string, resourcePath: string, resolveResourceUrl: (path: string) => string | undefined): string;
|
|
3
|
+
//# sourceMappingURL=sanitizeEpubDocument.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitizeEpubDocument.d.ts","sourceRoot":"","sources":["../../../src/reader/epub/sanitizeEpubDocument.ts"],"names":[],"mappings":"AAKA,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,kBAAkB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACvD,MAAM,CAkBR;AAuDD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,kBAAkB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GACvD,MAAM,CAaR"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { isExternalReference, resolveArchivePath } from './path';
|
|
2
|
+
const REMOVED_ELEMENTS = 'script, form, iframe, frame, object, embed';
|
|
3
|
+
const RESOURCE_ATTRIBUTES = ['src', 'poster', 'data'];
|
|
4
|
+
export function sanitizeEpubDocument(source, resourcePath, resolveResourceUrl) {
|
|
5
|
+
const document = new DOMParser().parseFromString(source, 'application/xhtml+xml');
|
|
6
|
+
if (document.querySelector('parsererror')) {
|
|
7
|
+
throw new Error(`Invalid EPUB XHTML resource: ${resourcePath}`);
|
|
8
|
+
}
|
|
9
|
+
document.querySelectorAll(REMOVED_ELEMENTS).forEach((element) => element.remove());
|
|
10
|
+
document
|
|
11
|
+
.querySelectorAll('meta[http-equiv]')
|
|
12
|
+
.forEach((element) => element.getAttribute('http-equiv')?.toLowerCase() === 'refresh' && element.remove());
|
|
13
|
+
sanitizeElementAttributes(document, resourcePath, resolveResourceUrl);
|
|
14
|
+
rewriteDocumentResources(document, resourcePath, resolveResourceUrl);
|
|
15
|
+
rewriteDocumentLinks(document);
|
|
16
|
+
return new XMLSerializer().serializeToString(document);
|
|
17
|
+
}
|
|
18
|
+
function sanitizeElementAttributes(document, resourcePath, resolveResourceUrl) {
|
|
19
|
+
for (const element of document.querySelectorAll('*')) {
|
|
20
|
+
for (const attribute of Array.from(element.attributes)) {
|
|
21
|
+
if (attribute.name.toLowerCase().startsWith('on'))
|
|
22
|
+
element.removeAttribute(attribute.name);
|
|
23
|
+
}
|
|
24
|
+
const inlineStyle = element.getAttribute('style');
|
|
25
|
+
if (inlineStyle) {
|
|
26
|
+
element.setAttribute('style', sanitizeEpubStylesheet(inlineStyle, resourcePath, resolveResourceUrl));
|
|
27
|
+
}
|
|
28
|
+
element.removeAttribute('srcset');
|
|
29
|
+
}
|
|
30
|
+
for (const style of document.querySelectorAll('style')) {
|
|
31
|
+
style.textContent = sanitizeEpubStylesheet(style.textContent, resourcePath, resolveResourceUrl);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function rewriteDocumentResources(document, resourcePath, resolveResourceUrl) {
|
|
35
|
+
for (const attributeName of RESOURCE_ATTRIBUTES) {
|
|
36
|
+
for (const element of document.querySelectorAll(`[${attributeName}]`)) {
|
|
37
|
+
rewriteResourceAttribute(element, attributeName, resourcePath, resolveResourceUrl);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
for (const link of document.querySelectorAll('link[href]')) {
|
|
41
|
+
rewriteResourceAttribute(link, 'href', resourcePath, resolveResourceUrl);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function rewriteDocumentLinks(document) {
|
|
45
|
+
for (const anchor of document.querySelectorAll('a[href]')) {
|
|
46
|
+
const href = anchor.getAttribute('href')?.trim();
|
|
47
|
+
if (!href)
|
|
48
|
+
continue;
|
|
49
|
+
if (/^(?:https?:|mailto:|tel:|\/\/)/i.test(href)) {
|
|
50
|
+
anchor.setAttribute('data-reader-external-href', href);
|
|
51
|
+
anchor.removeAttribute('href');
|
|
52
|
+
}
|
|
53
|
+
else if (isExternalReference(href)) {
|
|
54
|
+
anchor.removeAttribute('href');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function sanitizeEpubStylesheet(source, resourcePath, resolveResourceUrl) {
|
|
59
|
+
const withoutImports = source.replaceAll(/@import\s+(?:url\([^)]*\)|[^;]+);?/gi, '');
|
|
60
|
+
return withoutImports.replaceAll(/url\(\s*(['"]?)([^)'"\s]+)\1\s*\)/gi, (match, _quote, value) => {
|
|
61
|
+
if (isExternalReference(value))
|
|
62
|
+
return 'url(about:blank)';
|
|
63
|
+
try {
|
|
64
|
+
return `url("${resolveResourceUrl(resolveArchivePath(resourcePath, value)) ?? 'about:blank'}")`;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return 'url(about:blank)';
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function rewriteResourceAttribute(element, attributeName, resourcePath, resolveResourceUrl) {
|
|
72
|
+
const value = element.getAttribute(attributeName)?.trim();
|
|
73
|
+
if (!value)
|
|
74
|
+
return;
|
|
75
|
+
if (isExternalReference(value)) {
|
|
76
|
+
element.removeAttribute(attributeName);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const resolved = resolveResourceUrl(resolveArchivePath(resourcePath, value));
|
|
81
|
+
if (resolved === undefined)
|
|
82
|
+
element.removeAttribute(attributeName);
|
|
83
|
+
else
|
|
84
|
+
element.setAttribute(attributeName, resolved);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
element.removeAttribute(attributeName);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=sanitizeEpubDocument.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitizeEpubDocument.js","sourceRoot":"","sources":["../../../src/reader/epub/sanitizeEpubDocument.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAEjE,MAAM,gBAAgB,GAAG,4CAA4C,CAAC;AACtE,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAU,CAAC;AAE/D,MAAM,UAAU,oBAAoB,CAClC,MAAc,EACd,YAAoB,EACpB,kBAAwD;IAExD,MAAM,QAAQ,GAAG,IAAI,SAAS,EAAE,CAAC,eAAe,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;IAClF,IAAI,QAAQ,CAAC,aAAa,CAAC,aAAa,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,gCAAgC,YAAY,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,QAAQ,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACnF,QAAQ;SACL,gBAAgB,CAAC,kBAAkB,CAAC;SACpC,OAAO,CACN,CAAC,OAAO,EAAE,EAAE,CACV,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC,EAAE,WAAW,EAAE,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,CACtF,CAAC;IAEJ,yBAAyB,CAAC,QAAQ,EAAE,YAAY,EAAE,kBAAkB,CAAC,CAAC;IACtE,wBAAwB,CAAC,QAAQ,EAAE,YAAY,EAAE,kBAAkB,CAAC,CAAC;IACrE,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAC/B,OAAO,IAAI,aAAa,EAAE,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,yBAAyB,CAChC,QAAqB,EACrB,YAAoB,EACpB,kBAAwD;IAExD,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;QACrD,KAAK,MAAM,SAAS,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YACvD,IAAI,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,OAAO,CAAC,eAAe,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAC7F,CAAC;QACD,MAAM,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QAClD,IAAI,WAAW,EAAE,CAAC;YAChB,OAAO,CAAC,YAAY,CAClB,OAAO,EACP,sBAAsB,CAAC,WAAW,EAAE,YAAY,EAAE,kBAAkB,CAAC,CACtE,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;QACvD,KAAK,CAAC,WAAW,GAAG,sBAAsB,CAAC,KAAK,CAAC,WAAW,EAAE,YAAY,EAAE,kBAAkB,CAAC,CAAC;IAClG,CAAC;AACH,CAAC;AAED,SAAS,wBAAwB,CAC/B,QAAqB,EACrB,YAAoB,EACpB,kBAAwD;IAExD,KAAK,MAAM,aAAa,IAAI,mBAAmB,EAAE,CAAC;QAChD,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,gBAAgB,CAAC,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;YACtE,wBAAwB,CAAC,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,kBAAkB,CAAC,CAAC;QACrF,CAAC;IACH,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,gBAAgB,CAAC,YAAY,CAAC,EAAE,CAAC;QAC3D,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,kBAAkB,CAAC,CAAC;IAC3E,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB,CAAC,QAAqB;IACjD,KAAK,MAAM,MAAM,IAAI,QAAQ,CAAC,gBAAgB,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC;QACjD,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,IAAI,iCAAiC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACjD,MAAM,CAAC,YAAY,CAAC,2BAA2B,EAAE,IAAI,CAAC,CAAC;YACvD,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC;aAAM,IAAI,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;YACrC,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,MAAc,EACd,YAAoB,EACpB,kBAAwD;IAExD,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,sCAAsC,EAAE,EAAE,CAAC,CAAC;IACrF,OAAO,cAAc,CAAC,UAAU,CAC9B,qCAAqC,EACrC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAa,EAAE,EAAE;QAC/B,IAAI,mBAAmB,CAAC,KAAK,CAAC;YAAE,OAAO,kBAAkB,CAAC;QAC1D,IAAI,CAAC;YACH,OAAO,QAAQ,kBAAkB,CAAC,kBAAkB,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,IAAI,aAAa,IAAI,CAAC;QAClG,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,kBAAkB,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC;AAED,SAAS,wBAAwB,CAC/B,OAAgB,EAChB,aAAqB,EACrB,YAAoB,EACpB,kBAAwD;IAExD,MAAM,KAAK,GAAG,OAAO,CAAC,YAAY,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,CAAC;IAC1D,IAAI,CAAC,KAAK;QAAE,OAAO;IACnB,IAAI,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC;QACvC,OAAO;IACT,CAAC;IACD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,kBAAkB,CAAC,kBAAkB,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,CAAC;QAC7E,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC;;YAC9D,OAAO,CAAC,YAAY,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC;IACzC,CAAC;AACH,CAAC","sourcesContent":["import { isExternalReference, resolveArchivePath } from './path';\n\nconst REMOVED_ELEMENTS = 'script, form, iframe, frame, object, embed';\nconst RESOURCE_ATTRIBUTES = ['src', 'poster', 'data'] as const;\n\nexport function sanitizeEpubDocument(\n source: string,\n resourcePath: string,\n resolveResourceUrl: (path: string) => string | undefined,\n): string {\n const document = new DOMParser().parseFromString(source, 'application/xhtml+xml');\n if (document.querySelector('parsererror')) {\n throw new Error(`Invalid EPUB XHTML resource: ${resourcePath}`);\n }\n\n document.querySelectorAll(REMOVED_ELEMENTS).forEach((element) => element.remove());\n document\n .querySelectorAll('meta[http-equiv]')\n .forEach(\n (element) =>\n element.getAttribute('http-equiv')?.toLowerCase() === 'refresh' && element.remove(),\n );\n\n sanitizeElementAttributes(document, resourcePath, resolveResourceUrl);\n rewriteDocumentResources(document, resourcePath, resolveResourceUrl);\n rewriteDocumentLinks(document);\n return new XMLSerializer().serializeToString(document);\n}\n\nfunction sanitizeElementAttributes(\n document: XMLDocument,\n resourcePath: string,\n resolveResourceUrl: (path: string) => string | undefined,\n): void {\n for (const element of document.querySelectorAll('*')) {\n for (const attribute of Array.from(element.attributes)) {\n if (attribute.name.toLowerCase().startsWith('on')) element.removeAttribute(attribute.name);\n }\n const inlineStyle = element.getAttribute('style');\n if (inlineStyle) {\n element.setAttribute(\n 'style',\n sanitizeEpubStylesheet(inlineStyle, resourcePath, resolveResourceUrl),\n );\n }\n element.removeAttribute('srcset');\n }\n\n for (const style of document.querySelectorAll('style')) {\n style.textContent = sanitizeEpubStylesheet(style.textContent, resourcePath, resolveResourceUrl);\n }\n}\n\nfunction rewriteDocumentResources(\n document: XMLDocument,\n resourcePath: string,\n resolveResourceUrl: (path: string) => string | undefined,\n): void {\n for (const attributeName of RESOURCE_ATTRIBUTES) {\n for (const element of document.querySelectorAll(`[${attributeName}]`)) {\n rewriteResourceAttribute(element, attributeName, resourcePath, resolveResourceUrl);\n }\n }\n\n for (const link of document.querySelectorAll('link[href]')) {\n rewriteResourceAttribute(link, 'href', resourcePath, resolveResourceUrl);\n }\n}\n\nfunction rewriteDocumentLinks(document: XMLDocument): void {\n for (const anchor of document.querySelectorAll('a[href]')) {\n const href = anchor.getAttribute('href')?.trim();\n if (!href) continue;\n if (/^(?:https?:|mailto:|tel:|\\/\\/)/i.test(href)) {\n anchor.setAttribute('data-reader-external-href', href);\n anchor.removeAttribute('href');\n } else if (isExternalReference(href)) {\n anchor.removeAttribute('href');\n }\n }\n}\n\nexport function sanitizeEpubStylesheet(\n source: string,\n resourcePath: string,\n resolveResourceUrl: (path: string) => string | undefined,\n): string {\n const withoutImports = source.replaceAll(/@import\\s+(?:url\\([^)]*\\)|[^;]+);?/gi, '');\n return withoutImports.replaceAll(\n /url\\(\\s*(['\"]?)([^)'\"\\s]+)\\1\\s*\\)/gi,\n (match, _quote, value: string) => {\n if (isExternalReference(value)) return 'url(about:blank)';\n try {\n return `url(\"${resolveResourceUrl(resolveArchivePath(resourcePath, value)) ?? 'about:blank'}\")`;\n } catch {\n return 'url(about:blank)';\n }\n },\n );\n}\n\nfunction rewriteResourceAttribute(\n element: Element,\n attributeName: string,\n resourcePath: string,\n resolveResourceUrl: (path: string) => string | undefined,\n): void {\n const value = element.getAttribute(attributeName)?.trim();\n if (!value) return;\n if (isExternalReference(value)) {\n element.removeAttribute(attributeName);\n return;\n }\n try {\n const resolved = resolveResourceUrl(resolveArchivePath(resourcePath, value));\n if (resolved === undefined) element.removeAttribute(attributeName);\n else element.setAttribute(attributeName, resolved);\n } catch {\n element.removeAttribute(attributeName);\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/reader/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/reader/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC","sourcesContent":["export { ExpoReaderSurfaceAdapter } from './ExpoReaderSurfaceAdapter';\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadReaderSourceAsync.d.ts","sourceRoot":"","sources":["../../src/reader/loadReaderSourceAsync.ts"],"names":[],"mappings":"AAEA,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,UAAU,CAAC,CA8CrB"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const MAX_READER_SOURCE_BYTES = 256 * 1024 * 1024;
|
|
2
|
+
export async function loadReaderSourceAsync(sourceUri, signal) {
|
|
3
|
+
const response = await fetch(sourceUri, {
|
|
4
|
+
credentials: 'omit',
|
|
5
|
+
signal,
|
|
6
|
+
});
|
|
7
|
+
if (!response.ok) {
|
|
8
|
+
throw new Error(`Reader source request failed with HTTP ${response.status}.`);
|
|
9
|
+
}
|
|
10
|
+
const declaredLength = Number(response.headers.get('content-length'));
|
|
11
|
+
if (Number.isFinite(declaredLength) && declaredLength > MAX_READER_SOURCE_BYTES) {
|
|
12
|
+
throw new Error(`Reader source exceeds the ${MAX_READER_SOURCE_BYTES}-byte download limit.`);
|
|
13
|
+
}
|
|
14
|
+
if (response.body === null) {
|
|
15
|
+
return validateSourceSize(new Uint8Array(await response.arrayBuffer()));
|
|
16
|
+
}
|
|
17
|
+
const reader = response.body.getReader();
|
|
18
|
+
const chunks = [];
|
|
19
|
+
let byteLength = 0;
|
|
20
|
+
try {
|
|
21
|
+
for (;;) {
|
|
22
|
+
const result = await reader.read();
|
|
23
|
+
if (result.done)
|
|
24
|
+
break;
|
|
25
|
+
byteLength += result.value.byteLength;
|
|
26
|
+
if (byteLength > MAX_READER_SOURCE_BYTES) {
|
|
27
|
+
throw new Error(`Reader source exceeds the ${MAX_READER_SOURCE_BYTES}-byte download limit.`);
|
|
28
|
+
}
|
|
29
|
+
chunks.push(result.value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
reader.releaseLock();
|
|
34
|
+
}
|
|
35
|
+
const bytes = new Uint8Array(byteLength);
|
|
36
|
+
let offset = 0;
|
|
37
|
+
for (const chunk of chunks) {
|
|
38
|
+
bytes.set(chunk, offset);
|
|
39
|
+
offset += chunk.byteLength;
|
|
40
|
+
}
|
|
41
|
+
return bytes;
|
|
42
|
+
}
|
|
43
|
+
function validateSourceSize(bytes) {
|
|
44
|
+
if (bytes.byteLength > MAX_READER_SOURCE_BYTES) {
|
|
45
|
+
throw new Error(`Reader source exceeds the ${MAX_READER_SOURCE_BYTES}-byte download limit.`);
|
|
46
|
+
}
|
|
47
|
+
return bytes;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=loadReaderSourceAsync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loadReaderSourceAsync.js","sourceRoot":"","sources":["../../src/reader/loadReaderSourceAsync.ts"],"names":[],"mappings":"AAAA,MAAM,uBAAuB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAElD,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,SAAiB,EACjB,MAAmB;IAEnB,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;QACtC,WAAW,EAAE,MAAM;QACnB,MAAM;KACP,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,0CAA0C,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IAChF,CAAC;IAED,MAAM,cAAc,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACtE,IAAI,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,cAAc,GAAG,uBAAuB,EAAE,CAAC;QAChF,MAAM,IAAI,KAAK,CAAC,6BAA6B,uBAAuB,uBAAuB,CAAC,CAAC;IAC/F,CAAC;IAED,IAAI,QAAQ,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QAC3B,OAAO,kBAAkB,CAAC,IAAI,UAAU,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAC1E,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;IACzC,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,IAAI,UAAU,GAAG,CAAC,CAAC;IAEnB,IAAI,CAAC;QACH,SAAS,CAAC;YACR,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YACnC,IAAI,MAAM,CAAC,IAAI;gBAAE,MAAM;YACvB,UAAU,IAAI,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC;YACtC,IAAI,UAAU,GAAG,uBAAuB,EAAE,CAAC;gBACzC,MAAM,IAAI,KAAK,CACb,6BAA6B,uBAAuB,uBAAuB,CAC5E,CAAC;YACJ,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,WAAW,EAAE,CAAC;IACvB,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC;IACzC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC;IAC7B,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAiB;IAC3C,IAAI,KAAK,CAAC,UAAU,GAAG,uBAAuB,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,6BAA6B,uBAAuB,uBAAuB,CAAC,CAAC;IAC/F,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["const MAX_READER_SOURCE_BYTES = 256 * 1024 * 1024;\n\nexport async function loadReaderSourceAsync(\n sourceUri: string,\n signal: AbortSignal,\n): Promise<Uint8Array> {\n const response = await fetch(sourceUri, {\n credentials: 'omit',\n signal,\n });\n\n if (!response.ok) {\n throw new Error(`Reader source request failed with HTTP ${response.status}.`);\n }\n\n const declaredLength = Number(response.headers.get('content-length'));\n if (Number.isFinite(declaredLength) && declaredLength > MAX_READER_SOURCE_BYTES) {\n throw new Error(`Reader source exceeds the ${MAX_READER_SOURCE_BYTES}-byte download limit.`);\n }\n\n if (response.body === null) {\n return validateSourceSize(new Uint8Array(await response.arrayBuffer()));\n }\n\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let byteLength = 0;\n\n try {\n for (;;) {\n const result = await reader.read();\n if (result.done) break;\n byteLength += result.value.byteLength;\n if (byteLength > MAX_READER_SOURCE_BYTES) {\n throw new Error(\n `Reader source exceeds the ${MAX_READER_SOURCE_BYTES}-byte download limit.`,\n );\n }\n chunks.push(result.value);\n }\n } finally {\n reader.releaseLock();\n }\n\n const bytes = new Uint8Array(byteLength);\n let offset = 0;\n for (const chunk of chunks) {\n bytes.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return bytes;\n}\n\nfunction validateSourceSize(bytes: Uint8Array): Uint8Array {\n if (bytes.byteLength > MAX_READER_SOURCE_BYTES) {\n throw new Error(`Reader source exceeds the ${MAX_READER_SOURCE_BYTES}-byte download limit.`);\n }\n return bytes;\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ReaderViewportState } from './types';
|
|
2
|
+
export declare const LOADING_STATE: ReaderViewportState;
|
|
3
|
+
export declare const READER_STYLES = "\n :root, body, #root { height: 100%; margin: 0; overflow: hidden; }\n * { box-sizing: border-box; }\n .reader-root { background: #fff; color: #111; height: 100%; outline: none; overflow: hidden; touch-action: pan-y; width: 100%; }\n .reader-dark { background: #111; color: #f5f5f5; }\n .reader-sepia { background: #f4ecd8; color: #3f3527; }\n .reader-content { height: 100%; overflow: auto; position: relative; width: 100%; }\n .reader-content > div:not(.reader-pdf-page) { height: 100%; width: 100%; }\n .reader-pdf-page { --user-unit: 1; --total-scale-factor: calc(var(--scale-factor) * var(--user-unit)); margin: 0 auto; position: relative; }\n .reader-pdf-page canvas { display: block; position: absolute; inset: 0; }\n .reader-pdf-text-layer { --min-font-size: 1; --text-scale-factor: calc(var(--total-scale-factor) * var(--min-font-size)); --min-font-size-inv: calc(1 / var(--min-font-size)); inset: 0; line-height: 1; opacity: 1; overflow: hidden; position: absolute; text-align: initial; text-size-adjust: none; transform-origin: 0 0; }\n .reader-pdf-text-layer span, .reader-pdf-text-layer br { color: transparent; cursor: text; position: absolute; transform-origin: 0 0; user-select: text; white-space: pre; }\n .reader-pdf-text-layer > :not(.markedContent), .reader-pdf-text-layer .markedContent span:not(.markedContent) { --font-height: 0; --scale-x: 1; --rotate: 0deg; font-size: calc(var(--text-scale-factor) * var(--font-height)); transform: rotate(var(--rotate)) scaleX(var(--scale-x)) scale(var(--min-font-size-inv)); }\n .reader-pdf-text-layer .markedContent { display: contents; }\n .reader-pdf-text-layer ::selection { background: rgba(0, 100, 255, 0.35); }\n .reader-pdf-link-layer { inset: 0; pointer-events: none; position: absolute; }\n .reader-pdf-link-layer a { outline-offset: 2px; pointer-events: auto; position: absolute; }\n";
|
|
4
|
+
//# sourceMappingURL=readerViewportPresentation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readerViewportPresentation.d.ts","sourceRoot":"","sources":["../../src/reader/readerViewportPresentation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAEnD,eAAO,MAAM,aAAa,EAAE,mBAM3B,CAAC;AAEF,eAAO,MAAM,aAAa,o1DAiBzB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const LOADING_STATE = {
|
|
2
|
+
canGoNext: false,
|
|
3
|
+
canGoPrevious: false,
|
|
4
|
+
page: 1,
|
|
5
|
+
progress: 0,
|
|
6
|
+
status: 'loading',
|
|
7
|
+
};
|
|
8
|
+
export const READER_STYLES = `
|
|
9
|
+
:root, body, #root { height: 100%; margin: 0; overflow: hidden; }
|
|
10
|
+
* { box-sizing: border-box; }
|
|
11
|
+
.reader-root { background: #fff; color: #111; height: 100%; outline: none; overflow: hidden; touch-action: pan-y; width: 100%; }
|
|
12
|
+
.reader-dark { background: #111; color: #f5f5f5; }
|
|
13
|
+
.reader-sepia { background: #f4ecd8; color: #3f3527; }
|
|
14
|
+
.reader-content { height: 100%; overflow: auto; position: relative; width: 100%; }
|
|
15
|
+
.reader-content > div:not(.reader-pdf-page) { height: 100%; width: 100%; }
|
|
16
|
+
.reader-pdf-page { --user-unit: 1; --total-scale-factor: calc(var(--scale-factor) * var(--user-unit)); margin: 0 auto; position: relative; }
|
|
17
|
+
.reader-pdf-page canvas { display: block; position: absolute; inset: 0; }
|
|
18
|
+
.reader-pdf-text-layer { --min-font-size: 1; --text-scale-factor: calc(var(--total-scale-factor) * var(--min-font-size)); --min-font-size-inv: calc(1 / var(--min-font-size)); inset: 0; line-height: 1; opacity: 1; overflow: hidden; position: absolute; text-align: initial; text-size-adjust: none; transform-origin: 0 0; }
|
|
19
|
+
.reader-pdf-text-layer span, .reader-pdf-text-layer br { color: transparent; cursor: text; position: absolute; transform-origin: 0 0; user-select: text; white-space: pre; }
|
|
20
|
+
.reader-pdf-text-layer > :not(.markedContent), .reader-pdf-text-layer .markedContent span:not(.markedContent) { --font-height: 0; --scale-x: 1; --rotate: 0deg; font-size: calc(var(--text-scale-factor) * var(--font-height)); transform: rotate(var(--rotate)) scaleX(var(--scale-x)) scale(var(--min-font-size-inv)); }
|
|
21
|
+
.reader-pdf-text-layer .markedContent { display: contents; }
|
|
22
|
+
.reader-pdf-text-layer ::selection { background: rgba(0, 100, 255, 0.35); }
|
|
23
|
+
.reader-pdf-link-layer { inset: 0; pointer-events: none; position: absolute; }
|
|
24
|
+
.reader-pdf-link-layer a { outline-offset: 2px; pointer-events: auto; position: absolute; }
|
|
25
|
+
`;
|
|
26
|
+
//# sourceMappingURL=readerViewportPresentation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readerViewportPresentation.js","sourceRoot":"","sources":["../../src/reader/readerViewportPresentation.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,aAAa,GAAwB;IAChD,SAAS,EAAE,KAAK;IAChB,aAAa,EAAE,KAAK;IACpB,IAAI,EAAE,CAAC;IACP,QAAQ,EAAE,CAAC;IACX,MAAM,EAAE,SAAS;CAClB,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;CAiB5B,CAAC","sourcesContent":["import type { ReaderViewportState } from './types';\n\nexport const LOADING_STATE: ReaderViewportState = {\n canGoNext: false,\n canGoPrevious: false,\n page: 1,\n progress: 0,\n status: 'loading',\n};\n\nexport const READER_STYLES = `\n :root, body, #root { height: 100%; margin: 0; overflow: hidden; }\n * { box-sizing: border-box; }\n .reader-root { background: #fff; color: #111; height: 100%; outline: none; overflow: hidden; touch-action: pan-y; width: 100%; }\n .reader-dark { background: #111; color: #f5f5f5; }\n .reader-sepia { background: #f4ecd8; color: #3f3527; }\n .reader-content { height: 100%; overflow: auto; position: relative; width: 100%; }\n .reader-content > div:not(.reader-pdf-page) { height: 100%; width: 100%; }\n .reader-pdf-page { --user-unit: 1; --total-scale-factor: calc(var(--scale-factor) * var(--user-unit)); margin: 0 auto; position: relative; }\n .reader-pdf-page canvas { display: block; position: absolute; inset: 0; }\n .reader-pdf-text-layer { --min-font-size: 1; --text-scale-factor: calc(var(--total-scale-factor) * var(--min-font-size)); --min-font-size-inv: calc(1 / var(--min-font-size)); inset: 0; line-height: 1; opacity: 1; overflow: hidden; position: absolute; text-align: initial; text-size-adjust: none; transform-origin: 0 0; }\n .reader-pdf-text-layer span, .reader-pdf-text-layer br { color: transparent; cursor: text; position: absolute; transform-origin: 0 0; user-select: text; white-space: pre; }\n .reader-pdf-text-layer > :not(.markedContent), .reader-pdf-text-layer .markedContent span:not(.markedContent) { --font-height: 0; --scale-x: 1; --rotate: 0deg; font-size: calc(var(--text-scale-factor) * var(--font-height)); transform: rotate(var(--rotate)) scaleX(var(--scale-x)) scale(var(--min-font-size-inv)); }\n .reader-pdf-text-layer .markedContent { display: contents; }\n .reader-pdf-text-layer ::selection { background: rgba(0, 100, 255, 0.35); }\n .reader-pdf-link-layer { inset: 0; pointer-events: none; position: absolute; }\n .reader-pdf-link-layer a { outline-offset: 2px; pointer-events: auto; position: absolute; }\n`;\n"]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ReaderErrorEvent, ReaderLocationChangeEvent } from '@ankhorage/zora';
|
|
2
|
+
import type { DOMProps } from 'expo/dom';
|
|
3
|
+
export interface ReaderAppearance {
|
|
4
|
+
readonly colorScheme: 'dark' | 'light' | 'sepia' | 'system';
|
|
5
|
+
readonly fontScale: number;
|
|
6
|
+
readonly lineHeight: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ReaderCommand {
|
|
9
|
+
readonly id: number;
|
|
10
|
+
readonly sourceUri: string;
|
|
11
|
+
readonly type: 'next' | 'previous';
|
|
12
|
+
}
|
|
13
|
+
export interface ReaderViewportState {
|
|
14
|
+
readonly canGoNext: boolean;
|
|
15
|
+
readonly canGoPrevious: boolean;
|
|
16
|
+
readonly location?: ReaderLocationChangeEvent;
|
|
17
|
+
readonly page: number;
|
|
18
|
+
readonly pageCount?: number;
|
|
19
|
+
readonly progress: number;
|
|
20
|
+
readonly status: 'loading' | 'ready';
|
|
21
|
+
}
|
|
22
|
+
export interface ReaderViewportProps {
|
|
23
|
+
readonly appearance: ReaderAppearance;
|
|
24
|
+
readonly command?: ReaderCommand;
|
|
25
|
+
readonly format: 'epub' | 'pdf';
|
|
26
|
+
readonly initialLocation?: string;
|
|
27
|
+
readonly onError: (event: ReaderErrorEvent) => Promise<void>;
|
|
28
|
+
readonly onOpenExternalLink: (event: {
|
|
29
|
+
readonly url: string;
|
|
30
|
+
}) => Promise<void>;
|
|
31
|
+
readonly onStateChange: (state: ReaderViewportState) => Promise<void>;
|
|
32
|
+
readonly sourceUri: string;
|
|
33
|
+
readonly dom?: DOMProps;
|
|
34
|
+
}
|
|
35
|
+
export interface ReaderDriverState {
|
|
36
|
+
readonly canGoNext: boolean;
|
|
37
|
+
readonly canGoPrevious: boolean;
|
|
38
|
+
readonly chapterId?: string;
|
|
39
|
+
readonly chapterTitle?: string;
|
|
40
|
+
readonly locator: string;
|
|
41
|
+
readonly page: number;
|
|
42
|
+
readonly pageCount?: number;
|
|
43
|
+
readonly progression: number;
|
|
44
|
+
}
|
|
45
|
+
export interface ReaderDriver {
|
|
46
|
+
readonly direction: 'ltr' | 'rtl';
|
|
47
|
+
destroy(): Promise<void>;
|
|
48
|
+
getState(): ReaderDriverState;
|
|
49
|
+
goNext(trigger: ReaderLocationChangeEvent['trigger']): Promise<void>;
|
|
50
|
+
goPrevious(trigger: ReaderLocationChangeEvent['trigger']): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
export interface ReaderTouchStart {
|
|
53
|
+
readonly interactive: boolean;
|
|
54
|
+
readonly x: number;
|
|
55
|
+
readonly y: number;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/reader/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/reader/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ReaderErrorEvent, ReaderLocationChangeEvent } from '@ankhorage/zora';\nimport type { DOMProps } from 'expo/dom';\n\nexport interface ReaderAppearance {\n readonly colorScheme: 'dark' | 'light' | 'sepia' | 'system';\n readonly fontScale: number;\n readonly lineHeight: number;\n}\n\nexport interface ReaderCommand {\n readonly id: number;\n readonly sourceUri: string;\n readonly type: 'next' | 'previous';\n}\n\nexport interface ReaderViewportState {\n readonly canGoNext: boolean;\n readonly canGoPrevious: boolean;\n readonly location?: ReaderLocationChangeEvent;\n readonly page: number;\n readonly pageCount?: number;\n readonly progress: number;\n readonly status: 'loading' | 'ready';\n}\n\nexport interface ReaderViewportProps {\n readonly appearance: ReaderAppearance;\n readonly command?: ReaderCommand;\n readonly format: 'epub' | 'pdf';\n readonly initialLocation?: string;\n readonly onError: (event: ReaderErrorEvent) => Promise<void>;\n readonly onOpenExternalLink: (event: { readonly url: string }) => Promise<void>;\n readonly onStateChange: (state: ReaderViewportState) => Promise<void>;\n readonly sourceUri: string;\n readonly dom?: DOMProps;\n}\n\nexport interface ReaderDriverState {\n readonly canGoNext: boolean;\n readonly canGoPrevious: boolean;\n readonly chapterId?: string;\n readonly chapterTitle?: string;\n readonly locator: string;\n readonly page: number;\n readonly pageCount?: number;\n readonly progression: number;\n}\n\nexport interface ReaderDriver {\n readonly direction: 'ltr' | 'rtl';\n destroy(): Promise<void>;\n getState(): ReaderDriverState;\n goNext(trigger: ReaderLocationChangeEvent['trigger']): Promise<void>;\n goPrevious(trigger: ReaderLocationChangeEvent['trigger']): Promise<void>;\n}\n\nexport interface ReaderTouchStart {\n readonly interactive: boolean;\n readonly x: number;\n readonly y: number;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateEpubArchiveEntriesAsync.d.ts","sourceRoot":"","sources":["../../src/reader/validateEpubArchiveEntriesAsync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAQ5C,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,SAAS,KAAK,EAAE,EACzB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAkDf"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Uint8ArrayWriter } from '@zip.js/zip.js';
|
|
2
|
+
const MAX_COMPRESSION_RATIO = 100;
|
|
3
|
+
const MAX_ENTRY_COUNT = 10_000;
|
|
4
|
+
const MAX_ENTRY_UNCOMPRESSED_BYTES = 64 * 1024 * 1024;
|
|
5
|
+
const MAX_TOTAL_UNCOMPRESSED_BYTES = 512 * 1024 * 1024;
|
|
6
|
+
export async function validateEpubArchiveEntriesAsync(entries, signal) {
|
|
7
|
+
if (entries.length > MAX_ENTRY_COUNT) {
|
|
8
|
+
throw new Error(`EPUB archive exceeds the ${MAX_ENTRY_COUNT}-entry limit.`);
|
|
9
|
+
}
|
|
10
|
+
let totalUncompressedBytes = 0;
|
|
11
|
+
for (const entry of entries) {
|
|
12
|
+
validateDeclaredSizes(entry);
|
|
13
|
+
if (entry.directory) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (entry.uncompressedSize > MAX_ENTRY_UNCOMPRESSED_BYTES) {
|
|
17
|
+
throw new Error(`EPUB archive entry "${entry.filename}" exceeds the ${MAX_ENTRY_UNCOMPRESSED_BYTES}-byte uncompressed limit.`);
|
|
18
|
+
}
|
|
19
|
+
if (entry.uncompressedSize > 0 &&
|
|
20
|
+
(entry.compressedSize === 0 ||
|
|
21
|
+
entry.uncompressedSize > entry.compressedSize * MAX_COMPRESSION_RATIO)) {
|
|
22
|
+
throw new Error(`EPUB archive entry "${entry.filename}" exceeds the ${MAX_COMPRESSION_RATIO}:1 compression-ratio limit.`);
|
|
23
|
+
}
|
|
24
|
+
if (totalUncompressedBytes > MAX_TOTAL_UNCOMPRESSED_BYTES - entry.uncompressedSize) {
|
|
25
|
+
throw new Error(`EPUB archive exceeds the ${MAX_TOTAL_UNCOMPRESSED_BYTES}-byte aggregate uncompressed limit.`);
|
|
26
|
+
}
|
|
27
|
+
totalUncompressedBytes += entry.uncompressedSize;
|
|
28
|
+
}
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
if (entry.directory) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
await entry.getData(new Uint8ArrayWriter(), {
|
|
34
|
+
checkOverlappingEntryOnly: true,
|
|
35
|
+
signal,
|
|
36
|
+
strictness: 'strict',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function validateDeclaredSizes(entry) {
|
|
41
|
+
if (!Number.isSafeInteger(entry.compressedSize) ||
|
|
42
|
+
entry.compressedSize < 0 ||
|
|
43
|
+
!Number.isSafeInteger(entry.uncompressedSize) ||
|
|
44
|
+
entry.uncompressedSize < 0) {
|
|
45
|
+
throw new Error(`EPUB archive entry "${entry.filename}" declares invalid sizes.`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=validateEpubArchiveEntriesAsync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validateEpubArchiveEntriesAsync.js","sourceRoot":"","sources":["../../src/reader/validateEpubArchiveEntriesAsync.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAClC,MAAM,eAAe,GAAG,MAAM,CAAC;AAC/B,MAAM,4BAA4B,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AACtD,MAAM,4BAA4B,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAEvD,MAAM,CAAC,KAAK,UAAU,+BAA+B,CACnD,OAAyB,EACzB,MAAoB;IAEpB,IAAI,OAAO,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,4BAA4B,eAAe,eAAe,CAAC,CAAC;IAC9E,CAAC;IAED,IAAI,sBAAsB,GAAG,CAAC,CAAC;IAE/B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,qBAAqB,CAAC,KAAK,CAAC,CAAC;QAE7B,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YACpB,SAAS;QACX,CAAC;QAED,IAAI,KAAK,CAAC,gBAAgB,GAAG,4BAA4B,EAAE,CAAC;YAC1D,MAAM,IAAI,KAAK,CACb,uBAAuB,KAAK,CAAC,QAAQ,iBAAiB,4BAA4B,2BAA2B,CAC9G,CAAC;QACJ,CAAC;QAED,IACE,KAAK,CAAC,gBAAgB,GAAG,CAAC;YAC1B,CAAC,KAAK,CAAC,cAAc,KAAK,CAAC;gBACzB,KAAK,CAAC,gBAAgB,GAAG,KAAK,CAAC,cAAc,GAAG,qBAAqB,CAAC,EACxE,CAAC;YACD,MAAM,IAAI,KAAK,CACb,uBAAuB,KAAK,CAAC,QAAQ,iBAAiB,qBAAqB,6BAA6B,CACzG,CAAC;QACJ,CAAC;QAED,IAAI,sBAAsB,GAAG,4BAA4B,GAAG,KAAK,CAAC,gBAAgB,EAAE,CAAC;YACnF,MAAM,IAAI,KAAK,CACb,4BAA4B,4BAA4B,qCAAqC,CAC9F,CAAC;QACJ,CAAC;QAED,sBAAsB,IAAI,KAAK,CAAC,gBAAgB,CAAC;IACnD,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YACpB,SAAS;QACX,CAAC;QAED,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,gBAAgB,EAAE,EAAE;YAC1C,yBAAyB,EAAE,IAAI;YAC/B,MAAM;YACN,UAAU,EAAE,QAAQ;SACrB,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAY;IACzC,IACE,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,cAAc,CAAC;QAC3C,KAAK,CAAC,cAAc,GAAG,CAAC;QACxB,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,gBAAgB,CAAC;QAC7C,KAAK,CAAC,gBAAgB,GAAG,CAAC,EAC1B,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,uBAAuB,KAAK,CAAC,QAAQ,2BAA2B,CAAC,CAAC;IACpF,CAAC;AACH,CAAC","sourcesContent":["import type { Entry } from '@zip.js/zip.js';\nimport { Uint8ArrayWriter } from '@zip.js/zip.js';\n\nconst MAX_COMPRESSION_RATIO = 100;\nconst MAX_ENTRY_COUNT = 10_000;\nconst MAX_ENTRY_UNCOMPRESSED_BYTES = 64 * 1024 * 1024;\nconst MAX_TOTAL_UNCOMPRESSED_BYTES = 512 * 1024 * 1024;\n\nexport async function validateEpubArchiveEntriesAsync(\n entries: readonly Entry[],\n signal?: AbortSignal,\n): Promise<void> {\n if (entries.length > MAX_ENTRY_COUNT) {\n throw new Error(`EPUB archive exceeds the ${MAX_ENTRY_COUNT}-entry limit.`);\n }\n\n let totalUncompressedBytes = 0;\n\n for (const entry of entries) {\n validateDeclaredSizes(entry);\n\n if (entry.directory) {\n continue;\n }\n\n if (entry.uncompressedSize > MAX_ENTRY_UNCOMPRESSED_BYTES) {\n throw new Error(\n `EPUB archive entry \"${entry.filename}\" exceeds the ${MAX_ENTRY_UNCOMPRESSED_BYTES}-byte uncompressed limit.`,\n );\n }\n\n if (\n entry.uncompressedSize > 0 &&\n (entry.compressedSize === 0 ||\n entry.uncompressedSize > entry.compressedSize * MAX_COMPRESSION_RATIO)\n ) {\n throw new Error(\n `EPUB archive entry \"${entry.filename}\" exceeds the ${MAX_COMPRESSION_RATIO}:1 compression-ratio limit.`,\n );\n }\n\n if (totalUncompressedBytes > MAX_TOTAL_UNCOMPRESSED_BYTES - entry.uncompressedSize) {\n throw new Error(\n `EPUB archive exceeds the ${MAX_TOTAL_UNCOMPRESSED_BYTES}-byte aggregate uncompressed limit.`,\n );\n }\n\n totalUncompressedBytes += entry.uncompressedSize;\n }\n\n for (const entry of entries) {\n if (entry.directory) {\n continue;\n }\n\n await entry.getData(new Uint8ArrayWriter(), {\n checkOverlappingEntryOnly: true,\n signal,\n strictness: 'strict',\n });\n }\n}\n\nfunction validateDeclaredSizes(entry: Entry): void {\n if (\n !Number.isSafeInteger(entry.compressedSize) ||\n entry.compressedSize < 0 ||\n !Number.isSafeInteger(entry.uncompressedSize) ||\n entry.uncompressedSize < 0\n ) {\n throw new Error(`EPUB archive entry \"${entry.filename}\" declares invalid sizes.`);\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ankhorage/expo-runtime",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.2.2",
|
|
5
5
|
"description": "Declarative runtime integration for Expo apps: maps app capabilities to permissions, packages, config plugins, providers, and adapters",
|
|
6
6
|
"homepage": "https://github.com/ankhorage/expo-runtime#readme",
|
|
7
7
|
"bugs": {
|
|
@@ -99,6 +99,20 @@
|
|
|
99
99
|
"types": "./src/bundledMedia/index.ts",
|
|
100
100
|
"import": "./dist/bundledMedia/index.js",
|
|
101
101
|
"default": "./dist/bundledMedia/index.js"
|
|
102
|
+
},
|
|
103
|
+
"./barcode-scanner": {
|
|
104
|
+
"react-native": "./src/barcodeScanner.ts",
|
|
105
|
+
"browser": "./src/barcodeScanner.ts",
|
|
106
|
+
"types": "./src/barcodeScanner.ts",
|
|
107
|
+
"import": "./dist/barcodeScanner.js",
|
|
108
|
+
"default": "./dist/barcodeScanner.js"
|
|
109
|
+
},
|
|
110
|
+
"./reader": {
|
|
111
|
+
"react-native": "./src/reader/index.ts",
|
|
112
|
+
"browser": "./src/reader/index.ts",
|
|
113
|
+
"types": "./src/reader/index.ts",
|
|
114
|
+
"import": "./dist/reader/index.js",
|
|
115
|
+
"default": "./dist/reader/index.js"
|
|
102
116
|
}
|
|
103
117
|
},
|
|
104
118
|
"publishConfig": {
|
|
@@ -121,13 +135,15 @@
|
|
|
121
135
|
"validate:expo-platform": "bun scripts/validateExpoPlatform.ts",
|
|
122
136
|
"validate:headless-platform": "bun scripts/headlessPlatformAcceptance.ts",
|
|
123
137
|
"validate:packed-runtime": "bun scripts/packedRuntimeAcceptance.ts",
|
|
138
|
+
"validate:reader-dom": "bun scripts/readerDomAcceptance.ts",
|
|
124
139
|
"validate:web-barcode": "bun scripts/webBarcodeAcceptance.ts",
|
|
125
140
|
"version-packages": "ankhorage-changeset version"
|
|
126
141
|
},
|
|
127
142
|
"peerDependencies": {
|
|
128
143
|
"@ankhorage/permissions": "^0.2.3",
|
|
129
|
-
"@ankhorage/zora": "^3.
|
|
144
|
+
"@ankhorage/zora": "^3.1.1",
|
|
130
145
|
"@readium/navigator": "2.8.2",
|
|
146
|
+
"@readium/shared": "2.4.0",
|
|
131
147
|
"@react-native-vector-icons/fontawesome": "^13.1.3",
|
|
132
148
|
"@react-native-vector-icons/fontawesome5": "^13.1.3",
|
|
133
149
|
"@react-native-vector-icons/fontawesome6": "^13.1.3",
|
|
@@ -155,6 +171,9 @@
|
|
|
155
171
|
"@readium/navigator": {
|
|
156
172
|
"optional": true
|
|
157
173
|
},
|
|
174
|
+
"@readium/shared": {
|
|
175
|
+
"optional": true
|
|
176
|
+
},
|
|
158
177
|
"@react-native-vector-icons/fontawesome": {
|
|
159
178
|
"optional": true
|
|
160
179
|
},
|
|
@@ -205,11 +224,12 @@
|
|
|
205
224
|
}
|
|
206
225
|
},
|
|
207
226
|
"devDependencies": {
|
|
208
|
-
"@ankhorage/devtools": "^1.8.
|
|
227
|
+
"@ankhorage/devtools": "^1.8.4",
|
|
209
228
|
"@ankhorage/paradox": "^0.1.24",
|
|
210
229
|
"@ankhorage/permissions": "^0.2.3",
|
|
211
|
-
"@ankhorage/zora": "^3.
|
|
230
|
+
"@ankhorage/zora": "^3.1.1",
|
|
212
231
|
"@readium/navigator": "2.8.2",
|
|
232
|
+
"@readium/shared": "2.4.0",
|
|
213
233
|
"@react-native-picker/picker": "2.11.4",
|
|
214
234
|
"@react-native-vector-icons/fontawesome": "^13.1.3",
|
|
215
235
|
"@react-native-vector-icons/fontawesome5": "^13.1.3",
|
|
@@ -221,6 +241,7 @@
|
|
|
221
241
|
"@types/node": "^24.13.3",
|
|
222
242
|
"@types/pngjs": "^6.0.5",
|
|
223
243
|
"@types/react": "^19.2.18",
|
|
244
|
+
"@xmldom/xmldom": "0.9.10",
|
|
224
245
|
"bwip-js": "^4.11.4",
|
|
225
246
|
"expo": "57.0.18",
|
|
226
247
|
"expo-camera": "~57.0.4",
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
interface PackageJson {
|
|
4
|
+
readonly exports: Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
describe('capability-scoped adapter entrypoints', () => {
|
|
8
|
+
it('publishes focused barcode scanner and reader exports', async () => {
|
|
9
|
+
const packageJson = (await Bun.file(
|
|
10
|
+
new URL('../package.json', import.meta.url),
|
|
11
|
+
).json()) as PackageJson;
|
|
12
|
+
|
|
13
|
+
expect(packageJson.exports['./barcode-scanner']).toMatchObject({
|
|
14
|
+
import: './dist/barcodeScanner.js',
|
|
15
|
+
'react-native': './src/barcodeScanner.ts',
|
|
16
|
+
types: './src/barcodeScanner.ts',
|
|
17
|
+
});
|
|
18
|
+
expect(packageJson.exports['./reader']).toMatchObject({
|
|
19
|
+
import: './dist/reader/index.js',
|
|
20
|
+
'react-native': './src/reader/index.ts',
|
|
21
|
+
types: './src/reader/index.ts',
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('keeps camera ownership out of the reader entrypoint', async () => {
|
|
26
|
+
const entrypoint = await Bun.file(new URL('./reader/index.ts', import.meta.url)).text();
|
|
27
|
+
const adapter = await Bun.file(
|
|
28
|
+
new URL('./reader/ExpoReaderSurfaceAdapter.tsx', import.meta.url),
|
|
29
|
+
).text();
|
|
30
|
+
|
|
31
|
+
expect(entrypoint).toContain("from './ExpoReaderSurfaceAdapter'");
|
|
32
|
+
expect(`${entrypoint}\n${adapter}`).not.toContain('expo-camera');
|
|
33
|
+
expect(`${entrypoint}\n${adapter}`).not.toContain('@ankhorage/permissions');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('keeps document renderer ownership out of the scanner entrypoint', async () => {
|
|
37
|
+
const entrypoint = await Bun.file(new URL('./barcodeScanner.ts', import.meta.url)).text();
|
|
38
|
+
const adapter = await Bun.file(
|
|
39
|
+
new URL('./ExpoBarcodeScannerAdapter.tsx', import.meta.url),
|
|
40
|
+
).text();
|
|
41
|
+
const source = `${entrypoint}\n${adapter}`;
|
|
42
|
+
|
|
43
|
+
expect(entrypoint).toContain("from './ExpoBarcodeScannerAdapter'");
|
|
44
|
+
expect(source).not.toContain('@readium/');
|
|
45
|
+
expect(source).not.toContain('@zip.js/zip.js');
|
|
46
|
+
expect(source).not.toContain('pdfjs-dist');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ExpoBarcodeScannerAdapter } from './ExpoBarcodeScannerAdapter';
|
|
@@ -4,20 +4,26 @@ import React from 'react';
|
|
|
4
4
|
const BaseBarcodeScannerView = (): React.ReactElement => React.createElement(React.Fragment);
|
|
5
5
|
const BaseText = (): React.ReactElement => React.createElement(React.Fragment);
|
|
6
6
|
const MockExpoBarcodeScannerAdapter = (): React.ReactElement => React.createElement(React.Fragment);
|
|
7
|
+
const BaseReaderSurface = (): React.ReactElement => React.createElement(React.Fragment);
|
|
8
|
+
const MockExpoReaderSurfaceAdapter = (): React.ReactElement => React.createElement(React.Fragment);
|
|
7
9
|
|
|
8
10
|
describe('createExpoRuntimeRegistry', () => {
|
|
9
11
|
afterEach(() => {
|
|
10
12
|
mock.restore();
|
|
11
13
|
});
|
|
12
14
|
|
|
13
|
-
it('overrides
|
|
15
|
+
it('overrides platform-owned views while preserving unrelated registry entries', async () => {
|
|
14
16
|
void mock.module('./ExpoBarcodeScannerAdapter', () => ({
|
|
15
17
|
ExpoBarcodeScannerAdapter: MockExpoBarcodeScannerAdapter,
|
|
16
18
|
}));
|
|
19
|
+
void mock.module('./reader/ExpoReaderSurfaceAdapter', () => ({
|
|
20
|
+
ExpoReaderSurfaceAdapter: MockExpoReaderSurfaceAdapter,
|
|
21
|
+
}));
|
|
17
22
|
|
|
18
23
|
const { createExpoRuntimeRegistry } = await import('./createExpoRuntimeRegistry');
|
|
19
24
|
const baseRegistry = {
|
|
20
25
|
BarcodeScannerView: BaseBarcodeScannerView,
|
|
26
|
+
ReaderSurface: BaseReaderSurface,
|
|
21
27
|
Text: BaseText,
|
|
22
28
|
};
|
|
23
29
|
|
|
@@ -27,5 +33,7 @@ describe('createExpoRuntimeRegistry', () => {
|
|
|
27
33
|
expect(registry.Text).toBe(BaseText);
|
|
28
34
|
expect(registry.BarcodeScannerView).not.toBe(BaseBarcodeScannerView);
|
|
29
35
|
expect(typeof registry.BarcodeScannerView).toBe('function');
|
|
36
|
+
expect(registry.ReaderSurface).not.toBe(BaseReaderSurface);
|
|
37
|
+
expect(typeof registry.ReaderSurface).toBe('function');
|
|
30
38
|
});
|
|
31
39
|
});
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { type BarcodeScannerViewProps } from '@ankhorage/zora';
|
|
1
|
+
import { type BarcodeScannerViewProps, type ReaderSurfaceProps } from '@ankhorage/zora';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
4
|
import { type ComponentRegistry, createComponentRegistry } from './componentRegistry';
|
|
5
5
|
import { ExpoBarcodeScannerAdapter } from './ExpoBarcodeScannerAdapter';
|
|
6
|
+
import { ExpoReaderSurfaceAdapter } from './reader/ExpoReaderSurfaceAdapter';
|
|
6
7
|
|
|
7
8
|
const EXPO_RUNTIME_OVERRIDES: ComponentRegistry = {
|
|
8
9
|
BarcodeScannerView: ExpoBarcodeScannerView,
|
|
10
|
+
ReaderSurface: ExpoReaderSurface,
|
|
9
11
|
};
|
|
10
12
|
|
|
11
13
|
export function createExpoRuntimeRegistry(baseRegistry: ComponentRegistry): ComponentRegistry {
|
|
@@ -15,3 +17,7 @@ export function createExpoRuntimeRegistry(baseRegistry: ComponentRegistry): Comp
|
|
|
15
17
|
function ExpoBarcodeScannerView(props: BarcodeScannerViewProps) {
|
|
16
18
|
return <ExpoBarcodeScannerAdapter {...props} />;
|
|
17
19
|
}
|
|
20
|
+
|
|
21
|
+
function ExpoReaderSurface(props: ReaderSurfaceProps) {
|
|
22
|
+
return <ExpoReaderSurfaceAdapter {...props} />;
|
|
23
|
+
}
|
|
@@ -13,7 +13,7 @@ export interface ExpoRuntimeConfigPlugin {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export type ExpoRuntimeProviderId = 'permissions';
|
|
16
|
-
export type ExpoRuntimeAdapterId = 'ExpoBarcodeScannerAdapter';
|
|
16
|
+
export type ExpoRuntimeAdapterId = 'ExpoBarcodeScannerAdapter' | 'ExpoReaderSurfaceAdapter';
|
|
17
17
|
|
|
18
18
|
export interface ExpoRuntimeDiagnostic {
|
|
19
19
|
readonly severity: 'error' | 'warning';
|
|
@@ -40,6 +40,10 @@ export interface ExpoRuntimeHintMetadata {
|
|
|
40
40
|
const GENERATED_ANKHORAGE_DEPENDENCY_VERSIONS = {
|
|
41
41
|
'@ankhorage/permissions': '^0.2.3',
|
|
42
42
|
'@ankhorage/expo-runtime': '^3.0.0',
|
|
43
|
+
'@readium/navigator': '2.8.2',
|
|
44
|
+
'@readium/shared': '2.4.0',
|
|
45
|
+
'@zip.js/zip.js': '2.9.0',
|
|
46
|
+
'pdfjs-dist': '6.3.289',
|
|
43
47
|
} as const satisfies Record<string, string>;
|
|
44
48
|
|
|
45
49
|
const GENERATED_EXPO_DEPENDENCY_VERSIONS = Object.fromEntries(
|
|
@@ -148,6 +152,10 @@ export const EXPO_CAPABILITY_RUNTIME_REGISTRY = {
|
|
|
148
152
|
},
|
|
149
153
|
],
|
|
150
154
|
},
|
|
155
|
+
ebookReader: {
|
|
156
|
+
requiredPackages: ['@readium/navigator', '@readium/shared', '@zip.js/zip.js', 'pdfjs-dist'],
|
|
157
|
+
runtimeAdapters: ['ExpoReaderSurfaceAdapter'],
|
|
158
|
+
},
|
|
151
159
|
} as const satisfies Readonly<
|
|
152
160
|
Partial<Record<AnkhorageCapabilityName, ExpoRuntimeCapabilityMetadata>>
|
|
153
161
|
>;
|