dsh-bridge-gateway 0.2.1 → 0.3.0

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 (45) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/README.md +25 -3
  3. package/README.zh.md +35 -2
  4. package/assets/apple-touch-icon-180.png +0 -0
  5. package/assets/icon-192.png +0 -0
  6. package/assets/icon-512.png +0 -0
  7. package/assets/icon-maskable-192.png +0 -0
  8. package/assets/icon-maskable-512.png +0 -0
  9. package/client/client.js +302 -41
  10. package/client/index.js +438 -42
  11. package/docs/banner.jpg +0 -0
  12. package/docs/custom-tunnel.md +5 -5
  13. package/docs/platform-abstraction-design.md +1 -1
  14. package/docs/qq-usage.md +6 -6
  15. package/docs/screenshots/remote-auth-login-light.jpg +0 -0
  16. package/docs/screenshots/remote-auth-login.jpg +0 -0
  17. package/docs/wechat-usage.md +1 -1
  18. package/lib/auth/brand-assets.js +131 -0
  19. package/lib/auth/login-template.js +503 -361
  20. package/lib/auth/manager.js +47 -12
  21. package/lib/auth/theme-tokens.js +142 -0
  22. package/lib/bridge-rpc.js +2 -2
  23. package/lib/cloudflared-manager.mjs +1 -1
  24. package/lib/feishu/gateway.js +17 -17
  25. package/lib/feishu/index.js +3 -3
  26. package/lib/feishu/lark-bundled.mjs +284 -446
  27. package/lib/feishu/node.js +7 -7
  28. package/lib/gateway/cert.js +1 -1
  29. package/lib/index.js +289 -80
  30. package/lib/paths.js +43 -14
  31. package/lib/platform/base.js +2 -2
  32. package/lib/platform/conversation-bridge.js +17 -17
  33. package/lib/platform/index.js +1 -1
  34. package/lib/platform/manager.js +4 -4
  35. package/lib/qq/gateway.js +10 -10
  36. package/lib/qq/index.js +11 -11
  37. package/lib/qq/node.js +17 -17
  38. package/lib/telegram/gateway.js +10 -10
  39. package/lib/telegram/index.js +3 -3
  40. package/lib/telegram/node.js +5 -5
  41. package/lib/wechat/gateway.js +6 -6
  42. package/lib/wechat/index.js +4 -4
  43. package/lib/wechat/media.js +1 -1
  44. package/lib/wechat/node.js +13 -13
  45. package/package.json +6 -3
