dsh-jiuling-remote-mobile 1.0.12 → 1.0.13

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.
Files changed (2) hide show
  1. package/lib/index.js +82 -9
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -208,21 +208,94 @@ function saveRemoteSessions(set, logger) {
208
208
  * 所以本插件**不复用全局 dispatcher**,自己建一个关掉 `autoSelectFamily` 的池。
209
209
  * 只影响本插件发出的请求,不碰进程全局状态。
210
210
  *
211
- * @returns `{ dispatcher, fetch }`;拿不到 undici 时 `dispatcher` 为 `undefined`。
211
+ * @param url - 完整 URL。
212
+ * @param init - `fetch` 的第二参数。
213
+ * @returns 响应。
212
214
  */
213
- let remoteAgent;
214
215
  async function remoteFetch(url, init) {
215
- if (remoteAgent === undefined) {
216
+ const pool = await remotePool();
217
+ if (pool === null) {
218
+ // 退到全局 fetch 是**故障态**,不是正常路径 —— 必须留下痕迹,
219
+ // 否则又变成上面那种「看不出根因」的间歇性失败。
220
+ if (!remoteFallbackWarned) {
221
+ remoteFallbackWarned = true;
222
+ console.warn(
223
+ '[dsh-remote-mobile] 拿不到 undici,已退回全局 fetch:Android 上可能因 ' +
224
+ 'Happy Eyeballs 卡死(见 remoteFetch 注释)。配对/远程调用若报 ' +
225
+ '"fetch failed",根因在这里。',
226
+ );
227
+ }
228
+ return fetch(url, init);
229
+ }
230
+ return pool.undici.fetch(url, { ...init, dispatcher: pool.agent });
231
+ }
232
+
233
+ /** 解析 undici 失败时的告警只打一次(否则每条 RPC 刷一行)。 */
234
+ let remoteFallbackWarned = false;
235
+
236
+ /** 专用连接池的初始化 Promise(并发首调只初始化一次)。 */
237
+ let remotePoolPromise;
238
+
239
+ /**
240
+ * 建(或取)专用连接池。
241
+ *
242
+ * @returns `{ undici, agent }`;拿不到 undici 时 `null`。
243
+ */
244
+ function remotePool() {
245
+ if (remotePoolPromise === undefined) {
246
+ remotePoolPromise = (async () => {
247
+ const undici = await loadUndici();
248
+ if (undici === null) return null;
249
+ return { undici, agent: new undici.Agent({ connect: { autoSelectFamily: false } }) };
250
+ })();
251
+ }
252
+ return remotePoolPromise;
253
+ }
254
+
255
+ /**
256
+ * 解析 undici 模块。
257
+ *
258
+ * ## ⚠️ 为什么不能只写 `await import('undici')`(配对报 `fetch failed` 的根因)
259
+ *
260
+ * 本插件装在 `<DSH_HOME>/profiles/<profile>/node_modules/`,而 undici 是
261
+ * **引擎**的依赖,装在 `<engine>/node_modules/undici` —— 它在插件的解析链
262
+ * **之外**,于是 `import('undici')` 抛 `ERR_MODULE_NOT_FOUND`,`remoteFetch`
263
+ * 静默退回全局 `fetch`(即上面那张表里 12018 ms 超时的那个)。
264
+ *
265
+ * 真机实测(CDY_TN90 / Android 10 / 插件 1.0.12,2026-10-16):
266
+ *
267
+ * | 写法 | 结果 |
268
+ * |---|---|
269
+ * | `await import('undici')`(插件目录内) | **ERR_MODULE_NOT_FOUND** ❌ |
270
+ * | `createRequire(process.argv[1]).resolve('undici')` | ✅ → `<engine>/node_modules/undici/index.js` |
271
+ *
272
+ * 这个故障是**间歇性**的:网络恰好在 Happy Eyeballs 回退窗口内时全局 fetch
273
+ * 也能通,所以会看到「同一个版本、一台手机能配对、另一台报 fetch failed」——
274
+ * 而**配对服务器日志里根本没有那次请求**(请求没出网),这是最快的判据。
275
+ *
276
+ * @returns undici 模块;都失败时 `null`。
277
+ */
278
+ async function loadUndici() {
279
+ // ① 首选:以**引擎入口**为锚点解析。插件由引擎加载,`process.argv[1]` 就是
280
+ // 引擎的 bin.js(实测 …/engine/node_modules/@deepseek-ai/dsh/lib/bin.js),
281
+ // 它的 node_modules 里就有 undici。
282
+ const entry = process.argv[1];
283
+ if (typeof entry === 'string' && entry !== '') {
216
284
  try {
217
- const undici = await import('undici');
218
- remoteAgent = new undici.Agent({ connect: { autoSelectFamily: false } });
285
+ const { createRequire } = await import('node:module');
286
+ const { pathToFileURL } = await import('node:url');
287
+ const resolved = createRequire(pathToFileURL(entry).href).resolve('undici');
288
+ return await import(pathToFileURL(resolved).href);
219
289
  } catch {
220
- remoteAgent = null; // 没有 undici:退回全局 fetch
290
+ /* 换下一个锚点 */
221
291
  }
222
292
  }
223
- if (remoteAgent === null || remoteAgent === undefined) return fetch(url, init);
224
- const undici = await import('undici');
225
- return undici.fetch(url, { ...init, dispatcher: remoteAgent });
293
+ // ② 兜底:本插件自己的解析链(将来若把 undici 声明成依赖,这里会命中)。
294
+ try {
295
+ return await import('undici');
296
+ } catch {
297
+ return null;
298
+ }
226
299
  }
227
300
 
228
301
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-jiuling-remote-mobile",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "DSH 远程目录(手机端):在选文件夹流程里多出「电脑」页签,进入电脑目录、在电脑上发起对话,并把电脑的实时消息流同步到手机本地界面(只传文本,不映射桌面)。",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",