diff --git a/src/main/index.ts b/src/main/index.ts index 213204e..4b6df15 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -21,20 +21,23 @@ import { getStore } from './config/store' import { migrateProvidersToConnections } from './config/connectionMigration' import { isSmokeTestRequested, runSmokeTest } from './smokeTest' import { isEvalRequested, runEvalCli } from './eval/run' +import { declareDocumentScheme, registerDocumentProtocolHandler } from './protocol/documentProtocol' import { - registerDocumentProtocolHandler, - registerDocumentScheme -} from './protocol/documentProtocol' -import { - registerPdfjsAssetProtocolHandler, - registerPdfjsAssetScheme + declarePdfjsAssetScheme, + registerPdfjsAssetProtocolHandler } from './protocol/pdfjsAssetProtocol' +import { applyPrivilegedSchemes } from './protocol/privilegedSchemes' import Logger from '../shared/utils/logger' -// Scheme privileges must be registered before the app is ready, so this is a -// module-scope call rather than part of the whenReady sequence. -registerDocumentScheme() -registerPdfjsAssetScheme() +// Scheme privileges must be declared before the app is ready, so this is a +// module-scope sequence rather than part of the whenReady flow. They are +// submitted in one call on purpose: Electron *replaces* the privilege table +// instead of appending to it, so a second `registerSchemesAsPrivileged()` drops +// every scheme registered by the first - which is how `knownote-asset` silently +// broke `knownote-doc` and the PDF reader (#135). +declareDocumentScheme() +declarePdfjsAssetScheme() +applyPrivilegedSchemes() let connectionManager: ConnectionManager | null = null let embeddingService: EmbeddingService | null = null diff --git a/src/main/protocol/documentProtocol.ts b/src/main/protocol/documentProtocol.ts index 667779f..3a42337 100644 --- a/src/main/protocol/documentProtocol.ts +++ b/src/main/protocol/documentProtocol.ts @@ -4,13 +4,14 @@ * 渲染进程不应拿到任意文件路径。它只知道 documentId,主进程按 id 查库、只服务 * 这份来源自己的文件。路径永远来自数据库,不来自请求,因此不存在路径穿越。 * - * scheme 必须在 app ready 之前注册为 privileged;handler 在 ready 之后安装。 + * scheme 必须在 app ready 之前声明为 privileged;handler 在 ready 之后安装。 */ import { net, protocol } from 'electron' import { pathToFileURL } from 'url' import { DOCUMENT_SCHEME, parseDocumentUrl } from '../../shared/utils/documentUrl' import Logger from '../../shared/utils/logger' +import { declarePrivilegedScheme } from './privilegedSchemes' /** * Origins allowed to read a document. The renderer is `file://` in production @@ -22,23 +23,21 @@ const ALLOWED_ORIGINS = new Set(['null', 'http://localhost:5173', 'http://127.0. export { DOCUMENT_SCHEME, documentUrl, parseDocumentUrl } from '../../shared/utils/documentUrl' -/** 必须在 app ready 之前调用一次。 */ -export function registerDocumentScheme(): void { - protocol.registerSchemesAsPrivileged([ - { - scheme: DOCUMENT_SCHEME, - privileges: { - standard: true, - secure: true, - supportFetchAPI: true, - stream: true, - // The renderer fetches this scheme from a different origin (`file://` in - // production, the dev server in development), so it is a cross-origin - // request and must be CORS-enabled to succeed. - corsEnabled: true - } +/** 声明特权。只入队,提交由 `applyPrivilegedSchemes()` 在 ready 前统一完成(#135)。 */ +export function declareDocumentScheme(): void { + declarePrivilegedScheme({ + scheme: DOCUMENT_SCHEME, + privileges: { + standard: true, + secure: true, + supportFetchAPI: true, + stream: true, + // The renderer fetches this scheme from a different origin (`file://` in + // production, the dev server in development), so it is a cross-origin + // request and must be CORS-enabled to succeed. + corsEnabled: true } - ]) + }) } /** 在 app ready 之后调用,安装只读的字节服务 handler。 */ diff --git a/src/main/protocol/pdfjsAssetProtocol.ts b/src/main/protocol/pdfjsAssetProtocol.ts index 60a26b9..0febd21 100644 --- a/src/main/protocol/pdfjsAssetProtocol.ts +++ b/src/main/protocol/pdfjsAssetProtocol.ts @@ -5,7 +5,7 @@ * `resources/pdfjs/{cmaps,standard_fonts,wasm}` 下的文件。路径来自这里的 * `pdfjsAssetRoot()`,不来自请求。 * - * 和 `documentProtocol` 一样,scheme 必须在 app ready 之前注册为 privileged; + * 和 `documentProtocol` 一样,scheme 必须在 app ready 之前声明为 privileged; * handler 在 ready 之后安装。 */ @@ -15,6 +15,7 @@ import { pathToFileURL } from 'url' import { PDFJS_ASSET_SCHEME, parsePdfjsAssetUrl } from '../../shared/utils/pdfjsAssets' import { pdfjsAssetRoot } from '../pdfjsAssetPaths' import Logger from '../../shared/utils/logger' +import { declarePrivilegedScheme } from './privilegedSchemes' /** * Origins allowed to read an asset. Same set as `documentProtocol`: the renderer @@ -23,23 +24,21 @@ import Logger from '../../shared/utils/logger' */ const ALLOWED_ORIGINS = new Set(['null', 'http://localhost:5173', 'http://127.0.0.1:5173']) -/** 必须在 app ready 之前调用一次。 */ -export function registerPdfjsAssetScheme(): void { - protocol.registerSchemesAsPrivileged([ - { - scheme: PDFJS_ASSET_SCHEME, - privileges: { - standard: true, - secure: true, - supportFetchAPI: true, - stream: true, - // The renderer fetches this scheme from a different origin (`file://` in - // production, the dev server in development), so it is a cross-origin - // request and must be CORS-enabled to succeed. - corsEnabled: true - } +/** 声明特权。只入队,提交由 `applyPrivilegedSchemes()` 在 ready 前统一完成(#135)。 */ +export function declarePdfjsAssetScheme(): void { + declarePrivilegedScheme({ + scheme: PDFJS_ASSET_SCHEME, + privileges: { + standard: true, + secure: true, + supportFetchAPI: true, + stream: true, + // The renderer fetches this scheme from a different origin (`file://` in + // production, the dev server in development), so it is a cross-origin + // request and must be CORS-enabled to succeed. + corsEnabled: true } - ]) + }) } /** 在 app ready 之后调用,安装只读的资源 handler。 */ diff --git a/src/main/protocol/privilegedSchemes.ts b/src/main/protocol/privilegedSchemes.ts new file mode 100644 index 0000000..da527a6 --- /dev/null +++ b/src/main/protocol/privilegedSchemes.ts @@ -0,0 +1,30 @@ +/** + * 自定义 scheme 特权的**单一提交点**。 + * + * `protocol.registerSchemesAsPrivileged()` 是替换而不是追加:第二次调用会整张表覆盖 + * 上一次。之前 `documentProtocol` 和 `pdfjsAssetProtocol` 各调了一次,后注册的 + * `knownote-asset` 顶掉了 `knownote-doc` 的 `supportFetchAPI`,渲染进程的 + * `fetch('knownote-doc://...')` 立刻报 + * `URL scheme "knownote-doc" is not supported` —— PDF Reader 整个打不开(#135)。 + * + * 打包 smoke 当时没发现,是因为它用主进程 `net.fetch` 检查同一个协议:`protocol.handle` + * 在 scheme 没有特权时照样在 main 里工作,只有 renderer 的 `fetch` 会失败。所以这里 + * 也补了 renderer 侧检查。 + * + * 协议模块只声明自己的 scheme;提交集中在 `applyPrivilegedSchemes()`,在 app ready + * 之前调用一次。 + */ + +import { protocol, type CustomScheme } from 'electron' + +const declaredSchemes: CustomScheme[] = [] + +/** 声明一个 scheme 的特权。只入队,不调用 Electron API。 */ +export function declarePrivilegedScheme(scheme: CustomScheme): void { + declaredSchemes.push(scheme) +} + +/** 在 app ready 之前调用一次,把全部声明一次提交。 */ +export function applyPrivilegedSchemes(): void { + protocol.registerSchemesAsPrivileged(declaredSchemes) +} diff --git a/src/main/smokeTest.ts b/src/main/smokeTest.ts index 085f20b..40f0b3a 100644 --- a/src/main/smokeTest.ts +++ b/src/main/smokeTest.ts @@ -18,8 +18,12 @@ * actually broke - loading every external package the main process needs, the * native addons, the app's own vector-store code path and the document importers * (PDF, DOCX, HTML) against the fixtures in test/fixtures - and then exits - * without creating a window, so CI can gate on the exit code. It is driven by + * without showing a window, so CI can gate on the exit code. It is driven by * `scripts/smoke-packaged.mjs`, which passes `--smoke-fixtures=