@linxin666/dsh-remote-web-ui 0.1.9 → 0.1.11
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.md +17 -6
- package/lib/client.js +442 -17
- package/lib/client.js.map +1 -1
- package/lib/index.js +406 -17
- package/lib/types/client/PluginSettingsCard.d.ts +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +10 -1
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +9 -7
- package/lib/types/client/UpdateEntry.d.ts +14 -0
- package/lib/types/client/UpdateEntry.d.ts.map +1 -0
- package/lib/types/client/UpdateEntry.js +68 -0
- package/lib/types/client/UpdatePanel.d.ts +40 -0
- package/lib/types/client/UpdatePanel.d.ts.map +1 -0
- package/lib/types/client/UpdatePanel.js +82 -0
- package/lib/types/client/index.d.ts +2 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +58 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +60 -0
- package/lib/types/client/settings-form.d.ts +9 -1
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +2 -1
- package/lib/types/client/update-api.d.ts +19 -0
- package/lib/types/client/update-api.d.ts.map +1 -0
- package/lib/types/client/update-api.js +27 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +45 -1
- package/lib/types/routes.d.ts +15 -0
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +1 -1
- package/lib/types/update-routes.d.ts +30 -0
- package/lib/types/update-routes.d.ts.map +1 -0
- package/lib/types/update-routes.js +52 -0
- package/lib/types/update.d.ts +191 -0
- package/lib/types/update.d.ts.map +1 -0
- package/lib/types/update.js +333 -0
- package/package.json +1 -1
- package/src/client/PluginSettingsCard.tsx +31 -1
- package/src/client/RemoteEntry.tsx +12 -7
- package/src/client/UpdateEntry.tsx +96 -0
- package/src/client/UpdatePanel.tsx +203 -0
- package/src/client/index.ts +2 -0
- package/src/client/locales.ts +60 -0
- package/src/client/remote.module.css +109 -0
- package/src/client/settings-card.module.css +7 -0
- package/src/client/settings-form.ts +11 -2
- package/src/client/update-api.ts +29 -0
- package/src/index.ts +51 -1
- package/src/routes.ts +1 -1
- package/src/update-routes.ts +71 -0
- package/src/update.ts +435 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The sidebar update seat: the download trigger beside the remote-control
|
|
4
|
+
* trigger plus the update panel modal. Owns the flow — probe the registry
|
|
5
|
+
* on open, auto-run the update when a newer release exists, report the
|
|
6
|
+
* outcome (restart hint on success, translated failure on error).
|
|
7
|
+
* Component-local state per the client stack rules.
|
|
8
|
+
*/
|
|
9
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
10
|
+
import { createPortal } from "react-dom";
|
|
11
|
+
import { IconDownloadOutline16 } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
12
|
+
import { fetchUpdateStatus, runUpdate } from "./update-api.js";
|
|
13
|
+
import { UpdatePanel } from "./UpdatePanel.js";
|
|
14
|
+
import css from "./remote.module.css";
|
|
15
|
+
/**
|
|
16
|
+
* Render the update trigger and panel.
|
|
17
|
+
* @param props - column state and locale seat.
|
|
18
|
+
* @returns the entry element tree.
|
|
19
|
+
*/
|
|
20
|
+
export function UpdateEntry({ wide, t }) {
|
|
21
|
+
const [open, setOpen] = useState(false);
|
|
22
|
+
const [view, setView] = useState({ kind: "checking" });
|
|
23
|
+
const runToken = useRef(0);
|
|
24
|
+
const check = useCallback(async () => {
|
|
25
|
+
setView({ kind: "checking" });
|
|
26
|
+
let status;
|
|
27
|
+
try {
|
|
28
|
+
status = await fetchUpdateStatus();
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
setView({ kind: "error", message: t("update.offline"), detail: t("update.offlineDetail") });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (status.error === "registry-unreachable") {
|
|
35
|
+
setView({ kind: "result", status });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
setView({ kind: "result", status });
|
|
39
|
+
// Auto-update: an npm install with a newer release proceeds without a
|
|
40
|
+
// second confirmation — clicking the update trigger is the intent.
|
|
41
|
+
if (status.mode !== "npm" || !status.outdated)
|
|
42
|
+
return;
|
|
43
|
+
setView({ kind: "updating", status });
|
|
44
|
+
const token = ++runToken.current;
|
|
45
|
+
try {
|
|
46
|
+
const result = await runUpdate();
|
|
47
|
+
if (token !== runToken.current)
|
|
48
|
+
return;
|
|
49
|
+
setView({ kind: "done", result });
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
if (token !== runToken.current)
|
|
53
|
+
return;
|
|
54
|
+
setView({ kind: "error", message: t("update.error"), detail: t("update.offlineDetail") });
|
|
55
|
+
}
|
|
56
|
+
}, [t]);
|
|
57
|
+
const openPanel = useCallback(() => {
|
|
58
|
+
setOpen(true);
|
|
59
|
+
void check();
|
|
60
|
+
}, [check]);
|
|
61
|
+
const closePanel = useCallback(() => {
|
|
62
|
+
runToken.current++;
|
|
63
|
+
setOpen(false);
|
|
64
|
+
}, []);
|
|
65
|
+
// Unmount safety: an in-flight update must not land on a dead component.
|
|
66
|
+
useEffect(() => () => { runToken.current++; }, []);
|
|
67
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.trigger, "data-wide": wide ? undefined : "rail", "aria-label": t("update.label"), title: t("update.label"), onClick: openPanel, children: _jsx(IconDownloadOutline16, { size: wide ? 16 : 18 }) }), open && createPortal((_jsxs("div", { className: css.overlay, role: "presentation", children: [_jsx("div", { className: css.mask, "aria-hidden": "true", onClick: closePanel }), _jsx(UpdatePanel, { t: t, view: view, onClose: closePanel, onRecheck: () => { void check(); } })] })), document.body)] }));
|
|
68
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The update panel body: check progress, version comparison, the
|
|
3
|
+
* auto-update in-flight state, and the outcome (restart hint on success,
|
|
4
|
+
* translated failure on error). Pure presentation — all state arrives
|
|
5
|
+
* through props from the entry behavior component.
|
|
6
|
+
*/
|
|
7
|
+
import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots';
|
|
8
|
+
import type { UpdateRunResult, UpdateStatus } from '../update.ts';
|
|
9
|
+
/** The panel view state, owned by the entry component. */
|
|
10
|
+
export type UpdateView = {
|
|
11
|
+
kind: 'checking';
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'result';
|
|
14
|
+
status: UpdateStatus;
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'updating';
|
|
17
|
+
status: UpdateStatus;
|
|
18
|
+
} | {
|
|
19
|
+
kind: 'done';
|
|
20
|
+
result: UpdateRunResult;
|
|
21
|
+
} | {
|
|
22
|
+
kind: 'error';
|
|
23
|
+
message: string;
|
|
24
|
+
detail?: string;
|
|
25
|
+
};
|
|
26
|
+
/** Full panel props: copy + view state + actions. */
|
|
27
|
+
export interface UpdatePanelProps {
|
|
28
|
+
t: TranslateNS<'remote'>;
|
|
29
|
+
view: UpdateView;
|
|
30
|
+
onClose(): void;
|
|
31
|
+
/** Re-run the check from a terminal state. */
|
|
32
|
+
onRecheck(): void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Render the update panel.
|
|
36
|
+
* @param props - copy, view state, and actions.
|
|
37
|
+
* @returns the panel element tree.
|
|
38
|
+
*/
|
|
39
|
+
export declare function UpdatePanel({ t, view, onClose, onRecheck }: UpdatePanelProps): import("react").JSX.Element;
|
|
40
|
+
//# sourceMappingURL=UpdatePanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UpdatePanel.d.ts","sourceRoot":"","sources":["../../../src/client/UpdatePanel.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAEnE,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAGjE,0DAA0D;AAC1D,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvD,qDAAqD;AACrD,MAAM,WAAW,gBAAgB;IAC/B,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;IACxB,IAAI,EAAE,UAAU,CAAA;IAChB,OAAO,IAAI,IAAI,CAAA;IACf,8CAA8C;IAC9C,SAAS,IAAI,IAAI,CAAA;CAClB;AAYD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,gBAAgB,+BAyC5E"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { IconCloseOutline16, IconRefreshOutline16 } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
3
|
+
import css from "./remote.module.css";
|
|
4
|
+
/** The anchor package name (aggregate first) for copy purposes. */
|
|
5
|
+
function anchorName(status) {
|
|
6
|
+
return status?.anchor ?? status?.packages[0]?.name;
|
|
7
|
+
}
|
|
8
|
+
/** The latest npm release of the anchor, for reference copy. */
|
|
9
|
+
function anchorLatest(status) {
|
|
10
|
+
return status?.packages[0]?.latest;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Render the update panel.
|
|
14
|
+
* @param props - copy, view state, and actions.
|
|
15
|
+
* @returns the panel element tree.
|
|
16
|
+
*/
|
|
17
|
+
export function UpdatePanel({ t, view, onClose, onRecheck }) {
|
|
18
|
+
const status = view.kind === "result" || view.kind === "updating" ? view.status : undefined;
|
|
19
|
+
const title = view.kind === "done" && view.result.ok ? t("update.done") : t("update.title");
|
|
20
|
+
const subtitle = subtitleOf(t, view);
|
|
21
|
+
return (_jsxs("div", { className: css.panel, role: "dialog", "aria-modal": "true", "aria-label": title, children: [_jsxs("div", { className: css.header, children: [_jsxs("div", { className: css.heading, children: [_jsx("h2", { className: css.title, children: title }), subtitle !== undefined && _jsx("p", { className: css.subtitle, children: subtitle })] }), _jsx("button", { type: "button", className: css.close, "aria-label": t("update.close"), onClick: onClose, children: _jsx(IconCloseOutline16, {}) })] }), view.kind === "checking" && _jsx("p", { className: css.updateStatus, children: t("update.checking") }), view.kind === "result" && status !== undefined && _jsx(ResultBody, { t: t, status: status }), view.kind === "updating" && status !== undefined && (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.updating", { name: anchorName(status) ?? "", version: anchorLatest(status) ?? "" }) }), _jsx(PackageList, { status: status })] })), view.kind === "done" && _jsx(DoneBody, { t: t, result: view.result }), view.kind === "error" && (_jsxs("div", { children: [_jsx("p", { className: css.updateError, children: view.message }), view.detail !== undefined && _jsx("pre", { className: css.updateOutput, children: view.detail })] })), (view.kind === "done" || view.kind === "error") && (_jsx("div", { className: css.updateActions, children: _jsxs("button", { type: "button", className: css.updateRetry, onClick: onRecheck, children: [_jsx(IconRefreshOutline16, {}), " ", t("update.retry")] }) }))] }));
|
|
22
|
+
}
|
|
23
|
+
/** The subtitle copy per view state (absent on plain results). */
|
|
24
|
+
function subtitleOf(t, view) {
|
|
25
|
+
switch (view.kind) {
|
|
26
|
+
case "checking":
|
|
27
|
+
return t("update.checking");
|
|
28
|
+
case "updating":
|
|
29
|
+
return t("update.updatingTitle");
|
|
30
|
+
case "result":
|
|
31
|
+
return undefined;
|
|
32
|
+
case "done":
|
|
33
|
+
return view.result.ok ? t("update.doneDetail") : t("update.error");
|
|
34
|
+
case "error":
|
|
35
|
+
// The error body below carries the message; no subtitle duplication.
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** The checked result body: mode banner + version list. */
|
|
40
|
+
function ResultBody({ t, status }) {
|
|
41
|
+
const anchor = anchorName(status);
|
|
42
|
+
const latest = anchorLatest(status);
|
|
43
|
+
if (status.mode === "link") {
|
|
44
|
+
return (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.linkMode") }), _jsx("p", { className: css.updateDetail, children: t("update.linkModeDetail", { version: latest ?? "-" }) })] }));
|
|
45
|
+
}
|
|
46
|
+
if (status.mode === "missing") {
|
|
47
|
+
return (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.missing") }), _jsx("p", { className: css.updateDetail, children: t("update.missingDetail") })] }));
|
|
48
|
+
}
|
|
49
|
+
if (status.error === "registry-unreachable") {
|
|
50
|
+
return (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.offline") }), _jsx("p", { className: css.updateDetail, children: t("update.offlineDetail") })] }));
|
|
51
|
+
}
|
|
52
|
+
if (status.outdated) {
|
|
53
|
+
return (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.found") }), _jsx("p", { className: css.updateDetail, children: anchor !== undefined ? t("update.foundDetail", { name: anchor, version: latest ?? "" }) : "" })] }));
|
|
54
|
+
}
|
|
55
|
+
return (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.upToDate") }), anchor !== undefined && latest !== undefined && (_jsx("p", { className: css.updateDetail, children: t("update.upToDateDetail", { name: anchor, version: latest }) })), _jsx(PackageList, { status: status })] }));
|
|
56
|
+
}
|
|
57
|
+
/** The per-package current → latest comparison list. */
|
|
58
|
+
function PackageList({ status }) {
|
|
59
|
+
if (status.packages.length === 0)
|
|
60
|
+
return null;
|
|
61
|
+
return (_jsx("ul", { className: css.updateList, children: status.packages.map(packageStatus => (_jsxs("li", { className: css.updateListItem, children: [_jsx("span", { className: css.updateListName, children: packageStatus.name }), _jsxs("span", { className: css.updateListVersions, children: [packageStatus.current, packageStatus.latest !== undefined && packageStatus.latest !== packageStatus.current && (_jsxs(_Fragment, { children: [" \u2192 ", packageStatus.latest] }))] })] }, packageStatus.name))) }));
|
|
62
|
+
}
|
|
63
|
+
/** The outcome body: success + restart hint, or the translated failure. */
|
|
64
|
+
function DoneBody({ t, result }) {
|
|
65
|
+
if (result.ok) {
|
|
66
|
+
// The title already reads "Update complete"; the body carries the details.
|
|
67
|
+
return (_jsxs("div", { children: [_jsx("p", { className: css.updateDetail, children: t("update.doneDetail") }), _jsx("p", { className: css.updateDetail, children: t("update.restartHint") })] }));
|
|
68
|
+
}
|
|
69
|
+
const message = errorMessageOf(t, result);
|
|
70
|
+
return (_jsxs("div", { children: [_jsx("p", { className: css.updateError, children: message }), result.output.trim() !== "" && _jsx("pre", { className: css.updateOutput, children: result.output.trim() })] }));
|
|
71
|
+
}
|
|
72
|
+
/** Translate a structured failure code; fall back to the raw message. */
|
|
73
|
+
function errorMessageOf(t, result) {
|
|
74
|
+
switch (result.errorCode) {
|
|
75
|
+
case "pnpm-missing": return t("update.error.pnpmMissing");
|
|
76
|
+
case "timeout": return t("update.error.timeout");
|
|
77
|
+
case "not-found": return t("update.error.notFound");
|
|
78
|
+
case "link": return t("update.error.link");
|
|
79
|
+
case "pnpm-failed": return t("update.error.pnpmFailed", { code: String(result.exitCode ?? "?") });
|
|
80
|
+
default: return result.error ?? t("update.error.unknown");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -5,6 +5,8 @@ export type { PanelState, RemotePanelProps } from './RemotePanel.tsx';
|
|
|
5
5
|
export type { PairFailedNoticeProps } from './PairFailedNotice.tsx';
|
|
6
6
|
export type { RemoteKey } from './locales.ts';
|
|
7
7
|
export type { RemoteSettingsCardFace, RemoteSettingsCardState } from './RemoteSettingsCard.tsx';
|
|
8
|
+
export type { UpdateEntryProps } from './UpdateEntry.tsx';
|
|
9
|
+
export type { UpdatePanelProps, UpdateView } from './UpdatePanel.tsx';
|
|
8
10
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
9
11
|
interface LocaleNamespaceMap {
|
|
10
12
|
/** Mobile remote-control surface copy. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAA;AAa3E,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AAIrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACrE,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AACnE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,YAAY,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAA;AAa3E,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AAIrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACrE,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AACnE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,YAAY,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAC/F,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAErE,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,0CAA0C;QAC1C,MAAM,EAAE,SAAS,CAAA;KAClB;IAED,UAAU,OAAO;QACf;;;;WAIG;QACH,gBAAgB,EAAE;YAAE,IAAI,EAAE,QAAQ,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,uBAAuB,CAAA;SAAE,CAAA;QACnF;;;;;WAKG;QACH,oBAAoB,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,4BAA4B,CAAA;SAAE,CAAA;KAC3F;CACF;AAED,4GAA4G;AAC5G,MAAM,WAAW,uBAAuB;IACtC,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAWD,wCAAwC;AACxC,eAAO,MAAM,MAAM,UAA+D,CAAA;AAElF;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAyG9C"}
|
|
@@ -58,6 +58,7 @@ export declare const zh: {
|
|
|
58
58
|
'settings.off': string;
|
|
59
59
|
'settings.overridden': string;
|
|
60
60
|
'settings.reset': string;
|
|
61
|
+
'settings.notExposed': string;
|
|
61
62
|
'settings.readOnly': string;
|
|
62
63
|
'settings.expand': string;
|
|
63
64
|
'settings.collapse': string;
|
|
@@ -67,6 +68,34 @@ export declare const zh: {
|
|
|
67
68
|
'settings.unsaved': string;
|
|
68
69
|
'settings.saveFailed': string;
|
|
69
70
|
'settings.invalidNumber': string;
|
|
71
|
+
'update.label': string;
|
|
72
|
+
'update.title': string;
|
|
73
|
+
'update.checking': string;
|
|
74
|
+
'update.upToDate': string;
|
|
75
|
+
'update.upToDateDetail': string;
|
|
76
|
+
'update.found': string;
|
|
77
|
+
'update.foundDetail': string;
|
|
78
|
+
'update.updating': string;
|
|
79
|
+
'update.updatingTitle': string;
|
|
80
|
+
'update.done': string;
|
|
81
|
+
'update.doneDetail': string;
|
|
82
|
+
'update.restartHint': string;
|
|
83
|
+
'update.linkMode': string;
|
|
84
|
+
'update.linkModeDetail': string;
|
|
85
|
+
'update.missing': string;
|
|
86
|
+
'update.missingDetail': string;
|
|
87
|
+
'update.offline': string;
|
|
88
|
+
'update.offlineDetail': string;
|
|
89
|
+
'update.error': string;
|
|
90
|
+
'update.packages': string;
|
|
91
|
+
'update.retry': string;
|
|
92
|
+
'update.close': string;
|
|
93
|
+
'update.error.pnpmMissing': string;
|
|
94
|
+
'update.error.timeout': string;
|
|
95
|
+
'update.error.notFound': string;
|
|
96
|
+
'update.error.link': string;
|
|
97
|
+
'update.error.pnpmFailed': string;
|
|
98
|
+
'update.error.unknown': string;
|
|
70
99
|
};
|
|
71
100
|
/** The remote namespace key union. */
|
|
72
101
|
export type RemoteKey = keyof typeof zh;
|
|
@@ -131,6 +160,7 @@ export declare const en: {
|
|
|
131
160
|
'settings.off': string;
|
|
132
161
|
'settings.overridden': string;
|
|
133
162
|
'settings.reset': string;
|
|
163
|
+
'settings.notExposed': string;
|
|
134
164
|
'settings.readOnly': string;
|
|
135
165
|
'settings.expand': string;
|
|
136
166
|
'settings.collapse': string;
|
|
@@ -140,5 +170,33 @@ export declare const en: {
|
|
|
140
170
|
'settings.unsaved': string;
|
|
141
171
|
'settings.saveFailed': string;
|
|
142
172
|
'settings.invalidNumber': string;
|
|
173
|
+
'update.label': string;
|
|
174
|
+
'update.title': string;
|
|
175
|
+
'update.checking': string;
|
|
176
|
+
'update.upToDate': string;
|
|
177
|
+
'update.upToDateDetail': string;
|
|
178
|
+
'update.found': string;
|
|
179
|
+
'update.foundDetail': string;
|
|
180
|
+
'update.updating': string;
|
|
181
|
+
'update.updatingTitle': string;
|
|
182
|
+
'update.done': string;
|
|
183
|
+
'update.doneDetail': string;
|
|
184
|
+
'update.restartHint': string;
|
|
185
|
+
'update.linkMode': string;
|
|
186
|
+
'update.linkModeDetail': string;
|
|
187
|
+
'update.missing': string;
|
|
188
|
+
'update.missingDetail': string;
|
|
189
|
+
'update.offline': string;
|
|
190
|
+
'update.offlineDetail': string;
|
|
191
|
+
'update.error': string;
|
|
192
|
+
'update.packages': string;
|
|
193
|
+
'update.retry': string;
|
|
194
|
+
'update.close': string;
|
|
195
|
+
'update.error.pnpmMissing': string;
|
|
196
|
+
'update.error.timeout': string;
|
|
197
|
+
'update.error.notFound': string;
|
|
198
|
+
'update.error.link': string;
|
|
199
|
+
'update.error.pnpmFailed': string;
|
|
200
|
+
'update.error.unknown': string;
|
|
143
201
|
};
|
|
144
202
|
//# sourceMappingURL=locales.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAE/E,mEAAmE;AACnE,eAAO,MAAM,EAAE
|
|
1
|
+
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAE/E,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkGmB,CAAA;AAElC,sCAAsC;AACtC,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,EAAE,CAAA;AAEvC,wDAAwD;AACxD,MAAM,MAAM,eAAe,GAAG,SAAS,CAAA;AAEvC,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkGsB,CAAA"}
|
|
@@ -59,6 +59,7 @@ export const zh = {
|
|
|
59
59
|
'settings.off': '关',
|
|
60
60
|
'settings.overridden': '已覆盖',
|
|
61
61
|
'settings.reset': '恢复默认',
|
|
62
|
+
'settings.notExposed': '当前 DSH 版本未向设置页暴露本插件的配置命名空间,表单不可用。可编辑 ~/.dsh/settings.yaml 直接配置,或为 dsh-host-apiproxy 的 WEB_SETTINGS_NAMESPACES 白名单补充本命名空间后重启。',
|
|
62
63
|
'settings.readOnly': '当前部署的设置只读。',
|
|
63
64
|
'settings.expand': '展开设置',
|
|
64
65
|
'settings.collapse': '收起设置',
|
|
@@ -68,6 +69,35 @@ export const zh = {
|
|
|
68
69
|
'settings.unsaved': '未保存',
|
|
69
70
|
'settings.saveFailed': '部署未接受这些值,已保留供你修改。',
|
|
70
71
|
'settings.invalidNumber': '请输入数字,留空则使用默认值。',
|
|
72
|
+
// 更新面板(侧边栏更新按钮)。
|
|
73
|
+
'update.label': '检查更新',
|
|
74
|
+
'update.title': '检查更新',
|
|
75
|
+
'update.checking': '正在检查远程仓库中的新版本…',
|
|
76
|
+
'update.upToDate': '当前已是最新版本',
|
|
77
|
+
'update.upToDateDetail': '{name} 已是最新版本 v{version}。',
|
|
78
|
+
'update.found': '发现新版本',
|
|
79
|
+
'update.foundDetail': '{name} 有新版本 v{version},正在自动更新…',
|
|
80
|
+
'update.updating': '正在更新 {name} 至 v{version},可能需要 1-2 分钟…',
|
|
81
|
+
'update.updatingTitle': '正在自动更新…',
|
|
82
|
+
'update.done': '更新完成',
|
|
83
|
+
'update.doneDetail': '所有组件已更新到最新版本。',
|
|
84
|
+
'update.restartHint': '请重启 dsh web 使新版本生效:在终端按 Ctrl+C 停止后重新运行 dsh web。',
|
|
85
|
+
'update.linkMode': '当前为本地开发模式',
|
|
86
|
+
'update.linkModeDetail': 'dsh-web-ui 通过本地链接安装(开发模式),无法自动更新。npm 上最新版本:{version}。请在仓库中执行 git pull 更新。',
|
|
87
|
+
'update.missing': '未找到 dsh-web-ui 安装',
|
|
88
|
+
'update.missingDetail': '无法定位已安装的 dsh-web-ui 聚合包。请先运行 dsh plugin --profile <profile> add @linxin666/dsh-web-ui-all。',
|
|
89
|
+
'update.offline': '无法连接更新源',
|
|
90
|
+
'update.offlineDetail': '网络不可用或更新服务异常,请稍后重试。',
|
|
91
|
+
'update.error': '更新失败',
|
|
92
|
+
'update.packages': '组件版本',
|
|
93
|
+
'update.retry': '重新检查',
|
|
94
|
+
'update.close': '关闭更新面板',
|
|
95
|
+
'update.error.pnpmMissing': '未找到 pnpm,请先安装 pnpm(brew install pnpm 或 corepack enable)。',
|
|
96
|
+
'update.error.timeout': '更新超时,已终止安装进程,请重试。',
|
|
97
|
+
'update.error.notFound': '未找到 dsh-web-ui 聚合包安装,无法更新。',
|
|
98
|
+
'update.error.link': '当前为本地开发模式(链接安装),无法自动更新。',
|
|
99
|
+
'update.error.pnpmFailed': '更新执行失败(pnpm 退出码 {code}),详见下方输出。',
|
|
100
|
+
'update.error.unknown': '更新失败,请重试。',
|
|
71
101
|
};
|
|
72
102
|
/** English dictionary, checked complete against the zh key set. */
|
|
73
103
|
export const en = {
|
|
@@ -129,6 +159,7 @@ export const en = {
|
|
|
129
159
|
'settings.off': 'Off',
|
|
130
160
|
'settings.overridden': 'Overridden',
|
|
131
161
|
'settings.reset': 'Reset to default',
|
|
162
|
+
'settings.notExposed': 'This DSH version does not expose this plugin\'s settings namespace to the configuration page, so the form is unavailable. Edit ~/.dsh/settings.yaml directly, or add the namespace to dsh-host-apiproxy\'s WEB_SETTINGS_NAMESPACES allowlist and restart.',
|
|
132
163
|
'settings.readOnly': 'This deployment stores settings read-only.',
|
|
133
164
|
'settings.expand': 'Show settings',
|
|
134
165
|
'settings.collapse': 'Hide settings',
|
|
@@ -138,4 +169,33 @@ export const en = {
|
|
|
138
169
|
'settings.unsaved': 'Unsaved',
|
|
139
170
|
'settings.saveFailed': 'The deployment did not accept these values; they were left for you to correct.',
|
|
140
171
|
'settings.invalidNumber': 'Enter a number, or leave blank to use the default.',
|
|
172
|
+
// Update panel (the sidebar update trigger).
|
|
173
|
+
'update.label': 'Check for updates',
|
|
174
|
+
'update.title': 'Check for updates',
|
|
175
|
+
'update.checking': 'Checking the remote repository for newer releases…',
|
|
176
|
+
'update.upToDate': 'Everything is up to date',
|
|
177
|
+
'update.upToDateDetail': '{name} is at the latest version v{version}.',
|
|
178
|
+
'update.found': 'A new version is available',
|
|
179
|
+
'update.foundDetail': '{name} has a newer release v{version}; updating automatically…',
|
|
180
|
+
'update.updating': 'Updating {name} to v{version}; this may take 1-2 minutes…',
|
|
181
|
+
'update.updatingTitle': 'Updating automatically…',
|
|
182
|
+
'update.done': 'Update complete',
|
|
183
|
+
'update.doneDetail': 'All components are up to date.',
|
|
184
|
+
'update.restartHint': 'Restart dsh web for the new version to take effect: stop it with Ctrl+C in the terminal, then run dsh web again.',
|
|
185
|
+
'update.linkMode': 'Local development mode',
|
|
186
|
+
'update.linkModeDetail': 'dsh-web-ui is installed via a local link (development mode) and cannot be updated remotely. Latest npm release: {version}. Run git pull in the repository to update.',
|
|
187
|
+
'update.missing': 'dsh-web-ui not found',
|
|
188
|
+
'update.missingDetail': 'Could not locate the installed dsh-web-ui aggregate package. Install it with: dsh plugin --profile <profile> add @linxin666/dsh-web-ui-all',
|
|
189
|
+
'update.offline': 'Cannot reach the update source',
|
|
190
|
+
'update.offlineDetail': 'The network is unavailable or the update service is failing; try again later.',
|
|
191
|
+
'update.error': 'Update failed',
|
|
192
|
+
'update.packages': 'Component versions',
|
|
193
|
+
'update.retry': 'Check again',
|
|
194
|
+
'update.close': 'Close update panel',
|
|
195
|
+
'update.error.pnpmMissing': 'pnpm was not found. Install it first (brew install pnpm or corepack enable).',
|
|
196
|
+
'update.error.timeout': 'The update timed out and the install process was killed; try again.',
|
|
197
|
+
'update.error.notFound': 'The dsh-web-ui aggregate package is not installed; nothing to update.',
|
|
198
|
+
'update.error.link': 'Local development mode (link install) — automatic update is unavailable.',
|
|
199
|
+
'update.error.pnpmFailed': 'The update failed (pnpm exited with code {code}); see the output below.',
|
|
200
|
+
'update.error.unknown': 'Update failed; try again.',
|
|
141
201
|
};
|
|
@@ -37,8 +37,16 @@ export interface FieldState {
|
|
|
37
37
|
}
|
|
38
38
|
/** Form state every plugin settings card shares. */
|
|
39
39
|
export interface CardShell {
|
|
40
|
-
/** False while the namespace is
|
|
40
|
+
/** False while the namespace is still loading; the card renders nothing. */
|
|
41
41
|
available: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Whether the namespace is actually served to this client. False when the
|
|
44
|
+
* Host deployment does not expose it (e.g. the official apiproxy settings
|
|
45
|
+
* allowlist omits third-party namespaces): the card renders an explanation
|
|
46
|
+
* instead of its form, so a missing namespace never looks like a missing
|
|
47
|
+
* plugin.
|
|
48
|
+
*/
|
|
49
|
+
exposed: boolean;
|
|
42
50
|
/** Whether the Host document accepts writes. */
|
|
43
51
|
writable: boolean;
|
|
44
52
|
/** Whether the form holds edits that a save would write. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAyB,aAAa,EAAE,MAAM,wCAAwC,CAAA;AAGjH,yEAAyE;AACzE,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAA;AAErB,0EAA0E;AAC1E,MAAM,WAAW,SAAS;IACxB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAA;IACb,2FAA2F;IAC3F,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAA;IAClC;;;OAGG;IACH,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAA;CAChD;AAED,wCAAwC;AACxC,MAAM,WAAW,UAAU;IACzB,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAA;IACZ,oEAAoE;IACpE,UAAU,EAAE,OAAO,CAAA;IACnB,gFAAgF;IAChF,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,oDAAoD;AACpD,MAAM,WAAW,SAAS;IACxB,
|
|
1
|
+
{"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"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"}
|
|
@@ -78,7 +78,8 @@ export class CardForm {
|
|
|
78
78
|
const snapshot = this.scope.getSnapshot();
|
|
79
79
|
const plan = this.plan();
|
|
80
80
|
return {
|
|
81
|
-
available: snapshot.status
|
|
81
|
+
available: snapshot.status !== 'loading',
|
|
82
|
+
exposed: snapshot.status === 'ready',
|
|
82
83
|
writable: snapshot.writable,
|
|
83
84
|
dirty: plan.length > 0,
|
|
84
85
|
invalid: plan.some(item => item.run === undefined),
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side wire helpers for the /api/update surface. Plain fetch over
|
|
3
|
+
* same-origin /api like the pairing client; the host half enforces the
|
|
4
|
+
* loopback-only fence and owns the pnpm run.
|
|
5
|
+
*/
|
|
6
|
+
import type { UpdateRunResult, UpdateStatus } from '../update.ts';
|
|
7
|
+
/**
|
|
8
|
+
* Probe the update status: install mode, owning profile, and the
|
|
9
|
+
* current-vs-latest comparison for every family package.
|
|
10
|
+
* @returns the status snapshot.
|
|
11
|
+
*/
|
|
12
|
+
export declare function fetchUpdateStatus(): Promise<UpdateStatus>;
|
|
13
|
+
/**
|
|
14
|
+
* Run the update (pnpm update in the owning profile). Blocks until pnpm
|
|
15
|
+
* exits — the panel shows an in-flight state meanwhile.
|
|
16
|
+
* @returns the run outcome.
|
|
17
|
+
*/
|
|
18
|
+
export declare function runUpdate(): Promise<UpdateRunResult>;
|
|
19
|
+
//# sourceMappingURL=update-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-api.d.ts","sourceRoot":"","sources":["../../../src/client/update-api.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEjE;;;;GAIG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,YAAY,CAAC,CAI/D;AAED;;;;GAIG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,eAAe,CAAC,CAI1D"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side wire helpers for the /api/update surface. Plain fetch over
|
|
3
|
+
* same-origin /api like the pairing client; the host half enforces the
|
|
4
|
+
* loopback-only fence and owns the pnpm run.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Probe the update status: install mode, owning profile, and the
|
|
8
|
+
* current-vs-latest comparison for every family package.
|
|
9
|
+
* @returns the status snapshot.
|
|
10
|
+
*/
|
|
11
|
+
export async function fetchUpdateStatus() {
|
|
12
|
+
const response = await fetch('/api/update/status');
|
|
13
|
+
if (!response.ok)
|
|
14
|
+
throw new Error('update status unavailable');
|
|
15
|
+
return await response.json();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Run the update (pnpm update in the owning profile). Blocks until pnpm
|
|
19
|
+
* exits — the panel shows an in-flight state meanwhile.
|
|
20
|
+
* @returns the run outcome.
|
|
21
|
+
*/
|
|
22
|
+
export async function runUpdate() {
|
|
23
|
+
const response = await fetch('/api/update/run', { method: 'POST' });
|
|
24
|
+
if (!response.ok)
|
|
25
|
+
throw new Error('update run unavailable');
|
|
26
|
+
return await response.json();
|
|
27
|
+
}
|
package/lib/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,CAAC,MAAM,aAAa,CAAA;AAmB3B,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,MAAM;QACd;;;;;WAKG;QACH,UAAU,CACR,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,IAAI,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GACrC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAC9B;CACF;AAED,iCAAiC;AACjC,eAAO,MAAM,IAAI,kBAAkB,CAAA;AAEnC,+DAA+D;AAC/D,eAAO,MAAM,MAAM,UAA4B,CAAA;AAE/C;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,uDAAqC,CAAA;AAElF,qEAAqE;AACrE,MAAM,WAAW,MAAM;IACrB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAS3B,CAAA;AAwBF;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAsMzD"}
|
package/lib/types/index.js
CHANGED
|
@@ -7,16 +7,19 @@
|
|
|
7
7
|
* `./client` entry) renders the sidebar entry, the pairing panel, and the
|
|
8
8
|
* phone-side pair/accept + deep-link flow.
|
|
9
9
|
*/
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
10
11
|
import { setInterval as nodeSetInterval } from 'node:timers';
|
|
11
12
|
import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
12
13
|
import z from 'schemastery';
|
|
13
14
|
import { PairingService } from "./pairing.js";
|
|
14
15
|
import { makeGateListener } from "./gate.js";
|
|
15
|
-
import { makeRoutes } from "./routes.js";
|
|
16
|
+
import { isTrustedApiRequest, makeRoutes } from "./routes.js";
|
|
16
17
|
import { makeMobileRoutes } from "./mobile-routes.js";
|
|
17
18
|
import { makeMobileApiRoutes } from "./mobile-api.js";
|
|
18
19
|
import { lanIPv4Addresses } from "./lan.js";
|
|
19
20
|
import { TunnelManager } from "./tunnel.js";
|
|
21
|
+
import { checkUpdates, fetchLatestVersion, resolveAnchorManifest, resolveUpdateTarget, runUpdate, } from "./update.js";
|
|
22
|
+
import { makeUpdateRoutes } from "./update-routes.js";
|
|
20
23
|
/** Stable cordis plugin name. */
|
|
21
24
|
export const name = 'remote-web-ui';
|
|
22
25
|
/** Services required before the pairing surfaces can mount. */
|
|
@@ -143,10 +146,51 @@ export function apply(ctx, config) {
|
|
|
143
146
|
if (apiProxy === undefined) {
|
|
144
147
|
console.warn('remote-web-ui: apiProxy service unavailable — the mobile data channel is disabled');
|
|
145
148
|
}
|
|
149
|
+
// ── remote update ────────────────────────────────────────────────────────
|
|
150
|
+
// The dsh-web-ui self-update surface: probe the npm registry for family
|
|
151
|
+
// releases and run `pnpm update` in the owning profile. Resolutions anchor
|
|
152
|
+
// on the host process's own module graph, so the update always targets the
|
|
153
|
+
// profile the running web GUI was booted from. The probe path resolves once
|
|
154
|
+
// (the anchor stays the same package across updates); versions are re-read
|
|
155
|
+
// from disk per check.
|
|
156
|
+
const requireFromHost = createRequire(import.meta.url);
|
|
157
|
+
const anchorManifestPath = resolveAnchorManifest(specifier => requireFromHost.resolve(specifier));
|
|
158
|
+
const updateRoutes = makeUpdateRoutes({
|
|
159
|
+
// Control endpoints are host-surface only: a LAN/phone origin must never
|
|
160
|
+
// trigger a real install on this machine.
|
|
161
|
+
fence: request => isTrustedApiRequest(request, []),
|
|
162
|
+
check: () => checkUpdates({
|
|
163
|
+
anchorManifestPath,
|
|
164
|
+
resolve: specifier => {
|
|
165
|
+
try {
|
|
166
|
+
return requireFromHost.resolve(specifier);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
fetchLatest: name => fetchLatestVersion(name, fetch),
|
|
173
|
+
}),
|
|
174
|
+
run: async () => {
|
|
175
|
+
const target = resolveUpdateTarget({ anchorManifestPath });
|
|
176
|
+
if ('error' in target) {
|
|
177
|
+
const code = target.error;
|
|
178
|
+
return {
|
|
179
|
+
ok: false,
|
|
180
|
+
exitCode: null,
|
|
181
|
+
output: '',
|
|
182
|
+
error: code === 'not-found' ? 'dsh-web-ui aggregate not installed' : 'local link install — update unavailable',
|
|
183
|
+
errorCode: code,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
return runUpdate({ profileDir: target.profileDir, packages: target.packages });
|
|
187
|
+
},
|
|
188
|
+
});
|
|
146
189
|
const routes = [
|
|
147
190
|
...makeRoutes({ service, lanAddresses }),
|
|
148
191
|
...makeMobileRoutes(),
|
|
149
192
|
...(apiProxy !== undefined ? makeMobileApiRoutes({ service, apiProxy }) : []),
|
|
193
|
+
...updateRoutes,
|
|
150
194
|
];
|
|
151
195
|
const gate = makeGateListener(service, () => resolve().requirePairingForLan, () => resolve().enabled);
|
|
152
196
|
ctx.effect(() => ctx.on('api/gate', gate), 'remote-web-ui: api gate');
|
package/lib/types/routes.d.ts
CHANGED
|
@@ -10,6 +10,21 @@
|
|
|
10
10
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
11
11
|
import type { WebRoute } from '@deepseek-ai/dsh-host-webserver';
|
|
12
12
|
import { type PairingService, type PairingSnapshot } from './pairing.ts';
|
|
13
|
+
/**
|
|
14
|
+
* Browser-trust fence for the /api/pair routes, mirroring the connection
|
|
15
|
+
* package's internal fence semantics (Host/Origin based, DNS-rebinding and
|
|
16
|
+
* cross-site defense). The connection package no longer exports its trust
|
|
17
|
+
* predicate — the fence for the /api prefix lives inside the connection
|
|
18
|
+
* plugin — so the pairing routes, which must stay reachable from LAN phones
|
|
19
|
+
* ahead of the connection prefix route (exact routes match first), carry
|
|
20
|
+
* their own copy scoped to the literals the QR links advertise.
|
|
21
|
+
* @param request - the node HTTP request.
|
|
22
|
+
* @param trustedHosts - non-loopback authorities this surface serves: exact
|
|
23
|
+
* `host:port`, or port-less `host` matching any port.
|
|
24
|
+
* @returns true when the Host is ours (loopback or trusted) and any attached
|
|
25
|
+
* browser markers are same-origin.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isTrustedApiRequest(request: IncomingMessage, trustedHosts: readonly string[]): boolean;
|
|
13
28
|
/**
|
|
14
29
|
* The host authority of a configured public base URL, e.g. `foo.trycloudflare.com`
|
|
15
30
|
* from `https://foo.trycloudflare.com`. Undefined when the URL does not parse —
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,EAA0B,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,EAA0B,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAA;AAGhG;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CA6BtG;AAKD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAOxE;AAKD,8CAA8C;AAC9C,eAAO,MAAM,UAAU;;;;;;;CAOb,CAAA;AAiCV,gDAAgD;AAChD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAElD;;OAEG;gBACS,OAAO,EAAE,cAAc;IAInC,4DAA4D;IAC5D,IAAI,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI;IAiBrD,kEAAkE;IAClE,IAAI,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;IAYrC,wCAAwC;IACxC,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAED,6CAA6C;AAC7C,MAAM,WAAW,cAAc;IAC7B,2BAA2B;IAC3B,OAAO,EAAE,cAAc,CAAA;IACvB,0EAA0E;IAC1E,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;CAChC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,QAAQ,EAAE,CAgK3D"}
|
package/lib/types/routes.js
CHANGED
|
@@ -23,7 +23,7 @@ import { isLoopbackClient, readCookie } from "./gate.js";
|
|
|
23
23
|
* @returns true when the Host is ours (loopback or trusted) and any attached
|
|
24
24
|
* browser markers are same-origin.
|
|
25
25
|
*/
|
|
26
|
-
function isTrustedApiRequest(request, trustedHosts) {
|
|
26
|
+
export function isTrustedApiRequest(request, trustedHosts) {
|
|
27
27
|
const host = request.headers.host;
|
|
28
28
|
if (typeof host !== 'string')
|
|
29
29
|
return false;
|