Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,41 @@

# Changelog

### [Version 1.142.7](https://github.com/lobehub/lobe-chat/compare/v1.142.6...v1.142.7)

<sup>Released on **2025-10-28**</sup>

#### ♻ Code Refactoring

- **misc**: Change files page from RSC to SPA mode to improve performance.

#### 💄 Styles

- **aihubmix**: Update extendParams to include urlContext.
- **misc**: Update i18n.

<br/>

<details>
<summary><kbd>Improvements and Fixes</kbd></summary>

#### Code refactoring

- **misc**: Change files page from RSC to SPA mode to improve performance, closes [#9846](https://github.com/lobehub/lobe-chat/issues/9846) ([f46cc50](https://github.com/lobehub/lobe-chat/commit/f46cc50))

#### Styles

- **aihubmix**: Update extendParams to include urlContext, closes [#9914](https://github.com/lobehub/lobe-chat/issues/9914) ([5a8fd85](https://github.com/lobehub/lobe-chat/commit/5a8fd85))
- **misc**: Update i18n, closes [#9907](https://github.com/lobehub/lobe-chat/issues/9907) ([d149c4d](https://github.com/lobehub/lobe-chat/commit/d149c4d))

</details>

<div align="right">

[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)

</div>

### [Version 1.142.6](https://github.com/lobehub/lobe-chat/compare/v1.142.5...v1.142.6)

<sup>Released on **2025-10-28**</sup>
Expand Down
36 changes: 36 additions & 0 deletions apps/desktop/src/main/core/browser/Browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,9 @@ export default class Browser {
session: browserWindow.webContents.session,
});

// Setup CORS bypass for local file server
this.setupCORSBypass(browserWindow);

logger.debug(`[${this.identifier}] Initiating placeholder and URL loading sequence.`);
this.loadPlaceholder().then(() => {
this.loadUrl(path).catch((e) => {
Expand Down Expand Up @@ -491,4 +494,37 @@ export default class Browser {
logger.debug(`[${this.identifier}] Manually reapplying visual effects via Browser.`);
this.applyVisualEffects();
}

/**
* Setup CORS bypass for local file server (127.0.0.1:*)
* This is needed for Electron to access files from the local static file server
*/
private setupCORSBypass(browserWindow: BrowserWindow): void {
logger.debug(`[${this.identifier}] Setting up CORS bypass for local file server`);

const session = browserWindow.webContents.session;

// Intercept response headers to add CORS headers
session.webRequest.onHeadersReceived((details, callback) => {
const url = details.url;

// Only modify headers for local file server requests (127.0.0.1)
if (url.includes('127.0.0.1') || url.includes('lobe-desktop-file')) {
const responseHeaders = details.responseHeaders || {};

// Add CORS headers
responseHeaders['Access-Control-Allow-Origin'] = ['*'];
responseHeaders['Access-Control-Allow-Methods'] = ['GET, POST, PUT, DELETE, OPTIONS'];
responseHeaders['Access-Control-Allow-Headers'] = ['*'];

callback({
responseHeaders,
});
} else {
callback({ responseHeaders: details.responseHeaders });
}
});

logger.debug(`[${this.identifier}] CORS bypass setup completed`);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ import type { App } from '../App';

const logger = createLogger('core:StaticFileServerManager');

const getAllowedOrigin = (rawOrigin?: string) => {
if (!rawOrigin) return '*';

try {
const url = new URL(rawOrigin);
const normalizedOrigin = `${url.protocol}//${url.host}`;
return url.hostname === 'localhost' || url.hostname === '127.0.0.1' ? normalizedOrigin : '*';
} catch {
const normalizedOrigin = rawOrigin.replace(/\/$/, '');
return normalizedOrigin.includes('localhost') || normalizedOrigin.includes('127.0.0.1')
? normalizedOrigin
: '*';
}
};

export class StaticFileServerManager {
private app: App;
private fileService: FileService;
Expand Down Expand Up @@ -126,16 +141,38 @@ export class StaticFileServerManager {
return;
}

// 获取请求的 Origin 并设置 CORS
const origin = req.headers.origin || req.headers.referer;
const allowedOrigin = getAllowedOrigin(origin);

// 处理 CORS 预检请求
if (req.method === 'OPTIONS') {
res.writeHead(204, {
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Origin': allowedOrigin,
'Access-Control-Max-Age': '86400',
});
res.end();
return;
}

const url = new URL(req.url, `http://127.0.0.1:${this.serverPort}`);
logger.debug(`Processing HTTP file request: ${req.url}`);
logger.debug(`Request method: ${req.method}`);
logger.debug(`Request headers: ${JSON.stringify(req.headers)}`);

// 提取文件路径:从 /desktop-file/path/to/file.png 中提取相对路径
let filePath = decodeURIComponent(url.pathname.slice(1)); // 移除开头的 /
logger.debug(`Initial file path after decode: ${filePath}`);

// 如果路径以 desktop-file/ 开头,则移除该前缀
const prefixWithoutSlash = LOCAL_STORAGE_URL_PREFIX.slice(1) + '/'; // 移除开头的 / 并添加结尾的 /
logger.debug(`Prefix to remove: ${prefixWithoutSlash}`);

if (filePath.startsWith(prefixWithoutSlash)) {
filePath = filePath.slice(prefixWithoutSlash.length);
logger.debug(`File path after removing prefix: ${filePath}`);
}

if (!filePath) {
Expand All @@ -148,7 +185,12 @@ export class StaticFileServerManager {
}

// 使用 FileService 获取文件
const fileResult = await this.fileService.getFile(`desktop://${filePath}`);
const desktopPath = `desktop://${filePath}`;
logger.debug(`Attempting to get file: ${desktopPath}`);
const fileResult = await this.fileService.getFile(desktopPath);
logger.debug(
`File retrieved successfully, mime type: ${fileResult.mimeType}, size: ${fileResult.content.byteLength} bytes`,
);

// 再次检查响应状态
if (res.destroyed || res.headersSent) {
Expand All @@ -158,11 +200,8 @@ export class StaticFileServerManager {

// 设置响应头
res.writeHead(200, {
// 缓存一年
'Access-Control-Allow-Origin': 'http://localhost:*',

'Access-Control-Allow-Origin': allowedOrigin,
'Cache-Control': 'public, max-age=31536000',
// 允许 localhost 的任意端口
'Content-Length': Buffer.byteLength(fileResult.content),
'Content-Type': fileResult.mimeType,
});
Expand All @@ -173,16 +212,27 @@ export class StaticFileServerManager {
logger.debug(`HTTP file served successfully: desktop://${filePath}`);
} catch (error) {
logger.error(`Error serving HTTP file: ${error}`);
logger.error(`Error stack: ${error.stack}`);

// 检查响应是否仍然可写
if (!res.destroyed && !res.headersSent) {
try {
// 获取请求的 Origin 并设置 CORS(错误响应也需要!)
const origin = req.headers.origin || req.headers.referer;
const allowedOrigin = getAllowedOrigin(origin);

// 判断是否是文件未找到错误
if (error.name === 'FileNotFoundError') {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.writeHead(404, {
'Access-Control-Allow-Origin': allowedOrigin,
'Content-Type': 'text/plain',
});
res.end('File Not Found');
} else {
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.writeHead(500, {
'Access-Control-Allow-Origin': allowedOrigin,
'Content-Type': 'text/plain',
});
res.end('Internal Server Error');
}
} catch (writeError) {
Expand Down
7 changes: 7 additions & 0 deletions changelog/v1.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
[
{
"children": {
"improvements": ["Update i18n."]
},
"date": "2025-10-28",
"version": "1.142.7"
},
{
"children": {},
"date": "2025-10-28",
Expand Down
Loading
Loading