dsh-jiuling-remote-mobile 1.0.10 → 1.0.12

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
@@ -135,6 +135,12 @@ window.__ModuleLoader__.load({
135
135
  'flex:1 1 100%;min-width:0;' +
136
136
  'color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;',
137
137
  pairInput: 'width:140px;text-transform:uppercase;letter-spacing:2px;',
138
+ // 解绑二次确认那一行:提示文字占满整行,两个按钮跟在下面(手机上 360px
139
+ // 放不下「文字 + 两个按钮」一行,所以让文字 `flex:1 1 100%` 自己换行)。
140
+ pairConfirm:
141
+ 'display:flex;flex-wrap:wrap;align-items:center;gap:8px;' +
142
+ 'font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary);',
143
+ pairConfirmText: 'flex:1 1 100%;',
138
144
  pairMsg: 'padding:0 4px 8px;font-size:12px;line-height:18px;',
139
145
  pairOk: 'color:var(--dsw-alias-state-success-primary);',
140
146
  };
@@ -315,7 +321,15 @@ window.__ModuleLoader__.load({
315
321
  * `endpoint === null` 表示还没查过;`paired === false` 表示
316
322
  * 手机还没有任何凭据(令牌为空),需要用户去电脑上取一个配对码。
317
323
  */
318
- var pairPair = react.useState({ endpoint: null, paired: false, code: '', busy: false, message: null });
324
+ var pairPair = react.useState({
325
+ endpoint: null,
326
+ paired: false,
327
+ code: '',
328
+ busy: false,
329
+ // 「重新配对」的两步确认:`true` 时才显示「确定解绑 / 取消」。
330
+ confirming: false,
331
+ message: null,
332
+ });
319
333
  var pairState = pairPair[0];
320
334
  var setPairState = pairPair[1];
321
335
 
@@ -452,6 +466,95 @@ window.__ModuleLoader__.load({
452
466
  );
453
467
  };
454
468
 
469
+ /**
470
+ * 解除配对:清掉本地凭据,退回未配对状态。
471
+ *
472
+ * 成功后 `paired` 翻回 `false`,配对条自己就会换成输入框(同一个
473
+ * `pairState`),所以这里不用手工重挂组件。
474
+ */
475
+ var submitUnpair = function () {
476
+ if (pairState.busy) return;
477
+ patchPair({ busy: true, message: null });
478
+ bridge('unpair', {}).then(
479
+ function (result) {
480
+ if (!aliveRef.current) return;
481
+ patchPair({
482
+ busy: false,
483
+ confirming: false,
484
+ paired: false,
485
+ endpoint: result.endpoint,
486
+ code: '',
487
+ message: { kind: 'ok', text: '已解绑,请到电脑上生成新的配对码' },
488
+ });
489
+ patch({ path: null, listing: null });
490
+ },
491
+ function (reason) {
492
+ if (!aliveRef.current) return;
493
+ patchPair({
494
+ busy: false,
495
+ message: { kind: 'error', text: String((reason && reason.message) || reason) },
496
+ });
497
+ },
498
+ );
499
+ };
500
+
501
+ /**
502
+ * 已配对时的「重新配对」入口。
503
+ *
504
+ * ⚠️ **这是必需的回退路径,不是便利功能。** 电脑端一旦「解绑」,它会换掉
505
+ * 令牌并停掉隧道;而手机这边 `mobile.json` 里还留着旧令牌,`pairStatus`
506
+ * 会一直报「已配对」,于是输入框永远不渲染 —— 用户卡在 502,界面上无路
507
+ * 可走,只能手工删 `mobile.json` 或清 App 数据。补上这个入口,回退才闭环。
508
+ *
509
+ * 两步确认:解绑会停隧道、清远程会话清单,误触代价太大。
510
+ */
511
+ var unpairControls = function () {
512
+ if (pairState.confirming !== true) {
513
+ return h(
514
+ Button,
515
+ {
516
+ // ⚠️ 用 `outline` 而不是 `ghost`:ghost 在浅色面板上没有边框,
517
+ // 真机截图里「重新配对」看起来就是一行灰字,用户认不出可点 ——
518
+ // 而这是电脑解绑后唯一的回退入口,必须一眼看出来。
519
+ variant: 'outline',
520
+ size: 'sm',
521
+ onClick: function () {
522
+ patchPair({ confirming: true, message: null });
523
+ },
524
+ disabled: pairState.busy,
525
+ },
526
+ '重新配对',
527
+ );
528
+ }
529
+ return h(
530
+ 'span',
531
+ { style: CSS.pairConfirm },
532
+ h('span', { style: CSS.pairConfirmText }, '解绑后这台手机要重新输入配对码。'),
533
+ h(
534
+ Button,
535
+ {
536
+ variant: 'outline',
537
+ size: 'sm',
538
+ onClick: submitUnpair,
539
+ disabled: pairState.busy,
540
+ },
541
+ pairState.busy ? '解绑中…' : '确定解绑',
542
+ ),
543
+ h(
544
+ Button,
545
+ {
546
+ variant: 'ghost',
547
+ size: 'sm',
548
+ onClick: function () {
549
+ patchPair({ confirming: false });
550
+ },
551
+ disabled: pairState.busy,
552
+ },
553
+ '取消',
554
+ ),
555
+ );
556
+ };
557
+
455
558
  /**
456
559
  * 「电脑」页签顶部的配对条。
457
560
  *
@@ -475,7 +578,7 @@ window.__ModuleLoader__.load({
475
578
  '还没配对。请在电脑的「设置 → 手机远程」里生成 6 位配对码,填在这里。',
476
579
  ),
477
580
  pairState.paired === true
478
- ? null
581
+ ? unpairControls()
479
582
  : h(Input, {
480
583
  value: pairState.code,
481
584
  onChange: function (event) {
package/lib/index.js CHANGED
@@ -693,6 +693,9 @@ export function apply(ctx) {
693
693
  const result = await handleBridge(ctx, config, {
694
694
  remoteSessions,
695
695
  follow,
696
+ // 解绑要能停掉全部旧连接、清掉全部搬运状态(见 `unpair`)。
697
+ follows,
698
+ mirrorStates,
696
699
  persist: () => saveRemoteSessions(remoteSessions, ctx.logger),
697
700
  body,
698
701
  });
@@ -832,7 +835,7 @@ async function ensureDesktopWorkspace(ctx, desktopPath) {
832
835
  *
833
836
  * @param ctx - Cordis 上下文。
834
837
  * @param config - 远程配置。
835
- * @param io - `{ remoteSessions, follow, persist, body }`。
838
+ * @param io - `{ remoteSessions, follow, follows, mirrorStates, persist, body }`。
836
839
  */
837
840
  async function handleBridge(ctx, config, io) {
838
841
  const params = io.body?.params ?? {};
@@ -950,6 +953,43 @@ async function handleBridge(ctx, config, io) {
950
953
  // 那个常量已删(它是公开凭据,见 EMPTY_TOKEN 的注释)。
951
954
  return { endpoint: config.endpoint, paired: config.token !== EMPTY_TOKEN };
952
955
 
956
+ /**
957
+ * 解除配对:清掉本地凭据,退回未配对状态,让用户能重新填配对码。
958
+ *
959
+ * ⚠️ **这个方法是必需的回退路径,不是便利功能**。电脑端一旦「解绑」,
960
+ * 它会换掉令牌并停掉隧道;手机这边 `mobile.json` 里还留着旧令牌,
961
+ * `pairStatus` 就一直报「已配对」⇒ 前端那条「已配对」横幅永远挂着、
962
+ * **输入框根本不渲染**(见 client.js 里 `paired === true` 的分支)。
963
+ * 结果就是:电脑解绑后手机永久卡在 502,界面上没有任何重新配对的入口,
964
+ * 只能手工删 `mobile.json` 或清 App 数据。
965
+ *
966
+ * 清理分三部分,缺一不可:
967
+ * ① 凭据 —— `config` 就地清空 + 写盘,否则重启后旧令牌又回来了;
968
+ * ② 连接 —— 停掉所有跟随旧电脑的事件流,否则它们会拿着旧令牌
969
+ * 无限重连(退避到 15s 一次)一直打一个已经不属于自己的域名;
970
+ * ③ 会话归属 —— `remoteSessions` 里的会话原本靠「转发电脑」工作,
971
+ * 解绑后电脑那边可能已经不存在了,继续留着会让这些会话的 prompt
972
+ * 走 `callRemote` 报错。一并清掉,它们就退回普通本地会话。
973
+ *
974
+ * @returns 清空后的 `{ endpoint, paired: false }`。
975
+ */
976
+ case 'unpair': {
977
+ Object.assign(config, saveConfig(DEFAULT_ENDPOINT, EMPTY_TOKEN));
978
+ for (const controller of io.follows.values()) {
979
+ try {
980
+ controller.abort();
981
+ } catch {
982
+ /* 已经结束了 */
983
+ }
984
+ }
985
+ io.follows.clear();
986
+ io.mirrorStates.clear();
987
+ io.remoteSessions.clear();
988
+ io.persist();
989
+ ctx.logger?.info?.('[dsh-remote-mobile] 已解除配对,本地凭据与远程会话清单已清空');
990
+ return { endpoint: config.endpoint, paired: false };
991
+ }
992
+
953
993
  default:
954
994
  throw new Error(`unknown method "${String(io.body?.method)}"`);
955
995
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-jiuling-remote-mobile",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "DSH 远程目录(手机端):在选文件夹流程里多出「电脑」页签,进入电脑目录、在电脑上发起对话,并把电脑的实时消息流同步到手机本地界面(只传文本,不映射桌面)。",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",