@file-viewer/renderer-chm 3.0.1
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/LICENSE +196 -0
- package/README.en.md +13 -0
- package/README.md +13 -0
- package/THIRD_PARTY_NOTICES.md +12 -0
- package/dist/chm.d.ts +2 -0
- package/dist/chm.js +593 -0
- package/dist/chm.worker.d.ts +1 -0
- package/dist/chm.worker.js +2 -0
- package/dist/chm_wasm.js +583 -0
- package/dist/chm_wasm_bg.wasm +0 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +16 -0
- package/dist/model.d.ts +78 -0
- package/dist/model.js +125 -0
- package/dist/security.d.ts +33 -0
- package/dist/security.js +850 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +26 -0
- package/dist/workerClient.d.ts +32 -0
- package/dist/workerClient.js +157 -0
- package/dist/workerProtocol.d.ts +70 -0
- package/dist/workerProtocol.js +1 -0
- package/file-viewer.capability.json +38 -0
- package/package.json +92 -0
- package/rust/Cargo.lock +247 -0
- package/rust/Cargo.toml +29 -0
- package/rust/LICENSE +196 -0
- package/rust/NOTICE.md +47 -0
- package/rust/THIRD_PARTY_LICENSES.md +365 -0
- package/rust/src/chm.rs +702 -0
- package/rust/src/core.rs +745 -0
- package/rust/src/error.rs +64 -0
- package/rust/src/lib.rs +104 -0
- package/rust/src/lzx.rs +632 -0
- package/rust/src/metadata.rs +839 -0
- package/rust/src/sitemap.rs +442 -0
package/dist/style.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const chmViewerStyle = "\n.chm-viewer{position:relative;width:100%;height:100%;min-height:480px;display:flex;flex-direction:column;overflow:hidden;background:#edf2f6;color:#182233;font:14px/1.45 system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;box-sizing:border-box}\n.chm-viewer *{box-sizing:border-box}\n.chm-header{min-height:62px;display:flex;align-items:center;gap:12px;padding:10px 16px;border-bottom:1px solid rgba(24,34,51,.1);background:rgba(255,255,255,.94)}\n.chm-sidebar-toggle{display:none;width:36px;height:36px;border:0;border-radius:10px;background:#e8f3ee;color:#08785c;font:inherit;font-size:19px;cursor:pointer}\n.chm-heading{min-width:0;flex:1}.chm-heading h2{margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:17px;line-height:1.35}.chm-heading p{margin:2px 0 0;color:#667085;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.chm-badges{display:flex;align-items:center;gap:7px;flex-wrap:wrap;justify-content:flex-end}.chm-badge{padding:4px 8px;border-radius:999px;background:#e8f3ee;color:#08785c;font-size:11px;font-weight:750}.chm-badge.is-muted{background:#eef1f4;color:#596579}\n.chm-body{flex:1;min-height:0;display:grid;grid-template-columns:minmax(230px,300px) minmax(0,1fr)}\n.chm-sidebar{min-height:0;display:flex;flex-direction:column;border-right:1px solid rgba(24,34,51,.1);background:#f8fafb}\n.chm-tabs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:4px;padding:8px;border-bottom:1px solid rgba(24,34,51,.08)}\n.chm-tab{height:34px;border:0;border-radius:8px;background:transparent;color:#647084;font:inherit;font-size:12px;font-weight:750;cursor:pointer}.chm-tab:hover{background:#eef3f5}.chm-tab.is-active{background:#fff;color:#08785c;box-shadow:0 1px 5px rgba(24,34,51,.12)}\n.chm-sidebar-panels{flex:1;min-height:0;position:relative}.chm-panel{position:absolute;inset:0;overflow:auto;padding:8px}.chm-panel[hidden]{display:none}\n.chm-navigation,.chm-navigation ul{margin:0;padding:0;list-style:none}.chm-navigation ul{padding-left:14px;border-left:1px solid rgba(24,34,51,.09)}\n.chm-nav-button,.chm-search-result{width:100%;display:block;border:0;border-radius:8px;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}.chm-nav-button{min-height:32px;padding:6px 8px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chm-nav-button:hover,.chm-nav-button.is-active{background:#e7f3ee;color:#08785c}.chm-nav-label{min-height:28px;padding:5px 8px;color:#526073;font-weight:650}\n.chm-panel-note{margin:10px;padding:9px 10px;border-radius:8px;background:#fff6df;color:#845a00;font-size:12px}\n.chm-search-box{position:sticky;top:-8px;z-index:1;padding:8px 2px;background:#f8fafb}.chm-search-box input{width:100%;height:36px;padding:0 11px;border:1px solid rgba(24,34,51,.16);border-radius:9px;background:#fff;color:inherit;font:inherit;outline:none}.chm-search-box input:focus{border-color:#0a8b68;box-shadow:0 0 0 3px rgba(10,139,104,.12)}\n.chm-search-meta{margin:7px 4px;color:#6b7688;font-size:11px}.chm-search-results{display:flex;flex-direction:column;gap:5px}.chm-search-result{padding:9px;background:#fff;border:1px solid rgba(24,34,51,.08)}.chm-search-result:hover{border-color:rgba(10,139,104,.35);box-shadow:0 5px 16px rgba(24,34,51,.07)}.chm-search-result strong,.chm-search-result span{display:block}.chm-search-result strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chm-search-result span{margin-top:3px;color:#677386;font-size:11px;line-height:1.4}.chm-search-result mark{background:#fff1a6;color:inherit}\n.chm-topic{min-width:0;min-height:0;display:flex;flex-direction:column;background:#dfe5ea}.chm-topic-bar{min-height:42px;display:flex;align-items:center;gap:8px;padding:7px 12px;background:rgba(255,255,255,.88);border-bottom:1px solid rgba(24,34,51,.08)}.chm-topic-bar strong{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chm-topic-path{margin-left:auto;max-width:48%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#6b7688;font-size:11px}\n.chm-topic-frame{display:block;flex:1;min-height:0;width:100%;border:0;background:#fff}\n.chm-state{position:absolute;inset:62px 0 0;z-index:6;display:flex;align-items:center;justify-content:center;gap:12px;background:rgba(237,242,246,.88);backdrop-filter:blur(2px)}.chm-state[hidden]{display:none}.chm-spinner{width:32px;height:32px;border:3px solid rgba(8,120,92,.16);border-top-color:#08785c;border-radius:999px;animation:chm-spin .85s linear infinite}.chm-state p{margin:0;color:#435168;font-weight:650}\n.chm-error{position:absolute;inset:82px 18px auto;z-index:8;width:min(560px,calc(100% - 36px));margin:auto;padding:16px;border:1px solid rgba(190,91,0,.22);border-radius:14px;background:#fff8e9;color:#743f00;box-shadow:0 18px 48px rgba(24,34,51,.18)}.chm-error[hidden]{display:none}.chm-error strong{display:block;font-size:15px}.chm-error p{margin:6px 0 0;white-space:pre-wrap;overflow-wrap:anywhere}\n@keyframes chm-spin{to{transform:rotate(360deg)}}\n[data-viewer-theme=dark] .chm-viewer{background:#111827;color:#e6edf6}[data-viewer-theme=dark] .chm-header,[data-viewer-theme=dark] .chm-topic-bar{background:#151d2b;border-color:rgba(148,163,184,.18)}[data-viewer-theme=dark] .chm-heading p,[data-viewer-theme=dark] .chm-topic-path{color:#94a3b8}[data-viewer-theme=dark] .chm-sidebar,[data-viewer-theme=dark] .chm-search-box{background:#121a27;border-color:rgba(148,163,184,.18)}[data-viewer-theme=dark] .chm-tab.is-active,[data-viewer-theme=dark] .chm-search-result,[data-viewer-theme=dark] .chm-search-box input{background:#1c2635;color:#e6edf6}[data-viewer-theme=dark] .chm-topic{background:#0b1019}[data-viewer-theme=dark] .chm-state{background:rgba(17,24,39,.88);color:#dbe5f1}\n@media(prefers-color-scheme:dark){[data-viewer-theme=system] .chm-viewer{background:#111827;color:#e6edf6}[data-viewer-theme=system] .chm-header,[data-viewer-theme=system] .chm-topic-bar{background:#151d2b;border-color:rgba(148,163,184,.18)}[data-viewer-theme=system] .chm-heading p,[data-viewer-theme=system] .chm-topic-path{color:#94a3b8}[data-viewer-theme=system] .chm-sidebar,[data-viewer-theme=system] .chm-search-box{background:#121a27;border-color:rgba(148,163,184,.18)}[data-viewer-theme=system] .chm-tab.is-active,[data-viewer-theme=system] .chm-search-result,[data-viewer-theme=system] .chm-search-box input{background:#1c2635;color:#e6edf6}[data-viewer-theme=system] .chm-topic{background:#0b1019}[data-viewer-theme=system] .chm-state{background:rgba(17,24,39,.88);color:#dbe5f1}}\n@media(max-width:760px){.chm-sidebar-toggle{display:inline-flex;align-items:center;justify-content:center}.chm-body{grid-template-columns:1fr}.chm-sidebar{position:absolute;z-index:5;top:62px;bottom:0;left:0;width:min(88vw,310px);box-shadow:16px 0 36px rgba(24,34,51,.18);transform:translateX(-105%);transition:transform .2s ease}.chm-viewer.is-sidebar-open .chm-sidebar{transform:translateX(0)}.chm-badges .chm-badge.is-muted{display:none}.chm-topic-path{display:none}}\n";
|
package/dist/style.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const chmViewerStyle = `
|
|
2
|
+
.chm-viewer{position:relative;width:100%;height:100%;min-height:480px;display:flex;flex-direction:column;overflow:hidden;background:#edf2f6;color:#182233;font:14px/1.45 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;box-sizing:border-box}
|
|
3
|
+
.chm-viewer *{box-sizing:border-box}
|
|
4
|
+
.chm-header{min-height:62px;display:flex;align-items:center;gap:12px;padding:10px 16px;border-bottom:1px solid rgba(24,34,51,.1);background:rgba(255,255,255,.94)}
|
|
5
|
+
.chm-sidebar-toggle{display:none;width:36px;height:36px;border:0;border-radius:10px;background:#e8f3ee;color:#08785c;font:inherit;font-size:19px;cursor:pointer}
|
|
6
|
+
.chm-heading{min-width:0;flex:1}.chm-heading h2{margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:17px;line-height:1.35}.chm-heading p{margin:2px 0 0;color:#667085;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
7
|
+
.chm-badges{display:flex;align-items:center;gap:7px;flex-wrap:wrap;justify-content:flex-end}.chm-badge{padding:4px 8px;border-radius:999px;background:#e8f3ee;color:#08785c;font-size:11px;font-weight:750}.chm-badge.is-muted{background:#eef1f4;color:#596579}
|
|
8
|
+
.chm-body{flex:1;min-height:0;display:grid;grid-template-columns:minmax(230px,300px) minmax(0,1fr)}
|
|
9
|
+
.chm-sidebar{min-height:0;display:flex;flex-direction:column;border-right:1px solid rgba(24,34,51,.1);background:#f8fafb}
|
|
10
|
+
.chm-tabs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:4px;padding:8px;border-bottom:1px solid rgba(24,34,51,.08)}
|
|
11
|
+
.chm-tab{height:34px;border:0;border-radius:8px;background:transparent;color:#647084;font:inherit;font-size:12px;font-weight:750;cursor:pointer}.chm-tab:hover{background:#eef3f5}.chm-tab.is-active{background:#fff;color:#08785c;box-shadow:0 1px 5px rgba(24,34,51,.12)}
|
|
12
|
+
.chm-sidebar-panels{flex:1;min-height:0;position:relative}.chm-panel{position:absolute;inset:0;overflow:auto;padding:8px}.chm-panel[hidden]{display:none}
|
|
13
|
+
.chm-navigation,.chm-navigation ul{margin:0;padding:0;list-style:none}.chm-navigation ul{padding-left:14px;border-left:1px solid rgba(24,34,51,.09)}
|
|
14
|
+
.chm-nav-button,.chm-search-result{width:100%;display:block;border:0;border-radius:8px;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}.chm-nav-button{min-height:32px;padding:6px 8px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chm-nav-button:hover,.chm-nav-button.is-active{background:#e7f3ee;color:#08785c}.chm-nav-label{min-height:28px;padding:5px 8px;color:#526073;font-weight:650}
|
|
15
|
+
.chm-panel-note{margin:10px;padding:9px 10px;border-radius:8px;background:#fff6df;color:#845a00;font-size:12px}
|
|
16
|
+
.chm-search-box{position:sticky;top:-8px;z-index:1;padding:8px 2px;background:#f8fafb}.chm-search-box input{width:100%;height:36px;padding:0 11px;border:1px solid rgba(24,34,51,.16);border-radius:9px;background:#fff;color:inherit;font:inherit;outline:none}.chm-search-box input:focus{border-color:#0a8b68;box-shadow:0 0 0 3px rgba(10,139,104,.12)}
|
|
17
|
+
.chm-search-meta{margin:7px 4px;color:#6b7688;font-size:11px}.chm-search-results{display:flex;flex-direction:column;gap:5px}.chm-search-result{padding:9px;background:#fff;border:1px solid rgba(24,34,51,.08)}.chm-search-result:hover{border-color:rgba(10,139,104,.35);box-shadow:0 5px 16px rgba(24,34,51,.07)}.chm-search-result strong,.chm-search-result span{display:block}.chm-search-result strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chm-search-result span{margin-top:3px;color:#677386;font-size:11px;line-height:1.4}.chm-search-result mark{background:#fff1a6;color:inherit}
|
|
18
|
+
.chm-topic{min-width:0;min-height:0;display:flex;flex-direction:column;background:#dfe5ea}.chm-topic-bar{min-height:42px;display:flex;align-items:center;gap:8px;padding:7px 12px;background:rgba(255,255,255,.88);border-bottom:1px solid rgba(24,34,51,.08)}.chm-topic-bar strong{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chm-topic-path{margin-left:auto;max-width:48%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#6b7688;font-size:11px}
|
|
19
|
+
.chm-topic-frame{display:block;flex:1;min-height:0;width:100%;border:0;background:#fff}
|
|
20
|
+
.chm-state{position:absolute;inset:62px 0 0;z-index:6;display:flex;align-items:center;justify-content:center;gap:12px;background:rgba(237,242,246,.88);backdrop-filter:blur(2px)}.chm-state[hidden]{display:none}.chm-spinner{width:32px;height:32px;border:3px solid rgba(8,120,92,.16);border-top-color:#08785c;border-radius:999px;animation:chm-spin .85s linear infinite}.chm-state p{margin:0;color:#435168;font-weight:650}
|
|
21
|
+
.chm-error{position:absolute;inset:82px 18px auto;z-index:8;width:min(560px,calc(100% - 36px));margin:auto;padding:16px;border:1px solid rgba(190,91,0,.22);border-radius:14px;background:#fff8e9;color:#743f00;box-shadow:0 18px 48px rgba(24,34,51,.18)}.chm-error[hidden]{display:none}.chm-error strong{display:block;font-size:15px}.chm-error p{margin:6px 0 0;white-space:pre-wrap;overflow-wrap:anywhere}
|
|
22
|
+
@keyframes chm-spin{to{transform:rotate(360deg)}}
|
|
23
|
+
[data-viewer-theme=dark] .chm-viewer{background:#111827;color:#e6edf6}[data-viewer-theme=dark] .chm-header,[data-viewer-theme=dark] .chm-topic-bar{background:#151d2b;border-color:rgba(148,163,184,.18)}[data-viewer-theme=dark] .chm-heading p,[data-viewer-theme=dark] .chm-topic-path{color:#94a3b8}[data-viewer-theme=dark] .chm-sidebar,[data-viewer-theme=dark] .chm-search-box{background:#121a27;border-color:rgba(148,163,184,.18)}[data-viewer-theme=dark] .chm-tab.is-active,[data-viewer-theme=dark] .chm-search-result,[data-viewer-theme=dark] .chm-search-box input{background:#1c2635;color:#e6edf6}[data-viewer-theme=dark] .chm-topic{background:#0b1019}[data-viewer-theme=dark] .chm-state{background:rgba(17,24,39,.88);color:#dbe5f1}
|
|
24
|
+
@media(prefers-color-scheme:dark){[data-viewer-theme=system] .chm-viewer{background:#111827;color:#e6edf6}[data-viewer-theme=system] .chm-header,[data-viewer-theme=system] .chm-topic-bar{background:#151d2b;border-color:rgba(148,163,184,.18)}[data-viewer-theme=system] .chm-heading p,[data-viewer-theme=system] .chm-topic-path{color:#94a3b8}[data-viewer-theme=system] .chm-sidebar,[data-viewer-theme=system] .chm-search-box{background:#121a27;border-color:rgba(148,163,184,.18)}[data-viewer-theme=system] .chm-tab.is-active,[data-viewer-theme=system] .chm-search-result,[data-viewer-theme=system] .chm-search-box input{background:#1c2635;color:#e6edf6}[data-viewer-theme=system] .chm-topic{background:#0b1019}[data-viewer-theme=system] .chm-state{background:rgba(17,24,39,.88);color:#dbe5f1}}
|
|
25
|
+
@media(max-width:760px){.chm-sidebar-toggle{display:inline-flex;align-items:center;justify-content:center}.chm-body{grid-template-columns:1fr}.chm-sidebar{position:absolute;z-index:5;top:62px;bottom:0;left:0;width:min(88vw,310px);box-shadow:16px 0 36px rgba(24,34,51,.18);transform:translateX(-105%);transition:transform .2s ease}.chm-viewer.is-sidebar-open .chm-sidebar{transform:translateX(0)}.chm-badges .chm-badge.is-muted{display:none}.chm-topic-path{display:none}}
|
|
26
|
+
`;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { FileViewerChmOptions } from './model.js';
|
|
2
|
+
import { resolveChmOptions } from './model.js';
|
|
3
|
+
import type { ChmOpenResult, ChmSearchResult } from './workerProtocol.js';
|
|
4
|
+
export declare const DEFAULT_CHM_WORKER_PATH = "vendor/chm/chm.worker.js";
|
|
5
|
+
export declare const DEFAULT_CHM_WASM_MODULE_PATH = "vendor/chm/chm_wasm.js";
|
|
6
|
+
export declare const DEFAULT_CHM_WASM_PATH = "vendor/chm/chm_wasm_bg.wasm";
|
|
7
|
+
export interface ChmWorkerProgress {
|
|
8
|
+
phase: 'wasm' | 'directory' | 'manifest' | 'search';
|
|
9
|
+
current: number;
|
|
10
|
+
total?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare class ChmWorkerClient {
|
|
13
|
+
readonly options: ReturnType<typeof resolveChmOptions>;
|
|
14
|
+
private readonly worker;
|
|
15
|
+
private readonly moduleUrl;
|
|
16
|
+
private readonly wasmUrl;
|
|
17
|
+
private readonly pending;
|
|
18
|
+
private readonly onProgress?;
|
|
19
|
+
private requestId;
|
|
20
|
+
private destroyed;
|
|
21
|
+
constructor(options?: FileViewerChmOptions, onProgress?: (progress: ChmWorkerProgress) => void);
|
|
22
|
+
private handleMessage;
|
|
23
|
+
private handleError;
|
|
24
|
+
private handleMessageError;
|
|
25
|
+
private rejectAll;
|
|
26
|
+
private fail;
|
|
27
|
+
private request;
|
|
28
|
+
open(buffer: ArrayBuffer, signal?: AbortSignal): Promise<ChmOpenResult>;
|
|
29
|
+
read(path: string, signal?: AbortSignal): Promise<Uint8Array>;
|
|
30
|
+
search(query: string, signal?: AbortSignal): Promise<ChmSearchResult>;
|
|
31
|
+
destroy(): void;
|
|
32
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { DEFAULT_FILE_VIEWER_CHM_WASM_MODULE_PATH, DEFAULT_FILE_VIEWER_CHM_WASM_PATH, DEFAULT_FILE_VIEWER_CHM_WORKER_PATH, resolveFileViewerChmWasmModuleUrl, resolveFileViewerChmWasmUrl, resolveFileViewerChmWorkerUrl, } from '@file-viewer/core';
|
|
2
|
+
import { resolveChmOptions } from './model.js';
|
|
3
|
+
export const DEFAULT_CHM_WORKER_PATH = DEFAULT_FILE_VIEWER_CHM_WORKER_PATH;
|
|
4
|
+
export const DEFAULT_CHM_WASM_MODULE_PATH = DEFAULT_FILE_VIEWER_CHM_WASM_MODULE_PATH;
|
|
5
|
+
export const DEFAULT_CHM_WASM_PATH = DEFAULT_FILE_VIEWER_CHM_WASM_PATH;
|
|
6
|
+
const makeWorkerError = (response) => {
|
|
7
|
+
const error = new Error(response.error.message || 'CHM Worker failed.');
|
|
8
|
+
error.name = response.error.name || 'Error';
|
|
9
|
+
if (response.error.code)
|
|
10
|
+
Object.defineProperty(error, 'code', { value: response.error.code });
|
|
11
|
+
return error;
|
|
12
|
+
};
|
|
13
|
+
export class ChmWorkerClient {
|
|
14
|
+
constructor(options, onProgress) {
|
|
15
|
+
this.pending = new Map();
|
|
16
|
+
this.requestId = 0;
|
|
17
|
+
this.destroyed = false;
|
|
18
|
+
this.handleMessage = (event) => {
|
|
19
|
+
var _a;
|
|
20
|
+
const response = event.data;
|
|
21
|
+
if (response.type === 'progress') {
|
|
22
|
+
(_a = this.onProgress) === null || _a === void 0 ? void 0 : _a.call(this, { phase: response.phase, current: response.current, total: response.total });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const pending = this.pending.get(response.id);
|
|
26
|
+
if (!pending)
|
|
27
|
+
return;
|
|
28
|
+
this.pending.delete(response.id);
|
|
29
|
+
clearTimeout(pending.timer);
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
pending.reject(makeWorkerError(response));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (response.type !== pending.expectedType) {
|
|
35
|
+
pending.reject(new Error(`CHM_WORKER_PROTOCOL: expected ${pending.expectedType}, received ${response.type}.`));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
pending.resolve(response);
|
|
39
|
+
};
|
|
40
|
+
this.handleError = (event) => {
|
|
41
|
+
this.fail(new Error(event.message || 'CHM Worker failed to load.'));
|
|
42
|
+
};
|
|
43
|
+
this.handleMessageError = () => {
|
|
44
|
+
this.fail(new Error('CHM_WORKER_PROTOCOL: the browser could not deserialize a Worker response.'));
|
|
45
|
+
};
|
|
46
|
+
if (typeof Worker === 'undefined') {
|
|
47
|
+
throw new Error('CHM_WORKER_UNAVAILABLE: this browser does not support Web Workers.');
|
|
48
|
+
}
|
|
49
|
+
this.options = resolveChmOptions(options);
|
|
50
|
+
const documentBaseUrl = typeof document === 'undefined' ? undefined : document.baseURI;
|
|
51
|
+
this.moduleUrl = resolveFileViewerChmWasmModuleUrl(options, documentBaseUrl);
|
|
52
|
+
this.wasmUrl = resolveFileViewerChmWasmUrl(options, documentBaseUrl);
|
|
53
|
+
this.onProgress = onProgress;
|
|
54
|
+
const workerUrl = resolveFileViewerChmWorkerUrl(options, documentBaseUrl);
|
|
55
|
+
this.worker = new Worker(workerUrl, { type: 'module', name: 'file-viewer-chm' });
|
|
56
|
+
this.worker.addEventListener('message', this.handleMessage);
|
|
57
|
+
this.worker.addEventListener('error', this.handleError);
|
|
58
|
+
this.worker.addEventListener('messageerror', this.handleMessageError);
|
|
59
|
+
}
|
|
60
|
+
rejectAll(error) {
|
|
61
|
+
for (const pending of this.pending.values()) {
|
|
62
|
+
clearTimeout(pending.timer);
|
|
63
|
+
pending.reject(error);
|
|
64
|
+
}
|
|
65
|
+
this.pending.clear();
|
|
66
|
+
}
|
|
67
|
+
fail(error) {
|
|
68
|
+
if (this.destroyed)
|
|
69
|
+
return;
|
|
70
|
+
this.destroyed = true;
|
|
71
|
+
this.rejectAll(error);
|
|
72
|
+
this.worker.removeEventListener('message', this.handleMessage);
|
|
73
|
+
this.worker.removeEventListener('error', this.handleError);
|
|
74
|
+
this.worker.removeEventListener('messageerror', this.handleMessageError);
|
|
75
|
+
this.worker.terminate();
|
|
76
|
+
}
|
|
77
|
+
request(request, expectedType, transfer = [], signal) {
|
|
78
|
+
if (this.destroyed)
|
|
79
|
+
return Promise.reject(new Error('CHM_WORKER_CLOSED: the CHM session has been destroyed.'));
|
|
80
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
81
|
+
return Promise.reject(new DOMException('CHM operation was aborted.', 'AbortError'));
|
|
82
|
+
const id = ++this.requestId;
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
const onAbort = () => {
|
|
85
|
+
const pending = this.pending.get(id);
|
|
86
|
+
if (!pending)
|
|
87
|
+
return;
|
|
88
|
+
this.pending.delete(id);
|
|
89
|
+
clearTimeout(pending.timer);
|
|
90
|
+
reject(new DOMException('CHM operation was aborted.', 'AbortError'));
|
|
91
|
+
};
|
|
92
|
+
const timer = setTimeout(() => {
|
|
93
|
+
this.pending.delete(id);
|
|
94
|
+
signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
|
|
95
|
+
const error = new Error(`CHM_WORKER_TIMEOUT: ${expectedType} exceeded ${this.options.workerTimeoutMs}ms.`);
|
|
96
|
+
reject(error);
|
|
97
|
+
this.fail(error);
|
|
98
|
+
}, this.options.workerTimeoutMs);
|
|
99
|
+
this.pending.set(id, {
|
|
100
|
+
expectedType,
|
|
101
|
+
timer,
|
|
102
|
+
resolve: response => {
|
|
103
|
+
signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
|
|
104
|
+
resolve(response);
|
|
105
|
+
},
|
|
106
|
+
reject: error => {
|
|
107
|
+
signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
|
|
108
|
+
reject(error);
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', onAbort, { once: true });
|
|
112
|
+
this.worker.postMessage({ ...request, id }, transfer);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
async open(buffer, signal) {
|
|
116
|
+
if (buffer.byteLength > this.options.maxArchiveBytes) {
|
|
117
|
+
const error = new Error(`CHM_LIMIT_EXCEEDED: source is ${buffer.byteLength} bytes; limit is ${this.options.maxArchiveBytes}.`);
|
|
118
|
+
Object.defineProperty(error, 'code', { value: 'CHM_LIMIT_EXCEEDED' });
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
const copy = buffer.slice(0);
|
|
122
|
+
const response = await this.request({
|
|
123
|
+
type: 'open',
|
|
124
|
+
buffer: copy,
|
|
125
|
+
moduleUrl: this.moduleUrl,
|
|
126
|
+
wasmUrl: this.wasmUrl,
|
|
127
|
+
limits: {
|
|
128
|
+
maxArchiveBytes: this.options.maxArchiveBytes,
|
|
129
|
+
maxFileBytes: this.options.maxArchiveBytes,
|
|
130
|
+
maxEntries: this.options.maxEntries,
|
|
131
|
+
maxEntryBytes: this.options.maxEntryBytes,
|
|
132
|
+
maxTotalDecompressedBytes: this.options.maxTotalDecompressedBytes,
|
|
133
|
+
maxTotalDeclaredBytes: this.options.maxTotalDecompressedBytes,
|
|
134
|
+
maxMetadataBytes: this.options.maxHtmlBytes,
|
|
135
|
+
maxSitemapNodes: this.options.maxEntries,
|
|
136
|
+
maxSitemapDepth: 256,
|
|
137
|
+
},
|
|
138
|
+
}, 'open', [copy], signal);
|
|
139
|
+
return { manifest: response.manifest, entries: response.entries };
|
|
140
|
+
}
|
|
141
|
+
async read(path, signal) {
|
|
142
|
+
const response = await this.request({ type: 'read', path }, 'read', [], signal);
|
|
143
|
+
return response.data;
|
|
144
|
+
}
|
|
145
|
+
async search(query, signal) {
|
|
146
|
+
const response = await this.request({
|
|
147
|
+
type: 'search',
|
|
148
|
+
query,
|
|
149
|
+
limit: this.options.maxSearchResults,
|
|
150
|
+
maxTopics: this.options.maxSearchTopics,
|
|
151
|
+
}, 'search', [], signal);
|
|
152
|
+
return { hits: response.hits, inspected: response.inspected, truncated: response.truncated };
|
|
153
|
+
}
|
|
154
|
+
destroy() {
|
|
155
|
+
this.fail(new DOMException('CHM session was destroyed.', 'AbortError'));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ChmEntry, ChmManifest, ChmParseLimits, ChmSearchHit } from './model.js';
|
|
2
|
+
export type ChmWorkerRequest = {
|
|
3
|
+
id: number;
|
|
4
|
+
type: 'open';
|
|
5
|
+
buffer: ArrayBuffer;
|
|
6
|
+
moduleUrl: string;
|
|
7
|
+
wasmUrl: string;
|
|
8
|
+
limits: ChmParseLimits;
|
|
9
|
+
} | {
|
|
10
|
+
id: number;
|
|
11
|
+
type: 'read';
|
|
12
|
+
path: string;
|
|
13
|
+
} | {
|
|
14
|
+
id: number;
|
|
15
|
+
type: 'search';
|
|
16
|
+
query: string;
|
|
17
|
+
limit: number;
|
|
18
|
+
maxTopics: number;
|
|
19
|
+
} | {
|
|
20
|
+
id: number;
|
|
21
|
+
type: 'close';
|
|
22
|
+
};
|
|
23
|
+
export type ChmWorkerResponse = {
|
|
24
|
+
id: number;
|
|
25
|
+
ok: true;
|
|
26
|
+
type: 'open';
|
|
27
|
+
manifest: ChmManifest;
|
|
28
|
+
entries: ChmEntry[];
|
|
29
|
+
} | {
|
|
30
|
+
id: number;
|
|
31
|
+
ok: true;
|
|
32
|
+
type: 'read';
|
|
33
|
+
data: Uint8Array;
|
|
34
|
+
} | {
|
|
35
|
+
id: number;
|
|
36
|
+
ok: true;
|
|
37
|
+
type: 'search';
|
|
38
|
+
hits: ChmSearchHit[];
|
|
39
|
+
inspected: number;
|
|
40
|
+
truncated: boolean;
|
|
41
|
+
} | {
|
|
42
|
+
id: number;
|
|
43
|
+
ok: true;
|
|
44
|
+
type: 'close';
|
|
45
|
+
} | {
|
|
46
|
+
id: number;
|
|
47
|
+
ok: false;
|
|
48
|
+
type: ChmWorkerRequest['type'];
|
|
49
|
+
error: {
|
|
50
|
+
name: string;
|
|
51
|
+
message: string;
|
|
52
|
+
code?: string;
|
|
53
|
+
};
|
|
54
|
+
} | {
|
|
55
|
+
id: 0;
|
|
56
|
+
ok: true;
|
|
57
|
+
type: 'progress';
|
|
58
|
+
phase: 'wasm' | 'directory' | 'manifest' | 'search';
|
|
59
|
+
current: number;
|
|
60
|
+
total?: number;
|
|
61
|
+
};
|
|
62
|
+
export interface ChmOpenResult {
|
|
63
|
+
manifest: ChmManifest;
|
|
64
|
+
entries: ChmEntry[];
|
|
65
|
+
}
|
|
66
|
+
export interface ChmSearchResult {
|
|
67
|
+
hits: ChmSearchHit[];
|
|
68
|
+
inspected: number;
|
|
69
|
+
truncated: boolean;
|
|
70
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../../ecosystem/capability-manifest.schema.json",
|
|
3
|
+
"schemaVersion": 1,
|
|
4
|
+
"id": "chm",
|
|
5
|
+
"packageName": "@file-viewer/renderer-chm",
|
|
6
|
+
"rendererIds": ["chm"],
|
|
7
|
+
"formats": ["chm"],
|
|
8
|
+
"assets": {
|
|
9
|
+
"rendererIds": ["chm"],
|
|
10
|
+
"packageName": "@file-viewer/assets-chm",
|
|
11
|
+
"installerPackageName": "@file-viewer/assets-chm",
|
|
12
|
+
"bin": "file-viewer-assets-chm",
|
|
13
|
+
"apiExport": "installFileViewerCapabilityAssetPack",
|
|
14
|
+
"target": "public/file-viewer",
|
|
15
|
+
"copyGroups": ["chm"],
|
|
16
|
+
"copyMode": "capability-pack",
|
|
17
|
+
"receiptFilename": "file-viewer-assets-chm.receipt.json",
|
|
18
|
+
"notice": "Self-hosted CHM module Worker and Rust/WebAssembly parser runtime."
|
|
19
|
+
},
|
|
20
|
+
"license": {
|
|
21
|
+
"spdx": "Apache-2.0",
|
|
22
|
+
"policy": "permissive",
|
|
23
|
+
"notices": [
|
|
24
|
+
{
|
|
25
|
+
"packageName": "RustChm",
|
|
26
|
+
"spdx": "MIT",
|
|
27
|
+
"notice": "Permissively licensed CHM parser reference; exact source provenance is recorded in THIRD_PARTY_NOTICES.md and rust/NOTICE.md."
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"packageName": "FastChm",
|
|
31
|
+
"spdx": "MIT",
|
|
32
|
+
"notice": "Permissively licensed CHM and LZX reference; exact source provenance is recorded in THIRD_PARTY_NOTICES.md and rust/NOTICE.md."
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"weight": "heavy",
|
|
37
|
+
"profiles": ["all"]
|
|
38
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@file-viewer/renderer-chm",
|
|
3
|
+
"version": "3.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"description": "Browser-native offline CHM renderer for File Viewer with a sandboxed reader and Rust/WASM parser.",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"file-viewer",
|
|
10
|
+
"renderer",
|
|
11
|
+
"chm",
|
|
12
|
+
"html-help",
|
|
13
|
+
"lzx",
|
|
14
|
+
"wasm",
|
|
15
|
+
"web-worker",
|
|
16
|
+
"offline",
|
|
17
|
+
"self-hosted"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public",
|
|
21
|
+
"registry": "https://registry.npmjs.org/"
|
|
22
|
+
},
|
|
23
|
+
"author": {
|
|
24
|
+
"name": "Yu Wang",
|
|
25
|
+
"email": "admin@flyfish.dev"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/flyfish-dev/file-viewer.git",
|
|
30
|
+
"directory": "packages/renderers/chm"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://doc.file-viewer.app/guide/on-demand-renderers",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/flyfish-dev/file-viewer/issues"
|
|
35
|
+
},
|
|
36
|
+
"funding": {
|
|
37
|
+
"type": "individual",
|
|
38
|
+
"url": "https://dev.flyfish.group/donate?source=npm"
|
|
39
|
+
},
|
|
40
|
+
"fileViewer": {
|
|
41
|
+
"capabilityManifest": "./file-viewer.capability.json"
|
|
42
|
+
},
|
|
43
|
+
"main": "./dist/index.js",
|
|
44
|
+
"module": "./dist/index.js",
|
|
45
|
+
"types": "./dist/index.d.ts",
|
|
46
|
+
"exports": {
|
|
47
|
+
".": {
|
|
48
|
+
"types": "./dist/index.d.ts",
|
|
49
|
+
"import": "./dist/index.js",
|
|
50
|
+
"default": "./dist/index.js"
|
|
51
|
+
},
|
|
52
|
+
"./security": {
|
|
53
|
+
"types": "./dist/security.d.ts",
|
|
54
|
+
"import": "./dist/security.js",
|
|
55
|
+
"default": "./dist/security.js"
|
|
56
|
+
},
|
|
57
|
+
"./worker/chm.worker.js": "./dist/chm.worker.js",
|
|
58
|
+
"./wasm/chm_wasm.js": "./dist/chm_wasm.js",
|
|
59
|
+
"./wasm/chm_wasm_bg.wasm": "./dist/chm_wasm_bg.wasm",
|
|
60
|
+
"./capability": "./file-viewer.capability.json",
|
|
61
|
+
"./package.json": "./package.json"
|
|
62
|
+
},
|
|
63
|
+
"files": [
|
|
64
|
+
"dist",
|
|
65
|
+
"rust/Cargo.toml",
|
|
66
|
+
"rust/Cargo.lock",
|
|
67
|
+
"rust/LICENSE",
|
|
68
|
+
"rust/NOTICE.md",
|
|
69
|
+
"rust/THIRD_PARTY_LICENSES.md",
|
|
70
|
+
"rust/src",
|
|
71
|
+
"file-viewer.capability.json",
|
|
72
|
+
"README.md",
|
|
73
|
+
"README.en.md",
|
|
74
|
+
"LICENSE",
|
|
75
|
+
"THIRD_PARTY_NOTICES.md"
|
|
76
|
+
],
|
|
77
|
+
"dependencies": {
|
|
78
|
+
"@file-viewer/core": "3.0.1"
|
|
79
|
+
},
|
|
80
|
+
"devDependencies": {
|
|
81
|
+
"esbuild": "^0.28.1",
|
|
82
|
+
"typescript": "^6.0.3"
|
|
83
|
+
},
|
|
84
|
+
"license": "Apache-2.0",
|
|
85
|
+
"scripts": {
|
|
86
|
+
"build:ts": "tsc -b tsconfig.json --force",
|
|
87
|
+
"build:wasm": "node scripts/build-wasm.mjs",
|
|
88
|
+
"build:worker": "node scripts/build-worker.mjs",
|
|
89
|
+
"build": "pnpm build:ts && pnpm build:wasm && pnpm build:worker",
|
|
90
|
+
"type-check": "tsc -b tsconfig.json"
|
|
91
|
+
}
|
|
92
|
+
}
|