package/client/client.js CHANGED
@@ -2639,7 +2639,7 @@ function BackupRestoreWidget({ rpcCall, onUpdate }) {
2639
2639
  const now = /* @__PURE__ */ new Date();
2640
2640
  const dateStr = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}`;
2641
2641
  a.href = url;
2642
- a.download = `dsh-bridge-backup-${dateStr}.json`;
2642
+ a.download = `dsh-bridge-gateway-backup-${dateStr}.json`;
2643
2643
  document.body.appendChild(a);
2644
2644
  a.click();
2645
2645
  document.body.removeChild(a);
@@ -3935,11 +3935,165 @@ function BridgePanel({ rpcCall }) {
3935
3935
  )
3936
3936
  );
3937
3937
  }
3938
+ function setupMobileDiag() {
3939
+ if (document.getElementById("dsh-mobile-diag")) return;
3940
+ const box = document.createElement("div");
3941
+ box.id = "dsh-mobile-diag";
3942
+ box.style.cssText = [
3943
+ "position:fixed",
3944
+ "left:0",
3945
+ "right:0",
3946
+ "top:0",
3947
+ "z-index:2147483647",
3948
+ "background:rgba(0,0,0,.86)",
3949
+ "color:#0f0",
3950
+ "pointer-events:none",
3951
+ "font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace",
3952
+ "padding:5px 7px",
3953
+ "white-space:pre-wrap",
3954
+ "word-break:break-all"
3955
+ ].join(";");
3956
+ document.body.appendChild(box);
3957
+ const tick = () => {
3958
+ const sb = document.querySelector('[class*="wSkVaW_scrollBody"]');
3959
+ const seat = document.querySelector('[class*="wSkVaW_composerSeat"]');
3960
+ const vv = window.visualViewport;
3961
+ const L = [];
3962
+ if (sb) {
3963
+ const r = sb.getBoundingClientRect();
3964
+ const max = sb.scrollHeight - sb.clientHeight;
3965
+ L.push("scrollBody top=" + Math.round(r.top) + " h=" + Math.round(r.height) + " pad=" + getComputedStyle(sb).paddingBottom);
3966
+ L.push(" scrollTop=" + Math.round(sb.scrollTop) + " max=" + max + " atBottom=" + (sb.scrollTop >= max - 2));
3967
+ L.push(" scrollH=" + sb.scrollHeight + " clientH=" + sb.clientHeight + " children=" + sb.children.length);
3968
+ const idx = sb.querySelectorAll("[data-index]");
3969
+ L.push(" virtItems=" + idx.length);
3970
+ } else {
3971
+ L.push("scrollBody: NOT FOUND");
3972
+ }
3973
+ if (seat) {
3974
+ const r = seat.getBoundingClientRect();
3975
+ L.push("composer pos=" + getComputedStyle(seat).position + " top=" + Math.round(r.top) + " h=" + Math.round(r.height));
3976
+ }
3977
+ L.push("inner=" + window.innerHeight + " vv=" + (vv ? Math.round(vv.height) : "-") + " vvTop=" + (vv ? Math.round(vv.offsetTop) : "-") + " dpr=" + window.devicePixelRatio + " w=" + window.innerWidth);
3978
+ L.push("body=" + Array.from(document.body.classList).join("."));
3979
+ box.textContent = L.join("\n");
3980
+ };
3981
+ tick();
3982
+ window.setInterval(tick, 500);
3983
+ window.addEventListener("scroll", tick, true);
3984
+ window.addEventListener("resize", tick);
3985
+ }
3986
+ var SUSPECT_CSS = {
3987
+ // _frame 规则 —— 真机二分结论(2026-10-17):
3988
+ // ?dsh-mobile=no-frameSize (去掉 width:100vw + height:100dvh)→ scrollH 恢复正常
3989
+ // ?dsh-mobile=no-frameOverflow(去掉 overflow:hidden) → scrollH 恢复正常
3990
+ // 两个属性各自都能触发,说明是「固定高度 + 裁剪」这个组合在作怪:
3991
+ // height:100dvh 把主框架钉成固定高、overflow:hidden 又让它成为裁剪容器,
3992
+ // DSH 的布局测量因此算错,导致只渲染约 4 条消息(scrollH≈795,正常应 10 万+)。
3993
+ // 故**这两个属性组已删除**,不再注入。只保留 padding-top(本规则的目的:
3994
+ // 为固定顶栏腾出空间)与 display/flex-direction/position(抽屉化的前提)。
3995
+ // 注意 grid-template-columns:1fr 也已去掉 —— 它是 grid 属性,在 flex 容器里无效。
3996
+ // padding-top / box-sizing 是这条规则的**目的**(为固定顶栏腾空间),任何分组都保留
3997
+ frameBase: `
3998
+ div[class*="_frame"] {
3999
+ padding-top: var(--dsh-mobile-header-h) !important;
4000
+ margin: 0 !important;
4001
+ box-sizing: border-box !important;
4002
+ }`,
4003
+ frameLayout: `
4004
+ div[class*="_frame"] {
4005
+ display: flex !important;
4006
+ flex-direction: column !important;
4007
+ position: relative !important;
4008
+ }`,
4009
+ centercol: ` div[class*="_centerCol"] {
4010
+ flex: 1 1 100% !important;
4011
+ width: 100% !important;
4012
+ min-width: 0 !important;
4013
+ max-width: 100% !important;
4014
+ display: flex !important;
4015
+ height: 100% !important;
4016
+ }`,
4017
+ pad: ` div[class*="wSkVaW_scrollBody"] {
4018
+ padding-bottom: max(16px, env(safe-area-inset-bottom)) !important;
4019
+ }
4020
+ div[class*="wSkVaW_scrollBody"] {
4021
+ padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px)) !important;
4022
+ }`
4023
+ };
4024
+ function injectSuspectStyles(disabled) {
4025
+ const parts = Object.entries(SUSPECT_CSS).filter(([k]) => !disabled.has(k)).map(([, v]) => v);
4026
+ if (parts.length === 0) return;
4027
+ if (document.getElementById("dsh-bridge-gateway-mobile-styles-suspect")) return;
4028
+ const style = document.createElement("style");
4029
+ style.id = "dsh-bridge-gateway-mobile-styles-suspect";
4030
+ style.textContent = "@media (max-width: 768px) {\n" + parts.join("\n") + "\n}";
4031
+ document.head.appendChild(style);
4032
+ }
4033
+ var NEW_MOBILE_CSS = `
4034
+ /* #2 \u9762\u677F\u6253\u5F00\u65F6\u9876\u680F\u8BA9\u4F4D\uFF08body.dsh-panel-open \u7531 setupPanelAwareness \u6253\u4E0A\uFF09\u3002
4035
+ \u5B9E\u6D4B\uFF1A\u6E20\u9053\u4E2D\u5FC3\u9762\u677F\u6807\u9898\u5728 y19\uFF0C\u800C\u9876\u680F\u5360 y0-40 \u2192 \u4E0D\u6539\u5C31\u662F\u6807\u9898\u88AB\u76D6\u4F4F\u3002
4036
+ \u4E0D\u7528 :has() \u662F\u56E0\u4E3A\u6784\u5EFA\u76EE\u6807\u662F chrome100\uFF08:has() \u8981 105+\uFF09\u3002 */
4037
+ body.dsh-panel-open .dsh-mobile-app-header {
4038
+ display: none !important;
4039
+ }
4040
+
4041
+ /* #6 \u53CC\u5C42\u5934\u538B\u7F29\uFF1ADSH \u81EA\u5E26 min-height:76px\uFF0C\u53EA\u6539 padding \u538B\u4E0D\u4E0B\u53BB \u2014\u2014 \u5B9E\u6D4B
4042
+ 76px \u4E00\u76F4\u751F\u6548\u3001titleRow \u88AB\u62C9\u4F38\u586B\u6EE1\uFF0869px\uFF09\uFF0C\u4E24\u5C42\u5934\u5408\u8BA1\u5403\u6389 128px\uFF08\u5C4F\u5E55 15%\uFF09\u3002
4043
+ \u663E\u5F0F\u6E05\u96F6 + \u628A padding \u6536\u5230 2px\uFF0C\u5B9E\u6D4B\u5185\u5BB9\u533A +51px\u3002 */
4044
+ div[class*="_centerCol"] header,
4045
+ header[class*="wSkVaW_header"] {
4046
+ min-height: 0 !important;
4047
+ padding: 2px 16px !important;
4048
+ }
4049
+ div[class*="wSkVaW_titleRow"] {
4050
+ min-height: 0 !important;
4051
+ height: 32px !important;
4052
+ }
4053
+
4054
+ /* #4-C \u8868\u683C\u6A2A\u5411\u6EDA\u52A8\uFF1Atable \u5143\u7D20\u7684 overflow \u5728 CSS \u91CC\u662F**\u88AB\u5FFD\u7565**\u7684\uFF0C\u5FC5\u987B\u914D
4055
+ display:block \u624D\u4F1A\u53D8\u6210\u53EF\u6EDA\u5757\u76D2\uFF08\u63D2\u4EF6\u539F\u6765\u53EA\u5199\u4E86 overflow-x:auto\uFF0C\u6240\u4EE5\u65E0\u6548\uFF09\u3002 */
4056
+ div[class*="wSkVaW_scrollBody"] table {
4057
+ display: block !important;
4058
+ max-width: 100% !important;
4059
+ width: 100% !important;
4060
+ overflow-x: auto !important;
4061
+ -webkit-overflow-scrolling: touch !important;
4062
+ }
4063
+
4064
+ /* #4-E \u8F68\u8FF9\u91CC\u7684\u5DE5\u5177\u8C03\u7528\u53C2\u6570\u6362\u884C\uFF1A\u8BE5\u5217\u5185\u5BB9\u5BBD 3049px \u5374\u53EA\u6709 180px \u53EF\u89C1\uFF0817\xD7\uFF09\uFF0C
4065
+ \u6A2A\u6ED1\u8981\u5212 17 \u5C4F\u3001\u5B9E\u9645\u8BFB\u4E0D\u4E86\u3002overflow-wrap:anywhere \u662F\u5FC5\u987B\u7684 \u2014\u2014 bash \u547D\u4EE4\u91CC
4066
+ \u5E38\u6709\u8D85\u957F\u65E0\u7A7A\u683C token\u3002 */
4067
+ div[class*="wSkVaW_scrollBody"] [class*="resultRequest"] {
4068
+ white-space: normal !important;
4069
+ overflow-wrap: anywhere !important;
4070
+ word-break: break-word !important;
4071
+ text-overflow: clip !important;
4072
+ overflow: visible !important;
4073
+ }
4074
+
4075
+ /* #7 \u4E0B\u62C9\u6539 body \u7EA7 portal\uFF08position:fixed\uFF09\u3002\u4E3A\u4EC0\u4E48\u4E0D\u80FD absolute \u6302\u5728\u6309\u94AE\u91CC\uFF1A
4076
+ \u7956\u5148\u94FE titleRow \u4E0E wSkVaW_root \u90FD\u662F overflow:hidden\uFF08titleRow \u90A3\u6761\u8FD8\u662F\u63D2\u4EF6
4077
+ \u81EA\u5DF1\u4E3A\u4E86\u7701\u7565\u53F7\u8BBE\u7684\uFF09\u2192 \u4E0B\u62C9\u843D\u5728\u5934\u90E8\u4E0B\u65B9 y78 \u4F1A\u88AB**\u6574\u5757\u88C1\u6389**\uFF0C\u5B9E\u6D4B\u4E2D\u5FC3\u70B9\u547D\u4E2D
4078
+ \u7684\u662F\u6D88\u606F\u6587\u672C\u3002z-index \u518D\u9AD8\u4E5F\u6CA1\u7528\uFF0C\u88C1\u526A\u53D1\u751F\u5728\u5C42\u53E0\u4E4B\u524D\u3002
4079
+ \u6CE8\u610F\uFF1A**\u4E0D\u80FD**\u5199 top/left \u7684 !important \u2014\u2014 \u90A3\u4F1A\u538B\u8FC7 positionTabDropdown()
4080
+ \u5199\u7684\u5185\u8054\u5750\u6807\uFF0C\u628A\u4E0B\u62C9\u9489\u5728\u5DE6\u4E0A\u89D2\u3002 */
4081
+ .dsh-mobile-tab-dropdown {
4082
+ position: fixed !important;
4083
+ }
4084
+ `;
4085
+ function injectNewMobileStyles() {
4086
+ if (document.getElementById("dsh-bridge-gateway-mobile-styles-new")) return;
4087
+ const style = document.createElement("style");
4088
+ style.id = "dsh-bridge-gateway-mobile-styles-new";
4089
+ style.textContent = "@media (max-width: 768px) {\n" + NEW_MOBILE_CSS + "\n}";
4090
+ document.head.appendChild(style);
4091
+ }
3938
4092
  function injectMobileStyles() {
3939
4093
  if (typeof document === "undefined") return;
3940
- if (document.getElementById("dsh-bridge-mobile-styles")) return;
4094
+ if (document.getElementById("dsh-bridge-gateway-mobile-styles")) return;
3941
4095
  const style = document.createElement("style");
3942
- style.id = "dsh-bridge-mobile-styles";
4096
+ style.id = "dsh-bridge-gateway-mobile-styles";
3943
4097
  style.textContent = `
