Skip to content

Commit 8b49cf5

Browse files
committed
feat: 优化 Android Node 兼容性和错误日志
1 parent 7db77f5 commit 8b49cf5

6 files changed

Lines changed: 78 additions & 13 deletions

File tree

backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sub-store",
3-
"version": "2.36.19",
3+
"version": "2.36.20",
44
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.",
55
"main": "src/main.js",
66
"packageManager": "pnpm@11.0.9",

backend/src/main.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,21 @@ console.log(
2222
import migrate from '@/utils/migration';
2323
import serve from '@/restful';
2424

25+
if ($.env.isNode) {
26+
if (typeof Promise.withResolvers !== 'function') {
27+
eval("require('core-js/actual/promise/with-resolvers')");
28+
}
29+
30+
const workerThreads = eval("require('node:worker_threads')");
31+
if (typeof workerThreads.markAsUncloneable !== 'function') {
32+
// ponytail: Node < 22 cannot mark web objects uncloneable; remove this fallback when the Android runtime reaches Node 22.
33+
workerThreads.markAsUncloneable = () => {};
34+
}
35+
36+
eval('process').on('uncaughtExceptionMonitor', (error, origin) => {
37+
console.error(`[FATAL] ${origin}: ${error?.stack ?? error}`);
38+
});
39+
}
40+
2541
migrate();
2642
serve();

backend/src/test/utils/download.spec.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,27 @@ describe('download github proxy regex', function () {
208208
expect(maxActiveRequests).to.equal(1);
209209
});
210210

211+
it('logs download failures before rejecting', async function () {
212+
openApi.HTTP = () => ({
213+
get: async () => {
214+
throw new Error('request setup failed');
215+
},
216+
});
217+
218+
let error;
219+
try {
220+
await download('https://example.com/failing.txt');
221+
} catch (e) {
222+
error = e;
223+
}
224+
225+
expect(error).to.be.instanceOf(Error);
226+
expect(error.message).to.equal(
227+
'无法下载 URL https://example.com/failing.txt: request setup failed',
228+
);
229+
expect(errorLogs).to.deep.equal([error.message]);
230+
});
231+
211232
it('returns unpreprocessed raw content when requested', async function () {
212233
responseBody = [
213234
'proxies:',

backend/src/test/vendor/open-api.spec.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,31 @@ describe('open-api HTTP adapter', function () {
132132
});
133133
});
134134

135+
it('rejects request setup errors', async function () {
136+
let timeoutCalls = 0;
137+
const options = {
138+
url: 'https://example.com/subscription',
139+
timeout: 5,
140+
events: {
141+
onTimeout() {
142+
timeoutCalls += 1;
143+
},
144+
},
145+
};
146+
options.circular = options;
147+
148+
let error;
149+
try {
150+
await HTTP().get(options);
151+
} catch (e) {
152+
error = e;
153+
}
154+
155+
expect(error).to.be.instanceOf(TypeError);
156+
await new Promise((resolve) => setTimeout(resolve, 10));
157+
expect(timeoutCalls).to.equal(0);
158+
});
159+
135160
it('normalizes Node.js request header names to lowercase', async function () {
136161
await HTTP({
137162
headers: {

backend/src/utils/download.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,9 @@ export default async function download(
484484
);
485485
}
486486
}
487-
throw new Error(`无法下载 URL ${safeUrl}: ${e.message ?? e}`);
487+
const message = `无法下载 URL ${safeUrl}: ${e.message ?? e}`;
488+
$.error(message);
489+
throw new Error(message);
488490
}
489491
}
490492

backend/src/vendor/open-api.js

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
467467
opts: options.opts,
468468
});
469469
} else if (isLoon || isSurge || isNode) {
470-
worker = new Promise(async (resolve, reject) => {
470+
const run = async (resolve, reject) => {
471471
const body = options.body;
472472
const opts = JSON.parse(JSON.stringify(options));
473473
opts.body = body;
@@ -627,6 +627,9 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
627627
},
628628
);
629629
}
630+
};
631+
worker = new Promise((resolve, reject) => {
632+
run(resolve, reject).catch(reject);
630633
});
631634
} else if (isGUIforCores) {
632635
worker = new Promise(async (resolve, reject) => {
@@ -669,16 +672,14 @@ export function HTTP(defaultOptions = { baseURL: '' }) {
669672
})
670673
: null;
671674

672-
return (
673-
timer
674-
? Promise.race([timer, worker]).then((res) => {
675-
if (typeof clearTimeout !== 'undefined') {
676-
clearTimeout(timeoutid);
677-
}
678-
return res;
679-
})
680-
: worker
681-
).then((resp) => events.onResponse(resp));
675+
const request = timer ? Promise.race([timer, worker]) : worker;
676+
return request
677+
.finally(() => {
678+
if (timer && typeof clearTimeout !== 'undefined') {
679+
clearTimeout(timeoutid);
680+
}
681+
})
682+
.then((resp) => events.onResponse(resp));
682683
}
683684

684685
const http = {

0 commit comments

Comments
 (0)