Files
koring-launcher/electron/resource-protocol.ts
T
dream_pep fad8f612bf feat: 重构背景图处理体系,优化用户UX体验
- 重构背景图全链路处理:改用文件路径存储而非base64,新增`koring-res://`自定义协议流式加载本地壁纸,大幅降低内存占用
- 调整配置文件存储路径至userData目录,解决NSIS重装/升级时配置被清空的问题
- 新增路由调试页面,支持快速跳转所有已注册的应用页面
- 优化OOBE引导和更新完成页的「前往首页」按钮,添加4秒延迟渐入动画
- 自动迁移旧版配置:将安装目录的配置文件迁移至userData,同时将base64格式的背景配置自动转换为文件路径存储
- 重构预加载脚本、主进程IPC处理器与前端页面的背景图相关逻辑
- 更新AGENTS.md开发文档,修正路径与逻辑说明
- 更新应用背景图资源文件
2026-09-04 19:34:58 +08:00

95 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* `koring-res://` 自定义协议:让渲染进程以「资源引用」方式使用本地文件
* (不再把图片以 BASE64 dataURL 传入渲染进程 / 写入配置文件)。
*
* 权限/安全约束:
* - 只允许 host 为 `userdata`,即仅服务 app.getPath('userData') 目录;
* - 只允许文件名以 `background-custom` 开头的受管壁纸文件(白名单);
* - realpath 二次校验目标必须位于 userData 之内(防目录穿越 / 符号链接);
* - 以流方式返回文件(stream),渲染进程按需解码,主进程不产生 base64 副本。
*/
import electron from 'electron';
import * as fs from 'fs';
import * as path from 'path';
import { Readable } from 'stream';
import { isManagedBackgroundFile, isPathInside, mimeForFile } from './core/background-image';
const { protocol, app } = electron;
export const RESOURCE_SCHEME = 'koring-res';
/** 必须在 app ready 之前调用(privileged scheme 注册) */
export function registerResourceSchemePrivileges(): void {
protocol.registerSchemesAsPrivileged([
{
scheme: RESOURCE_SCHEME,
privileges: {
standard: true,
secure: true,
supportFetchAPI: true,
corsEnabled: true,
stream: true,
},
},
]);
}
/** app ready 之后调用:注册协议处理器 */
export function registerResourceProtocol(): void {
protocol.handle(RESOURCE_SCHEME, async (request) => {
try {
const url = new URL(request.url);
if (url.host !== 'userdata') {
return new Response('Forbidden', { status: 403 });
}
let relative: string;
try {
relative = decodeURIComponent(url.pathname).replace(/^[/\\]+/, '');
} catch {
return new Response('Bad Request', { status: 400 });
}
// 路径穿越 / 绝对路径直接拒绝
if (!relative || relative.includes('..') || path.isAbsolute(relative)) {
return new Response('Forbidden', { status: 403 });
}
const fileName = path.basename(relative);
if (fileName !== relative || !isManagedBackgroundFile(fileName)) {
return new Response('Forbidden', { status: 403 });
}
const userDataDir = app.getPath('userData');
const target = path.join(userDataDir, fileName);
if (!isPathInside(userDataDir, target)) {
return new Response('Forbidden', { status: 403 });
}
const stat = await fs.promises.stat(target);
if (!stat.isFile()) {
return new Response('Not Found', { status: 404 });
}
const body = Readable.toWeb(fs.createReadStream(target)) as unknown as BodyInit;
return new Response(body, {
status: 200,
headers: {
'content-type': mimeForFile(target),
'content-length': String(stat.size),
'cache-control': 'no-store',
},
});
} catch {
return new Response('Not Found', { status: 404 });
}
});
}
/** 生成 userData 壁纸文件的协议 URLfileName 需已通过白名单校验) */
export function backgroundResourceUrl(fileName: string): string {
return `${RESOURCE_SCHEME}://userdata/${encodeURIComponent(fileName)}`;
}