fix(runtime): polyfill PDF.js Map APIs for older WebViews

This commit is contained in:
2026-10-18 13:16:32 +08:00
parent b13b71b27c
commit e84906652e
4 changed files with 169 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
import { readFileSync, writeFileSync } from 'node:fs';
import { join, resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
// Executed independently in the page and the embedded PDF worker.
export function installMapCompat() {
'use strict';
const { has, get, set } = Map.prototype;
if (typeof Map.prototype.getOrInsertComputed !== 'function') {
Object.defineProperty(Map.prototype, 'getOrInsertComputed', {
configurable: true, writable: true,
value: function getOrInsertComputed(key, callback) {
const exists = has.call(this, key);
if (typeof callback !== 'function') throw new TypeError('callback must be callable');
if (exists) return get.call(this, key);
if (key === 0) key = 0; // Canonicalize -0 before invoking callback.
const value = callback(key);
set.call(this, key, value);
return value;
},
});
}
if (typeof Map.prototype.getOrInsert !== 'function') {
Object.defineProperty(Map.prototype, 'getOrInsert', {
configurable: true, writable: true,
value: function getOrInsert(key, value) {
if (has.call(this, key)) return get.call(this, key);
set.call(this, key, value);
return value;
},
});
}
}
const newline = String.fromCharCode(10);
export const prelude = '/* dsh-mgr: webview-map-compat */' + newline + '(' + installMapCompat.toString() + ')();' + newline;
export const workerPrefix = 'var _dsh_pdf_worker_default = ';
export function patchPdfClient(source, version, pluginVersion) {
if (version !== '0.2.1-rc.2' || pluginVersion !== version) {
throw new Error('unsupported DSH/documentpreview version for WebView compatibility patch');
}
if (source.includes('dsh-mgr: webview-map-compat')) throw new Error('WebView compatibility patch already applied');
const lines = source.split(newline);
const targets = lines.filter(line => line.trimStart().startsWith(workerPrefix));
if (targets.length !== 1 || !source.includes('this.#methodPromises.getOrInsertComputed(')) {
throw new Error('expected PDF.js method cache and exactly one embedded worker');
}
const target = targets[0];
// Preserve the JS string literal verbatim (it can contain non-JSON escapes).
const worker = target.trim().slice(workerPrefix.length, -1);
if (!worker.includes('getOrInsertComputed') || !worker.includes('pdfjsVersion = 6.3.289')) {
throw new Error('unexpected embedded PDF.js worker');
}
// A page-level polyfill cannot cross into the worker realm.
return prelude + source.replace(target, () => workerPrefix + JSON.stringify(prelude) + ' + ' + worker + ';');
}
export function install(root = '/usr/local/lib/node_modules/@deepseek-ai/dsh') {
const plugin = join(root, 'node_modules/@deepseek-ai/dsh-client-ui-sidebar-documentpreview');
const version = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version;
const pluginVersion = JSON.parse(readFileSync(join(plugin, 'package.json'), 'utf8')).version;
const file = join(plugin, 'lib/client.pdf.js');
writeFileSync(file, patchPdfClient(readFileSync(file, 'utf8'), version, pluginVersion));
}
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) install();