@khorsheed/dsh-mobile 0.1.0 → 0.1.2
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/CHANGELOG.md +20 -0
- package/README.en.md +118 -0
- package/README.i18n.yaml +3 -3
- package/README.md +65 -79
- package/lib/client.js +287 -41
- package/lib/index.js +30 -2
- package/lib/types/client/ConnectPhone.js +42 -3
- package/lib/types/client/MobileChrome.js +1 -1
- package/lib/types/client/MobileLibrary.d.ts +2 -1
- package/lib/types/client/MobileLibrary.js +34 -15
- package/lib/types/client/MobileTools.d.ts +13 -3
- package/lib/types/client/MobileTools.js +7 -5
- package/lib/types/client/catalogReads.d.ts +10 -0
- package/lib/types/client/catalogReads.js +28 -0
- package/lib/types/client/commands.d.ts +18 -0
- package/lib/types/client/commands.js +6 -0
- package/lib/types/client/composerActions.d.ts +3 -3
- package/lib/types/client/composerActions.js +18 -9
- package/lib/types/client/index.js +8 -1
- package/lib/types/client/locales.d.ts +1 -0
- package/lib/types/client/locales.js +2 -0
- package/lib/types/client/sheetGestures.d.ts +3 -0
- package/lib/types/client/sheetGestures.js +81 -0
- package/lib/types/client/styles.d.ts +2 -2
- package/lib/types/client/styles.js +26 -7
- package/lib/types/client/surface.js +7 -4
- package/lib/types/connect.d.ts +1 -1
- package/lib/types/connect.js +8 -2
- package/lib/types/protocol.d.ts +1 -1
- package/lib/types/reachability.d.ts +5 -0
- package/lib/types/reachability.js +14 -0
- package/package.json +4 -3
- package/README.zh.md +0 -132
package/lib/client.js
CHANGED
|
@@ -2603,9 +2603,16 @@ window.__ModuleLoader__.load({
|
|
|
2603
2603
|
body: "{}"
|
|
2604
2604
|
} : {}
|
|
2605
2605
|
});
|
|
2606
|
-
if (!response.ok) throw new Error("unavailable");
|
|
2607
2606
|
const result = await response.json();
|
|
2608
2607
|
if (controller.signal.aborted) return;
|
|
2608
|
+
if (!response.ok) {
|
|
2609
|
+
if (result.state === "unreachable") {
|
|
2610
|
+
setInfo(result);
|
|
2611
|
+
return;
|
|
2612
|
+
}
|
|
2613
|
+
throw new Error("unavailable");
|
|
2614
|
+
}
|
|
2615
|
+
if (controller.signal.aborted) return;
|
|
2609
2616
|
if (generate) {
|
|
2610
2617
|
const url = new URL(result.loginUrl);
|
|
2611
2618
|
if (!info?.origin || url.origin !== info.origin || url.protocol !== "https:" || !url.searchParams.get("token")) throw new Error("invalid");
|
|
@@ -2632,6 +2639,38 @@ window.__ModuleLoader__.load({
|
|
|
2632
2639
|
window.removeEventListener("pagehide", hide);
|
|
2633
2640
|
};
|
|
2634
2641
|
}, []);
|
|
2642
|
+
(0, react.useEffect)(() => {
|
|
2643
|
+
if (!login) return;
|
|
2644
|
+
const controller = new AbortController();
|
|
2645
|
+
let checking = false;
|
|
2646
|
+
const poll = setInterval(async () => {
|
|
2647
|
+
if (checking) return;
|
|
2648
|
+
checking = true;
|
|
2649
|
+
try {
|
|
2650
|
+
const response = await fetch(CONNECT_PATH, {
|
|
2651
|
+
credentials: "same-origin",
|
|
2652
|
+
cache: "no-store",
|
|
2653
|
+
signal: controller.signal
|
|
2654
|
+
});
|
|
2655
|
+
if (!response.ok) throw new Error("unavailable");
|
|
2656
|
+
const next = await response.json();
|
|
2657
|
+
if (controller.signal.aborted) return;
|
|
2658
|
+
setInfo(next);
|
|
2659
|
+
if (next.state !== "ready" || next.origin !== new URL(login).origin) hide();
|
|
2660
|
+
} catch {
|
|
2661
|
+
if (!controller.signal.aborted) {
|
|
2662
|
+
hide();
|
|
2663
|
+
setFailed(true);
|
|
2664
|
+
}
|
|
2665
|
+
} finally {
|
|
2666
|
+
checking = false;
|
|
2667
|
+
}
|
|
2668
|
+
}, 15e3);
|
|
2669
|
+
return () => {
|
|
2670
|
+
controller.abort();
|
|
2671
|
+
clearInterval(poll);
|
|
2672
|
+
};
|
|
2673
|
+
}, [login]);
|
|
2635
2674
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2636
2675
|
"data-mobile-connect": true,
|
|
2637
2676
|
children: [
|
|
@@ -2644,7 +2683,7 @@ window.__ModuleLoader__.load({
|
|
|
2644
2683
|
}),
|
|
2645
2684
|
info && info.state !== "ready" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2646
2685
|
role: "status",
|
|
2647
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t(info.state === "untrusted-origin" ? "connectUntrusted" : info.state === "unsupported" ? "connectUnsupported" : "connectSetup") })
|
|
2686
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t(info.state === "unreachable" ? "connectUnreachable" : info.state === "untrusted-origin" ? "connectUntrusted" : info.state === "unsupported" ? "connectUnsupported" : "connectSetup") })
|
|
2648
2687
|
}),
|
|
2649
2688
|
failed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2650
2689
|
role: "alert",
|
|
@@ -2929,8 +2968,12 @@ html[data-dsh-mobile] [data-slot="conversation.input.right"] { max-width: 100%;
|
|
|
2929
2968
|
[data-mobile-dialog] button[aria-pressed="true"] { color: #4d6bfe; background: #4d6bfe12; }
|
|
2930
2969
|
[data-mobile-dialog] [data-mobile-done] { display: block; width: 100%; margin-top: 24px; background: #4d6bfe; color: white; border-radius: 100px; }
|
|
2931
2970
|
|
|
2971
|
+
/* The host trigger menu is translucent; mobile overlays need an opaque face. */
|
|
2972
|
+
html[data-dsh-mobile] [data-trigger-menu] { background:var(--mobile-face,var(--dsw-alias-bg-base))!important; --dsw-specific-menu:var(--mobile-face,var(--dsw-alias-bg-base)); opacity:1; backdrop-filter:none; -webkit-backdrop-filter:none; border:1px solid var(--mobile-edge); padding:8px; border-radius:22px; box-shadow:var(--mobile-shadow); }
|
|
2973
|
+
html[data-dsh-mobile] [data-trigger-menu] [role=option] { min-height:48px; padding:12px; font-size:16px; line-height:1.45; }
|
|
2974
|
+
html[data-dsh-mobile] [data-trigger-menu] [role=listbox] { overscroll-behavior:contain; }
|
|
2932
2975
|
/* Hide an empty ordinary breadcrumb; populated catalogs keep their Host trigger. */
|
|
2933
|
-
html[data-dsh-mobile]:has([data-mobile-session-title]) [data-slot="main.conversation"] header:has([data-conversation-header-corner]) nav:has(> span:only-child > button:disabled + [data-slot="conversation.session.header.lineage"]:empty) { display:none; }
|
|
2976
|
+
html[data-dsh-mobile]:has([data-mobile-session-title]) [data-slot="main.conversation"] header:has([data-conversation-header-corner]) nav:has(> span:only-child > :is(button:disabled,span:not([data-slot])) + [data-slot="conversation.session.header.lineage"]:empty) { display:none; }
|
|
2934
2977
|
html[data-dsh-mobile] [data-mobile-lineage-count] { flex-shrink:0; white-space:nowrap; }
|
|
2935
2978
|
[data-mobile-restore] { position:fixed; right:16px; bottom:max(16px,env(safe-area-inset-bottom)); z-index:1200; pointer-events:auto; min-height:44px; padding:10px 18px; border:1px solid #4d6bfe40; border-radius:24px; background:var(--dsw-alias-bg-base,white); color:#4d6bfe; font:inherit; box-shadow:0 3px 18px #15244318; }
|
|
2936
2979
|
|
|
@@ -3067,17 +3110,17 @@ html[data-dsh-mobile]:has([data-mobile-session-title]) [data-mobile-header-hidde
|
|
|
3067
3110
|
[data-mobile-folded-action] { display:none!important; }
|
|
3068
3111
|
[data-mobile-tools-seat] { display:contents; }
|
|
3069
3112
|
html[data-dsh-mobile] [data-mobile-tools-open] { display:grid; place-items:center; width:44px; height:44px; min-width:44px; padding:0; border:0; border-radius:50%; corner-shape:round; color:var(--dsw-alias-label-primary); background:var(--mobile-soft); }
|
|
3070
|
-
[data-mobile-tools-dialog] { box-sizing:border-box; margin:auto auto 0; width:min(100%,520px); max-width:100%; max-height:75dvh; overflow-y:auto; padding:16px 20px max(20px,env(safe-area-inset-bottom)); border:1px solid var(--mobile-line); border-bottom:0; border-radius:26px 26px 0 0; background:var(--mobile-face); color:var(--dsw-alias-label-primary); box-shadow:0 -8px 40px #0002; }
|
|
3113
|
+
[data-mobile-tools-dialog] { box-sizing:border-box; margin:auto auto 0; width:min(100%,520px); max-width:100%; max-height:75dvh; overscroll-behavior:contain; overflow-y:auto; padding:16px 20px max(20px,env(safe-area-inset-bottom)); border:1px solid var(--mobile-line); border-bottom:0; border-radius:26px 26px 0 0; background:var(--mobile-face); color:var(--dsw-alias-label-primary); box-shadow:0 -8px 40px #0002; }
|
|
3071
3114
|
[data-mobile-tools-dialog]::backdrop { background:#0005; }
|
|
3072
|
-
[data-mobile-tools-dialog] [data-mobile-tools-handle] { width:34px; height:4px; margin:0 auto 12px; border-radius:4px; background:var(--dsw-alias-label-tertiary); opacity:.5; }
|
|
3073
|
-
[data-mobile-tools-dialog] header { display:flex; align-items:center; justify-content:space-between; margin-bottom:12px; }
|
|
3074
|
-
[data-mobile-tools-dialog] strong { font-size:
|
|
3115
|
+
[data-mobile-tools-dialog] [data-mobile-tools-handle] { width:34px; height:4px; margin:0 auto 12px; touch-action:pan-x; border-radius:4px; background:var(--dsw-alias-label-tertiary); opacity:.5; }
|
|
3116
|
+
[data-mobile-tools-dialog] header { touch-action:pan-x; display:flex; align-items:center; justify-content:space-between; margin-bottom:12px; }
|
|
3117
|
+
[data-mobile-tools-dialog] strong { font-size:20px; font-weight:600; }
|
|
3075
3118
|
[data-mobile-tools-dialog] button { font:inherit; color:inherit; cursor:pointer; border:0; background:transparent; -webkit-tap-highlight-color:transparent; }
|
|
3076
3119
|
[data-mobile-tools-dialog] button:disabled { opacity:.45; cursor:default; }
|
|
3077
3120
|
[data-mobile-tools-dialog] header button { width:44px; height:44px; display:grid; place-items:center; border-radius:50%; padding:0; }
|
|
3078
3121
|
[data-mobile-tools-grid] { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
|
|
3079
|
-
[data-mobile-tools-grid] button { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px; min-height:
|
|
3080
|
-
[data-mobile-tools-permission] { display:flex; align-items:center; width:100%; gap:10px; margin-top:12px; min-height:64px; padding:12px 0; text-align:left; font-size:14px!important; }
|
|
3122
|
+
[data-mobile-tools-grid] button { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px; min-height:96px; padding:16px; border-radius:18px; background:var(--mobile-soft); font-size:14px; }
|
|
3123
|
+
[data-mobile-tools-permission] { display:flex; align-items:center; width:100%; gap:10px; margin-top:12px; border-top:1px solid var(--mobile-line)!important; min-height:64px; padding:12px 0; text-align:left; font-size:14px!important; }
|
|
3081
3124
|
[data-mobile-tools-permission] > span { flex-shrink:0; }
|
|
3082
3125
|
[data-mobile-tools-permission] > small { margin-left:auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; color:var(--dsw-alias-label-secondary); }
|
|
3083
3126
|
[data-mobile-tools-dialog] svg { flex:none; }
|
|
@@ -3186,6 +3229,12 @@ html[data-dsh-mobile] [data-mobile-room-form] :is(input,textarea,select) { box-s
|
|
|
3186
3229
|
html[data-dsh-mobile] [data-mobile-room-form] textarea { min-height:94px; resize:vertical; }
|
|
3187
3230
|
html[data-dsh-mobile] [data-mobile-room-form] button { min-height:44px; min-width:44px; font-size:16px; border-radius:14px; }
|
|
3188
3231
|
html[data-dsh-mobile] [data-mobile-room-form] summary { min-height:44px; padding:10px 0; font-size:16px; color:var(--dsw-alias-label-primary); }
|
|
3232
|
+
/* Harness model controls are expandable summaries, not native selects. Keep the
|
|
3233
|
+
advanced-settings disclosure plain while aligning picker faces with form fields. */
|
|
3234
|
+
html[data-dsh-mobile] [data-mobile-room-form] details > summary[aria-expanded] { display:flex; align-items:center; box-sizing:border-box; width:100%; max-width:none; min-width:0; min-height:48px; gap:12px; padding:11px 12px; border:1px solid var(--mobile-edge); border-radius:14px; background:var(--mobile-soft); font-size:17px; line-height:1.5; color:var(--dsw-alias-label-primary); }
|
|
3235
|
+
html[data-dsh-mobile] [data-mobile-room-form] details > summary[aria-expanded] > span { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
3236
|
+
html[data-dsh-mobile] [data-mobile-room-form] details > summary[aria-expanded]::after { flex-shrink:0; margin-inline-start:auto; margin-inline-end:2px; }
|
|
3237
|
+
|
|
3189
3238
|
html[data-dsh-mobile] [data-mobile-room-form] > div:last-child { position:sticky; bottom:-16px; background:var(--mobile-face); padding:12px 0 0; margin-top:0; display:grid; grid-template-columns:1fr 1fr; gap:12px; }
|
|
3190
3239
|
html[data-dsh-mobile] [data-mobile-room-form] [role=alert] { font-size:15px; line-height:1.5; }
|
|
3191
3240
|
html[data-dsh-mobile] [data-member] { font-size:16px; }
|
|
@@ -3246,6 +3295,15 @@ html[data-dsh-mobile] [data-mobile-picker] { position:fixed!important; inset:aut
|
|
|
3246
3295
|
html[data-dsh-mobile] [data-mobile-picker] > [role=presentation] { max-height:none; overflow:visible; }
|
|
3247
3296
|
html[data-dsh-mobile] [data-mobile-picker] [role=menuitem] { min-height:52px; padding:12px; border-radius:14px; font-size:17px; }
|
|
3248
3297
|
html[data-dsh-mobile] [data-mobile-picker] [role=menuitem][hidden] { display:none; }
|
|
3298
|
+
/* Keep the workspace list inside its flex viewport: visible overflow used to
|
|
3299
|
+
* paint the last folder over the pinned add-action divider on short screens. */
|
|
3300
|
+
html[data-dsh-mobile] [data-mobile-picker=workspace] { overflow:hidden; }
|
|
3301
|
+
html[data-dsh-mobile] [data-mobile-picker=workspace] > [data-mobile-picker-chrome] { flex:none; }
|
|
3302
|
+
html[data-dsh-mobile] [data-mobile-picker=workspace] > [role=presentation]:nth-child(2) { flex:1 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:contain; }
|
|
3303
|
+
html[data-dsh-mobile] [data-mobile-picker=workspace] > [role=presentation] > * { flex:none; }
|
|
3304
|
+
html[data-dsh-mobile] [data-mobile-picker=workspace] > [role=presentation]:nth-child(3):last-child { flex:none; margin-top:8px; padding-top:8px; border-top:1px solid var(--mobile-edge); }
|
|
3305
|
+
html[data-dsh-mobile] [data-mobile-picker=workspace] [role=menuitem] { box-sizing:border-box; min-height:52px; padding:12px; line-height:28px; }
|
|
3306
|
+
|
|
3249
3307
|
html[data-dsh-mobile] [data-mobile-picker=preset] [role=menuitem] span > span:last-child { font-size:14px; line-height:1.55; color:var(--dsw-alias-label-secondary); }
|
|
3250
3308
|
|
|
3251
3309
|
html[data-mobile-gesture] [data-mobile-library-pane][hidden] { display:block; }
|
|
@@ -3327,13 +3385,13 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
3327
3385
|
this.clearMarks();
|
|
3328
3386
|
const root = frame?.querySelector("[data-slot=\"main.conversation\"] > [data-phase]");
|
|
3329
3387
|
const row = root?.querySelector("header [data-conversation-header-corner]")?.parentElement;
|
|
3330
|
-
const ordinary = row?.querySelector("nav > span:only-child > button:disabled + [data-slot=\"conversation.session.header.lineage\"]:empty");
|
|
3388
|
+
const ordinary = row?.querySelector("nav > span:only-child > :is(button:disabled,span:not([data-slot])) + [data-slot=\"conversation.session.header.lineage\"]:empty");
|
|
3331
3389
|
let preset = "";
|
|
3332
3390
|
if (row) {
|
|
3333
3391
|
for (const button of row.querySelectorAll("[data-slot=\"conversation.session.header.actions\"] button[aria-label]")) if (/^(重命名会话|Rename session)$/.test(button.getAttribute("aria-label") ?? "")) this.mark(button, "data-mobile-original-rename");
|
|
3334
3392
|
}
|
|
3335
3393
|
if (root?.dataset.phase === "active" && row) {
|
|
3336
|
-
for (const title of row.querySelectorAll("nav > span > button:disabled")) {
|
|
3394
|
+
for (const title of row.querySelectorAll("nav > span > :is(button:disabled,span:not([data-slot]))")) {
|
|
3337
3395
|
const slot = title.nextElementSibling;
|
|
3338
3396
|
if (slot?.getAttribute("data-slot") !== "conversation.session.header.lineage") continue;
|
|
3339
3397
|
const count = slot.querySelector(":scope > div:only-child > button[aria-haspopup=\"tree\"]");
|
|
@@ -3343,17 +3401,18 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
3343
3401
|
this.mark(separator, "data-mobile-header-hidden");
|
|
3344
3402
|
this.mark(count, "data-mobile-lineage-count");
|
|
3345
3403
|
}
|
|
3346
|
-
const label = row.querySelector("[data-slot=\"conversation.session.header.actions\"] > span[title]:has(svg mask[id^=\"mask0_agent_preset\"])");
|
|
3404
|
+
const label = row.querySelector("[data-slot=\"conversation.session.header.actions\"] > span[title]:has(svg :is(mask[id^=\"mask0_agent_preset\"],path[d^=\"M6.51867 12.3282\"]))");
|
|
3347
3405
|
if (label) {
|
|
3348
3406
|
preset = label.textContent?.trim() ?? "";
|
|
3349
3407
|
this.mark(label, "data-mobile-header-hidden");
|
|
3350
3408
|
}
|
|
3409
|
+
for (const target of row.querySelectorAll("[data-slot=\"conversation.session.header.utilities\"] [data-open-target=\"directory\"]")) this.mark(target, "data-mobile-header-hidden");
|
|
3351
3410
|
for (const img of row.querySelectorAll("img[src^=\"/open-in-app/icon/\"]")) {
|
|
3352
3411
|
const split = img.closest("button")?.parentElement;
|
|
3353
3412
|
if (split && split.querySelectorAll("button").length === 2) this.mark(split, "data-mobile-header-hidden");
|
|
3354
3413
|
}
|
|
3355
3414
|
for (const button of row.querySelectorAll("[data-sidebar-right-expand], [data-slot=\"conversation.session.header.utilities\"] > span > button[aria-haspopup=\"menu\"][aria-busy]")) if (button.hasAttribute("data-sidebar-right-expand") || /^(更多操作|More actions)$/i.test(button.getAttribute("aria-label") ?? "")) this.mark(button, "data-mobile-header-hidden");
|
|
3356
|
-
if (this.doc.querySelector("[data-mobile-members-open]
|
|
3415
|
+
if (this.doc.querySelector("[data-mobile-members-open]:not([disabled])")) {
|
|
3357
3416
|
for (const button of row.querySelectorAll("[data-slot=\"conversation.session.header.actions\"] button")) if (/^(+|\+)?\s*(邀请 agent|Invite agent)$/i.test(button.textContent?.trim() ?? "")) this.mark(button, "data-mobile-header-hidden");
|
|
3358
3417
|
}
|
|
3359
3418
|
const content = row.cloneNode(true);
|
|
@@ -4092,6 +4151,19 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4092
4151
|
return null;
|
|
4093
4152
|
}
|
|
4094
4153
|
//#endregion
|
|
4154
|
+
//#region src/client/commands.ts
|
|
4155
|
+
function openMobileCommands(controller, draft, span) {
|
|
4156
|
+
if (!controller || typeof controller.toggleSource !== "function") return false;
|
|
4157
|
+
controller.toggleSource("command", {
|
|
4158
|
+
trigger: "/",
|
|
4159
|
+
query: "",
|
|
4160
|
+
quoted: false,
|
|
4161
|
+
position: draft.slice(0, span.start).trim() === "" ? "leading" : "inline",
|
|
4162
|
+
span
|
|
4163
|
+
});
|
|
4164
|
+
return true;
|
|
4165
|
+
}
|
|
4166
|
+
//#endregion
|
|
4095
4167
|
//#region src/client/composerActions.ts
|
|
4096
4168
|
var ComposerActions = class {
|
|
4097
4169
|
seat;
|
|
@@ -4108,7 +4180,11 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4108
4180
|
subtree: true,
|
|
4109
4181
|
characterData: true,
|
|
4110
4182
|
attributes: true,
|
|
4111
|
-
attributeFilter: [
|
|
4183
|
+
attributeFilter: [
|
|
4184
|
+
"disabled",
|
|
4185
|
+
"aria-label",
|
|
4186
|
+
"contenteditable"
|
|
4187
|
+
]
|
|
4112
4188
|
});
|
|
4113
4189
|
this.sync();
|
|
4114
4190
|
}
|
|
@@ -4120,21 +4196,25 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4120
4196
|
};
|
|
4121
4197
|
};
|
|
4122
4198
|
sync() {
|
|
4123
|
-
const
|
|
4124
|
-
const
|
|
4125
|
-
const
|
|
4199
|
+
const card = this.seat.closest("[data-composer-card]");
|
|
4200
|
+
const file = card?.querySelector(":scope > div:last-child > div:first-child > input[type=file][hidden]");
|
|
4201
|
+
const previous = file?.previousElementSibling;
|
|
4202
|
+
const unified = previous instanceof HTMLButtonElement && previous.getAttribute("aria-haspopup") === "listbox";
|
|
4203
|
+
const attachments = unified ? file : previous;
|
|
4204
|
+
const commands = unified ? previous : previous?.previousElementSibling;
|
|
4126
4205
|
const next = {};
|
|
4127
|
-
if (commands instanceof HTMLButtonElement && commands.getAttribute("aria-haspopup") === "listbox" && attachments instanceof HTMLButtonElement) {
|
|
4206
|
+
if (commands instanceof HTMLButtonElement && commands.getAttribute("aria-haspopup") === "listbox" && (attachments instanceof HTMLButtonElement || attachments instanceof HTMLInputElement)) {
|
|
4128
4207
|
next.commands = this.target(commands);
|
|
4129
4208
|
next.attachments = this.target(attachments);
|
|
4130
|
-
|
|
4209
|
+
if (unified) next.attachments.disabled ||= commands.disabled || !card?.querySelector("[contenteditable=\"true\"]");
|
|
4210
|
+
const permission = (file?.nextElementSibling)?.querySelector("[data-slot=\"conversation.input.permission\"] button[aria-label], :scope > span > button[aria-label]");
|
|
4131
4211
|
if (permission?.querySelector(":scope > span:not([aria-hidden])") && permission.querySelector(":scope > span[aria-hidden] svg")) next.permissions = this.target(permission);
|
|
4132
4212
|
}
|
|
4133
4213
|
for (const button of this.marked) if (!Object.values(next).some((value) => value.button === button)) {
|
|
4134
4214
|
button.removeAttribute("data-mobile-folded-action");
|
|
4135
4215
|
this.marked.delete(button);
|
|
4136
4216
|
}
|
|
4137
|
-
for (const value of Object.values(next)) if (!this.marked.has(value.button)) {
|
|
4217
|
+
for (const value of Object.values(next)) if (value.button instanceof HTMLButtonElement && !this.marked.has(value.button)) {
|
|
4138
4218
|
value.button.setAttribute("data-mobile-folded-action", "");
|
|
4139
4219
|
this.marked.add(value.button);
|
|
4140
4220
|
}
|
|
@@ -4154,10 +4234,11 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4154
4234
|
disabled: button.disabled
|
|
4155
4235
|
};
|
|
4156
4236
|
}
|
|
4157
|
-
invoke(action) {
|
|
4237
|
+
invoke(action, openCommands) {
|
|
4158
4238
|
this.sync();
|
|
4159
4239
|
const target = this.snapshot[action];
|
|
4160
4240
|
if (!target || target.disabled || !target.button.isConnected) return false;
|
|
4241
|
+
if (action === "commands" && openCommands?.()) return true;
|
|
4161
4242
|
if (action === "commands") this.seat.closest("[data-composer-card]")?.querySelector("[contenteditable=\"true\"]")?.focus({ preventScroll: true });
|
|
4162
4243
|
target.button.click();
|
|
4163
4244
|
return true;
|
|
@@ -4447,8 +4528,9 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4447
4528
|
//#endregion
|
|
4448
4529
|
//#region src/client/MobileTools.tsx
|
|
4449
4530
|
/** Own only the launcher and grouped navigation; action targets stay host-owned. */
|
|
4450
|
-
function MobileTools({ t, useSession, rooms }) {
|
|
4531
|
+
function MobileTools({ t, useSession, useInput, inputActions, rooms, openCommands }) {
|
|
4451
4532
|
const sessionId = useSession((s) => s.sessionId);
|
|
4533
|
+
const draft = useInput((s) => s.draft);
|
|
4452
4534
|
const seat = (0, react.useRef)(null);
|
|
4453
4535
|
const [actions, setActions] = (0, react.useState)();
|
|
4454
4536
|
(0, react.useLayoutEffect)(() => {
|
|
@@ -4463,11 +4545,12 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4463
4545
|
children: actions && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tools, {
|
|
4464
4546
|
actions,
|
|
4465
4547
|
t,
|
|
4548
|
+
openCommands: openCommands ? () => openCommands(sessionId, draft, inputActions.captureInsertion()) : void 0,
|
|
4466
4549
|
invite: rooms.available() ? () => openMobileMembers(sessionId, true) : void 0
|
|
4467
4550
|
})
|
|
4468
4551
|
});
|
|
4469
4552
|
}
|
|
4470
|
-
function Tools({ actions, t, invite }) {
|
|
4553
|
+
function Tools({ actions, t, invite, openCommands }) {
|
|
4471
4554
|
const targets = (0, react.useSyncExternalStore)(actions.subscribe, actions.getSnapshot);
|
|
4472
4555
|
const dialog = (0, react.useRef)(null), launcher = (0, react.useRef)(null);
|
|
4473
4556
|
const close = () => {
|
|
@@ -4480,7 +4563,7 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4480
4563
|
if (!targets.commands || !targets.attachments) return null;
|
|
4481
4564
|
const invoke = (action) => {
|
|
4482
4565
|
close();
|
|
4483
|
-
actions.invoke(action);
|
|
4566
|
+
actions.invoke(action, openCommands);
|
|
4484
4567
|
};
|
|
4485
4568
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4486
4569
|
ref: launcher,
|
|
@@ -4489,6 +4572,8 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4489
4572
|
"aria-haspopup": "dialog",
|
|
4490
4573
|
"aria-expanded": "false",
|
|
4491
4574
|
onClick: () => {
|
|
4575
|
+
if (document.activeElement instanceof HTMLElement) document.activeElement.blur();
|
|
4576
|
+
launcher.current?.focus({ preventScroll: true });
|
|
4492
4577
|
dialog.current?.showModal();
|
|
4493
4578
|
launcher.current?.setAttribute("aria-expanded", "true");
|
|
4494
4579
|
},
|
|
@@ -4772,6 +4857,42 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4772
4857
|
}), document.body);
|
|
4773
4858
|
}
|
|
4774
4859
|
//#endregion
|
|
4860
|
+
//#region src/client/catalogReads.ts
|
|
4861
|
+
/** Bound optional catalog enrichment so cold log reads cannot fan out across the library. */
|
|
4862
|
+
var CatalogReads = class {
|
|
4863
|
+
limit;
|
|
4864
|
+
pending = [];
|
|
4865
|
+
running = 0;
|
|
4866
|
+
constructor(limit = 2) {
|
|
4867
|
+
this.limit = limit;
|
|
4868
|
+
}
|
|
4869
|
+
add(work, priority = 1) {
|
|
4870
|
+
const item = {
|
|
4871
|
+
cancelled: false,
|
|
4872
|
+
priority,
|
|
4873
|
+
work
|
|
4874
|
+
};
|
|
4875
|
+
this.pending.push(item);
|
|
4876
|
+
this.pending.sort((a, b) => a.priority - b.priority);
|
|
4877
|
+
this.pump();
|
|
4878
|
+
return () => {
|
|
4879
|
+
item.cancelled = true;
|
|
4880
|
+
this.pending = this.pending.filter((entry) => entry !== item);
|
|
4881
|
+
};
|
|
4882
|
+
}
|
|
4883
|
+
pump() {
|
|
4884
|
+
while (this.running < this.limit && this.pending.length) {
|
|
4885
|
+
const item = this.pending.shift();
|
|
4886
|
+
if (item.cancelled) continue;
|
|
4887
|
+
this.running++;
|
|
4888
|
+
Promise.resolve().then(() => item.cancelled ? void 0 : item.work(() => !item.cancelled)).catch(() => {}).finally(() => {
|
|
4889
|
+
this.running--;
|
|
4890
|
+
this.pump();
|
|
4891
|
+
});
|
|
4892
|
+
}
|
|
4893
|
+
}
|
|
4894
|
+
};
|
|
4895
|
+
//#endregion
|
|
4775
4896
|
//#region src/client/native.ts
|
|
4776
4897
|
/** Additive bridge features: older shells and ordinary browsers retain Web controls. */
|
|
4777
4898
|
function hasNativeAction(action) {
|
|
@@ -4793,7 +4914,8 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4793
4914
|
//#endregion
|
|
4794
4915
|
//#region src/client/MobileLibrary.tsx
|
|
4795
4916
|
/** Browse official metadata; the official workspace service remains the only navigation writer. */
|
|
4796
|
-
function MobileLibrary({ rooms, navigation, onOpen, onBeforeOpen, t }) {
|
|
4917
|
+
function MobileLibrary({ rooms, navigation, onOpen, onBeforeOpen, visible = true, t }) {
|
|
4918
|
+
const reads = (0, react.useMemo)(() => new CatalogReads(), [navigation.sessions]);
|
|
4797
4919
|
const sessionFeed = navigation.sessions.list, workspaceFeed = navigation.workspaces.list;
|
|
4798
4920
|
const sessions = (0, react.useSyncExternalStore)((0, react.useCallback)((listener) => sessionFeed.subscribe(listener), [sessionFeed]), (0, react.useCallback)(() => sessionFeed.getSnapshot(), [sessionFeed]));
|
|
4799
4921
|
const workspaces = (0, react.useSyncExternalStore)((0, react.useCallback)((listener) => workspaceFeed.subscribe(listener), [workspaceFeed]), (0, react.useCallback)(() => workspaceFeed.getSnapshot(), [workspaceFeed]));
|
|
@@ -4891,8 +5013,10 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4891
5013
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4892
5014
|
"data-mobile-session-copy": true,
|
|
4893
5015
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: row.title || (row.blank ? t("newSession") : row.displayTitle) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionMetadata, {
|
|
4894
|
-
|
|
4895
|
-
|
|
5016
|
+
row,
|
|
5017
|
+
sessions: navigation.sessions,
|
|
5018
|
+
reads,
|
|
5019
|
+
visible,
|
|
4896
5020
|
...rooms ? { rooms } : {},
|
|
4897
5021
|
t
|
|
4898
5022
|
})]
|
|
@@ -4950,29 +5074,56 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
4950
5074
|
}
|
|
4951
5075
|
const emptyRoomSubscribe = () => () => {};
|
|
4952
5076
|
const emptyRoomSnapshot = () => 0;
|
|
4953
|
-
function SessionMetadata({
|
|
5077
|
+
function SessionMetadata({ row, sessions, reads, visible, rooms, t }) {
|
|
4954
5078
|
const ref = (0, react.useRef)(null);
|
|
4955
5079
|
(0, react.useSyncExternalStore)(rooms?.subscribe ?? emptyRoomSubscribe, rooms?.getSnapshot ?? emptyRoomSnapshot);
|
|
4956
5080
|
(0, react.useEffect)(() => {
|
|
4957
|
-
if (!
|
|
5081
|
+
if (!visible || !ref.current) return;
|
|
5082
|
+
let cancel;
|
|
5083
|
+
const enter = () => {
|
|
5084
|
+
if (cancel) return;
|
|
5085
|
+
const cancelTitle = !row.title && !row.blank && typeof sessions.refreshProjections === "function" ? reads.add(async () => {
|
|
5086
|
+
await sessions.refreshProjections(row.id);
|
|
5087
|
+
}, 0) : void 0;
|
|
5088
|
+
const cancelRoom = rooms?.available() ? reads.add(async () => {
|
|
5089
|
+
await rooms.refresh(row.id);
|
|
5090
|
+
}) : void 0;
|
|
5091
|
+
cancel = () => {
|
|
5092
|
+
cancelTitle?.();
|
|
5093
|
+
cancelRoom?.();
|
|
5094
|
+
};
|
|
5095
|
+
};
|
|
5096
|
+
const leave = () => {
|
|
5097
|
+
cancel?.();
|
|
5098
|
+
cancel = void 0;
|
|
5099
|
+
};
|
|
4958
5100
|
if (typeof IntersectionObserver === "undefined") {
|
|
4959
|
-
|
|
4960
|
-
return;
|
|
5101
|
+
enter();
|
|
5102
|
+
return leave;
|
|
4961
5103
|
}
|
|
4962
5104
|
const observer = new IntersectionObserver((entries) => {
|
|
4963
|
-
if (entries.some((
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
}
|
|
4967
|
-
});
|
|
5105
|
+
if (entries.some((entry) => entry.isIntersecting)) enter();
|
|
5106
|
+
else leave();
|
|
5107
|
+
}, { root: ref.current.closest("[data-mobile-recents]") });
|
|
4968
5108
|
observer.observe(ref.current);
|
|
4969
|
-
return () =>
|
|
4970
|
-
|
|
4971
|
-
|
|
5109
|
+
return () => {
|
|
5110
|
+
observer.disconnect();
|
|
5111
|
+
leave();
|
|
5112
|
+
};
|
|
5113
|
+
}, [
|
|
5114
|
+
rooms,
|
|
5115
|
+
row.id,
|
|
5116
|
+
row.title,
|
|
5117
|
+
row.blank,
|
|
5118
|
+
sessions,
|
|
5119
|
+
reads,
|
|
5120
|
+
visible
|
|
5121
|
+
]);
|
|
5122
|
+
const state = rooms?.get(row.id), count = state ? state.members.length : state === null || !rooms?.available() ? 1 : "…";
|
|
4972
5123
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("small", {
|
|
4973
5124
|
ref,
|
|
4974
5125
|
children: [
|
|
4975
|
-
cwd.split(/[\\/]/).filter(Boolean).at(-1) || t("unassignedWorkspace"),
|
|
5126
|
+
(row.cwd ?? "").split(/[\\/]/).filter(Boolean).at(-1) || t("unassignedWorkspace"),
|
|
4976
5127
|
" · ",
|
|
4977
5128
|
count,
|
|
4978
5129
|
" ",
|
|
@@ -5186,6 +5337,7 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
5186
5337
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MobileLibrary, {
|
|
5187
5338
|
...rooms ? { rooms } : {},
|
|
5188
5339
|
navigation: available,
|
|
5340
|
+
visible: library || wide,
|
|
5189
5341
|
t,
|
|
5190
5342
|
onBeforeOpen: presentation.prepareNavigation,
|
|
5191
5343
|
onOpen: () => setLibrary(false)
|
|
@@ -5581,6 +5733,90 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
5581
5733
|
}
|
|
5582
5734
|
};
|
|
5583
5735
|
//#endregion
|
|
5736
|
+
//#region src/client/sheetGestures.ts
|
|
5737
|
+
/** Mobile dismissal delegates to native dialog.close or the Room owner's close handler. */
|
|
5738
|
+
function installSheetGestures(doc) {
|
|
5739
|
+
let drag;
|
|
5740
|
+
const reset = () => {
|
|
5741
|
+
if (drag) drag.sheet.style.transform = drag.transform;
|
|
5742
|
+
drag = void 0;
|
|
5743
|
+
};
|
|
5744
|
+
const close = (overlay) => {
|
|
5745
|
+
if (overlay.isConnected && doc.documentElement.hasAttribute("data-dsh-mobile")) {
|
|
5746
|
+
if (overlay instanceof HTMLDialogElement) {
|
|
5747
|
+
overlay.close();
|
|
5748
|
+
return;
|
|
5749
|
+
}
|
|
5750
|
+
overlay.dispatchEvent(new MouseEvent("mousedown", {
|
|
5751
|
+
bubbles: true,
|
|
5752
|
+
cancelable: true
|
|
5753
|
+
}));
|
|
5754
|
+
}
|
|
5755
|
+
};
|
|
5756
|
+
const down = (event) => {
|
|
5757
|
+
reset();
|
|
5758
|
+
if (!doc.documentElement.hasAttribute("data-dsh-mobile") || event.touches.length !== 1) return;
|
|
5759
|
+
const target = event.target;
|
|
5760
|
+
const native = target.closest("dialog[data-mobile-tools-dialog][open]");
|
|
5761
|
+
const overlay = native ?? target.closest("[data-mobile-room-overlay]");
|
|
5762
|
+
const sheet = native ?? overlay?.querySelector("[data-mobile-room-form]");
|
|
5763
|
+
if (!sheet || !overlay || !native && doc.querySelector("dialog[open]") || doc.getSelection()?.toString()) return;
|
|
5764
|
+
const point = event.touches[0], bounds = sheet.getBoundingClientRect();
|
|
5765
|
+
const outside = native ? target === native && (point.clientY < bounds.top || point.clientY > bounds.bottom || point.clientX < bounds.left || point.clientX > bounds.right) : target === overlay;
|
|
5766
|
+
if (!outside && (sheet.scrollTop > 0 || point.clientY > sheet.getBoundingClientRect().top + 96 || target.closest("button,input,textarea,select,details,a,[contenteditable],[role=menu]"))) return;
|
|
5767
|
+
drag = {
|
|
5768
|
+
sheet,
|
|
5769
|
+
overlay,
|
|
5770
|
+
x: point.clientX,
|
|
5771
|
+
y: point.clientY,
|
|
5772
|
+
outside,
|
|
5773
|
+
dragging: false,
|
|
5774
|
+
transform: sheet.style.transform
|
|
5775
|
+
};
|
|
5776
|
+
};
|
|
5777
|
+
const move = (event) => {
|
|
5778
|
+
if (!drag) return;
|
|
5779
|
+
if (event.touches.length !== 1 || !drag.sheet.isConnected || !doc.documentElement.hasAttribute("data-dsh-mobile")) {
|
|
5780
|
+
reset();
|
|
5781
|
+
return;
|
|
5782
|
+
}
|
|
5783
|
+
const dx = event.touches[0].clientX - drag.x, dy = event.touches[0].clientY - drag.y;
|
|
5784
|
+
if (drag.outside) {
|
|
5785
|
+
if (Math.hypot(dx, dy) > 10) reset();
|
|
5786
|
+
return;
|
|
5787
|
+
}
|
|
5788
|
+
if (!drag.dragging && (dy < -10 || Math.abs(dx) > 12 && Math.abs(dx) > Math.abs(dy))) {
|
|
5789
|
+
reset();
|
|
5790
|
+
return;
|
|
5791
|
+
}
|
|
5792
|
+
if (!drag.dragging && (dy < 12 || dy < Math.abs(dx) * 1.5)) return;
|
|
5793
|
+
drag.dragging = true;
|
|
5794
|
+
drag.sheet.style.transform = `translate3d(0,${Math.max(0, dy)}px,0)`;
|
|
5795
|
+
if (event.cancelable) event.preventDefault();
|
|
5796
|
+
};
|
|
5797
|
+
const up = (event) => {
|
|
5798
|
+
const from = drag, point = event.changedTouches[0];
|
|
5799
|
+
reset();
|
|
5800
|
+
if (!from || !point) return;
|
|
5801
|
+
const dx = point.clientX - from.x, dy = point.clientY - from.y;
|
|
5802
|
+
if (from.outside && Math.hypot(dx, dy) <= 10 || from.dragging && dy >= 72 && dy > Math.abs(dx) * 1.5) {
|
|
5803
|
+
if (event.cancelable) event.preventDefault();
|
|
5804
|
+
close(from.overlay);
|
|
5805
|
+
}
|
|
5806
|
+
};
|
|
5807
|
+
doc.addEventListener("touchstart", down, { passive: true });
|
|
5808
|
+
doc.addEventListener("touchmove", move, { passive: false });
|
|
5809
|
+
doc.addEventListener("touchend", up, { passive: false });
|
|
5810
|
+
doc.addEventListener("touchcancel", reset);
|
|
5811
|
+
return () => {
|
|
5812
|
+
reset();
|
|
5813
|
+
doc.removeEventListener("touchstart", down);
|
|
5814
|
+
doc.removeEventListener("touchmove", move);
|
|
5815
|
+
doc.removeEventListener("touchend", up);
|
|
5816
|
+
doc.removeEventListener("touchcancel", reset);
|
|
5817
|
+
};
|
|
5818
|
+
}
|
|
5819
|
+
//#endregion
|
|
5584
5820
|
//#region src/client/foreground.ts
|
|
5585
5821
|
/** Resume a stale official follow generation, preserving live reply baselines.
|
|
5586
5822
|
* Brief interruptions and duplicate native/browser signals leave healthy streams alone. */
|
|
@@ -5647,6 +5883,7 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
5647
5883
|
connectStepConfirm: "Scan this code, check the host address, then confirm the connection.",
|
|
5648
5884
|
connectPrivacy: "This QR code grants access to this host. Keep it private. Hiding the code does not revoke the official login link; it remains valid until the host process restarts.",
|
|
5649
5885
|
connectNetwork: "Keep the computer and its HTTPS entry online. Scanning reconnects the App; it cannot repair a disconnected tunnel.",
|
|
5886
|
+
connectUnreachable: "The public connection is unavailable. Wait for the computer to restore it, then retry to get a new QR code.",
|
|
5650
5887
|
connectSetup: "The operator must configure a reachable HTTPS origin using mobile.publicOrigin or DSH_MOBILE_PUBLIC_ORIGIN, and add its hostname to the host trusted hosts.",
|
|
5651
5888
|
connectUntrusted: "The configured address is not trusted by this host. Update --trusted-host and restart the host before generating a code.",
|
|
5652
5889
|
connectUnsupported: "This host version cannot provide official login links. Update the supported host/plugin combination.",
|
|
@@ -5783,6 +6020,7 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
5783
6020
|
connectStepConfirm: "扫描二维码,核对主机地址后确认连接。",
|
|
5784
6021
|
connectPrivacy: "二维码可授予这台主机的访问权限,请勿分享。隐藏二维码不会撤销官方登录链接;链接在宿主进程重启前仍然有效。",
|
|
5785
6022
|
connectNetwork: "请保持电脑和 HTTPS 入口在线。扫码用于重新连接 App,无法修复已经断开的隧道。",
|
|
6023
|
+
connectUnreachable: "公网入口暂不可用。请等待电脑端恢复后重试,获取新的二维码。",
|
|
5786
6024
|
connectSetup: "请由部署者通过 mobile.publicOrigin 或 DSH_MOBILE_PUBLIC_ORIGIN 配置手机可达的 HTTPS 地址,并将域名加入宿主信任列表。",
|
|
5787
6025
|
connectUntrusted: "宿主尚未信任配置的地址。请更新 --trusted-host 并重启宿主后再生成二维码。",
|
|
5788
6026
|
connectUnsupported: "此宿主版本不能提供官方登录链接,请更新到支持的宿主和插件组合。",
|
|
@@ -5969,6 +6207,7 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
5969
6207
|
navigation
|
|
5970
6208
|
})
|
|
5971
6209
|
}, MobileDirectoryOverlay));
|
|
6210
|
+
ctx.effect(() => installSheetGestures(document), "mobile: sheet dismissal");
|
|
5972
6211
|
ctx.effect(() => () => {
|
|
5973
6212
|
presentation.dispose();
|
|
5974
6213
|
}, "mobile: presentation");
|
|
@@ -6067,7 +6306,14 @@ html[data-dsh-mobile] [data-mobile-picker=preset] [data-mobile-picker-divider] {
|
|
|
6067
6306
|
id: "mobile-input-tools",
|
|
6068
6307
|
order: -100,
|
|
6069
6308
|
locale: NS,
|
|
6070
|
-
inject: () => ({
|
|
6309
|
+
inject: () => ({
|
|
6310
|
+
rooms,
|
|
6311
|
+
openCommands: (id, draft, span) => {
|
|
6312
|
+
const actx = ctx.get("sessions")?.scope(id);
|
|
6313
|
+
const service = ctx.get("inputTriggers");
|
|
6314
|
+
return actx !== void 0 && typeof service?.sessionOf === "function" && openMobileCommands(service.sessionOf(actx), draft, span);
|
|
6315
|
+
}
|
|
6316
|
+
})
|
|
6071
6317
|
}, MobileTools);
|
|
6072
6318
|
else {
|
|
6073
6319
|
remove?.();
|
package/lib/index.js
CHANGED
|
@@ -23,6 +23,25 @@ function mobileHandshake() {
|
|
|
23
23
|
}
|
|
24
24
|
const DIRECTORY_PATH = "/api/mobile/directories";
|
|
25
25
|
//#endregion
|
|
26
|
+
//#region lib/types/reachability.js
|
|
27
|
+
/** Probe only the operator-configured origin, anonymously and without redirects.
|
|
28
|
+
* Official protected roots answer 401; unprotected roots can answer 2xx.
|
|
29
|
+
* A login token must never be used as a connectivity probe. */
|
|
30
|
+
async function publicOriginReachable(origin, request = fetch) {
|
|
31
|
+
try {
|
|
32
|
+
const response = await request(`${origin}/`, {
|
|
33
|
+
method: "GET",
|
|
34
|
+
redirect: "manual",
|
|
35
|
+
signal: AbortSignal.timeout(6e3),
|
|
36
|
+
headers: { "cache-control": "no-cache" }
|
|
37
|
+
});
|
|
38
|
+
await response.body?.cancel();
|
|
39
|
+
return response.status === 401 || response.ok;
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
26
45
|
//#region lib/types/connect.js
|
|
27
46
|
const headers = {
|
|
28
47
|
"cache-control": "no-store",
|
|
@@ -65,10 +84,14 @@ function connectInfo(connection, configured) {
|
|
|
65
84
|
};
|
|
66
85
|
}
|
|
67
86
|
/** Called only behind the official authenticated Fetch registry. */
|
|
68
|
-
function connectResponse(connection, configured) {
|
|
87
|
+
function connectResponse(connection, configured, reachable = publicOriginReachable) {
|
|
69
88
|
return async (request) => {
|
|
70
89
|
const info = connectInfo(connection, configured);
|
|
71
|
-
|
|
90
|
+
const checked = async () => info.state === "ready" && info.origin && !await reachable(info.origin) ? {
|
|
91
|
+
...info,
|
|
92
|
+
state: "unreachable"
|
|
93
|
+
} : info;
|
|
94
|
+
if (request.method === "GET") return Response.json(await checked(), { headers });
|
|
72
95
|
let sameOrigin = false;
|
|
73
96
|
try {
|
|
74
97
|
const origin = new URL(request.headers.get("origin") ?? "");
|
|
@@ -83,6 +106,11 @@ function connectResponse(connection, configured) {
|
|
|
83
106
|
status: 409,
|
|
84
107
|
headers
|
|
85
108
|
});
|
|
109
|
+
const live = await checked();
|
|
110
|
+
if (live.state !== "ready") return Response.json(live, {
|
|
111
|
+
status: 503,
|
|
112
|
+
headers
|
|
113
|
+
});
|
|
86
114
|
try {
|
|
87
115
|
const login = new URL(connection.authenticatedUrl(info.origin));
|
|
88
116
|
if (login.origin !== info.origin || login.pathname !== "/" || login.username || login.password || login.hash || !login.searchParams.get("token") || [...login.searchParams].length !== 1) throw new Error("unsupported");
|