@linxin666/dsh-remote-web-ui 0.1.19 → 0.2.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.
- package/README.i18n.yaml +2 -2
- package/README.md +41 -19
- package/README.zh.md +17 -12
- package/assets/mobile-icon-192.png +0 -0
- package/assets/mobile-icon-512.png +0 -0
- package/assets/mobile-service-worker.js +64 -0
- package/lib/client.js +57 -13
- package/lib/client.js.map +1 -1
- package/lib/index.js +408 -130
- package/lib/mobile.js +296 -22
- package/lib/mobile.js.map +1 -1
- package/lib/types/client/FooterRemoteEntry.js +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts +7 -1
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +5 -3
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +43 -13
- package/lib/types/client/RemotePanel.d.ts +1 -0
- package/lib/types/client/RemotePanel.d.ts.map +1 -1
- package/lib/types/client/RemotePanel.js +2 -2
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +3 -3
- package/lib/types/client/UpdateEntry.d.ts.map +1 -1
- package/lib/types/client/UpdateEntry.js +37 -4
- package/lib/types/client/UpdatePanel.js +1 -1
- package/lib/types/client/deep-link.js +2 -2
- package/lib/types/client/index.js +7 -7
- package/lib/types/client/locales.d.ts +2 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +2 -0
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/update-api.d.ts.map +1 -1
- package/lib/types/gate.d.ts +9 -0
- package/lib/types/gate.d.ts.map +1 -1
- package/lib/types/gate.js +16 -5
- package/lib/types/http.d.ts +14 -0
- package/lib/types/http.d.ts.map +1 -0
- package/lib/types/http.js +27 -0
- package/lib/types/index.d.ts +28 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +41 -22
- package/lib/types/invariant.d.ts.map +1 -1
- package/lib/types/mobile/PairRequiredView.d.ts +7 -0
- package/lib/types/mobile/PairRequiredView.d.ts.map +1 -0
- package/lib/types/mobile/PairRequiredView.js +28 -0
- package/lib/types/mobile/PairRequiredView.test.d.ts +2 -0
- package/lib/types/mobile/PairRequiredView.test.d.ts.map +1 -0
- package/lib/types/mobile/PairRequiredView.test.js +24 -0
- package/lib/types/mobile/api.js +1 -1
- package/lib/types/mobile/index.js +17 -4
- package/lib/types/mobile/markdown.d.ts +6 -4
- package/lib/types/mobile/markdown.d.ts.map +1 -1
- package/lib/types/mobile/markdown.js +8 -4
- package/lib/types/mobile/markdown.test.js +5 -1
- package/lib/types/mobile/messages.d.ts.map +1 -1
- package/lib/types/mobile/messages.js +14 -1
- package/lib/types/mobile/messages.test.js +1 -1
- package/lib/types/mobile/mobile-styles.d.ts +1 -1
- package/lib/types/mobile/mobile-styles.d.ts.map +1 -1
- package/lib/types/mobile/mobile-styles.js +48 -0
- package/lib/types/mobile/mobile-theme.test.js +2 -2
- package/lib/types/mobile/mux.d.ts.map +1 -1
- package/lib/types/mobile/mux.js +1 -1
- package/lib/types/mobile/mux.test.js +1 -1
- package/lib/types/mobile/pairing.d.ts +31 -0
- package/lib/types/mobile/pairing.d.ts.map +1 -0
- package/lib/types/mobile/pairing.js +62 -0
- package/lib/types/mobile/rpc.d.ts.map +1 -1
- package/lib/types/mobile/theme-toggle.js +1 -1
- package/lib/types/mobile/views/App.d.ts +12 -16
- package/lib/types/mobile/views/App.d.ts.map +1 -1
- package/lib/types/mobile/views/App.js +39 -12
- package/lib/types/mobile/views/App.test.d.ts +2 -0
- package/lib/types/mobile/views/App.test.d.ts.map +1 -0
- package/lib/types/mobile/views/App.test.js +52 -0
- package/lib/types/mobile/views/ChatView.js +6 -6
- package/lib/types/mobile/views/ChatView.test.js +3 -3
- package/lib/types/mobile/views/SessionListView.js +3 -3
- package/lib/types/mobile/views/SessionListView.test.js +2 -2
- package/lib/types/mobile/views/WorkspaceView.js +3 -3
- package/lib/types/mobile/views/WorkspaceView.test.js +3 -3
- package/lib/types/mobile-api.d.ts +1 -1
- package/lib/types/mobile-api.d.ts.map +1 -1
- package/lib/types/mobile-api.js +11 -18
- package/lib/types/mobile-pwa.d.ts +6 -0
- package/lib/types/mobile-pwa.d.ts.map +1 -0
- package/lib/types/mobile-pwa.js +17 -0
- package/lib/types/mobile-routes.d.ts +5 -8
- package/lib/types/mobile-routes.d.ts.map +1 -1
- package/lib/types/mobile-routes.js +130 -39
- package/lib/types/mount-once.d.ts +25 -0
- package/lib/types/mount-once.d.ts.map +1 -0
- package/lib/types/mount-once.js +42 -0
- package/lib/types/pairing-access.d.ts +28 -0
- package/lib/types/pairing-access.d.ts.map +1 -0
- package/lib/types/pairing-access.js +26 -0
- package/lib/types/pairing.d.ts +27 -0
- package/lib/types/pairing.d.ts.map +1 -1
- package/lib/types/pairing.js +68 -0
- package/lib/types/pairing.persist.test.d.ts +2 -0
- package/lib/types/pairing.persist.test.d.ts.map +1 -0
- package/lib/types/pairing.persist.test.js +128 -0
- package/lib/types/routes.d.ts +6 -6
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +15 -20
- package/lib/types/tunnel.d.ts.map +1 -1
- package/lib/types/update-routes.d.ts +2 -2
- package/lib/types/update-routes.d.ts.map +1 -1
- package/lib/types/update-routes.js +1 -6
- package/lib/types/update.d.ts +3 -3
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +40 -14
- package/package.json +12 -12
- package/src/client/PluginSettingsCard.tsx +10 -0
- package/src/client/RemoteEntry.tsx +40 -8
- package/src/client/RemotePanel.tsx +1 -1
- package/src/client/RemoteSettingsCard.tsx +1 -0
- package/src/client/UpdateEntry.tsx +36 -4
- package/src/client/deep-link.ts +1 -1
- package/src/client/locales.ts +2 -0
- package/src/client/remote.module.css +26 -0
- package/src/gate.ts +15 -3
- package/src/http.ts +30 -0
- package/src/index.ts +50 -16
- package/src/mobile/PairRequiredView.test.tsx +28 -0
- package/src/mobile/PairRequiredView.tsx +58 -0
- package/src/mobile/index.tsx +15 -1
- package/src/mobile/markdown.test.ts +5 -0
- package/src/mobile/markdown.ts +7 -4
- package/src/mobile/messages.ts +14 -1
- package/src/mobile/mobile-styles.ts +48 -0
- package/src/mobile/pairing.ts +77 -0
- package/src/mobile/views/App.test.tsx +62 -0
- package/src/mobile/views/App.tsx +53 -18
- package/src/mobile-api.ts +9 -17
- package/src/mobile-pwa.ts +19 -0
- package/src/mobile-routes.ts +137 -38
- package/src/mount-once.ts +48 -0
- package/src/pairing-access.ts +36 -0
- package/src/pairing.persist.test.ts +145 -0
- package/src/pairing.ts +70 -1
- package/src/routes.ts +12 -18
- package/src/update-routes.ts +1 -7
- package/src/update.ts +46 -15
package/lib/mobile.js
CHANGED
|
@@ -11958,7 +11958,7 @@ function superRefine(fn, params) {
|
|
|
11958
11958
|
return /* @__PURE__ */ _superRefine(fn, params);
|
|
11959
11959
|
}
|
|
11960
11960
|
//#endregion
|
|
11961
|
-
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.
|
|
11961
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.7_36988429fa7c61ceeac966dddad3597f/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/types/api/rpc.schema.js
|
|
11962
11962
|
/**
|
|
11963
11963
|
* Message-layer zod schemas: the four wire full forms + error body +
|
|
11964
11964
|
* carrier receipt. The payload slot is unknown in the full-form schemas — business payloads
|
|
@@ -12139,11 +12139,6 @@ const rpcErrorSchema = discriminatedUnion("code", [
|
|
|
12139
12139
|
message: string(),
|
|
12140
12140
|
details: object({ ns: string() })
|
|
12141
12141
|
}),
|
|
12142
|
-
object({
|
|
12143
|
-
code: literal("settings-not-exposed"),
|
|
12144
|
-
message: string(),
|
|
12145
|
-
details: object({ ns: string() })
|
|
12146
|
-
}),
|
|
12147
12142
|
object({
|
|
12148
12143
|
code: literal("settings-conflict"),
|
|
12149
12144
|
message: string(),
|
|
@@ -12272,7 +12267,7 @@ union([object({ accepted: literal(true) }), object({
|
|
|
12272
12267
|
reason: union([literal("not-pending"), literal("bad-response")])
|
|
12273
12268
|
})]);
|
|
12274
12269
|
//#endregion
|
|
12275
|
-
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.
|
|
12270
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.7_36988429fa7c61ceeac966dddad3597f/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/types/api/session-search.js
|
|
12276
12271
|
/**
|
|
12277
12272
|
* Return the longest prefix containing at most `maximum` Unicode code points.
|
|
12278
12273
|
* @param value - text to bound.
|
|
@@ -12290,7 +12285,7 @@ function truncateUnicodeCodePoints(value, maximum) {
|
|
|
12290
12285
|
return value;
|
|
12291
12286
|
}
|
|
12292
12287
|
//#endregion
|
|
12293
|
-
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.
|
|
12288
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.7_36988429fa7c61ceeac966dddad3597f/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/types/api/sessions.schema.js
|
|
12294
12289
|
/**
|
|
12295
12290
|
* sessions domain zod schemas (names derived from map keys: sessionListRequestSchema /
|
|
12296
12291
|
* sessionListValueSchema). SessionEvent passthrough = strict envelope (type/seq/time) + wide
|
|
@@ -12526,7 +12521,7 @@ object({ accepted: literal(true) });
|
|
|
12526
12521
|
object({ sessionId: sessionIdSchema });
|
|
12527
12522
|
object({ accepted: literal(true) });
|
|
12528
12523
|
//#endregion
|
|
12529
|
-
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.
|
|
12524
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.7_36988429fa7c61ceeac966dddad3597f/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/types/api/approvals.schema.js
|
|
12530
12525
|
/**
|
|
12531
12526
|
* approvals domain zod schemas (respond is a client-response; the payload schema serves
|
|
12532
12527
|
* the /api/respond endpoint's second parse after routing via the pending table).
|
|
@@ -12560,7 +12555,7 @@ const taskViewSchema = object({
|
|
|
12560
12555
|
finishedAt: number().int().nonnegative().optional()
|
|
12561
12556
|
});
|
|
12562
12557
|
//#endregion
|
|
12563
|
-
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.
|
|
12558
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.7_36988429fa7c61ceeac966dddad3597f/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/types/api/workspace.schema.js
|
|
12564
12559
|
/**
|
|
12565
12560
|
* workspace domain zod schemas (names derived from map keys). The
|
|
12566
12561
|
* WorkspaceId brand cast lives in sessions.schema (see the note there) and
|
|
@@ -12606,7 +12601,7 @@ object({ workspace: workspaceViewSchema });
|
|
|
12606
12601
|
object({ sessionId: sessionIdSchema });
|
|
12607
12602
|
object({ archivedSessionIds: array(sessionIdSchema) });
|
|
12608
12603
|
//#endregion
|
|
12609
|
-
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.
|
|
12604
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.7_36988429fa7c61ceeac966dddad3597f/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/types/api/events.schema.js
|
|
12610
12605
|
/**
|
|
12611
12606
|
* events domain zod schemas: MuxFrame / HostFrame unions (discriminatedUnion('type')).
|
|
12612
12607
|
* A frame is the payload slot of the ServerRequest full form; the SessionEvent inside
|
|
@@ -13429,7 +13424,17 @@ function foldEvents(events, existing) {
|
|
|
13429
13424
|
if (event.seq <= watermark) continue;
|
|
13430
13425
|
applyEvent(state, event);
|
|
13431
13426
|
}
|
|
13432
|
-
|
|
13427
|
+
const out = state.messages;
|
|
13428
|
+
let ordered = true;
|
|
13429
|
+
for (let index = 1; index < out.length; index += 1) {
|
|
13430
|
+
const prev = out[index - 1];
|
|
13431
|
+
const current = out[index];
|
|
13432
|
+
if (prev.seq > current.seq || prev.seq === current.seq && prev.id >= current.id) {
|
|
13433
|
+
ordered = false;
|
|
13434
|
+
break;
|
|
13435
|
+
}
|
|
13436
|
+
}
|
|
13437
|
+
return ordered ? out : [...out].sort((a, b) => a.seq - b.seq || (a.id < b.id ? -1 : 1));
|
|
13433
13438
|
}
|
|
13434
13439
|
//#endregion
|
|
13435
13440
|
//#region src/mobile/markdown.ts
|
|
@@ -13450,15 +13455,18 @@ function escapeHtml(text) {
|
|
|
13450
13455
|
}
|
|
13451
13456
|
/**
|
|
13452
13457
|
* Guard a raw link/image target against dangerous protocols. Returns the
|
|
13453
|
-
* (trimmed) raw string when safe, else null. Only http:, https:, mailto
|
|
13454
|
-
*
|
|
13455
|
-
* — javascript:, data:, vbscript:, etc. —
|
|
13456
|
-
*
|
|
13458
|
+
* (trimmed) raw string when safe, else null. Only http:, https:, mailto:,
|
|
13459
|
+
* fragment anchors (#...) and strictly relative paths are allowed; anything
|
|
13460
|
+
* with another scheme — javascript:, data:, vbscript:, etc. — or a
|
|
13461
|
+
* protocol-relative //host target (the browser resolves it against the
|
|
13462
|
+
* current scheme, reaching an arbitrary origin) is rejected so the value
|
|
13463
|
+
* never reaches dangerouslySetInnerHTML.
|
|
13457
13464
|
*/
|
|
13458
13465
|
function safeUrl(raw) {
|
|
13459
13466
|
const trimmed = raw.trim();
|
|
13460
13467
|
if (trimmed === "") return null;
|
|
13461
13468
|
if (trimmed.startsWith("#")) return trimmed;
|
|
13469
|
+
if (trimmed.startsWith("//")) return null;
|
|
13462
13470
|
const scheme = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(trimmed);
|
|
13463
13471
|
if (scheme === null) return trimmed;
|
|
13464
13472
|
const name = scheme[1].toLowerCase();
|
|
@@ -15123,6 +15131,154 @@ function WorkspaceView({ initialWorkspaceId, onPick }) {
|
|
|
15123
15131
|
});
|
|
15124
15132
|
}
|
|
15125
15133
|
//#endregion
|
|
15134
|
+
//#region src/mobile/pairing.ts
|
|
15135
|
+
const MOBILE_ROOT = "/m/";
|
|
15136
|
+
/** Parse a pairing token or a copied pairing link without following its origin. */
|
|
15137
|
+
function parseMobilePairInput(value) {
|
|
15138
|
+
const trimmed = value.trim();
|
|
15139
|
+
if (trimmed === "") return void 0;
|
|
15140
|
+
try {
|
|
15141
|
+
const url = new URL(trimmed);
|
|
15142
|
+
const token = url.searchParams.get("pair");
|
|
15143
|
+
if (token === null || token === "") return void 0;
|
|
15144
|
+
const workspaceId = url.searchParams.get("workspace");
|
|
15145
|
+
return {
|
|
15146
|
+
token,
|
|
15147
|
+
...workspaceId !== null && workspaceId !== "" ? { workspaceId } : {}
|
|
15148
|
+
};
|
|
15149
|
+
} catch {
|
|
15150
|
+
return { token: trimmed };
|
|
15151
|
+
}
|
|
15152
|
+
}
|
|
15153
|
+
/** Build the safe, token-free mobile destination after successful pairing. */
|
|
15154
|
+
function mobilePairPath(workspaceId) {
|
|
15155
|
+
return workspaceId === void 0 ? MOBILE_ROOT : "/m/?workspace=" + encodeURIComponent(workspaceId);
|
|
15156
|
+
}
|
|
15157
|
+
/** Accept one pairing token on this exact browser or installed-web-app context. */
|
|
15158
|
+
async function acceptMobilePair(token, fetcher = fetch) {
|
|
15159
|
+
try {
|
|
15160
|
+
const response = await fetcher("/api/pair/accept", {
|
|
15161
|
+
method: "POST",
|
|
15162
|
+
headers: { "content-type": "application/json" },
|
|
15163
|
+
body: JSON.stringify({ token })
|
|
15164
|
+
});
|
|
15165
|
+
if (response.ok) return { ok: true };
|
|
15166
|
+
if (response.status === 404) return {
|
|
15167
|
+
ok: false,
|
|
15168
|
+
message: "配对链接无效或已过期。"
|
|
15169
|
+
};
|
|
15170
|
+
if (response.status === 409) return {
|
|
15171
|
+
ok: false,
|
|
15172
|
+
message: "配对链接已被使用。"
|
|
15173
|
+
};
|
|
15174
|
+
return {
|
|
15175
|
+
ok: false,
|
|
15176
|
+
message: "此设备无法使用该配对链接。"
|
|
15177
|
+
};
|
|
15178
|
+
} catch {
|
|
15179
|
+
return {
|
|
15180
|
+
ok: false,
|
|
15181
|
+
message: "无法连接到配对服务。"
|
|
15182
|
+
};
|
|
15183
|
+
}
|
|
15184
|
+
}
|
|
15185
|
+
/** Consume a QR pairing token before the mobile application starts making RPC calls. */
|
|
15186
|
+
async function consumeMobilePairUrl(href, fetcher = fetch) {
|
|
15187
|
+
let url;
|
|
15188
|
+
try {
|
|
15189
|
+
url = new URL(href);
|
|
15190
|
+
} catch {
|
|
15191
|
+
return { kind: "none" };
|
|
15192
|
+
}
|
|
15193
|
+
const token = url.searchParams.get("pair");
|
|
15194
|
+
if (token === null || token === "") return { kind: "none" };
|
|
15195
|
+
const workspaceId = url.searchParams.get("workspace");
|
|
15196
|
+
const path = mobilePairPath(workspaceId === null || workspaceId === "" ? void 0 : workspaceId);
|
|
15197
|
+
const result = await acceptMobilePair(token, fetcher);
|
|
15198
|
+
return result.ok ? {
|
|
15199
|
+
kind: "accepted",
|
|
15200
|
+
path
|
|
15201
|
+
} : {
|
|
15202
|
+
kind: "failed",
|
|
15203
|
+
path,
|
|
15204
|
+
message: result.message
|
|
15205
|
+
};
|
|
15206
|
+
}
|
|
15207
|
+
//#endregion
|
|
15208
|
+
//#region src/mobile/PairRequiredView.tsx
|
|
15209
|
+
/** Device-pairing gate for installed mobile web-app contexts with isolated storage. */
|
|
15210
|
+
/** Collect a fresh desktop-issued pairing link when this client has no paired cookie. */
|
|
15211
|
+
function PairRequiredView({ initialError, onPaired }) {
|
|
15212
|
+
const [value, setValue] = (0, import_react.useState)("");
|
|
15213
|
+
const [error, setError] = (0, import_react.useState)(initialError);
|
|
15214
|
+
const [submitting, setSubmitting] = (0, import_react.useState)(false);
|
|
15215
|
+
const submit = async (event) => {
|
|
15216
|
+
event.preventDefault();
|
|
15217
|
+
const input = parseMobilePairInput(value);
|
|
15218
|
+
if (input === void 0) {
|
|
15219
|
+
setError("请输入有效的配对链接。");
|
|
15220
|
+
return;
|
|
15221
|
+
}
|
|
15222
|
+
setSubmitting(true);
|
|
15223
|
+
setError(void 0);
|
|
15224
|
+
const result = await acceptMobilePair(input.token);
|
|
15225
|
+
setSubmitting(false);
|
|
15226
|
+
if (!result.ok) {
|
|
15227
|
+
setError(result.message);
|
|
15228
|
+
return;
|
|
15229
|
+
}
|
|
15230
|
+
onPaired(mobilePairPath(input.workspaceId));
|
|
15231
|
+
};
|
|
15232
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("main", {
|
|
15233
|
+
className: "mobile mobile-pair",
|
|
15234
|
+
"aria-labelledby": "mobile-pair-title",
|
|
15235
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", {
|
|
15236
|
+
className: "mobile-pairCard",
|
|
15237
|
+
onSubmit: (event) => {
|
|
15238
|
+
submit(event);
|
|
15239
|
+
},
|
|
15240
|
+
children: [
|
|
15241
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
|
|
15242
|
+
id: "mobile-pair-title",
|
|
15243
|
+
className: "mobile-title",
|
|
15244
|
+
children: "设备配对"
|
|
15245
|
+
}),
|
|
15246
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
15247
|
+
className: "mobile-muted",
|
|
15248
|
+
children: "粘贴桌面端复制的配对链接以连接此设备。"
|
|
15249
|
+
}),
|
|
15250
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
15251
|
+
className: "mobile-pairLabel",
|
|
15252
|
+
htmlFor: "mobile-pair-link",
|
|
15253
|
+
children: "配对链接"
|
|
15254
|
+
}),
|
|
15255
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
15256
|
+
id: "mobile-pair-link",
|
|
15257
|
+
className: "mobile-pairInput",
|
|
15258
|
+
value,
|
|
15259
|
+
onChange: (event) => {
|
|
15260
|
+
setValue(event.target.value);
|
|
15261
|
+
},
|
|
15262
|
+
autoComplete: "off",
|
|
15263
|
+
autoCapitalize: "off",
|
|
15264
|
+
spellCheck: false
|
|
15265
|
+
}),
|
|
15266
|
+
error === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
15267
|
+
className: "mobile-error",
|
|
15268
|
+
role: "alert",
|
|
15269
|
+
children: error
|
|
15270
|
+
}),
|
|
15271
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
15272
|
+
className: "mobile-new mobile-pairSubmit",
|
|
15273
|
+
type: "submit",
|
|
15274
|
+
disabled: submitting,
|
|
15275
|
+
children: submitting ? "正在配对" : "配对"
|
|
15276
|
+
})
|
|
15277
|
+
]
|
|
15278
|
+
})
|
|
15279
|
+
});
|
|
15280
|
+
}
|
|
15281
|
+
//#endregion
|
|
15126
15282
|
//#region src/mobile/views/App.tsx
|
|
15127
15283
|
/**
|
|
15128
15284
|
* Mobile surface root: the view state machine (workspaces → sessions →
|
|
@@ -15159,11 +15315,60 @@ function formatTime(epochMs) {
|
|
|
15159
15315
|
if (date.toDateString() === yesterday.toDateString()) return `昨天 ${clock}`;
|
|
15160
15316
|
return `${String(date.getMonth() + 1)}月${String(date.getDate())}日 ${clock}`;
|
|
15161
15317
|
}
|
|
15162
|
-
/**
|
|
15163
|
-
|
|
15164
|
-
|
|
15165
|
-
|
|
15166
|
-
|
|
15318
|
+
/** Whether the mobile gateway rejected this browser for lack of a paired cookie. */
|
|
15319
|
+
function isUnpairedMobileError(error) {
|
|
15320
|
+
return error instanceof RpcTransportError && error.message === "HTTP 403";
|
|
15321
|
+
}
|
|
15322
|
+
/** Classify an initial mobile preference failure without treating outage as authorization. */
|
|
15323
|
+
function mobilePairStateForError(error) {
|
|
15324
|
+
return isUnpairedMobileError(error) ? "unpaired" : "unavailable";
|
|
15325
|
+
}
|
|
15326
|
+
/** Gate the independent mobile bundle until its own browser context is paired. */
|
|
15327
|
+
function App({ initialPairError }) {
|
|
15328
|
+
const [pairState, setPairState] = (0, import_react.useState)("checking");
|
|
15329
|
+
(0, import_react.useEffect)(() => {
|
|
15330
|
+
let current = true;
|
|
15331
|
+
fetchMobilePreferences().then(() => {
|
|
15332
|
+
if (current) setPairState("paired");
|
|
15333
|
+
}, (error) => {
|
|
15334
|
+
if (current) setPairState(mobilePairStateForError(error));
|
|
15335
|
+
});
|
|
15336
|
+
return () => {
|
|
15337
|
+
current = false;
|
|
15338
|
+
};
|
|
15339
|
+
}, []);
|
|
15340
|
+
if (pairState === "checking") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("main", {
|
|
15341
|
+
className: "mobile mobile-empty",
|
|
15342
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
15343
|
+
className: "mobile-muted",
|
|
15344
|
+
children: "正在连接..."
|
|
15345
|
+
})
|
|
15346
|
+
});
|
|
15347
|
+
if (pairState === "unpaired") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PairRequiredView, {
|
|
15348
|
+
initialError: initialPairError,
|
|
15349
|
+
onPaired: (path) => {
|
|
15350
|
+
window.location.replace(path);
|
|
15351
|
+
}
|
|
15352
|
+
});
|
|
15353
|
+
if (pairState === "unavailable") return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", {
|
|
15354
|
+
className: "mobile mobile-empty",
|
|
15355
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
15356
|
+
className: "mobile-error",
|
|
15357
|
+
role: "alert",
|
|
15358
|
+
children: "无法连接到运行中的 DSH host。"
|
|
15359
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
15360
|
+
className: "mobile-new",
|
|
15361
|
+
type: "button",
|
|
15362
|
+
onClick: () => {
|
|
15363
|
+
window.location.reload();
|
|
15364
|
+
},
|
|
15365
|
+
children: "重试"
|
|
15366
|
+
})]
|
|
15367
|
+
});
|
|
15368
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PairedApp, {});
|
|
15369
|
+
}
|
|
15370
|
+
/** The existing remote mobile surface, mounted only after device pairing succeeds. */
|
|
15371
|
+
function PairedApp() {
|
|
15167
15372
|
const [route, setRoute] = (0, import_react.useState)({ kind: "workspaces" });
|
|
15168
15373
|
const [initialWorkspaceId, setInitialWorkspaceId] = (0, import_react.useState)(() => mobileWorkspaceTarget(window.location.search));
|
|
15169
15374
|
const muxRef = (0, import_react.useRef)(void 0);
|
|
@@ -16270,7 +16475,66 @@ body {
|
|
|
16270
16475
|
transition: none;
|
|
16271
16476
|
}
|
|
16272
16477
|
}
|
|
16478
|
+
|
|
16479
|
+
/* ── installed-app pairing ───────────────────────────────────────────── */
|
|
16480
|
+
|
|
16481
|
+
.mobile-pair {
|
|
16482
|
+
min-height: 100dvh;
|
|
16483
|
+
display: grid;
|
|
16484
|
+
place-items: center;
|
|
16485
|
+
padding: 24px;
|
|
16486
|
+
}
|
|
16487
|
+
|
|
16488
|
+
.mobile-pairCard {
|
|
16489
|
+
display: grid;
|
|
16490
|
+
width: min(100%, 420px);
|
|
16491
|
+
gap: 12px;
|
|
16492
|
+
padding: 24px;
|
|
16493
|
+
border: 1px solid var(--m-border);
|
|
16494
|
+
border-radius: 12px;
|
|
16495
|
+
background: var(--m-bg-raised);
|
|
16496
|
+
box-shadow: var(--m-shadow-raise);
|
|
16497
|
+
}
|
|
16498
|
+
|
|
16499
|
+
.mobile-pairLabel {
|
|
16500
|
+
color: var(--m-text-secondary);
|
|
16501
|
+
font-size: 13px;
|
|
16502
|
+
font-weight: 600;
|
|
16503
|
+
}
|
|
16504
|
+
|
|
16505
|
+
.mobile-pairInput {
|
|
16506
|
+
width: 100%;
|
|
16507
|
+
min-height: 42px;
|
|
16508
|
+
box-sizing: border-box;
|
|
16509
|
+
padding: 9px 12px;
|
|
16510
|
+
border: 1px solid var(--m-border);
|
|
16511
|
+
border-radius: 8px;
|
|
16512
|
+
background: var(--m-bg-input);
|
|
16513
|
+
color: var(--m-text);
|
|
16514
|
+
font: inherit;
|
|
16515
|
+
outline: none;
|
|
16516
|
+
}
|
|
16517
|
+
|
|
16518
|
+
.mobile-pairInput:focus-visible {
|
|
16519
|
+
border-color: var(--m-accent);
|
|
16520
|
+
box-shadow: 0 0 0 2px var(--m-bg), 0 0 0 4px var(--m-accent);
|
|
16521
|
+
}
|
|
16522
|
+
|
|
16523
|
+
.mobile-pairSubmit {
|
|
16524
|
+
width: 100%;
|
|
16525
|
+
}
|
|
16273
16526
|
`;
|
|
16527
|
+
const MOBILE_SERVICE_WORKER_URL = "/m/service-worker.js";
|
|
16528
|
+
/** Register the mobile shell worker when the current browser supports it. */
|
|
16529
|
+
async function registerMobilePwa(serviceWorker = typeof navigator === "undefined" ? void 0 : navigator.serviceWorker) {
|
|
16530
|
+
if (serviceWorker === void 0) return;
|
|
16531
|
+
try {
|
|
16532
|
+
await serviceWorker.register(MOBILE_SERVICE_WORKER_URL, {
|
|
16533
|
+
scope: "/m/",
|
|
16534
|
+
updateViaCache: "none"
|
|
16535
|
+
});
|
|
16536
|
+
} catch {}
|
|
16537
|
+
}
|
|
16274
16538
|
//#endregion
|
|
16275
16539
|
//#region src/mobile/index.tsx
|
|
16276
16540
|
/**
|
|
@@ -16288,7 +16552,17 @@ style.textContent = mobileCss;
|
|
|
16288
16552
|
document.head.appendChild(style);
|
|
16289
16553
|
const root = document.getElementById("root");
|
|
16290
16554
|
if (root === null) throw new Error("mobile: #root missing");
|
|
16291
|
-
(
|
|
16555
|
+
bootMobile(root);
|
|
16556
|
+
async function bootMobile(mount) {
|
|
16557
|
+
const pair = await consumeMobilePairUrl(window.location.href);
|
|
16558
|
+
if (pair.kind === "accepted") {
|
|
16559
|
+
window.location.replace(pair.path);
|
|
16560
|
+
return;
|
|
16561
|
+
}
|
|
16562
|
+
if (pair.kind === "failed") window.history.replaceState(null, "", pair.path);
|
|
16563
|
+
registerMobilePwa();
|
|
16564
|
+
(0, import_client.createRoot)(mount).render(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(App, { initialPairError: pair.kind === "failed" ? pair.message : void 0 }));
|
|
16565
|
+
}
|
|
16292
16566
|
//#endregion
|
|
16293
16567
|
|
|
16294
16568
|
//# sourceMappingURL=mobile.js.map
|