feat: 引入SUB_STORE_BACKEND_MERGE 变量实现前后端端口合并及安全增强

1. 新增SUB_STORE_BACKEND_MERGE配置变量,支持功能整合模式:
   - 当设置SUB_STORE_BACKEND_MERGE为非空任意值时,后端支持同时处理API和前端资源请求
   - 新增配置示例:
     #合并前后端端口
     SUB_STORE_BACKEND_MERGE=true
     #设置接口安全地址
     SUB_STORE_FRONTEND_BACKEND_PATH=/safe-api
     #设置前端文件的路径
     SUB_STORE_FRONTEND_PATH=./dist
     #后端监听的端口
     SUB_STORE_BACKEND_API_PORT=3000
     #后端监听的HOST
     SUB_STORE_BACKEND_API_HOST="127.0.0.1"

2. 合并后支持前端在子路由界面刷新:
   - 原前端在subs、files、sync等页面刷新时会出现404问题,合并后修复了该问题
This commit is contained in:
Aritro37 2025-03-19 15:26:17 +08:00 committed by GitHub
parent ff5283a66f
commit dfc619a181
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 51 additions and 9 deletions

View File

@ -1,6 +1,6 @@
{
"name": "sub-store",
"version": "2.18.7",
"version": "2.18.8",
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.",
"main": "src/main.js",
"scripts": {
@ -32,6 +32,7 @@
"cron": "^3.1.6",
"dns-packet": "^5.6.1",
"express": "^4.17.1",
"mime-types": "^2.1.35",
"http-proxy-middleware": "^3.0.3",
"ip-address": "^9.0.5",
"js-base64": "^3.7.2",

View File

@ -30,10 +30,12 @@ export default function serve() {
}
const $app = express({ substore: $, port, host });
if ($.env.isNode) {
const be_merge = eval('process.env.SUB_STORE_BACKEND_MERGE');
const be_prefix = eval('process.env.SUB_STORE_BACKEND_PREFIX');
const fe_be_path = eval('process.env.SUB_STORE_FRONTEND_BACKEND_PATH');
if (be_prefix) {
if (!fe_be_path.startsWith('/')) {
const fe_path = eval('process.env.SUB_STORE_FRONTEND_PATH');
if (be_prefix || be_merge) {
if(!fe_be_path.startsWith('/')){
throw new Error(
'SUB_STORE_FRONTEND_BACKEND_PATH should start with /',
);
@ -43,12 +45,50 @@ export default function serve() {
);
$app.use((req, res, next) => {
if (req.path.startsWith(fe_be_path)) {
const newPath = req.url.replace(fe_be_path, '') || '/';
req.url = newPath;
next();
} else {
res.status(403).send();
req.url = req.url.replace(fe_be_path, '') || '/';
if(be_merge && req.url.startsWith('/api/')){
req.query['share'] = 'true';
}
next();
return;
}
const pathname = req._parsedUrl.pathname || '/';
if(be_merge && req.path.startsWith('/share/') && req.query.token){
if (req.method.toLowerCase() !== 'get'){
res.status(405).send('Method not allowed');
return;
}
const tokens = $.read(TOKENS_KEY) || [];
const token = tokens.find(
(t) =>
t.token === req.query.token &&
`/share/${t.type}/${t.name}` === pathname &&
(t.exp == null || t.exp > Date.now()),
);
if (token){
next();
return;
}
}
if (be_merge && fe_path && req.path.indexOf('/',1) == -1) {
if (req.path.indexOf('.') == -1){
req.url = "/index.html"
}
const express_ = eval(`require("express")`);
const mime_ = eval(`require("mime-types")`);
const staticFileMiddleware = express_.static(fe_path, {
setHeaders: (res, path) => {
const type = mime_.contentType(path);
if (type) {
res.set('Content-Type', type);
}
}
});
staticFileMiddleware(req, res, next);
return;
}
res.status(403).end('Forbbiden');
return;
});
}
}
@ -198,7 +238,8 @@ export default function serve() {
const fe_abs_path = path.resolve(
fe_path || path.join(__dirname, 'frontend'),
);
if (fe_path) {
const be_merge = eval('process.env.SUB_STORE_BACKEND_MERGE');
if (fe_path && !be_merge) {
try {
fs.accessSync(path.join(fe_abs_path, 'index.html'));
} catch (e) {