File size: 6,531 Bytes
2c1d035 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | export function createReaderDocumentLoader(readerContext) {
function loadScript(url) {
readerContext.assertReaderActive();
return new Promise((resolve, reject) => {
const script = document.createElement("script");
let settled = false, timeout = 0, untrack = () => {};
const finish = (error) => {
if (settled) return;
settled = true;
clearTimeout(timeout);
untrack();
script.onload = script.onerror = null;
if (error) {
script.remove();
reject(error);
} else resolve();
};
const networkError = () => Object.assign(
new Error("阅读组件下载失败,请检查网络后重试加载,或下载原文件。"),
{ code: "READER_ENGINE_NETWORK" }
);
untrack = readerContext.trackReaderResource(() => finish(readerContext.readerAbortError()));
// Download sibling engines concurrently, execute in dependency order.
script.async = false;
script.src = url;
script.onload = () => finish();
script.onerror = () => finish(networkError());
timeout = setTimeout(() => finish(networkError()), 20000);
document.head.appendChild(script);
});
}
function fetchWithReaderTimeout(url, timeoutMs = readerContext.READER_PROXY_TIMEOUT_MS, requestInit = {}) {
return readerContext.readerRequestManager.request(url, timeoutMs, requestInit);
}
function retryableReaderProxyError(error) {
return [408, 429, 500, 502, 503, 504].includes(error?.status) ||
["TypeError", "TimeoutError"].includes(error?.name) ||
/failed to fetch|network|timeout|HTTP\s*(?:408|429|5\d\d)|server response\s*\((?:408|429|5\d\d)\)/i.test(error?.message || "");
}
async function retryReaderProxy(open) {
for (let attempt = 0; ; attempt++) {
readerContext.assertReaderActive();
try {
return await open();
} catch (error) {
if (attempt >= 2 || !readerContext.retryableReaderProxyError(error)) throw error;
await readerContext.waitForReader(350 * (attempt + 1));
}
}
}
function fetchReaderUrl(rawUrl, requestInit = {}) {
if (String(rawUrl).includes("/api/reader-bucket-resource?"))
return readerContext.fetchWithReaderTimeout(rawUrl, readerContext.READER_PROXY_TIMEOUT_MS, requestInit);
const proxyUrl = readerContext.readerContentUrl(rawUrl);
return readerContext.retryReaderProxy(async () => {
const response = await readerContext.fetchWithReaderTimeout(proxyUrl, readerContext.READER_PROXY_TIMEOUT_MS, requestInit);
if (response.ok) return response;
await response.body?.cancel();
throw Object.assign(new Error(`HTTP ${response.status}`), { status: response.status });
}).catch((error) => {
readerContext.assertReaderActive();
requestInit.signal?.throwIfAborted();
if (readerContext.retryableReaderProxyError(error)) throw error;
return readerContext.fetchWithReaderTimeout(rawUrl, readerContext.READER_PROXY_TIMEOUT_MS, requestInit);
});
}
function fetchReaderResponse() {
const early = window.__VOICE_PDF_PRELOAD__;
if (early?.manifest && early.manifestUrl === new URL(readerContext.sourceUrl, location.href).href) {
const pending = early.manifest;
early.manifest = null;
return pending.then(
(response) => (response?.ok ? response : readerContext.fetchReaderUrl(readerContext.sourceUrl)),
() => readerContext.fetchReaderUrl(readerContext.sourceUrl)
);
}
return readerContext.fetchReaderUrl(readerContext.sourceUrl);
}
function prepareReaderDocument(engineTasks) {
const engines = Promise.all(engineTasks);
const response = Promise.race([readerContext.fetchReaderResponse(), engines.then(() => new Promise(() => {}))]);
// Rendering owns both promises; early rejection must not become unhandled.
response.catch(() => {});
engines.catch(() => {});
return {response, engines};
}
function readPreparedBytes(response, engines, limit = VoiceOfMLReaderSecurity.LIMITS.documentBytes) {
return Promise.all([Promise.resolve(response).then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return VoiceOfMLReaderSecurity.readBytes(response,limit);
}), engines]).then(([bytes]) => bytes);
}
function loadTextDocument() {
return readerContext.fetchReaderResponse().then((response) => ({ response }));
}
function loadMarkdownDocument() {
return readerContext.prepareReaderDocument([readerContext.loadScript(readerContext.MARKED_URL), readerContext.loadScript(readerContext.PURIFY_URL)]);
}
function loadHtmlDocument() {
const {response, engines} = readerContext.prepareReaderDocument([readerContext.loadScript(readerContext.PURIFY_URL)]);
return {response, engine:engines};
}
function loadMediaDocument() {
return Promise.resolve(null);
}
async function loadImageDocument(target = readerContext.sourceUrl) {
const deadline = performance.now() + readerContext.READER_PROXY_TIMEOUT_MS;
const image = new Image();
image.className = "reader-image";
image.alt = readerContext.documentState.title;
image.decoding = "async";
try {
return await readerContext.imageLoader.load(image, readerContext.readerContentUrl(target), readerContext.readerAbortController.signal, deadline);
} catch (error) {
readerContext.assertReaderActive();
if (error?.code === "READER_RESOURCE_LIMIT") throw error;
return readerContext.imageLoader.load(image, target, readerContext.readerAbortController.signal, deadline);
}
}
function loadChapterManifestDocument() {
const target = readerContext.chapterManifestUrl || readerContext.sourceUrl, early = window.__VOICE_READER_CHAPTER_PRELOAD__;
if (early?.manifest && early.manifestUrl === new URL(target, location.href).href) {
const pending = early.manifest;
early.manifest = null;
return pending.then(response => response.ok ? response : readerContext.fetchReaderUrl(target), () => readerContext.fetchReaderUrl(target));
}
return readerContext.fetchReaderUrl(target);
}
return {
loadScript,
fetchWithReaderTimeout,
retryableReaderProxyError,
retryReaderProxy,
fetchReaderUrl,
fetchReaderResponse,
prepareReaderDocument,
readPreparedBytes,
loadTextDocument,
loadMarkdownDocument,
loadHtmlDocument,
loadMediaDocument,
loadImageDocument,
loadChapterManifestDocument
};
}
|