3944
4098
  /* DSH Bridge \u9690\u85CF Tab \u680F\u539F\u751F\u6EDA\u52A8\u6761\u5E76\u4FDD\u6301\u5E73\u6ED1\u6ED1\u52A8 */
3945
4099
  .dsh-tabbar-container {
@@ -3954,25 +4108,16 @@ function injectMobileStyles() {
3954
4108
 
3955
4109
  /* DSH Bridge \u79FB\u52A8\u7AEF\u81EA\u9002\u5E94\u4E0E\u89E6\u63A7\u4EA4\u4E92\u589E\u5F3A\u6837\u5F0F */
3956
4110
  :root {
3957
- --dsh-mobile-header-h: 52px;
4111
+ /* \u9876\u680F\u9AD8\u5EA6 52 -> 40\uFF082026-10-16 \u771F\u673A\u53CD\u9988\uFF1A"\u4E0A\u9762\u9AD8\u5EA6\u592A\u9AD8\u4E86\uFF0C\u628A\u5BF9\u8BDD\u6846\u7684\u7A7A\u95F4\u538B\u7F29\u4E86"\uFF09\u3002
4112
+ 40px \u4ECD\u80FD\u5BB9\u7EB3 32px \u7684 \u2630/\u2295 \u6309\u94AE\u4E0E\u4E00\u884C\u4F1A\u8BDD\u540D\u3002 */
4113
+ --dsh-mobile-header-h: 40px;
3958
4114
  --dsh-mobile-safe-top: env(safe-area-inset-top, 0px);
3959
4115
  --dsh-mobile-safe-bottom: env(safe-area-inset-bottom, 0px);
3960
4116
  }
3961
4117
 
3962
4118
  @media (max-width: 768px) {
3963
4119
  /* 1. \u4E3B\u6846\u67B6\u4E3A Header \u817E\u51FA\u9876\u90E8\u7A7A\u95F4 */
3964
- div[class*="_frame"] {
3965
- display: flex !important;
3966
- flex-direction: column !important;
3967
- width: 100vw !important;
3968
- height: 100dvh !important;
3969
- margin: 0 !important;
3970
- padding-top: var(--dsh-mobile-header-h) !important;
3971
- position: relative !important;
3972
- grid-template-columns: 1fr !important;
3973
- overflow: hidden !important;
3974
- box-sizing: border-box !important;
3975
- }
4120
+
3976
4121
 
3977
4122
  /* 2. \u9876\u90E8\u539F\u751F\u5BFC\u822A\u6761\uFF1A100% \u8FD8\u539F DeepSeek App (\u5DE6\u4FA7\u53CC\u6A2A\u7EBF\uFF0C\u53F3\u4FA7(+)\uFF0C\u4E2D\u95F4\u7559\u767D\uFF0C\u65E0\u591A\u4F59\u8BBE\u7F6E\u6309\u94AE) */
3978
4123
  .dsh-mobile-app-header {
@@ -4052,14 +4197,7 @@ function injectMobileStyles() {
4052
4197
  }
4053
4198
 
4054
4199
  /* 3. \u4E2D\u95F4\u4E3B\u5185\u5BB9\u533A\u4E0E\u8F93\u5165\u6846 */
4055
- div[class*="_centerCol"] {
4056
- flex: 1 1 100% !important;
4057
- width: 100% !important;
4058
- min-width: 0 !important;
4059
- max-width: 100% !important;
4060
- display: flex !important;
4061
- height: 100% !important;
4062
- }
4200
+
4063
4201
 
4064
4202
  div[class*="_detailsCol"],
4065
4203
  div[class*="toggleCluster"],
@@ -4450,9 +4588,7 @@ function injectMobileStyles() {
4450
4588
  }
4451
4589
 
4452
4590
  /* \u8F93\u5165\u6846\u5E95\u5EA7\uFF1ADeepSeek App \u5C45\u4E2D\u53CA\u5E95\u90E8\u56FA\u5B9A */
4453
- div[class*="wSkVaW_scrollBody"] {
4454
- padding-bottom: max(16px, env(safe-area-inset-bottom)) !important;
4455
- }
4591
+
4456
4592
 
4457
4593
  /* \u8F93\u5165\u5361\u7247\uFF1ADeepSeek App \u5706\u89D2\u5927\u80F6\u56CA\u9020\u578B */
4458
4594
  div[class*="uV2eYG_card"] {
@@ -4646,11 +4782,20 @@ function injectMobileStyles() {
4646
4782
  }
4647
4783
 
4648
4784
  /* \u8F93\u5165\u6846\u8D34\u5E95\uFF1AscrollBody \u7684 16px \u5E95\u90E8\u886C\u8DDD\u662F\u7A7A\u9699\u6765\u6E90\uFF0C\u6536\u6389\u53EA\u7559\u5B89\u5168\u533A\uFF1B
4649
- \u518D\u57AB 6px \u8BA9\u7EDF\u8BA1\u836F\u4E38\u5B8C\u6574\u53EF\u89C1 */
4650
- div[class*="wSkVaW_scrollBody"] {
4651
- padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px)) !important;
4652
- }
4653
- div[class*="wSkVaW_composerStack"] {
4785
+ \u518D\u57AB 6px \u8BA9\u7EDF\u8BA1\u836F\u4E38\u5B8C\u6574\u53EF\u89C1\u3002
4786
+
4787
+ \u26A0\uFE0F **\u4E0D\u8981**\u518D\u8BD5\u56FE\u5728\u8FD9\u91CC\u52A0 --dsh-composer-height\u3001\u4E5F\u4E0D\u8981\u7ED9 composerSeat \u52A0
4788
+ position:fixed\uFF082026-10-16 \u4E24\u6B21\u5C1D\u8BD5\uFF0C\u5747\u5DF2\u56DE\u9000\uFF09\u3002\u539F\u56E0\uFF1ADSH \u7684\u6EDA\u52A8\u951A\u5B9A
4789
+ \uFF08dsh-client-ui-conversation\uFF09\u662F**\u6309 DOM \u6D41\u5185\u4F4D\u7F6E**\u904D\u5386\u7956\u5148\u7B97\u504F\u79FB\u6765\u4FEE\u6B63
4790
+ scrollTop \u7684\uFF1B\u628A composer \u6458\u51FA\u6587\u6863\u6D41\u3001\u6216\u7ED9\u6EDA\u52A8\u5185\u5BB9\u52A0 padding\uFF0C\u951A\u5B9A\u5C31\u7B97\u9519\uFF0C
4791
+ \u771F\u673A\u75C7\u72B6\u662F\u300C\u8FDB\u53BB\u505C\u5728\u5F88\u4E45\u4EE5\u524D\u7684\u67D0\u6761\uFF0C\u70B9\u300E\u52A0\u8F7D\u66F4\u65E9\u300F\u540E\u518D\u4E5F\u56DE\u4E0D\u5230\u6700\u65B0\u300D\u3002
4792
+ \u8FD9\u4E2A\u906E\u6321\u662F DSH \u5BF9\u8BDD\u89C6\u56FE sticky \u5438\u5E95\u8BBE\u8BA1\u7684\u56FA\u6709\u4EE3\u4EF7\uFF1BDSH \u53E6\u6709"\u8986\u76D6\u5C42\u6A21\u5F0F"
4793
+ \uFF08:has([data-conversation-composer-overlay])\uFF1AcomposerSeat \u8F6C absolute\u3001
4794
+ viewArea \u81EA\u5DF1\u6EDA\uFF09\uFF0C\u4F46\u90A3\u662F **dsh-client-ui-trajectory\uFF08\u8F68\u8FF9\u89C6\u56FE\uFF09** \u5728\u7528\u7684\uFF0C
4795
+ \u5BF9\u8BDD\u89C6\u56FE\u6CA1\u6709\u5F00\u3002\u8981\u6839\u6CBB\u5F97\u8D70 DSH \u4FA7\uFF0C\u4E0D\u662F\u63D2\u4EF6 CSS \u80FD\u8865\u7684\u3002 */
4796
+
4797
+
4798
+ div[class*="wSkVaW_composerStack"] {
4654
4799
  padding-bottom: env(safe-area-inset-bottom, 0px) !important;
4655
4800
  }
4656
4801
 
@@ -4957,15 +5102,15 @@ function injectMobileStyles() {
4957
5102
  box-sizing: border-box !important;
4958
5103
  }
4959
5104
 
4960
- /* 7. \u4EE3\u7801\u5757\u3001\u8868\u683C\u4E0E\u5FBD\u6807\u81EA\u9002\u5E94 */
4961
- pre, code, pre > code, table {
5105
+ /* 7. \u4EE3\u7801\u5757\u4E0E\u5FBD\u6807\u81EA\u9002\u5E94 */
5106
+ pre, code, pre > code {
4962
5107
  max-width: 100% !important;
4963
5108
  overflow-x: auto !important;
4964
5109
  -webkit-overflow-scrolling: touch !important;
4965
5110
  font-size: 12.5px !important;
4966
5111
  }
4967
5112
 
4968
- /* \u72B6\u6001\u5FBD\u6807\u4E0E\u836F\u4E38\u6309\u94AE\u6C38\u4E0D\u6298\u5B57 */
5113
+ /* \u72B6\u6001\u5FBD\u6807\u4E0E\u836F\u4E38\u6309\u94AE\u6C38\u4E0D\u6298\u5B57 */
4969
5114
  span[style*="border-radius: 999"],
4970
5115
  span[style*="border-radius:999"] {
4971
5116
  white-space: nowrap !important;
@@ -5181,10 +5326,82 @@ function createUpstreamBridge(ctx) {
5181
5326
  }
5182
5327
  };
5183
5328
  }
5329
+ function setupPanelAwareness(ctx) {
5330
+ if (typeof document === "undefined") return () => {
5331
+ };
5332
+ const PANEL_SELECTORS = [
5333
+ ".dshc-center-backdrop",
5334
+ // dsh-chanhub 渠道中心
5335
+ "[data-dsh-panel-host]",
5336
+ // dsh-better-sidebar(注意:常驻 DOM,需判可见)
5337
+ "[data-dsh-better-sidebar]"
5338
+ ];
5339
+ const isPanelOpen = () => PANEL_SELECTORS.some((sel) => {
5340
+ const el = document.querySelector(sel);
5341
+ if (!el) return false;
5342
+ const cs = getComputedStyle(el);
5343
+ if (cs.display === "none" || cs.visibility === "hidden") return false;
5344
+ const r = el.getBoundingClientRect();
5345
+ return r.width > 0 && r.height > 0;
5346
+ });
5347
+ const sync = () => {
5348
+ const open = isPanelOpen();
5349
+ if (document.body.classList.contains("dsh-panel-open") !== open) {
5350
+ document.body.classList.toggle("dsh-panel-open", open);
5351
+ }
5352
+ };
5353
+ sync();
5354
+ const observer = new MutationObserver(sync);
5355
+ observer.observe(document.body, { childList: true });
5356
+ const timer = setInterval(sync, 1e3);
5357
+ const dispose = () => {
5358
+ observer.disconnect();
5359
+ clearInterval(timer);
5360
+ };
5361
+ ctx?.effect?.(() => dispose);
5362
+ return dispose;
5363
+ }
5184
5364
  function setupMobileExperience(rpcCall, ctx) {
5185
5365
  if (typeof document === "undefined" || typeof window === "undefined") return;
5186
- injectMobileStyles();
5366
+ const MOBILE_FLAG_KEY = "dsh-bridge-gateway:mobile";
5367
+ const flag = new URLSearchParams(window.location.search).get("dsh-mobile");
5368
+ if (flag !== null) {
5369
+ try {
5370
+ window.localStorage.setItem(MOBILE_FLAG_KEY, flag);
5371
+ } catch {
5372
+ }
5373
+ }
5374
+ let mobileFlag = "on";
5375
+ try {
5376
+ mobileFlag = window.localStorage.getItem(MOBILE_FLAG_KEY) || "on";
5377
+ } catch {
5378
+ }
5379
+ document.documentElement.dataset.dshMobile = mobileFlag;
5380
+ const diagOn = new URLSearchParams(window.location.search).get("dsh-diag") === "1";
5381
+ if (diagOn) window.setTimeout(setupMobileDiag, 1500);
5382
+ const flagSet = new Set(String(mobileFlag).split(",").map((x) => x.trim()).filter(Boolean));
5383
+ const disabledSuspects = new Set(
5384
+ [...flagSet].filter((f) => f.startsWith("no-")).map((f) => f.slice(3))
5385
+ );
5386
+ if (disabledSuspects.has("frame")) {
5387
+ for (const k of ["frameLayout", "frameSize", "frameOverflow"]) disabledSuspects.add(k);
5388
+ }
5389
+ const withCss = !flagSet.has("off") && !flagSet.has("no-css");
5390
+ const withJs = !flagSet.has("off") && !flagSet.has("no-js");
5391
+ const withNew = withCss && !flagSet.has("no-new");
5392
+ const MOBILE_NO_OBSERVER = flagSet.has("no-observer");
5393
+ if (!withCss && !withJs) {
5394
+ if (diagOn) window.setTimeout(setupMobileDiag, 1500);
5395
+ return;
5396
+ }
5397
+ if (withCss) {
5398
+ injectMobileStyles();
5399
+ injectSuspectStyles(disabledSuspects);
5400
+ }
5401
+ if (withNew) injectNewMobileStyles();
5402
+ if (!withJs) return;
5187
5403
  const U = createUpstreamBridge(ctx);
5404
+ setupPanelAwareness(ctx);
5188
5405
  let header = document.querySelector(".dsh-mobile-app-header");
5189
5406
  let titleEl = document.querySelector(".dsh-mobile-header-title");
5190
5407
  if (!header) {
@@ -5229,17 +5446,39 @@ function setupMobileExperience(rpcCall, ctx) {
5229
5446
  header.appendChild(rightBtn);
5230
5447
  document.body.appendChild(header);
5231
5448
  }
5449
+ if (header && !header.dataset.dshVisibilityBound) {
5450
+ header.dataset.dshVisibilityBound = "1";
5451
+ header.style.display = "none";
5452
+ try {
5453
+ const mq = window.matchMedia("(max-width: 768px)");
5454
+ const syncHeaderVisibility = () => {
5455
+ header.style.display = mq.matches ? "" : "none";
5456
+ };
5457
+ syncHeaderVisibility();
5458
+ if (typeof mq.addEventListener === "function") mq.addEventListener("change", syncHeaderVisibility);
5459
+ else if (typeof mq.addListener === "function") mq.addListener(syncHeaderVisibility);
5460
+ } catch {
5461
+ }
5462
+ }
5463
+ const pickCurrentSession = (snap) => {
5464
+ const list = snap?.byId ? Object.values(snap.byId) : [];
5465
+ if (!list.length) return null;
5466
+ const retained = list.find((x) => (x?.retainedBy?.mainView ?? 0) > 0);
5467
+ if (retained) return retained;
5468
+ return list.find((x) => !x?.blank) || null;
5469
+ };
5232
5470
  const syncMobileTitle = () => {
5233
5471
  if (!titleEl) titleEl = document.querySelector(".dsh-mobile-header-title");
5234
5472
  if (!titleEl) return;
5235
5473
  const snap = ctx?.sessions?.list?.getSnapshot?.();
5236
5474
  if (!snap) return;
5237
- const cur = snap.current ? snap.byId?.[snap.current] : null;
5475
+ const cur = pickCurrentSession(snap);
5238
5476
  if (!cur || cur.blank) {
5239
5477
  titleEl.innerText = "\u65B0\u4F1A\u8BDD";
5240
5478
  } else {
5241
5479
  titleEl.innerText = cur.displayTitle || cur.title || "\u4F1A\u8BDD";
5242
5480
  }
5481
+ titleEl.title = titleEl.innerText;
5243
5482
  };
5244
5483
  syncMobileTitle();
5245
5484
  if (typeof ctx?.sessions?.list?.subscribe === "function") {
@@ -5279,9 +5518,21 @@ function setupMobileExperience(rpcCall, ctx) {
5279
5518
  }
5280
5519
  });
5281
5520
  };
5282
- const panelObserver = new MutationObserver(ensurePanelCloseButton);
5283
- panelObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"] });
5284
- window.addEventListener("resize", ensurePanelCloseButton);
5521
+ let panelCloseRaf = 0;
5522
+ const schedulePanelCloseButton = () => {
5523
+ if (panelCloseRaf) return;
5524
+ panelCloseRaf = window.requestAnimationFrame(() => {
5525
+ panelCloseRaf = 0;
5526
+ ensurePanelCloseButton();
5527
+ });
5528
+ };
5529
+ if (MOBILE_NO_OBSERVER) {
5530
+ document.documentElement.dataset.dshMobileObserver = "off";
5531
+ } else {
5532
+ const panelObserver = new MutationObserver(schedulePanelCloseButton);
5533
+ panelObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"] });
5534
+ }
5535
+ window.addEventListener("resize", schedulePanelCloseButton);
5285
5536
  const mobileTabState = { btn: null, dropdown: null };
5286
5537
  const nativeTabs = () => [...document.querySelectorAll('[role="tablist"] [role="tab"]')];
5287
5538
  const closeTabDropdown = () => {
@@ -5290,6 +5541,14 @@ function setupMobileExperience(rpcCall, ctx) {
5290
5541
  mobileTabState.dropdown = null;
5291
5542
  }
5292
5543
  };
5544
+ const positionTabDropdown = () => {
5545
+ const dd = mobileTabState.dropdown;
5546
+ const btn = mobileTabState.btn;
5547
+ if (!dd || !btn || !btn.isConnected) return;
5548
+ const r = btn.getBoundingClientRect();
5549
+ dd.style.top = Math.round(r.bottom + 6) + "px";
5550
+ dd.style.left = Math.round(r.left) + "px";
5551
+ };
5293
5552
  const renderTabDropdown = () => {
5294
5553
  closeTabDropdown();
5295
5554
  if (!mobileTabState.btn) return;
@@ -5326,8 +5585,9 @@ function setupMobileExperience(rpcCall, ctx) {
5326
5585
  };
5327
5586
  dropdown.appendChild(option);
5328
5587
  });
5329
- mobileTabState.btn.appendChild(dropdown);
5588
+ document.body.appendChild(dropdown);
5330
5589
  mobileTabState.dropdown = dropdown;
5590
+ positionTabDropdown();
5331
5591
  };
5332
5592
  const syncTabButtonLabel = () => {
5333
5593
  if (!mobileTabState.btn || !mobileTabState.btn.isConnected) return;
@@ -5390,6 +5650,7 @@ function setupMobileExperience(rpcCall, ctx) {
5390
5650
  titleExtrasObserver.observe(document.body, { childList: true, subtree: true });
5391
5651
  window.addEventListener("resize", () => {
5392
5652
  if (window.innerWidth > 768) closeTabDropdown();
5653
+ else positionTabDropdown();
5393
5654
  });
5394
5655
  document.addEventListener(
5395
5656
  "click",