dsh-jiuling-remote-mobile 1.0.14 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +42 -20
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -216,28 +216,50 @@ function saveRemoteSessions(set, logger) {
|
|
|
216
216
|
*/
|
|
217
217
|
async function remoteFetch(url, init) {
|
|
218
218
|
const pool = await remotePool();
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
219
|
+
// ① 首选:undici 专用池(关掉 autoSelectFamily,Android 上最稳)。
|
|
220
|
+
// ② 备用:`node:https`(强制 IPv4,不依赖 undici 解析)。
|
|
221
|
+
//
|
|
222
|
+
// ⚠️ **两条通道必须互为备份,不能只做「解析失败才兜底」**。线上实测过:
|
|
223
|
+
// 同一个版本、同一个账号,配对服务器日志里既有 `POST /pair/claim 400`
|
|
224
|
+
// (请求到了)又有界面上的 `fetch failed`(请求没到)—— 说明是**间歇性**
|
|
225
|
+
// 建连失败。只修「undici 没解析到」那一种情形,另一种照样挂。
|
|
226
|
+
//
|
|
227
|
+
// 重试是安全的:`fetch()` 抛错意味着**建连或响应头阶段**就失败了,
|
|
228
|
+
// 此时响应体一个字节都还没消费,重发不会产生重复副作用。
|
|
229
|
+
// (流中途断开不会走到这里 —— 那是 `for await` 里的事,不在本函数内。)
|
|
230
|
+
const attempts = [];
|
|
231
|
+
if (pool !== null) {
|
|
232
|
+
try {
|
|
233
|
+
return await pool.undici.fetch(url, { ...init, dispatcher: pool.agent });
|
|
234
|
+
} catch (error) {
|
|
235
|
+
if (isAbort(error)) throw error;
|
|
236
|
+
attempts.push(describeFetchError(error, '走的是专用 undici 池'));
|
|
232
237
|
}
|
|
233
|
-
|
|
238
|
+
} else if (!remoteFallbackWarned) {
|
|
239
|
+
remoteFallbackWarned = true;
|
|
240
|
+
console.warn('[dsh-remote-mobile] 拿不到 undici,改用 node:https 通道(强制 IPv4)。');
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
return await nodeHttpsFetch(url, init);
|
|
234
244
|
} catch (error) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
throw new Error(
|
|
245
|
+
if (isAbort(error)) throw error;
|
|
246
|
+
attempts.push(describeFetchError(error, '走的是 node:https 通道(强制 IPv4)'));
|
|
247
|
+
throw new Error(`两条通道都失败:\n ① ${attempts[0]}\n ② ${attempts[1]}`);
|
|
238
248
|
}
|
|
239
249
|
}
|
|
240
250
|
|
|
251
|
+
/**
|
|
252
|
+
* 是否是**主动中止**(不是网络故障)。
|
|
253
|
+
*
|
|
254
|
+
* ⚠️ SSE 断开重连靠 `AbortController`,把它当成网络故障包装掉,重连逻辑就失效了。
|
|
255
|
+
*
|
|
256
|
+
* @param error - 抛出的错误。
|
|
257
|
+
* @returns 是否应当原样上抛。
|
|
258
|
+
*/
|
|
259
|
+
function isAbort(error) {
|
|
260
|
+
return error?.name === 'AbortError' || error?.name === 'TimeoutError';
|
|
261
|
+
}
|
|
262
|
+
|
|
241
263
|
/**
|
|
242
264
|
* 不依赖 undici 的 HTTPS 兜底通道(只用 Node 内置的 `node:https`)。
|
|
243
265
|
*
|
|
@@ -341,10 +363,10 @@ function nodeHttpsFetch(url, init = {}) {
|
|
|
341
363
|
* | `CERT_*` | 证书校验失败(手机时间不对也会这样) | 校时 |
|
|
342
364
|
*
|
|
343
365
|
* @param error - `fetch` 抛出的错误。
|
|
344
|
-
* @param
|
|
366
|
+
* @param channel - 这条错误来自哪条通道(`'undici'` / `'https'`)。
|
|
345
367
|
* @returns 形如 `fetch failed ← ENOTFOUND getaddrinfo ENOTFOUND pair… ← 走的是专用 undici 池`。
|
|
346
368
|
*/
|
|
347
|
-
function describeFetchError(error,
|
|
369
|
+
function describeFetchError(error, channel) {
|
|
348
370
|
const parts = [];
|
|
349
371
|
if (error?.message) parts.push(String(error.message));
|
|
350
372
|
let cause = error?.cause;
|
|
@@ -363,7 +385,7 @@ function describeFetchError(error, usedGlobalFetch) {
|
|
|
363
385
|
.join(' | '),
|
|
364
386
|
);
|
|
365
387
|
}
|
|
366
|
-
parts.push(
|
|
388
|
+
parts.push(channel);
|
|
367
389
|
return parts.join(' ← ');
|
|
368
390
|
}
|
|
369
391
|
|