dsh-jiuling-remote-mobile 1.0.9 → 1.0.10

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/client.js CHANGED
@@ -313,7 +313,7 @@ window.__ModuleLoader__.load({
313
313
  * 配对状态(与目录浏览无关,所以单独一个 state)。
314
314
  *
315
315
  * `endpoint === null` 表示还没查过;`paired === false` 表示
316
- * 手机现在用的还是内置的默认地址与令牌,需要用户去电脑上取一个配对码。
316
+ * 手机还没有任何凭据(令牌为空),需要用户去电脑上取一个配对码。
317
317
  */
318
318
  var pairPair = react.useState({ endpoint: null, paired: false, code: '', busy: false, message: null });
319
319
  var pairState = pairPair[0];
@@ -372,10 +372,9 @@ window.__ModuleLoader__.load({
372
372
  react.useEffect(
373
373
  function () {
374
374
  if (!open) return undefined;
375
- // ⚠️ **没配对就别去问电脑要目录**。未配对时 `listDesktop`
376
- // 会拿着内置的默认地址打过去,那台「电脑」并不存在 ——
377
- // 用户看到的是转圈 10 秒后一句「远程请求失败: HTTP 502」,
378
- // 完全不知道要先去配对。这里直接给出可操作的提示。
375
+ // ⚠️ **没配对就别去问电脑要目录**。未配对时手机没有任何凭据,
376
+ // 打过去必然是 401/502 —— 用户看到的是转圈 10 秒后一句
377
+ // 「远程请求失败」,完全不知道要先去配对。这里直接给出可操作的提示。
379
378
  if (tab === 'desktop' && pairState.paired !== true) {
380
379
  patch({ loading: false, listing: null, error: null });
381
380
  return undefined;
package/lib/index.js CHANGED
@@ -105,24 +105,41 @@ function saveConfig(endpoint, token) {
105
105
  }
106
106
 
107
107
  /**
108
- * 内置访问令牌 —— **必须与 `dsh-jiuling-remote-desktop` 里的内置值逐字相同**。
108
+ * 令牌为空 —— 表示**尚未配对**。
109
+ *
110
+ * ⚠️ 早期这里硬编码了一个「内置访问令牌」,让两端装完即连。那是真实漏洞:
111
+ * 常量随 npm 包公开,任何未配对的电脑都跑在**同一个公开令牌**上,谁都能连上去
112
+ * 读他的电脑(实测用包内公开令牌打公网隧道 → HTTP 200,读到 177 个目录条目)。
113
+ * 现在**包里不含任何凭据**:令牌只能由配对流程从服务器 claim 得到(见
114
+ * `claimPairing`),未配对时就是空字符串,`callRemote` / `follow` 会直接报
115
+ * 「尚未配对」,不会静默用一个假令牌去连。
116
+ */
117
+ const EMPTY_TOKEN = '';
118
+
119
+ /**
120
+ * **已作废**的旧公开令牌 —— 只用于识别,**不是凭据**。
109
121
  *
110
- * 两端同值 ⇒ 装完即可连通,用户不用手工同步任何东西。想隔离就改这个文件里的
111
- * `token` 字段,并把电脑端 `<DSH_HOME>/dsh-remote/token.txt` 改成一样的值。
122
+ * 1.0.9 及更早的版本把它当默认令牌写进 `mobile.json`。老用户升级后文件里还留着
123
+ * 这个值,若不识别就会被 `pairStatus` 判成「已配对」,而实际上电脑端早已改用随机
124
+ * 令牌 —— 症状是「显示已配对,一发消息就 401」。所以见到它就当成未配对。
112
125
  */
113
- const DEFAULT_TOKEN = 'f6721b0cf82414709df172056193c78a6c374e9a920e51145dd7883923cf16e7';
126
+ const LEGACY_PUBLIC_TOKEN = 'f6721b0cf82414709df172056193c78a6c374e9a920e51145dd7883923cf16e7';
114
127
 
115
128
  /**
116
129
  * 读配置。首次运行写一份模板,用户直接改文件即可。
117
130
  *
118
- * `token` 为空时退回内置值(与电脑端同值),所以**装完就能连**。
131
+ * ⚠️ 这里**没有**任何内置令牌兜底(早期有,是漏洞)。`token` 为空即「未配对」,
132
+ * 调用方据此直接拒绝,而不是拿一个包里公开的常量去连。
119
133
  *
120
134
  * @returns `{ endpoint, token }`。
121
135
  */
122
136
  function loadConfig() {
123
137
  try {
124
138
  const parsed = JSON.parse(readFileSync(CONFIG_PATH, 'utf8'));
125
- const token = typeof parsed.token === 'string' && parsed.token !== '' ? parsed.token : DEFAULT_TOKEN;
139
+ // ⚠️ 旧版写进文件的公开令牌要当成**未配对**,否则界面显示已配对、
140
+ // 实际一调用就 401(电脑端已换随机令牌)。
141
+ const raw = typeof parsed.token === 'string' ? parsed.token : EMPTY_TOKEN;
142
+ const token = raw === LEGACY_PUBLIC_TOKEN ? EMPTY_TOKEN : raw;
126
143
  return {
127
144
  endpoint:
128
145
  typeof parsed.endpoint === 'string' && parsed.endpoint !== ''
@@ -135,13 +152,13 @@ function loadConfig() {
135
152
  mkdirSync(STATE_DIR, { recursive: true });
136
153
  writeFileSync(
137
154
  CONFIG_PATH,
138
- JSON.stringify({ endpoint: DEFAULT_ENDPOINT, token: DEFAULT_TOKEN }, null, 2),
155
+ JSON.stringify({ endpoint: DEFAULT_ENDPOINT, token: EMPTY_TOKEN }, null, 2),
139
156
  { mode: 0o600 },
140
157
  );
141
158
  } catch {
142
159
  /* 只读环境:用默认值继续 */
143
160
  }
144
- return { endpoint: DEFAULT_ENDPOINT, token: DEFAULT_TOKEN };
161
+ return { endpoint: DEFAULT_ENDPOINT, token: EMPTY_TOKEN };
145
162
  }
146
163
  }
147
164
 
@@ -218,7 +235,8 @@ async function remoteFetch(url, init) {
218
235
  */
219
236
  async function callRemote(config, method, params = {}) {
220
237
  if (config.token === '') {
221
- throw new Error(`未配置访问令牌:请在 ${CONFIG_PATH} 里填 token(电脑端启动日志会打印它)`);
238
+ // 未配对 = 没有令牌。**不要**提示用户去手改文件:面向大众的主路径是配对。
239
+ throw new Error('还没配对:请在电脑的「设置 → 手机远程」里生成配对码,再到「添加工作区 → 电脑」填进去');
222
240
  }
223
241
  const res = await remoteFetch(`${config.endpoint}/api/dsh-remote/rpc`, {
224
242
  method: 'POST',
@@ -352,7 +370,7 @@ export function apply(ctx) {
352
370
  const config = loadConfig();
353
371
  ctx.logger?.info?.(`[dsh-remote-mobile] 远端: ${config.endpoint}`);
354
372
  if (config.token === '') {
355
- ctx.logger?.warn?.(`[dsh-remote-mobile] 未配置令牌,请在 ${CONFIG_PATH} 里填 token`);
373
+ ctx.logger?.info?.('[dsh-remote-mobile] 尚未配对:请到「添加工作区 → 电脑」填配对码(配对后自动连接)');
356
374
  }
357
375
 
358
376
  /** 远程会话 id 集合:这些会话的 prompt 转发电脑,不在手机本地跑模型。 */
@@ -838,7 +856,7 @@ async function handleBridge(ctx, config, io) {
838
856
  throw new Error('请先在电脑页签里选一个目录');
839
857
  }
840
858
  if (config.token === '') {
841
- throw new Error(`未配置访问令牌:请在 ${CONFIG_PATH} 里填 token`);
859
+ throw new Error('还没配对:请先在「电脑」页签里填配对码');
842
860
  }
843
861
  const sessionId = `session-${randomUUID()}`;
844
862
  // ⚠️ 手机端 cwd 必须是**手机本地**的绝对路径,绝不把电脑路径传进来
@@ -928,7 +946,9 @@ async function handleBridge(ctx, config, io) {
928
946
 
929
947
  /** 当前连的是哪台电脑(前端用来显示状态)。 */
930
948
  case 'pairStatus':
931
- return { endpoint: config.endpoint, paired: config.token !== DEFAULT_TOKEN };
949
+ // 「已配对」的判据是**有令牌**。早期拿「令牌不等于内置常量」当判据,
950
+ // 那个常量已删(它是公开凭据,见 EMPTY_TOKEN 的注释)。
951
+ return { endpoint: config.endpoint, paired: config.token !== EMPTY_TOKEN };
932
952
 
933
953
  default:
934
954
  throw new Error(`unknown method "${String(io.body?.method)}"`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-jiuling-remote-mobile",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "DSH 远程目录(手机端):在选文件夹流程里多出「电脑」页签,进入电脑目录、在电脑上发起对话,并把电脑的实时消息流同步到手机本地界面(只传文本,不映射桌面)。",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",