@wenbin_wb/dsh-bridge 2.5.5 → 2.6.1

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/client/client.js CHANGED
@@ -40,6 +40,8 @@ var BRIDGE_ENDPOINTS = {
40
40
  startCloudflared: "startCloudflared",
41
41
  stopCloudflared: "stopCloudflared",
42
42
  resetCloudflared: "resetCloudflared",
43
+ saveCloudflaredConfig: "saveCloudflaredConfig",
44
+ setTunnelAutoStart: "setTunnelAutoStart",
43
45
  saveCustomTunnelConfig: "saveCustomTunnelConfig",
44
46
  checkVersion: "checkVersion",
45
47
  upgradePlugin: "upgradePlugin",
@@ -115,7 +117,7 @@ function semverGt(a, b) {
115
117
  return av.pre > bv.pre;
116
118
  }
117
119
  var s = {
118
- card: { background: "var(--dsw-alias-bg-layer-2,#f9fafb)", border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", borderRadius: 12, padding: "16px 20px", marginBottom: 16 },
120
+ card: { background: "var(--dsw-alias-bg-layer-2,#f9fafb)", border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", borderRadius: 12, padding: "16px 18px", marginBottom: 16, boxSizing: "border-box" },
119
121
  block: { borderTop: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", marginTop: 12, paddingTop: 12 },
120
122
  muted: { color: "var(--dsw-alias-label-tertiary,#8b93a1)", fontSize: 12, lineHeight: 1.5 },
121
123
  label: { color: "var(--dsw-alias-label-primary,currentColor)", fontSize: 13, fontWeight: 500 },
@@ -123,8 +125,8 @@ var s = {
123
125
  btnPri: { font: "inherit", cursor: "pointer", border: "none", background: "var(--dsw-alias-brand-primary,#4f6ef7)", color: "var(--dsw-alias-label-primary-foreground,#fff)", height: 32, padding: "0 14px", borderRadius: 999, fontSize: 13, fontWeight: 500, display: "inline-flex", alignItems: "center", gap: 4 },
124
126
  btnGhost: { font: "inherit", cursor: "pointer", border: "1px solid var(--dsw-alias-border-l2,#d1d5db)", background: "var(--dsw-alias-bg-layer-2,#f9fafb)", color: "var(--dsw-alias-label-primary,currentColor)", height: 32, padding: "0 14px", borderRadius: 999, fontSize: 13, display: "inline-flex", alignItems: "center", gap: 4, textDecoration: "none" },
125
127
  btnLink: { font: "inherit", cursor: "pointer", border: "none", background: "none", color: "var(--dsw-alias-brand-primary,#4f6ef7)", fontSize: 12, padding: 0, display: "inline-flex", alignItems: "center", gap: 3, textDecoration: "none" },
126
- qr: { width: 200, height: 200, borderRadius: 10, border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", margin: "8px 0", display: "block", background: "#ffffff", padding: 6, boxSizing: "border-box" },
127
- tag: { display: "inline-flex", alignItems: "center", padding: "2px 8px", borderRadius: 999, fontSize: 12, fontWeight: 500 },
128
+ qr: { width: 200, height: 200, maxWidth: "100%", borderRadius: 10, border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", margin: "8px 0", display: "block", background: "#ffffff", padding: 6, boxSizing: "border-box" },
129
+ tag: { display: "inline-flex", alignItems: "center", justifyContent: "center", padding: "3px 10px", borderRadius: 999, fontSize: 12, fontWeight: 500, whiteSpace: "nowrap", flexShrink: 0, minWidth: "max-content", lineHeight: 1.4 },
128
130
  input: { width: "100%", font: "inherit", fontSize: 13, padding: "7px 10px", borderRadius: 8, border: "1px solid var(--dsw-alias-border-l2,#d1d5db)", background: "var(--dsw-alias-bg-layer-2,#f9fafb)", color: "var(--dsw-alias-label-primary,currentColor)", outline: "none", boxSizing: "border-box" },
129
131
  warn: { background: "var(--dsw-alias-state-warn-bg,#fffbeb)", border: "1px solid var(--dsw-alias-state-warn-border,#fde68a)", borderRadius: 8, padding: "10px 14px", fontSize: 12, color: "var(--dsw-alias-state-warn-primary,#92400e)", lineHeight: 1.6 },
130
132
  tip: { background: "var(--dsw-alias-bg-layer-2,#f9fafb)", borderRadius: 8, padding: "10px 14px", fontSize: 12, color: "var(--dsw-alias-label-secondary,#6b7280)", lineHeight: 1.6 }
@@ -281,10 +283,11 @@ function QrBlock({ url, qr, onReset, auth, onNavigateSecurity }) {
281
283
  "div",
282
284
  {
283
285
  style: {
284
- display: "inline-flex",
286
+ display: "flex",
285
287
  alignItems: "center",
288
+ justifyContent: "space-between",
286
289
  gap: 6,
287
- padding: "4px 10px",
290
+ padding: "6px 10px",
288
291
  background: "var(--dsw-alias-state-success-bg,#ecfdf5)",
289
292
  border: "1px solid var(--dsw-alias-state-success-primary,#10b981)",
290
293
  borderRadius: 8,
@@ -292,13 +295,14 @@ function QrBlock({ url, qr, onReset, auth, onNavigateSecurity }) {
292
295
  color: "var(--dsw-alias-state-success-primary,#059669)",
293
296
  marginBottom: 8,
294
297
  fontWeight: 500,
298
+ flexWrap: "wrap",
295
299
  cursor: onNavigateSecurity ? "pointer" : "default"
296
300
  },
297
301
  onClick: onNavigateSecurity,
298
302
  title: onNavigateSecurity ? "\u70B9\u51FB\u524D\u5F80\u300C\u5B89\u5168\u8BA4\u8BC1\u300D\u914D\u7F6E" : void 0
299
303
  },
300
- "\u{1F6E1}\uFE0F \u8BBF\u95EE\u5B89\u5168\u8BA4\u8BC1\u5DF2\u751F\u6548 \xB7 \u626B\u7801\u8BBE\u5907\u81EA\u52A8\u5B8C\u6210\u514D\u5BC6\u6388\u6743",
301
- onNavigateSecurity && React.createElement("span", { style: { textDecoration: "underline", fontSize: 11, marginLeft: 4 } }, "\u8BBE\u7F6E \u2794")
304
+ React.createElement("span", { style: { display: "inline-flex", alignItems: "center", gap: 4 } }, "\u{1F6E1}\uFE0F \u8BBF\u95EE\u5B89\u5168\u8BA4\u8BC1\u5DF2\u751F\u6548 \xB7 \u626B\u7801\u8BBE\u5907\u514D\u5BC6"),
305
+ onNavigateSecurity && React.createElement("span", { style: { textDecoration: "underline", fontSize: 11, fontWeight: 600 } }, "\u8BBE\u7F6E \u2794")
302
306
  ) : React.createElement(
303
307
  "div",
304
308
  {
@@ -311,20 +315,28 @@ function QrBlock({ url, qr, onReset, auth, onNavigateSecurity }) {
311
315
  ),
312
316
  React.createElement(
313
317
  "div",
314
- { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 6 } },
315
- React.createElement("code", { style: { ...s.code, flex: 1 } }, url),
316
- React.createElement("button", {
317
- style: { ...s.btnGhost, height: 26, padding: "0 10px", fontSize: 12, flexShrink: 0 },
318
- onClick: copy
319
- }, copied ? "\u2713 \u5DF2\u590D\u5236" : "\u590D\u5236"),
320
- React.createElement("button", {
321
- style: { ...s.btnGhost, height: 26, padding: "0 10px", fontSize: 12, flexShrink: 0 },
322
- onClick: toggleQr
323
- }, showQr ? "\u9690\u85CF\u4E8C\u7EF4\u7801" : "\u663E\u793A\u4E8C\u7EF4\u7801")
318
+ { style: { display: "flex", flexDirection: "column", gap: 8, marginTop: 8 } },
319
+ React.createElement(
320
+ "div",
321
+ { style: { padding: "6px 10px", background: "var(--dsw-alias-bg-layer-1,#ffffff)", border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)", borderRadius: 8 } },
322
+ React.createElement("code", { style: { ...s.code, display: "block", wordBreak: "break-all", fontSize: 12, lineHeight: 1.5 } }, url)
323
+ ),
324
+ React.createElement(
325
+ "div",
326
+ { style: { display: "flex", gap: 8, alignItems: "center" } },
327
+ React.createElement("button", {
328
+ style: { ...s.btnGhost, height: 28, padding: "0 12px", fontSize: 12, flex: "1 1 auto", justifyContent: "center" },
329
+ onClick: copy
330
+ }, copied ? "\u2713 \u5DF2\u590D\u5236" : "\u590D\u5236\u94FE\u63A5"),
331
+ React.createElement("button", {
332
+ style: { ...s.btnGhost, height: 28, padding: "0 12px", fontSize: 12, flex: "1 1 auto", justifyContent: "center" },
333
+ onClick: toggleQr
334
+ }, showQr ? "\u9690\u85CF\u4E8C\u7EF4\u7801" : "\u663E\u793A\u4E8C\u7EF4\u7801")
335
+ )
324
336
  ),
325
337
  showQr && qr && React.createElement(
326
338
  "div",
327
- { style: { marginTop: 8 } },
339
+ { style: { marginTop: 10 } },
328
340
  React.createElement("img", { src: qr, alt: "QR", style: s.qr }),
329
341
  React.createElement("div", { style: { ...s.muted, marginTop: 4 } }, "\u8BF7\u5728\u79C1\u5BC6\u73AF\u5883\u4E0B\u4F7F\u7528")
330
342
  ),
@@ -437,7 +449,19 @@ var CustomTunnelConfigForm = React.memo(function CustomTunnelConfigForm2({ serve
437
449
  )
438
450
  );
439
451
  });
440
- var TunnelCard = React.memo(function TunnelCard2({ title, desc, data, onStart, onStop, onReset, auth, onNavigateSecurity, children }) {
452
+ var TunnelCard = React.memo(function TunnelCard2({
453
+ title,
454
+ desc,
455
+ data,
456
+ autoStart,
457
+ onToggleAutoStart,
458
+ onStart,
459
+ onStop,
460
+ onReset,
461
+ auth,
462
+ onNavigateSecurity,
463
+ children
464
+ }) {
441
465
  const { running, configured, url, qr, state } = data ?? {};
442
466
  const phase = state?.phase ?? "idle";
443
467
  return React.createElement(
@@ -445,14 +469,39 @@ var TunnelCard = React.memo(function TunnelCard2({ title, desc, data, onStart, o
445
469
  { style: s.card },
446
470
  React.createElement(
447
471
  "div",
448
- { style: { display: "flex", justifyContent: "space-between", alignItems: "center" } },
472
+ { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 10 } },
449
473
  React.createElement(
450
474
  "div",
451
- null,
475
+ { style: { flex: "1 1 auto", minWidth: 0 } },
452
476
  React.createElement("div", { style: s.label }, title),
453
477
  React.createElement("div", { style: { ...s.muted, marginTop: 2 } }, desc)
454
478
  ),
455
- React.createElement(StatusTag, { running })
479
+ React.createElement(
480
+ "div",
481
+ { style: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 6 } },
482
+ React.createElement(StatusTag, { running }),
483
+ onToggleAutoStart && React.createElement(
484
+ "label",
485
+ {
486
+ style: {
487
+ display: "flex",
488
+ alignItems: "center",
489
+ gap: 4,
490
+ fontSize: 11,
491
+ color: "var(--dsw-alias-label-secondary,#6b7280)",
492
+ cursor: "pointer",
493
+ userSelect: "none"
494
+ },
495
+ title: "DSH \u542F\u52A8\u65F6\u81EA\u52A8\u6062\u590D\u8BE5\u96A7\u9053\u7684\u8FD0\u884C\u72B6\u6001"
496
+ },
497
+ React.createElement("input", {
498
+ type: "checkbox",
499
+ checked: Boolean(autoStart),
500
+ onChange: (e) => onToggleAutoStart(e.target.checked)
501
+ }),
502
+ "\u968F DSH \u542F\u52A8\u81EA\u52A8\u5F00\u542F"
503
+ )
504
+ )
456
505
  ),
457
506
  children,
458
507
  phase !== "idle" && phase !== "ready" && React.createElement("div", {
@@ -478,6 +527,107 @@ var TunnelCard = React.memo(function TunnelCard2({ title, desc, data, onStart, o
478
527
  )
479
528
  );
480
529
  });
530
+ var CloudflareConfigForm = React.memo(function CloudflareConfigForm2({ token, hostname, onSave }) {
531
+ const [open, setOpen] = React.useState(Boolean(token || hostname));
532
+ const [tokenVal, setTokenVal] = React.useState(token || "");
533
+ const [hostnameVal, setHostnameVal] = React.useState(hostname || "");
534
+ const [saving, setSaving] = React.useState(false);
535
+ const [msg, setMsg] = React.useState(null);
536
+ React.useEffect(() => {
537
+ setTokenVal(token || "");
538
+ setHostnameVal(hostname || "");
539
+ }, [token, hostname]);
540
+ const handleSave = async (e) => {
541
+ e.preventDefault();
542
+ setSaving(true);
543
+ setMsg(null);
544
+ try {
545
+ await onSave({ token: tokenVal, hostname: hostnameVal });
546
+ setMsg({ ok: true, text: "\u2713 \u56FA\u5B9A\u57DF\u540D\u914D\u7F6E\u5DF2\u4FDD\u5B58" });
547
+ } catch (err) {
548
+ setMsg({ ok: false, text: err.message || "\u4FDD\u5B58\u5931\u8D25" });
549
+ } finally {
550
+ setSaving(false);
551
+ }
552
+ };
553
+ return React.createElement(
554
+ "div",
555
+ {
556
+ style: {
557
+ ...s.block,
558
+ borderTop: "1px solid var(--dsw-alias-border-secondary, #e5e7eb)",
559
+ paddingTop: 10,
560
+ marginTop: 10
561
+ }
562
+ },
563
+ React.createElement(
564
+ "div",
565
+ {
566
+ style: { display: "flex", alignItems: "center", justifyContent: "space-between", cursor: "pointer", userSelect: "none" },
567
+ onClick: () => setOpen((v) => !v)
568
+ },
569
+ React.createElement(
570
+ "div",
571
+ { style: { fontSize: 12, fontWeight: 500, color: "var(--dsw-alias-brand-primary, #3b82f6)" } },
572
+ "\u2699\uFE0F \u9AD8\u7EA7\u914D\u7F6E\uFF1A\u56FA\u5B9A\u57DF\u540D (Cloudflare Token) ",
573
+ (token || hostname) && React.createElement("span", { style: { fontSize: 11, color: "var(--dsw-alias-state-success-primary, #059669)", fontWeight: 400 } }, "\u25CF \u5DF2\u914D\u7F6E\u56FA\u5B9A\u57DF\u540D")
574
+ ),
575
+ React.createElement("span", { style: { fontSize: 11, color: "var(--dsw-alias-label-secondary, #9ca3af)" } }, open ? "\u25B4 \u6298\u53E0" : "\u25BE \u5C55\u5F00")
576
+ ),
577
+ open && React.createElement(
578
+ "form",
579
+ { onSubmit: handleSave, style: { marginTop: 10 } },
580
+ React.createElement(
581
+ "div",
582
+ { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary, #6b7280)", marginBottom: 8, lineHeight: 1.5 } },
583
+ "\u5728 Cloudflare Zero Trust \u63A7\u5236\u53F0\u521B\u5EFA Tunnel \u5373\u53EF\u83B7\u53D6\u4E13\u5C5E Token \u5E76\u7ED1\u5B9A\u81EA\u5DF1\u7684\u57DF\u540D\uFF08\u5982 dsh.yourname.com\uFF09\uFF0C\u6BCF\u6B21\u91CD\u542F URL \u6C38\u4E0D\u53D8\u66F4\u3002\u4E0D\u586B\u5219\u4F7F\u7528\u9ED8\u8BA4\u514D\u767B\u5F55\u4E34\u65F6\u968F\u673A\u57DF\u540D\u3002"
584
+ ),
585
+ React.createElement(
586
+ "div",
587
+ { style: { marginBottom: 8 } },
588
+ React.createElement("input", {
589
+ style: s.input,
590
+ type: "text",
591
+ placeholder: "\u81EA\u5B9A\u4E49\u56FA\u5B9A\u57DF\u540D (\u4F8B\u5982: dsh.yourdomain.com)",
592
+ value: hostnameVal,
593
+ onChange: (e) => setHostnameVal(e.target.value)
594
+ })
595
+ ),
596
+ React.createElement(
597
+ "div",
598
+ { style: { marginBottom: 8 } },
599
+ React.createElement("input", {
600
+ style: s.input,
601
+ type: "password",
602
+ placeholder: "Tunnel Token (\u4F8B\u5982: eyJhIjoi...)",
603
+ value: tokenVal,
604
+ onChange: (e) => setTokenVal(e.target.value)
605
+ })
606
+ ),
607
+ React.createElement(
608
+ "div",
609
+ { style: { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" } },
610
+ React.createElement("button", {
611
+ type: "submit",
612
+ style: { ...s.btnPri, height: 28, fontSize: 12, padding: "0 12px" },
613
+ disabled: saving
614
+ }, saving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u56FA\u5B9A\u57DF\u540D\u914D\u7F6E"),
615
+ (tokenVal || hostnameVal) && React.createElement("button", {
616
+ type: "button",
617
+ style: { ...s.btnGhost, height: 28, fontSize: 12, padding: "0 10px" },
618
+ onClick: () => {
619
+ setTokenVal("");
620
+ setHostnameVal("");
621
+ onSave({ token: "", hostname: "" });
622
+ }
623
+ }, "\u6E05\u9664"),
624
+ msg && React.createElement("span", {
625
+ style: { fontSize: 12, color: msg.ok ? "var(--dsw-alias-state-success-primary, #059669)" : "var(--dsw-alias-state-error-primary, #dc2626)" }
626
+ }, msg.text)
627
+ )
628
+ )
629
+ );
630
+ });
481
631
  var AccessAuthCard = React.memo(function AccessAuthCard2({ auth, rpcCall, onUpdate }) {
482
632
  const [enabled, setEnabled] = React.useState(auth?.enabled ?? false);
483
633
  const [mode, setMode] = React.useState(auth?.mode ?? "token_and_password");
@@ -1129,10 +1279,10 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
1129
1279
  { style: s.card },
1130
1280
  React.createElement(
1131
1281
  "div",
1132
- { style: { display: "flex", justifyContent: "space-between", alignItems: "center" } },
1282
+ { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 10 } },
1133
1283
  React.createElement(
1134
1284
  "div",
1135
- null,
1285
+ { style: { flex: "1 1 auto", minWidth: 0 } },
1136
1286
  React.createElement(
1137
1287
  "div",
1138
1288
  { style: { ...s.label, display: "flex", alignItems: "center", gap: 7 } },
@@ -1737,6 +1887,25 @@ function VersionBanner({ rpcCall }) {
1737
1887
  ) : upgradeResult?.ok ? "\u2713 \u5DF2\u5B8C\u6210\u5347\u7EA7" : `\u4E00\u952E\u5347\u7EA7\u5230 v${info.latest}`
1738
1888
  )
1739
1889
  ),
1890
+ // 简短更新内容 / Release Notes 亮点展示
1891
+ info?.releaseNotes && React.createElement(
1892
+ "div",
1893
+ {
1894
+ style: {
1895
+ fontSize: 12,
1896
+ color: "var(--dsw-alias-label-secondary,#374151)",
1897
+ background: "rgba(255, 255, 255, 0.75)",
1898
+ border: "1px solid rgba(191, 219, 254, 0.7)",
1899
+ borderRadius: 8,
1900
+ padding: "8px 12px",
1901
+ marginBottom: 10,
1902
+ lineHeight: 1.6,
1903
+ whiteSpace: "pre-line"
1904
+ }
1905
+ },
1906
+ React.createElement("div", { style: { fontWeight: 600, color: "var(--dsw-alias-state-info-primary,#1e40af)", marginBottom: 2 } }, "\u2728 \u66F4\u65B0\u4EAE\u70B9\uFF1A"),
1907
+ info.releaseNotes
1908
+ ),
1740
1909
  upgradeResult && React.createElement("div", {
1741
1910
  style: {
1742
1911
  background: upgradeResult.ok ? "var(--dsw-alias-state-success-bg,#ecfdf5)" : "var(--dsw-alias-state-error-bg,#fef2f2)",
@@ -1785,7 +1954,11 @@ function TabBar({ active, onChange, dots }) {
1785
1954
  display: "flex",
1786
1955
  gap: 4,
1787
1956
  marginBottom: 20,
1788
- borderBottom: "1px solid var(--dsw-alias-border-l2,#e5e7eb)"
1957
+ borderBottom: "1px solid var(--dsw-alias-border-l2,#e5e7eb)",
1958
+ overflowX: "auto",
1959
+ WebkitOverflowScrolling: "touch",
1960
+ maxWidth: "100%",
1961
+ flexWrap: "nowrap"
1789
1962
  }
1790
1963
  },
1791
1964
  TABS.map(({ id, label, icon: TabIcon }) => {
@@ -1801,7 +1974,7 @@ function TabBar({ active, onChange, dots }) {
1801
1974
  cursor: "pointer",
1802
1975
  border: "none",
1803
1976
  background: "none",
1804
- padding: "8px 16px",
1977
+ padding: "8px 14px",
1805
1978
  fontSize: 13,
1806
1979
  fontWeight: isActive ? 600 : 400,
1807
1980
  color: isActive ? "var(--dsw-alias-brand-primary,#4f6ef7)" : "var(--dsw-alias-label-secondary,#6b7280)",
@@ -1809,9 +1982,10 @@ function TabBar({ active, onChange, dots }) {
1809
1982
  marginBottom: -1,
1810
1983
  display: "inline-flex",
1811
1984
  alignItems: "center",
1812
- gap: 7,
1985
+ gap: 6,
1813
1986
  transition: "color .15s, border-color .15s",
1814
- whiteSpace: "nowrap"
1987
+ whiteSpace: "nowrap",
1988
+ flexShrink: 0
1815
1989
  }
1816
1990
  },
1817
1991
  TabIcon && React.createElement(TabIcon, {
@@ -1997,8 +2171,20 @@ function BridgePanel({ rpcCall }) {
1997
2171
  () => act(BRIDGE_ENDPOINTS.stopCloudflared).then(() => act(BRIDGE_ENDPOINTS.startCloudflared)),
1998
2172
  [act]
1999
2173
  );
2174
+ const onToggleCloudflaredAutoStart = React.useCallback(
2175
+ (autoStart) => act(BRIDGE_ENDPOINTS.setTunnelAutoStart, { tunnel: "cloudflared", autoStart }),
2176
+ [act]
2177
+ );
2178
+ const saveCloudflaredConfig = React.useCallback(
2179
+ ({ token, hostname }) => act(BRIDGE_ENDPOINTS.saveCloudflaredConfig, { token, hostname }),
2180
+ [act]
2181
+ );
2000
2182
  const onStartCustom = React.useCallback(() => act(BRIDGE_ENDPOINTS.startCustomTunnel), [act]);
2001
2183
  const onStopCustom = React.useCallback(() => act(BRIDGE_ENDPOINTS.stopCustomTunnel), [act]);
2184
+ const onToggleCustomAutoStart = React.useCallback(
2185
+ (autoStart) => act(BRIDGE_ENDPOINTS.setTunnelAutoStart, { tunnel: "customTunnel", autoStart }),
2186
+ [act]
2187
+ );
2002
2188
  const saveConfig = React.useCallback(
2003
2189
  (serverUrl, accessToken) => act(BRIDGE_ENDPOINTS.saveCustomTunnelConfig, { serverUrl, accessToken }),
2004
2190
  [act]
@@ -2032,21 +2218,31 @@ function BridgePanel({ rpcCall }) {
2032
2218
  tabContent = React.createElement(
2033
2219
  React.Fragment,
2034
2220
  null,
2035
- React.createElement(TunnelCard, {
2036
- title: "Cloudflare \u96A7\u9053",
2037
- desc: "\u4E00\u952E\u83B7\u53D6\u516C\u7F51\u5730\u5740\uFF08\u91CD\u542F\u540E URL \u4F1A\u53D8\u5316\uFF09",
2038
- data: {
2039
- running: status?.cloudflared?.running,
2040
- url: status?.cloudflared?.url,
2041
- qr: status?.cloudflared?.qr,
2042
- state: status?.cloudflared?.state
2221
+ React.createElement(
2222
+ TunnelCard,
2223
+ {
2224
+ title: "Cloudflare \u96A7\u9053",
2225
+ desc: status?.cloudflared?.tokenConfigured ? "\u56FA\u5B9A\u57DF\u540D\u6A21\u5F0F\uFF08Token \u8FD0\u884C \xB7 \u91CD\u542F URL \u4FDD\u6301\u4E0D\u53D8\uFF09" : "\u4E00\u952E\u83B7\u53D6\u516C\u7F51\u5730\u5740\uFF08\u514D\u767B\u5F55\u4E34\u65F6\u968F\u673A\u57DF\u540D\uFF09",
2226
+ data: {
2227
+ running: status?.cloudflared?.running,
2228
+ url: status?.cloudflared?.url,
2229
+ qr: status?.cloudflared?.qr,
2230
+ state: status?.cloudflared?.state
2231
+ },
2232
+ autoStart: status?.cloudflared?.autoStart,
2233
+ onToggleAutoStart: onToggleCloudflaredAutoStart,
2234
+ auth: status?.auth,
2235
+ onNavigateSecurity: navSecurity,
2236
+ onStart: onStartCloudflared,
2237
+ onStop: onStopCloudflared,
2238
+ onReset: status?.cloudflared?.running ? onResetCloudflared : null
2043
2239
  },
2044
- auth: status?.auth,
2045
- onNavigateSecurity: navSecurity,
2046
- onStart: onStartCloudflared,
2047
- onStop: onStopCloudflared,
2048
- onReset: status?.cloudflared?.running ? onResetCloudflared : null
2049
- }),
2240
+ React.createElement(CloudflareConfigForm, {
2241
+ token: status?.cloudflared?.token ?? "",
2242
+ hostname: status?.cloudflared?.hostname ?? "",
2243
+ onSave: saveCloudflaredConfig
2244
+ })
2245
+ ),
2050
2246
  React.createElement(
2051
2247
  TunnelCard,
2052
2248
  {
@@ -2059,6 +2255,8 @@ function BridgePanel({ rpcCall }) {
2059
2255
  qr: ct?.qr,
2060
2256
  state: ct?.state
2061
2257
  },
2258
+ autoStart: ct?.autoStart,
2259
+ onToggleAutoStart: onToggleCustomAutoStart,
2062
2260
  auth: status?.auth,
2063
2261
  onNavigateSecurity: navSecurity,
2064
2262
  onStart: onStartCustom,
@@ -2442,7 +2640,485 @@ function BridgePanel({ rpcCall }) {
2442
2640
  )
2443
2641
  );
2444
2642
  }
2643
+ function injectMobileStyles() {
2644
+ if (typeof document === "undefined") return;
2645
+ if (document.getElementById("dsh-bridge-mobile-styles")) return;
2646
+ const style = document.createElement("style");
2647
+ style.id = "dsh-bridge-mobile-styles";
2648
+ style.textContent = `
2649
+ /* DSH Bridge \u79FB\u52A8\u7AEF\u81EA\u9002\u5E94\u4E0E\u89E6\u63A7\u4EA4\u4E92\u589E\u5F3A\u6837\u5F0F */
2650
+ :root {
2651
+ --dsh-mobile-header-h: 52px;
2652
+ --dsh-mobile-safe-top: env(safe-area-inset-top, 0px);
2653
+ --dsh-mobile-safe-bottom: env(safe-area-inset-bottom, 0px);
2654
+ }
2655
+
2656
+ @media (max-width: 768px) {
2657
+ /* 1. \u4E3B\u6846\u67B6\u4E3A Header \u817E\u51FA\u9876\u90E8\u7A7A\u95F4 */
2658
+ div[class*="_frame"] {
2659
+ display: flex !important;
2660
+ flex-direction: column !important;
2661
+ width: 100vw !important;
2662
+ height: 100dvh !important;
2663
+ margin: 0 !important;
2664
+ padding-top: var(--dsh-mobile-header-h) !important;
2665
+ position: relative !important;
2666
+ grid-template-columns: 1fr !important;
2667
+ overflow: hidden !important;
2668
+ box-sizing: border-box !important;
2669
+ }
2670
+
2671
+ /* 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) */
2672
+ .dsh-mobile-app-header {
2673
+ position: fixed !important;
2674
+ top: 0 !important;
2675
+ left: 0 !important;
2676
+ right: 0 !important;
2677
+ height: var(--dsh-mobile-header-h) !important;
2678
+ padding-top: var(--dsh-mobile-safe-top) !important;
2679
+ background: transparent !important;
2680
+ display: flex !important;
2681
+ align-items: center !important;
2682
+ justify-content: space-between !important;
2683
+ padding-left: 16px !important;
2684
+ padding-right: 16px !important;
2685
+ z-index: 9998 !important;
2686
+ box-sizing: border-box !important;
2687
+ user-select: none !important;
2688
+ }
2689
+
2690
+ /* \u5DE6\u4FA7\u53CC\u6A2A\u7EBF\u6309\u94AE (DeepSeek App \u539F\u751F\u56FE\u6807) */
2691
+ .dsh-header-menu-btn {
2692
+ width: 40px;
2693
+ height: 40px;
2694
+ border-radius: 50%;
2695
+ border: none;
2696
+ background: transparent;
2697
+ color: var(--dsw-alias-label-primary, #111827);
2698
+ display: inline-flex;
2699
+ align-items: center;
2700
+ justify-content: flex-start;
2701
+ cursor: pointer;
2702
+ padding: 0;
2703
+ transition: opacity 0.15s;
2704
+ }
2705
+ .dsh-header-menu-btn:active {
2706
+ opacity: 0.6;
2707
+ }
2708
+
2709
+ /* \u53F3\u4FA7 (+) \u65B0\u5EFA\u4F1A\u8BDD\u6309\u94AE (DeepSeek App \u539F\u751F\u56FE\u6807) */
2710
+ .dsh-header-new-btn {
2711
+ width: 40px;
2712
+ height: 40px;
2713
+ border-radius: 50%;
2714
+ border: none;
2715
+ background: transparent;
2716
+ color: var(--dsw-alias-label-primary, #111827);
2717
+ display: inline-flex;
2718
+ align-items: center;
2719
+ justify-content: flex-end;
2720
+ cursor: pointer;
2721
+ padding: 0;
2722
+ transition: opacity 0.15s;
2723
+ }
2724
+ .dsh-header-new-btn:active {
2725
+ opacity: 0.6;
2726
+ }
2727
+
2728
+ /* 3. \u4E2D\u95F4\u4E3B\u5185\u5BB9\u533A\u4E0E\u8F93\u5165\u6846 */
2729
+ div[class*="_centerCol"] {
2730
+ flex: 1 1 100% !important;
2731
+ width: 100% !important;
2732
+ min-width: 0 !important;
2733
+ max-width: 100% !important;
2734
+ display: flex !important;
2735
+ height: 100% !important;
2736
+ }
2737
+
2738
+ div[class*="_detailsCol"],
2739
+ div[class*="toggleCluster"],
2740
+ div[class*="W-zNGW_toggleCluster"] {
2741
+ display: none !important;
2742
+ }
2743
+
2744
+ /* \u8F93\u5165\u6846\u5E95\u5EA7\uFF1ADeepSeek App \u5C45\u4E2D\u53CA\u5E95\u90E8\u56FA\u5B9A */
2745
+ div[class*="wSkVaW_scrollBody"] {
2746
+ padding-bottom: max(16px, env(safe-area-inset-bottom)) !important;
2747
+ }
2748
+
2749
+ /* \u8F93\u5165\u5361\u7247\uFF1ADeepSeek App \u5706\u89D2\u5927\u80F6\u56CA\u9020\u578B */
2750
+ div[class*="uV2eYG_card"] {
2751
+ border-radius: 26px !important;
2752
+ padding: 14px 16px 12px !important;
2753
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05) !important;
2754
+ border: 1px solid rgba(0, 0, 0, 0.07) !important;
2755
+ background: var(--dsw-alias-bg-layer-2, #f4f4f7) !important;
2756
+ }
2757
+
2758
+ /* 4. \u539F\u751F\u4FA7\u8FB9\u680F\u62BD\u5C49\u5316 (Drawer) */
2759
+ div[class*="_sidebarCol"] {
2760
+ position: fixed !important;
2761
+ left: 0 !important;
2762
+ top: 0 !important;
2763
+ bottom: 0 !important;
2764
+ height: 100dvh !important;
2765
+ width: 290px !important;
2766
+ max-width: 82vw !important;
2767
+ z-index: 10000 !important;
2768
+ background: var(--dsw-alias-bg-layer-1, #ffffff) !important;
2769
+ transform: translateX(-105%);
2770
+ transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
2771
+ overflow-y: auto !important;
2772
+ border-right: 1px solid rgba(0, 0, 0, 0.06) !important;
2773
+ }
2774
+ body.dsh-drawer-open div[class*="_sidebarCol"] {
2775
+ transform: translateX(0) !important;
2776
+ box-shadow: 4px 0 28px rgba(0, 0, 0, 0.25) !important;
2777
+ }
2778
+
2779
+ /* \u62BD\u5C49\u5185\u90E8\uFF1A\u5F3A\u5236 100% \u5BBD\u5EA6\uFF0C\u9690\u85CF\u5197\u4F59\u6298\u53E0\u6309\u94AE */
2780
+ body.dsh-drawer-open div[class*="hHd-Xa_root"] {
2781
+ width: 100% !important;
2782
+ max-width: 100% !important;
2783
+ }
2784
+ div[class*="hHd-Xa_logoRow"] button[class*="hHd-Xa_toggle"] {
2785
+ display: none !important;
2786
+ }
2787
+
2788
+ /* \u8BBE\u7F6E\u5F39\u7A97\u6253\u5F00\u65F6\u89E3\u9664\u62BD\u5C49\u9690\u85CF\u9650\u5236 */
2789
+ div[class*="_sidebarCol"]:has(div[class*="VOzbGW_overlay"]) {
2790
+ transform: none !important;
2791
+ width: 100vw !important;
2792
+ max-width: 100vw !important;
2793
+ background: transparent !important;
2794
+ box-shadow: none !important;
2795
+ pointer-events: none !important;
2796
+ }
2797
+
2798
+ /* 5. \u534A\u900F\u660E\u80CC\u666F\u906E\u7F69 */
2799
+ .dsh-mobile-backdrop {
2800
+ position: fixed;
2801
+ inset: 0;
2802
+ background: rgba(0, 0, 0, 0.4);
2803
+ backdrop-filter: blur(4px);
2804
+ -webkit-backdrop-filter: blur(4px);
2805
+ z-index: 9999;
2806
+ display: none !important;
2807
+ }
2808
+ body.dsh-drawer-open .dsh-mobile-backdrop {
2809
+ display: block !important;
2810
+ pointer-events: auto !important;
2811
+ }
2812
+
2813
+ /* 6. \u8BBE\u7F6E\u4E2D\u5FC3\u5168\u81EA\u9002\u5E94\u9002\u914D */
2814
+ div[class*="VOzbGW_overlay"] {
2815
+ position: fixed !important;
2816
+ inset: 0 !important;
2817
+ width: 100vw !important;
2818
+ height: 100dvh !important;
2819
+ display: flex !important;
2820
+ align-items: center !important;
2821
+ justify-content: center !important;
2822
+ background: rgba(0, 0, 0, 0.45) !important;
2823
+ backdrop-filter: blur(4px) !important;
2824
+ -webkit-backdrop-filter: blur(4px) !important;
2825
+ z-index: 10002 !important;
2826
+ padding: 10px !important;
2827
+ box-sizing: border-box !important;
2828
+ pointer-events: auto !important;
2829
+ }
2830
+ div[class*="VOzbGW_panel"] {
2831
+ width: 100% !important;
2832
+ max-width: 100% !important;
2833
+ height: 92dvh !important;
2834
+ max-height: 92dvh !important;
2835
+ display: flex !important;
2836
+ flex-direction: row !important;
2837
+ border-radius: 18px !important;
2838
+ overflow: hidden !important;
2839
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.25) !important;
2840
+ background: var(--dsw-alias-bg-layer-1, #ffffff) !important;
2841
+ }
2842
+ nav[class*="VOzbGW_nav"] {
2843
+ width: 78px !important;
2844
+ min-width: 78px !important;
2845
+ max-width: 78px !important;
2846
+ padding: 10px 4px !important;
2847
+ box-sizing: border-box !important;
2848
+ border-right: 1px solid var(--dsw-alias-border-l2, #e5e7eb) !important;
2849
+ display: flex !important;
2850
+ flex-direction: column !important;
2851
+ gap: 6px !important;
2852
+ overflow-y: auto !important;
2853
+ }
2854
+ nav[class*="VOzbGW_nav"] button[class*="VOzbGW_navCell"],
2855
+ button[class*="VOzbGW_navCell"] {
2856
+ padding: 8px 2px !important;
2857
+ display: flex !important;
2858
+ flex-direction: column !important;
2859
+ align-items: center !important;
2860
+ justify-content: center !important;
2861
+ text-align: center !important;
2862
+ height: auto !important;
2863
+ min-height: 48px !important;
2864
+ gap: 4px !important;
2865
+ border-radius: 10px !important;
2866
+ }
2867
+ span[class*="VOzbGW_navLabel"] {
2868
+ font-size: 10.5px !important;
2869
+ line-height: 1.2 !important;
2870
+ white-space: normal !important;
2871
+ word-break: break-all !important;
2872
+ text-align: center !important;
2873
+ }
2874
+ div[class*="VOzbGW_content"] {
2875
+ flex: 1 1 auto !important;
2876
+ min-width: 0 !important;
2877
+ width: calc(100% - 78px) !important;
2878
+ max-width: calc(100% - 78px) !important;
2879
+ display: flex !important;
2880
+ flex-direction: column !important;
2881
+ overflow: hidden !important;
2882
+ }
2883
+ div[class*="VOzbGW_options"] {
2884
+ flex: 1 1 auto !important;
2885
+ width: 100% !important;
2886
+ max-width: 100% !important;
2887
+ box-sizing: border-box !important;
2888
+ padding: 0 14px 20px !important;
2889
+ overflow-x: hidden !important;
2890
+ overflow-y: auto !important;
2891
+ -webkit-overflow-scrolling: touch !important;
2892
+ }
2893
+
2894
+ /* \u8BBE\u7F6E\u4E2D\u5FC3\u9009\u9879\u884C\u624B\u673A\u81EA\u9002\u5E94\uFF08\u5782\u76F4\u6D41\u5F0F\uFF0C\u9632\u6587\u5B57\u5355\u5B57\u6298\u884C\uFF09 */
2895
+ div[class*="VOzbGW_options"] div[class*="_row"] {
2896
+ display: flex !important;
2897
+ flex-direction: column !important;
2898
+ align-items: stretch !important;
2899
+ gap: 8px !important;
2900
+ width: 100% !important;
2901
+ padding: 12px 0 !important;
2902
+ box-sizing: border-box !important;
2903
+ }
2904
+ div[class*="VOzbGW_options"] div[class*="_rowText"] {
2905
+ width: 100% !important;
2906
+ max-width: 100% !important;
2907
+ }
2908
+ div[class*="VOzbGW_options"] button[class*="_selector"],
2909
+ div[class*="VOzbGW_options"] select,
2910
+ div[class*="VOzbGW_options"] input {
2911
+ width: 100% !important;
2912
+ max-width: 100% !important;
2913
+ box-sizing: border-box !important;
2914
+ }
2915
+
2916
+ /* 7. \u4EE3\u7801\u5757\u3001\u8868\u683C\u4E0E\u5FBD\u6807\u81EA\u9002\u5E94 */
2917
+ pre, code, pre > code, table {
2918
+ max-width: 100% !important;
2919
+ overflow-x: auto !important;
2920
+ -webkit-overflow-scrolling: touch !important;
2921
+ font-size: 12.5px !important;
2922
+ }
2923
+
2924
+ /* \u72B6\u6001\u5FBD\u6807\u4E0E\u836F\u4E38\u6309\u94AE\u6C38\u4E0D\u6298\u5B57 */
2925
+ span[style*="border-radius: 999"],
2926
+ span[style*="border-radius:999"] {
2927
+ white-space: nowrap !important;
2928
+ flex-shrink: 0 !important;
2929
+ min-width: max-content !important;
2930
+ }
2931
+
2932
+ /* \u4E8C\u7EF4\u7801\u4E0E\u56FE\u7247\u79FB\u52A8\u7AEF\u5F39\u6027\u7F29\u653E */
2933
+ img[alt="QR"], img[src^="data:image"] {
2934
+ max-width: 100% !important;
2935
+ box-sizing: border-box !important;
2936
+ }
2937
+
2938
+ /* 8. \u786E\u4FDD\u6240\u6709 Popover \u5F39\u51FA\u83DC\u5355\u3001\u64CD\u4F5C\u6C14\u6CE1\u3001\u4E0B\u62C9\u6846\u4F4D\u4E8E\u62BD\u5C49\u4E4B\u4E0A\u4E14\u652F\u6301\u89E6\u63A7\u4EA4\u4E92 */
2939
+ div[class*="_portal"],
2940
+ div[class*="portal"],
2941
+ div[class*="popup"],
2942
+ div[class*="dropdown"],
2943
+ div[class*="menu"],
2944
+ div[role="menu"],
2945
+ div[role="dialog"] {
2946
+ z-index: 10005 !important;
2947
+ pointer-events: auto !important;
2948
+ }
2949
+
2950
+ /* 9. \u79FB\u52A8\u7AEF\u4FA7\u8FB9\u680F\uFF1A\u4F1A\u8BDD\u4E0E\u5DE5\u4F5C\u533A\u4E09\u70B9\u64CD\u4F5C\u6309\u94AE\u59CB\u7EC8\u6E05\u6670\u53EF\u89C1\u4E14\u6613\u4E8E\u70B9\u51FB */
2951
+ div[class*="sessionRow"] span[class*="rowActions"],
2952
+ div[class*="sessionRow"] button[class*="iconButton"],
2953
+ div[class*="treeBody"] button[class*="iconButton"] {
2954
+ opacity: 0.8 !important;
2955
+ display: inline-flex !important;
2956
+ visibility: visible !important;
2957
+ pointer-events: auto !important;
2958
+ }
2959
+ div[class*="sessionRow"]:active {
2960
+ background: var(--dsw-alias-bg-layer-2, #f3f4f6) !important;
2961
+ }
2962
+
2963
+ /* \u5168\u5C40 overlayLayer \u7EDD\u4E0D\u88AB\u67D3\u9ED1 */
2964
+ div[class*="overlayLayer"],
2965
+ div[class*="uV2eYG_overlayAnchor"] {
2966
+ background: transparent !important;
2967
+ pointer-events: none !important;
2968
+ }
2969
+ div[class*="overlayLayer"] > * {
2970
+ pointer-events: auto !important;
2971
+ }
2972
+ }
2973
+
2974
+ @media (min-width: 769px) {
2975
+ .dsh-mobile-app-header,
2976
+ .dsh-mobile-backdrop {
2977
+ display: none !important;
2978
+ }
2979
+ }
2980
+ `;
2981
+ document.head.appendChild(style);
2982
+ }
2983
+ function setupMobileExperience() {
2984
+ if (typeof document === "undefined" || typeof window === "undefined") return;
2985
+ injectMobileStyles();
2986
+ let header = document.querySelector(".dsh-mobile-app-header");
2987
+ if (!header) {
2988
+ header = document.createElement("header");
2989
+ header.className = "dsh-mobile-app-header";
2990
+ const leftBtn = document.createElement("button");
2991
+ leftBtn.className = "dsh-header-menu-btn";
2992
+ leftBtn.title = "\u6253\u5F00\u83DC\u5355";
2993
+ leftBtn.innerHTML = `
2994
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round">
2995
+ <line x1="3" y1="8" x2="21" y2="8"></line>
2996
+ <line x1="3" y1="15" x2="14" y2="15"></line>
2997
+ </svg>
2998
+ `;
2999
+ leftBtn.onclick = () => {
3000
+ const isOpen = document.body.classList.toggle("dsh-drawer-open");
3001
+ if (isOpen) {
3002
+ const collapsedToggle = document.querySelector('div[class*="hHd-Xa_collapsed"] button[class*="hHd-Xa_toggle"]');
3003
+ if (collapsedToggle) collapsedToggle.click();
3004
+ }
3005
+ };
3006
+ const rightBtn = document.createElement("button");
3007
+ rightBtn.className = "dsh-header-new-btn";
3008
+ rightBtn.title = "\u65B0\u5EFA\u4F1A\u8BDD";
3009
+ rightBtn.innerHTML = `
3010
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
3011
+ <circle cx="12" cy="12" r="9.5"></circle>
3012
+ <line x1="12" y1="8" x2="12" y2="16"></line>
3013
+ <line x1="8" y1="12" x2="16" y2="12"></line>
3014
+ </svg>
3015
+ `;
3016
+ rightBtn.onclick = () => {
3017
+ const dshNewBtn = document.querySelector('button[aria-label="\u65B0\u5EFA\u4F1A\u8BDD"]');
3018
+ if (dshNewBtn) dshNewBtn.click();
3019
+ };
3020
+ header.appendChild(leftBtn);
3021
+ header.appendChild(rightBtn);
3022
+ document.body.appendChild(header);
3023
+ }
3024
+ let backdrop = document.querySelector(".dsh-mobile-backdrop");
3025
+ if (!backdrop) {
3026
+ backdrop = document.createElement("div");
3027
+ backdrop.className = "dsh-mobile-backdrop";
3028
+ backdrop.addEventListener("click", () => {
3029
+ document.body.classList.remove("dsh-drawer-open");
3030
+ });
3031
+ document.body.appendChild(backdrop);
3032
+ }
3033
+ let lastLongPressTime = 0;
3034
+ document.addEventListener("click", (e) => {
3035
+ if (!document.body.classList.contains("dsh-drawer-open")) return;
3036
+ if (Date.now() - lastLongPressTime < 600) {
3037
+ e.stopPropagation();
3038
+ e.preventDefault();
3039
+ return;
3040
+ }
3041
+ if (e.target.closest('div[class*="portal"], div[class*="_portal"], div[role="menu"], div[role="dialog"], div[class*="popup"], div[class*="dropdown"], div[class*="overlay"]')) {
3042
+ return;
3043
+ }
3044
+ const sidebar = document.querySelector('div[class*="_sidebarCol"]');
3045
+ if (sidebar && sidebar.contains(e.target)) {
3046
+ if (e.target.closest('input, select, textarea, div[class*="searchInput"]')) {
3047
+ return;
3048
+ }
3049
+ const btn = e.target.closest('button, [role="button"]');
3050
+ if (btn) {
3051
+ const label = btn.getAttribute("aria-label") || btn.innerText || "";
3052
+ if (label.includes("\u64CD\u4F5C") || label.includes("\u89C6\u56FE") || label.includes("\u6DFB\u52A0") || label.includes("\u641C\u7D22") || label.includes("\u8BBE\u7F6E") || btn.matches('button[class*="toggle"], button[class*="trigger"], button[class*="iconButton"], button[class*="searchButton"]')) {
3053
+ return;
3054
+ }
3055
+ if (label.includes("\u65B0\u4F1A\u8BDD") || btn.matches('button[class*="newSession"]')) {
3056
+ document.body.classList.remove("dsh-drawer-open");
3057
+ return;
3058
+ }
3059
+ }
3060
+ const sessionRow = e.target.closest('div[class*="sessionRow"], div[role="treeitem"]');
3061
+ if (sessionRow) {
3062
+ document.body.classList.remove("dsh-drawer-open");
3063
+ }
3064
+ }
3065
+ }, true);
3066
+ let longPressTimer = null;
3067
+ let touchStartX = 0;
3068
+ let touchStartY = 0;
3069
+ window.addEventListener("touchstart", (e) => {
3070
+ if (e.touches && e.touches.length > 0) {
3071
+ touchStartX = e.touches[0].clientX;
3072
+ touchStartY = e.touches[0].clientY;
3073
+ }
3074
+ if (!document.body.classList.contains("dsh-drawer-open")) return;
3075
+ const sidebar = document.querySelector('div[class*="_sidebarCol"]');
3076
+ if (!sidebar || !sidebar.contains(e.target)) return;
3077
+ const sessionRow = e.target.closest('div[class*="sessionRow"], div[role="treeitem"]');
3078
+ if (!sessionRow) return;
3079
+ longPressTimer = setTimeout(() => {
3080
+ lastLongPressTime = Date.now();
3081
+ try {
3082
+ if (navigator.vibrate) navigator.vibrate(40);
3083
+ } catch (_) {
3084
+ }
3085
+ const actionBtn = sessionRow.querySelector('button[aria-label*="\u64CD\u4F5C"], button[class*="iconButton"], button');
3086
+ if (actionBtn) {
3087
+ actionBtn.click();
3088
+ }
3089
+ }, 380);
3090
+ }, { passive: true });
3091
+ window.addEventListener("touchmove", (e) => {
3092
+ if (longPressTimer && e.touches && e.touches.length > 0) {
3093
+ const moveX = Math.abs(e.touches[0].clientX - touchStartX);
3094
+ const moveY = Math.abs(e.touches[0].clientY - touchStartY);
3095
+ if (moveX > 10 || moveY > 10) {
3096
+ clearTimeout(longPressTimer);
3097
+ longPressTimer = null;
3098
+ }
3099
+ }
3100
+ }, { passive: true });
3101
+ window.addEventListener("touchend", (e) => {
3102
+ if (longPressTimer) {
3103
+ clearTimeout(longPressTimer);
3104
+ longPressTimer = null;
3105
+ }
3106
+ if (!e.changedTouches || e.changedTouches.length === 0) return;
3107
+ const deltaX = e.changedTouches[0].clientX - touchStartX;
3108
+ const deltaY = e.changedTouches[0].clientY - touchStartY;
3109
+ if (Math.abs(deltaX) > 60 && Math.abs(deltaX) > Math.abs(deltaY) * 1.5) {
3110
+ if (deltaX > 0 && touchStartX <= 35) {
3111
+ document.body.classList.add("dsh-drawer-open");
3112
+ const collapsedToggle = document.querySelector('div[class*="hHd-Xa_collapsed"] button[class*="hHd-Xa_toggle"]');
3113
+ if (collapsedToggle) collapsedToggle.click();
3114
+ } else if (deltaX < 0 && document.body.classList.contains("dsh-drawer-open")) {
3115
+ document.body.classList.remove("dsh-drawer-open");
3116
+ }
3117
+ }
3118
+ }, { passive: true });
3119
+ }
2445
3120
  function apply(ctx) {
3121
+ setupMobileExperience();
2446
3122
  const rpcCall = (endpoint, payload, signal) => ctx.connection.rpc.call(BRIDGE_RPC_CHANNEL, endpoint, payload, signal);
2447
3123
  ctx.slots.inject(
2448
3124
  "settings.section",