dsh-zen-remote 1.1.15 → 2.0.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/AGENTS.md +54 -9
- package/README.md +249 -190
- package/README.zh-CN.md +248 -202
- package/assets/home.png +0 -0
- package/assets/info.png +0 -0
- package/assets/pairing.png +0 -0
- package/assets/session.png +0 -0
- package/assets/sheet.png +0 -0
- package/cordis.patch.yml +12 -11
- package/cordis.patch.yml.example +24 -9
- package/docs/interface.md +82 -4
- package/docs/remote-access.en.md +99 -58
- package/docs/remote-access.md +99 -42
- package/dsh-push.mjs +67 -54
- package/lan-gate.mjs +88 -20
- package/lib/activity.js +168 -0
- package/lib/activity.js.map +1 -0
- package/lib/admin-routes.js +526 -0
- package/lib/admin-routes.js.map +1 -0
- package/lib/client-pairing.js +224 -0
- package/lib/client-pairing.js.map +1 -0
- package/lib/client-routes.js +674 -0
- package/lib/client-routes.js.map +1 -0
- package/lib/client.js +11268 -5844
- package/lib/config.js +346 -0
- package/lib/config.js.map +1 -0
- package/lib/fetch-route-intercept.js +418 -0
- package/lib/fetch-route-intercept.js.map +1 -0
- package/lib/fingerprint.js +363 -0
- package/lib/fingerprint.js.map +1 -0
- package/lib/http.js +69 -0
- package/lib/http.js.map +1 -0
- package/lib/index.js +675 -54
- package/lib/index.js.map +1 -1
- package/lib/intercept-shape.js +81 -0
- package/lib/intercept-shape.js.map +1 -0
- package/lib/intercept.js +3040 -0
- package/lib/intercept.js.map +1 -0
- package/lib/lan-gate-server.cjs +532 -121
- package/lib/merge-streams.js +1165 -0
- package/lib/merge-streams.js.map +1 -0
- package/lib/relay-access.js +496 -0
- package/lib/relay-access.js.map +1 -0
- package/lib/relay-client.js +1303 -0
- package/lib/relay-client.js.map +1 -0
- package/lib/relay-filter.js +282 -0
- package/lib/relay-filter.js.map +1 -0
- package/lib/relay-server.js +1792 -0
- package/lib/relay-server.js.map +1 -0
- package/lib/restart-fields.js +34 -0
- package/lib/restart-fields.js.map +1 -0
- package/lib/restart-watcher.js +106 -0
- package/lib/restart-watcher.js.map +1 -0
- package/lib/session-reference.js +157 -0
- package/lib/session-reference.js.map +1 -0
- package/lib/share-export.js +16 -1
- package/lib/share-export.js.map +1 -1
- package/lib/share-ops.js +86 -0
- package/lib/share-ops.js.map +1 -0
- package/lib/share-store.js +294 -0
- package/lib/share-store.js.map +1 -0
- package/lib/types/activity.d.ts +99 -0
- package/lib/types/activity.d.ts.map +1 -0
- package/lib/types/admin-routes.d.ts +129 -0
- package/lib/types/admin-routes.d.ts.map +1 -0
- package/lib/types/client/MobileDrawerFooter.d.ts.map +1 -1
- package/lib/types/client/MobileHome.d.ts +1 -1
- package/lib/types/client/MobileHome.d.ts.map +1 -1
- package/lib/types/client/MobileHomeChips.d.ts +1 -1
- package/lib/types/client/MobileHomeChips.d.ts.map +1 -1
- package/lib/types/client/MobileSessionHeader.d.ts +19 -8
- package/lib/types/client/MobileSessionHeader.d.ts.map +1 -1
- package/lib/types/client/MobileSessionInfo.d.ts +20 -3
- package/lib/types/client/MobileSessionInfo.d.ts.map +1 -1
- package/lib/types/client/RemoteComposerBanner.d.ts +42 -0
- package/lib/types/client/RemoteComposerBanner.d.ts.map +1 -0
- package/lib/types/client/RemoteHeaderIcon.d.ts +37 -0
- package/lib/types/client/RemoteHeaderIcon.d.ts.map +1 -0
- package/lib/types/client/RemoteShareMenu.d.ts +28 -0
- package/lib/types/client/RemoteShareMenu.d.ts.map +1 -0
- package/lib/types/client/RemoteStatusIcon.d.ts +36 -0
- package/lib/types/client/RemoteStatusIcon.d.ts.map +1 -0
- package/lib/types/client/compat/desktop.d.ts +21 -0
- package/lib/types/client/compat/desktop.d.ts.map +1 -0
- package/lib/types/client/compat/types.d.ts +57 -0
- package/lib/types/client/compat/types.d.ts.map +1 -1
- package/lib/types/client/current-session-report.d.ts +37 -0
- package/lib/types/client/current-session-report.d.ts.map +1 -0
- package/lib/types/client/effects/gestures.d.ts.map +1 -1
- package/lib/types/client/effects/header-status.d.ts.map +1 -1
- package/lib/types/client/effects/keyboard-guard.d.ts.map +1 -1
- package/lib/types/client/effects/model-sheet-extras.d.ts.map +1 -1
- package/lib/types/client/effects/permission-sheet.d.ts +39 -0
- package/lib/types/client/effects/permission-sheet.d.ts.map +1 -0
- package/lib/types/client/effects/remote-session.d.ts +25 -0
- package/lib/types/client/effects/remote-session.d.ts.map +1 -0
- package/lib/types/client/effects/turn-fold.d.ts +9 -0
- package/lib/types/client/effects/turn-fold.d.ts.map +1 -1
- package/lib/types/client/effects/workbench-ref-close.d.ts +2 -1
- package/lib/types/client/effects/workbench-ref-close.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +187 -2
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/remote-fetch.d.ts +51 -0
- package/lib/types/client/remote-fetch.d.ts.map +1 -0
- package/lib/types/client/remote-group-expansion.d.ts +41 -0
- package/lib/types/client/remote-group-expansion.d.ts.map +1 -0
- package/lib/types/client/remote-share-css.d.ts +19 -0
- package/lib/types/client/remote-share-css.d.ts.map +1 -0
- package/lib/types/client/remote-share-register.d.ts +113 -0
- package/lib/types/client/remote-share-register.d.ts.map +1 -0
- package/lib/types/client/remote-status-css.d.ts +15 -0
- package/lib/types/client/remote-status-css.d.ts.map +1 -0
- package/lib/types/client/remote-status-register.d.ts +46 -0
- package/lib/types/client/remote-status-register.d.ts.map +1 -0
- package/lib/types/client/settings/SettingsSection.d.ts +46 -0
- package/lib/types/client/settings/SettingsSection.d.ts.map +1 -0
- package/lib/types/client/settings/register-settings.d.ts +58 -0
- package/lib/types/client/settings/register-settings.d.ts.map +1 -0
- package/lib/types/client/settings/settings-css.d.ts +10 -0
- package/lib/types/client/settings/settings-css.d.ts.map +1 -0
- package/lib/types/client/share/share-preview.d.ts.map +1 -1
- package/lib/types/client/sidebar-panels.d.ts +63 -0
- package/lib/types/client/sidebar-panels.d.ts.map +1 -0
- package/lib/types/client/styles/compat.css.d.ts +1 -1
- package/lib/types/client/styles/compat.css.d.ts.map +1 -1
- package/lib/types/client/styles/composer.css.d.ts +1 -1
- package/lib/types/client/styles/composer.css.d.ts.map +1 -1
- package/lib/types/client/styles/header.css.d.ts +1 -1
- package/lib/types/client/styles/header.css.d.ts.map +1 -1
- package/lib/types/client/styles/info.css.d.ts +1 -1
- package/lib/types/client/styles/info.css.d.ts.map +1 -1
- package/lib/types/client/styles/layout.css.d.ts +1 -1
- package/lib/types/client/styles/layout.css.d.ts.map +1 -1
- package/lib/types/client/styles/remote-session.css.d.ts +2 -0
- package/lib/types/client/styles/remote-session.css.d.ts.map +1 -0
- package/lib/types/client-data/current-session.d.ts +41 -0
- package/lib/types/client-data/current-session.d.ts.map +1 -0
- package/lib/types/client-data/remote-group-expansion.d.ts +76 -0
- package/lib/types/client-data/remote-group-expansion.d.ts.map +1 -0
- package/lib/types/client-data/remote-status.d.ts +148 -0
- package/lib/types/client-data/remote-status.d.ts.map +1 -0
- package/lib/types/client-data/settings-form.d.ts +774 -0
- package/lib/types/client-data/settings-form.d.ts.map +1 -0
- package/lib/types/client-data/shares.d.ts +211 -0
- package/lib/types/client-data/shares.d.ts.map +1 -0
- package/lib/types/client-pairing.d.ts +114 -0
- package/lib/types/client-pairing.d.ts.map +1 -0
- package/lib/types/client-routes.d.ts +112 -0
- package/lib/types/client-routes.d.ts.map +1 -0
- package/lib/types/config.d.ts +200 -0
- package/lib/types/config.d.ts.map +1 -0
- package/lib/types/fetch-route-intercept.d.ts +156 -0
- package/lib/types/fetch-route-intercept.d.ts.map +1 -0
- package/lib/types/fingerprint.d.ts +164 -0
- package/lib/types/fingerprint.d.ts.map +1 -0
- package/lib/types/http.d.ts +41 -0
- package/lib/types/http.d.ts.map +1 -0
- package/lib/types/index.d.ts +56 -24
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/intercept-shape.d.ts +32 -0
- package/lib/types/intercept-shape.d.ts.map +1 -0
- package/lib/types/intercept.d.ts +432 -0
- package/lib/types/intercept.d.ts.map +1 -0
- package/lib/types/merge-streams.d.ts +393 -0
- package/lib/types/merge-streams.d.ts.map +1 -0
- package/lib/types/relay-access.d.ts +310 -0
- package/lib/types/relay-access.d.ts.map +1 -0
- package/lib/types/relay-client.d.ts +274 -0
- package/lib/types/relay-client.d.ts.map +1 -0
- package/lib/types/relay-filter.d.ts +103 -0
- package/lib/types/relay-filter.d.ts.map +1 -0
- package/lib/types/relay-server.d.ts +208 -0
- package/lib/types/relay-server.d.ts.map +1 -0
- package/lib/types/restart-fields.d.ts +22 -0
- package/lib/types/restart-fields.d.ts.map +1 -0
- package/lib/types/restart-watcher.d.ts +83 -0
- package/lib/types/restart-watcher.d.ts.map +1 -0
- package/lib/types/session-reference.d.ts +66 -0
- package/lib/types/session-reference.d.ts.map +1 -0
- package/lib/types/share-export.d.ts +14 -1
- package/lib/types/share-export.d.ts.map +1 -1
- package/lib/types/share-ops.d.ts +79 -0
- package/lib/types/share-ops.d.ts.map +1 -0
- package/lib/types/share-store.d.ts +87 -0
- package/lib/types/share-store.d.ts.map +1 -0
- package/lib/types/virtual-id.d.ts +38 -0
- package/lib/types/virtual-id.d.ts.map +1 -0
- package/lib/virtual-id.js +52 -0
- package/lib/virtual-id.js.map +1 -0
- package/llms.txt +17 -14
- package/package.json +60 -50
- package/scripts/build-client.mjs +5 -3
- package/scripts/check-host-shadow.mjs +19 -2
- package/scripts/check-settings-form.mjs +933 -0
- package/scripts/check-shares.mjs +397 -0
- package/scripts/check-upload-endpoint.mjs +59 -3
- package/scripts/sync-doc-version.mjs +8 -15
- package/scripts/verify-fingerprint-live.mjs +111 -0
- package/src/activity.ts +218 -0
- package/src/admin-routes.ts +627 -0
- package/src/client/MobileAttachButton.tsx +2 -2
- package/src/client/MobileAttachChips.tsx +3 -3
- package/src/client/MobileDrawerFooter.tsx +7 -4
- package/src/client/MobileHome.tsx +20 -16
- package/src/client/MobileHomeChips.tsx +25 -21
- package/src/client/MobileNavOverlay.tsx +2 -2
- package/src/client/MobileNavToggle.tsx +3 -3
- package/src/client/MobileSessionHeader.tsx +100 -107
- package/src/client/MobileSessionInfo.tsx +118 -17
- package/src/client/RemoteComposerBanner.tsx +76 -0
- package/src/client/RemoteHeaderIcon.tsx +103 -0
- package/src/client/RemoteShareMenu.tsx +57 -0
- package/src/client/RemoteStatusIcon.tsx +91 -0
- package/src/client/compat/desktop.ts +22 -0
- package/src/client/compat/types.ts +62 -0
- package/src/client/current-session-report.ts +164 -0
- package/src/client/effects/gestures.ts +8 -17
- package/src/client/effects/header-status.ts +21 -17
- package/src/client/effects/keyboard-avoid.ts +2 -2
- package/src/client/effects/keyboard-guard.ts +75 -7
- package/src/client/effects/model-sheet-extras.ts +8 -2
- package/src/client/effects/permission-sheet.ts +149 -0
- package/src/client/effects/remote-session.ts +68 -0
- package/src/client/effects/turn-fold.ts +52 -0
- package/src/client/effects/workbench-ref-close.ts +16 -23
- package/src/client/index.tsx +151 -36
- package/src/client/locales.ts +396 -2
- package/src/client/remote-fetch.ts +125 -0
- package/src/client/remote-group-expansion.ts +190 -0
- package/src/client/remote-share-css.ts +54 -0
- package/src/client/remote-share-register.ts +244 -0
- package/src/client/remote-status-css.ts +64 -0
- package/src/client/remote-status-register.ts +107 -0
- package/src/client/settings/SettingsSection.tsx +1229 -0
- package/src/client/settings/register-settings.ts +116 -0
- package/src/client/settings/settings-css.ts +236 -0
- package/src/client/share/share-preview.tsx +6 -3
- package/src/client/share/share-sheet.tsx +3 -3
- package/src/client/sidebar-panels.ts +99 -0
- package/src/client/styles/compat.css.ts +10 -0
- package/src/client/styles/composer.css.ts +147 -36
- package/src/client/styles/header.css.ts +15 -11
- package/src/client/styles/info.css.ts +74 -0
- package/src/client/styles/layout.css.ts +20 -0
- package/src/client/styles/remote-session.css.ts +251 -0
- package/src/client-data/current-session.ts +57 -0
- package/src/client-data/remote-group-expansion.ts +145 -0
- package/src/client-data/remote-status.ts +374 -0
- package/src/client-data/settings-form.ts +1371 -0
- package/src/client-data/shares.ts +467 -0
- package/src/client-pairing.ts +244 -0
- package/src/client-routes.ts +732 -0
- package/src/config.ts +447 -0
- package/src/fetch-route-intercept.ts +510 -0
- package/src/fingerprint.ts +408 -0
- package/src/http.ts +68 -0
- package/src/index.ts +719 -62
- package/src/intercept-shape.ts +87 -0
- package/src/intercept.ts +3362 -0
- package/src/merge-streams.ts +1272 -0
- package/src/relay-access.ts +669 -0
- package/src/relay-client.ts +1514 -0
- package/src/relay-filter.ts +291 -0
- package/src/relay-server.ts +2025 -0
- package/src/restart-fields.ts +36 -0
- package/src/restart-watcher.ts +140 -0
- package/src/session-reference.ts +156 -0
- package/src/share-export.ts +26 -1
- package/src/share-ops.ts +114 -0
- package/src/share-store.ts +373 -0
- package/src/virtual-id.ts +59 -0
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Staged configuration form for the `dsh-zen-remote` plugin row, in the same
|
|
3
|
+
* shape the dsh-llm-verifier page uses (a control stages what the user types,
|
|
4
|
+
* one save writes every staged edit as a single revision-fenced path mutation
|
|
5
|
+
* through the shared `configForms` form). Browser-import-free so tests drive
|
|
6
|
+
* it directly:
|
|
7
|
+
*
|
|
8
|
+
* - `deriveSettingsView(status)` maps the `GET /_dsh/zen-remote/admin/status`
|
|
9
|
+
* body (T14's same-origin route wrapping the gateway's local admin API) into
|
|
10
|
+
* what the settings block renders: per-field effective value + source layer,
|
|
11
|
+
* the device list, the live pairing code with its remaining seconds, and the
|
|
12
|
+
* `viaGateway` flag that disables every server-local operation.
|
|
13
|
+
* - `deriveClientStatusView(status)` maps the `GET /_dsh/zen-remote/client/status`
|
|
14
|
+
* body (T16's sub-client probe) into the client group's connection line.
|
|
15
|
+
* - `ZenRemoteSettingsForm` stages the row-layer field edits and saves them as
|
|
16
|
+
* one `mutate(ops, expectedRevision)` call, and owns the two DIRECT writes
|
|
17
|
+
* of the client group (pairing write / token clear) plus the device token's
|
|
18
|
+
* configured flag read from the describe view's secrets sidecar. Since T17
|
|
19
|
+
* it also carries the page's two-level role decision — the saved row role
|
|
20
|
+
* first, the client-config probe as fallback — and the `restartPending`
|
|
21
|
+
* flag behind the "saving reloads the plugin" note.
|
|
22
|
+
*
|
|
23
|
+
* The wire contract lives here because the host half that serves it is a
|
|
24
|
+
* separate task; the shapes mirror `lib/lan-gate-server.cjs`'s status payload
|
|
25
|
+
* (devices / pairing) plus the T14 envelope (ok / gateway / config / viaGateway).
|
|
26
|
+
*/
|
|
27
|
+
/** Where a resolved field value came from (mirror of src/config.ts's union; the client half cannot import the host module). */
|
|
28
|
+
export type ConfigSource = 'env' | 'row' | 'file' | 'default';
|
|
29
|
+
/** One paired gateway device, as the gateway's status payload lists it. */
|
|
30
|
+
export interface AdminDevice {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
role: 'web' | 'desktop-client';
|
|
34
|
+
kind: 'auto' | 'phone' | 'desktop';
|
|
35
|
+
createdAt: number;
|
|
36
|
+
lastSeen: number;
|
|
37
|
+
ua?: string;
|
|
38
|
+
hasPush: boolean;
|
|
39
|
+
}
|
|
40
|
+
/** The one currently-live pairing code, if any. */
|
|
41
|
+
export interface AdminPairing {
|
|
42
|
+
code: string;
|
|
43
|
+
expiresAt: number;
|
|
44
|
+
role: 'web' | 'desktop-client';
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The gateway's `/lan-gate/status` reply, relayed verbatim inside
|
|
48
|
+
* `admin/status`'s `gateway` field (see lib/lan-gate-server.cjs's
|
|
49
|
+
* statusHandler — this mirror keeps only what the settings block reads).
|
|
50
|
+
*/
|
|
51
|
+
export interface GatewayStatus {
|
|
52
|
+
state?: string;
|
|
53
|
+
port?: number;
|
|
54
|
+
target?: string;
|
|
55
|
+
devices?: AdminDevice[];
|
|
56
|
+
pairing?: AdminPairing | null;
|
|
57
|
+
pushSubscriptions?: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The `GET /_dsh/zen-remote/admin/status` body, exactly as src/admin-routes.ts
|
|
61
|
+
* answers it: the gateway payload lives NESTED under `gateway` (null when the
|
|
62
|
+
* gateway did not answer healthily) and `gatewayStatus` is the HTTP status the
|
|
63
|
+
* route saw (`null` when there was no answer at all).
|
|
64
|
+
*/
|
|
65
|
+
export interface AdminStatusBody {
|
|
66
|
+
ok?: boolean;
|
|
67
|
+
gateway?: GatewayStatus | null;
|
|
68
|
+
gatewayReachable?: boolean;
|
|
69
|
+
gatewayStatus?: number | null;
|
|
70
|
+
config?: {
|
|
71
|
+
values?: Record<string, unknown>;
|
|
72
|
+
sources?: Record<string, string>;
|
|
73
|
+
};
|
|
74
|
+
viaGateway?: boolean;
|
|
75
|
+
}
|
|
76
|
+
/** Same-origin admin routes the settings block talks to (host half: T14). */
|
|
77
|
+
export declare const ADMIN_STATUS_ROUTE = "/_dsh/zen-remote/admin/status";
|
|
78
|
+
export declare const ADMIN_PAIR_ROUTE = "/_dsh/zen-remote/admin/pair";
|
|
79
|
+
export declare const ADMIN_ACTION_ROUTE = "/_dsh/zen-remote/admin/action";
|
|
80
|
+
export declare const ADMIN_PUSH_TEST_ROUTE = "/_dsh/zen-remote/admin/push-test";
|
|
81
|
+
/** Same-origin client routes the sub-client block talks to (host half: T16). */
|
|
82
|
+
export declare const CLIENT_CLAIM_ROUTE = "/_dsh/zen-remote/client/claim";
|
|
83
|
+
export declare const CLIENT_STATUS_ROUTE = "/_dsh/zen-remote/client/status";
|
|
84
|
+
/** T43: one immediate reconnect — answered 409 unless the client is offline. */
|
|
85
|
+
export declare const CLIENT_RECONNECT_ROUTE = "/_dsh/zen-remote/client/reconnect";
|
|
86
|
+
/** The lightweight client-facing config route (host half, both roles): the
|
|
87
|
+
* settings page's FALLBACK role probe (T17) — it registers wherever a
|
|
88
|
+
* webServer exists, and since T17 its body carries the effective `role`
|
|
89
|
+
* (resolveConfig's merged value, nothing sensitive). */
|
|
90
|
+
export declare const CLIENT_CONFIG_ROUTE = "/_dsh/mobile-nav/client-config";
|
|
91
|
+
/** The body of `GET /_dsh/mobile-nav/client-config` — the interface-half knobs
|
|
92
|
+
* the client bundle reads, plus (T17) the effective role the settings page
|
|
93
|
+
* falls back to. */
|
|
94
|
+
export interface ClientConfigBody {
|
|
95
|
+
role?: unknown;
|
|
96
|
+
turnFoldDesktop?: boolean;
|
|
97
|
+
keyboardLiftRatio?: number;
|
|
98
|
+
keyboardLiftMaxPx?: number;
|
|
99
|
+
keyboardSafetyPadPx?: number;
|
|
100
|
+
}
|
|
101
|
+
/** Field name of the row secret the pairing flow writes (never echoed back
|
|
102
|
+
* anywhere; the describe view's secrets sidecar is the only "is it set"). */
|
|
103
|
+
export declare const DEVICE_TOKEN_FIELD = "deviceToken";
|
|
104
|
+
/**
|
|
105
|
+
* The `GET /_dsh/zen-remote/client/status` body, exactly as
|
|
106
|
+
* src/client-routes.ts answers it: `serverUrl` is present only once a token
|
|
107
|
+
* exists (the unpaired answer is `{ state: 'unpaired' }` alone). The token
|
|
108
|
+
* itself never rides any status response. The T43 diagnostics fields ride
|
|
109
|
+
* only on a failed live connect of a wired relay client; `intercept` (T23b)
|
|
110
|
+
* and `compat` (T42) are rendered only when the body carries them — earlier
|
|
111
|
+
* servers answer neither.
|
|
112
|
+
*/
|
|
113
|
+
export interface ClientStatusBody {
|
|
114
|
+
/** The probe vocabulary (`connected` / `unreachable` / `revoked` /
|
|
115
|
+
* `unexpected`), the row's two own answers (`unpaired` / `invalid-url`),
|
|
116
|
+
* the relay verdicts (`revoked` / `incompatible`) — and, since the
|
|
117
|
+
* relay-wired route reports the relay client's own state verbatim, the
|
|
118
|
+
* `online` / `offline` words too ({@link deriveClientStatusView} maps
|
|
119
|
+
* them; T55). */
|
|
120
|
+
state?: string;
|
|
121
|
+
serverUrl?: string;
|
|
122
|
+
serverName?: unknown;
|
|
123
|
+
/** Epoch ms of the relay client's next automatic reconnect (offline only). */
|
|
124
|
+
nextRetryAt?: unknown;
|
|
125
|
+
/** The error code of the last failure — a code, never a message or URL. */
|
|
126
|
+
lastError?: unknown;
|
|
127
|
+
/**
|
|
128
|
+
* The SERVER's record of THIS device's name (T59, presence-gated like the
|
|
129
|
+
* diagnostics fields — older servers answer without it). The settings page
|
|
130
|
+
* follows it into the row while the user is not editing the field.
|
|
131
|
+
*/
|
|
132
|
+
deviceName?: unknown;
|
|
133
|
+
/** T23b request-interceptor diagnostics (provisional shape; presence-gated). */
|
|
134
|
+
intercept?: unknown;
|
|
135
|
+
/** T42 relay compat diagnostics: `{ identical: string[], different:
|
|
136
|
+
* string[], unavailable: string[], incompatibleCalls: { time: number,
|
|
137
|
+
* endpoint: string, code: string }[] }` (presence-gated). */
|
|
138
|
+
compat?: unknown;
|
|
139
|
+
}
|
|
140
|
+
/** One remote-call failure line in the diagnostics lists. `time` is epoch
|
|
141
|
+
* milliseconds — ISO-stamped rings are parsed at derive time; `method`
|
|
142
|
+
* carries the wire's method-or-endpoint name. */
|
|
143
|
+
export interface ClientDiagFailureView {
|
|
144
|
+
time: number;
|
|
145
|
+
method: string;
|
|
146
|
+
code: string;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* T23b interceptor diagnostics as the block renders them. The wire field is
|
|
150
|
+
* presence-gated: `undefined` here means the body carried none (an older
|
|
151
|
+
* server), and the whole interceptor group stays hidden.
|
|
152
|
+
*/
|
|
153
|
+
export interface ClientInterceptView {
|
|
154
|
+
installed: boolean;
|
|
155
|
+
/** Shape-detection failure reasons — non-empty means remote features are off. */
|
|
156
|
+
reasons: string[];
|
|
157
|
+
/** The most recent remote call failures (at most 10). */
|
|
158
|
+
recentFailures: ClientDiagFailureView[];
|
|
159
|
+
}
|
|
160
|
+
/** T42 compat diagnostics as the block renders them (presence-gated like {@link ClientInterceptView}). */
|
|
161
|
+
export interface ClientCompatView {
|
|
162
|
+
/** Names of the groups whose fingerprints differ. */
|
|
163
|
+
mismatchedGroups: string[];
|
|
164
|
+
/** The most recent incompatible calls (at most 10). */
|
|
165
|
+
recentCalls: ClientDiagFailureView[];
|
|
166
|
+
}
|
|
167
|
+
/** One client connection as the block renders it. */
|
|
168
|
+
export interface ClientConnectionView {
|
|
169
|
+
state: 'unpaired' | 'connected' | 'revoked' | 'unreachable' | 'unexpected' | 'invalid-url' | 'incompatible';
|
|
170
|
+
serverUrl: string;
|
|
171
|
+
serverName: string;
|
|
172
|
+
/** Verbatim from the body when it carried a finite number; the countdown
|
|
173
|
+
* math happens at render time against the live clock. */
|
|
174
|
+
nextRetryAt: number | undefined;
|
|
175
|
+
/** The last failure's code, '' when none is reported. */
|
|
176
|
+
lastError: string;
|
|
177
|
+
/** The SERVER's record of this device's name (T59): '' when the body did
|
|
178
|
+
* not carry one — empty means "nothing to follow". */
|
|
179
|
+
deviceName: string;
|
|
180
|
+
intercept: ClientInterceptView | undefined;
|
|
181
|
+
compat: ClientCompatView | undefined;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Map one `client/status` body into the view the client group renders.
|
|
185
|
+
* Tolerant like {@link deriveSettingsView}: an unexpected shape degrades to
|
|
186
|
+
* "unpaired" instead of throwing into the plugin page.
|
|
187
|
+
*/
|
|
188
|
+
export declare function deriveClientStatusView(body: ClientStatusBody): ClientConnectionView;
|
|
189
|
+
/**
|
|
190
|
+
* Which connection line the block renders (T43 diagnostics wording, chosen
|
|
191
|
+
* as a pure descriptor so the copy table and the countdown math stay
|
|
192
|
+
* testable without a browser): connected prefers the handshake's server
|
|
193
|
+
* name; any verdict the body backs with a `nextRetryAt` is OFFLINE first —
|
|
194
|
+
* the relay client is mid-retry and the line counts down to it, whether the
|
|
195
|
+
* probe classified the failure `unreachable` or `unexpected` (T43-fix);
|
|
196
|
+
* without a `nextRetryAt` the two map one plain copy each. The rest map one
|
|
197
|
+
* state each.
|
|
198
|
+
*/
|
|
199
|
+
export type ClientStatusLine = {
|
|
200
|
+
kind: 'connectedName';
|
|
201
|
+
serverName: string;
|
|
202
|
+
} | {
|
|
203
|
+
kind: 'connected';
|
|
204
|
+
serverUrl: string;
|
|
205
|
+
} | {
|
|
206
|
+
kind: 'offlineRetry';
|
|
207
|
+
seconds: number;
|
|
208
|
+
} | {
|
|
209
|
+
kind: 'offlineRetrySoon';
|
|
210
|
+
} | {
|
|
211
|
+
kind: 'unreachable';
|
|
212
|
+
} | {
|
|
213
|
+
kind: 'revoked';
|
|
214
|
+
} | {
|
|
215
|
+
kind: 'incompatible';
|
|
216
|
+
} | {
|
|
217
|
+
kind: 'unexpected';
|
|
218
|
+
} | {
|
|
219
|
+
kind: 'invalidUrl';
|
|
220
|
+
} | {
|
|
221
|
+
kind: 'unpaired';
|
|
222
|
+
};
|
|
223
|
+
export declare function clientStatusLineOf(view: ClientConnectionView, now: number): ClientStatusLine;
|
|
224
|
+
/** The `POST /_dsh/zen-remote/client/claim` body (T16's pairing round-trip;
|
|
225
|
+
* on success `token` is the gateway-minted device token — it appears exactly
|
|
226
|
+
* once, on its way into the row's secret field). */
|
|
227
|
+
export interface ClaimRouteBody {
|
|
228
|
+
ok?: boolean;
|
|
229
|
+
token?: unknown;
|
|
230
|
+
deviceId?: unknown;
|
|
231
|
+
deviceName?: unknown;
|
|
232
|
+
/** The normalized address the backend validated — what the form writes. */
|
|
233
|
+
serverUrl?: unknown;
|
|
234
|
+
code?: string;
|
|
235
|
+
message?: string;
|
|
236
|
+
retryAfterMs?: number;
|
|
237
|
+
}
|
|
238
|
+
/** What the pairing-code box keeps as its draft: uppercase, no spaces or
|
|
239
|
+
* hyphens (the gateway strips every other character at claim time anyway). */
|
|
240
|
+
export declare function normalizePairingCode(input: string): string;
|
|
241
|
+
/**
|
|
242
|
+
* Whether the settings page may follow the SERVER's record of this device's
|
|
243
|
+
* name into the row (T59): the body carried a name, it differs from the row
|
|
244
|
+
* (nothing to do otherwise — and this equality is the anti-bounce half: the
|
|
245
|
+
* page's own write echoes back through client/status with both sides equal),
|
|
246
|
+
* and the user has no staged draft in the field (their edit wins until they
|
|
247
|
+
* save — a follow mid-typing would clobber it).
|
|
248
|
+
*/
|
|
249
|
+
export declare function shouldFollowServerDeviceName(deviceName: string, rowName: string, hasDraft: boolean): boolean;
|
|
250
|
+
/**
|
|
251
|
+
* Latest-wins sequencing for the status loads: every request takes a ticket,
|
|
252
|
+
* and only the newest ticket may still apply its result. An earlier request
|
|
253
|
+
* that answers LATE is dropped, so a stale body can never overwrite a fresh
|
|
254
|
+
* one (a revoked device reappearing, an old "no pairing code" answer wiping
|
|
255
|
+
* a just-minted code, an unpair racing a refresh).
|
|
256
|
+
*/
|
|
257
|
+
export interface LatestGate {
|
|
258
|
+
/** Issue the ticket for one new in-flight request. */
|
|
259
|
+
next(): number;
|
|
260
|
+
/** Whether that ticket is still the newest issued one. */
|
|
261
|
+
isLatest(ticket: number): boolean;
|
|
262
|
+
}
|
|
263
|
+
export declare function createLatestGate(): LatestGate;
|
|
264
|
+
/**
|
|
265
|
+
* The SAVED role of the plugin row, read from the configForms snapshot's row
|
|
266
|
+
* document — the settings page's FIRST role signal (T16-fix 3). The resolved
|
|
267
|
+
* role an admin/status body reports is unusable here: a client deployment has
|
|
268
|
+
* no admin route at all, and a stale kept body pinned the page to the old
|
|
269
|
+
* role after a switch. `value` is the schema-resolved section the Host
|
|
270
|
+
* accepted; a row value that only lives in the raw user layer reads from
|
|
271
|
+
* there. Since T17 every row field is volatile, so the form (and this
|
|
272
|
+
* snapshot) carries every stored field — `role` included, with any volatile
|
|
273
|
+
* wrapper already peeled by the host's plainConfig (the dsh-settings
|
|
274
|
+
* describe path calls `value.get()` recursively before the wire).
|
|
275
|
+
*
|
|
276
|
+
* `undefined` when NEITHER layer carries a role: the field's value lives only
|
|
277
|
+
* in a lower layer (`lan-gate.config.json`) or nowhere. That is not "host" —
|
|
278
|
+
* the caller falls back to the effective role the client-config route
|
|
279
|
+
* reports ({@link settingsPollOf}), because a file-layer client row must
|
|
280
|
+
* still render the client page.
|
|
281
|
+
*/
|
|
282
|
+
export declare function savedRowRole(snapshot: {
|
|
283
|
+
value?: unknown;
|
|
284
|
+
user?: unknown;
|
|
285
|
+
}): 'host' | 'client' | undefined;
|
|
286
|
+
/**
|
|
287
|
+
* The EFFECTIVE role for one scope snapshot — the shared two-level decision
|
|
288
|
+
* every role-aware surface uses (the settings page via {@link settingsPollOf},
|
|
289
|
+
* the T33b session-sharing parts through the remote-share registration):
|
|
290
|
+
* while the namespace mirror is still loading the role is not knowable
|
|
291
|
+
* (`'unknown'` — a client deployment must never see a wasted `admin/*` 404);
|
|
292
|
+
* a snapshot whose row carries a role decides from it; a row-silent snapshot
|
|
293
|
+
* falls back to `probed`, the effective role fetched from
|
|
294
|
+
* `/_dsh/mobile-nav/client-config` (T17: the route carries the merged role),
|
|
295
|
+
* and stays `'unknown'` until that probe answers.
|
|
296
|
+
*/
|
|
297
|
+
export declare function settingsRoleOf(status: 'loading' | 'ready' | 'unavailable', snapshot: {
|
|
298
|
+
value?: unknown;
|
|
299
|
+
user?: unknown;
|
|
300
|
+
}, probed?: 'host' | 'client'): 'unknown' | 'host' | 'client';
|
|
301
|
+
/**
|
|
302
|
+
* Which status source the page polls for one scope snapshot —
|
|
303
|
+
* {@link settingsRoleOf} mapped onto poll sources (`'unknown'` polls
|
|
304
|
+
* nothing). See there for the decision.
|
|
305
|
+
*/
|
|
306
|
+
export declare function settingsPollOf(status: 'loading' | 'ready' | 'unavailable', snapshot: {
|
|
307
|
+
value?: unknown;
|
|
308
|
+
user?: unknown;
|
|
309
|
+
}, probed?: 'host' | 'client'): 'none' | 'admin' | 'client';
|
|
310
|
+
/**
|
|
311
|
+
* The row fields whose changed value reloads the plugin row after a save —
|
|
312
|
+
* re-exported from src/restart-fields.ts, the single list the host half's
|
|
313
|
+
* restart watcher fingerprints too (T17b collapsed the two copies; the leaf
|
|
314
|
+
* module is dependency-free so the client bundler can inline it). Only the
|
|
315
|
+
* fields this page edits can ever stage a draft here — `targetPort` /
|
|
316
|
+
* `pushEvents` are not page fields — but the list is the full shared set so
|
|
317
|
+
* a host-side change surfaces in the diff.
|
|
318
|
+
*/
|
|
319
|
+
export { RESTART_FIELDS } from '../restart-fields.ts';
|
|
320
|
+
/** One path-addressed edit a save sends (the wire `SettingsPathOpView` shape). */
|
|
321
|
+
export type SettingsFormOp = {
|
|
322
|
+
op: 'set';
|
|
323
|
+
path: string[];
|
|
324
|
+
value: unknown;
|
|
325
|
+
} | {
|
|
326
|
+
op: 'unset';
|
|
327
|
+
path: string[];
|
|
328
|
+
};
|
|
329
|
+
/** Structural subset of the shared `ConfigForm` snapshot this form reads. */
|
|
330
|
+
export interface SettingsFormScopeSnapshot {
|
|
331
|
+
status: 'loading' | 'ready' | 'unavailable';
|
|
332
|
+
value: Record<string, unknown> | undefined;
|
|
333
|
+
/** Composition layer; what a field reverts to once cleared. */
|
|
334
|
+
base: unknown;
|
|
335
|
+
/** Raw user layer as stored; field PRESENCE here marks an override. */
|
|
336
|
+
user: unknown;
|
|
337
|
+
/** Revision fencing the next write; sent back as `expectedRevision`. */
|
|
338
|
+
revision: number | undefined;
|
|
339
|
+
writable: boolean;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* The form face this controller stages over (structural subset of
|
|
343
|
+
* ui-settings' `ConfigForm` — declared locally because this package does not
|
|
344
|
+
* depend on the ui-settings types).
|
|
345
|
+
*/
|
|
346
|
+
export interface SettingsFormScope {
|
|
347
|
+
getSnapshot(): SettingsFormScopeSnapshot;
|
|
348
|
+
subscribe(listener: () => void): () => void;
|
|
349
|
+
mutate(ops: readonly SettingsFormOp[], expectedRevision?: number): Promise<boolean>;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* One secret path in the describe view's sidecar: the path plus whether it
|
|
353
|
+
* is set — the VALUE itself never rides any describe surface.
|
|
354
|
+
*/
|
|
355
|
+
export interface DescribeSecret {
|
|
356
|
+
path: string[];
|
|
357
|
+
set: boolean;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* The `configForms.describe()` mirror (structural, like {@link ConfigFormsLike}
|
|
361
|
+
* — the dsh-client-ui-settings package is not a dependency). Read only for
|
|
362
|
+
* the secrets sidecar: whether the row's `deviceToken` is set.
|
|
363
|
+
*/
|
|
364
|
+
export interface ConfigFormsDescribe {
|
|
365
|
+
getSnapshot(): {
|
|
366
|
+
view?: {
|
|
367
|
+
namespaces?: ReadonlyArray<{
|
|
368
|
+
ns: string;
|
|
369
|
+
secrets?: ReadonlyArray<DescribeSecret>;
|
|
370
|
+
}>;
|
|
371
|
+
};
|
|
372
|
+
};
|
|
373
|
+
subscribe(listener: () => void): () => void;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* The `configForms` service face this plugin uses, mirrored onto the cordis
|
|
377
|
+
* Context. Declared locally (the dsh-client-ui-settings package is not a
|
|
378
|
+
* dependency — same trick as the local `UiWorkspaceLike`): the runtime
|
|
379
|
+
* instance is provided by the host, and the lazy `ctx.inject(['configForms'], …)`
|
|
380
|
+
* in register-settings keeps a composition without the service loadable.
|
|
381
|
+
*/
|
|
382
|
+
export interface ConfigFormsLike {
|
|
383
|
+
get(entryId: string): SettingsFormScope;
|
|
384
|
+
describe(): ConfigFormsDescribe;
|
|
385
|
+
whileServed(namespaces: readonly string[], register: (served: ReadonlySet<string>) => () => void): () => void;
|
|
386
|
+
}
|
|
387
|
+
/** The write one staged draft performs when the form is saved. */
|
|
388
|
+
export type SettingsFieldWrite = {
|
|
389
|
+
kind: 'set';
|
|
390
|
+
value: unknown;
|
|
391
|
+
} | {
|
|
392
|
+
kind: 'clear';
|
|
393
|
+
};
|
|
394
|
+
/** How one field converts between its stored value and draft text. */
|
|
395
|
+
export interface SettingsFieldSpec {
|
|
396
|
+
field: string;
|
|
397
|
+
format(value: unknown): string;
|
|
398
|
+
/** The staged write, or undefined when the draft is not a value this field accepts. */
|
|
399
|
+
parse(text: string): SettingsFieldWrite | undefined;
|
|
400
|
+
}
|
|
401
|
+
/** Free-text field: an empty draft clears, so the field re-inherits its default. */
|
|
402
|
+
export declare function textField(field: string): SettingsFieldSpec;
|
|
403
|
+
/** Whole-number field within inclusive bounds; empty clears, anything else out of range blocks the save. */
|
|
404
|
+
export declare function intField(field: string, min: number, max: number): SettingsFieldSpec;
|
|
405
|
+
/** Finite number in (min, max] — the `idleHours` shape: zero excluded, 8760 allowed. */
|
|
406
|
+
export declare function hoursField(field: string, max: number): SettingsFieldSpec;
|
|
407
|
+
/** `serverName`: an empty draft clears; a blank-but-nonempty draft or one over
|
|
408
|
+
* the 40-character cap is invalid and blocks the save (src/config.ts clips at
|
|
409
|
+
* resolve time, so an over-long write would silently lose its tail). */
|
|
410
|
+
export declare function nameField(field: string, max: number): SettingsFieldSpec;
|
|
411
|
+
/**
|
|
412
|
+
* The name one pairing claim registers under (T57, trimmed since T60): the
|
|
413
|
+
* shared 设备名称 field's CURRENT displayed value — a staged draft when one
|
|
414
|
+
* exists — trimmed FIRST and judged on the trimmed text (a 40-character name
|
|
415
|
+
* with padded spaces is legal; whitespace alone is not a name), returned
|
|
416
|
+
* trimmed so what the claim SENDS is exactly what a later save would store.
|
|
417
|
+
* Falls back to the default device-name copy otherwise. The fallback is
|
|
418
|
+
* load-bearing since T55: a client page reads no admin baseline, so an
|
|
419
|
+
* unstored name displays EMPTY and the claim must still carry a usable name.
|
|
420
|
+
*/
|
|
421
|
+
export declare function claimDeviceNameOf(displayText: string, fallback: string): string;
|
|
422
|
+
/** One-of field over a fixed vocabulary (rendered as a select). */
|
|
423
|
+
export declare function oneOfField(field: string, options: readonly string[]): SettingsFieldSpec;
|
|
424
|
+
/** Boolean field staged as 'true'/'false' draft text (rendered as a checkbox). */
|
|
425
|
+
export declare function boolField(field: string): SettingsFieldSpec;
|
|
426
|
+
/** Names of the row fields the settings page edits, in group order. */
|
|
427
|
+
export type SettingsFieldName = 'role' | 'host' | 'port' | 'trustedProxies' | 'rateLimit' | 'vapidSubject' | 'pushSummary' | 'pushTurnEnd' | 'pushTool' | 'pushDebounceMs' | 'lang' | 'serverName' | 'idleHours' | 'autoShareNewSessions';
|
|
428
|
+
/** Longest legal `serverName`, mirroring src/config.ts's cap. */
|
|
429
|
+
export declare const SERVER_NAME_MAX = 40;
|
|
430
|
+
/** Upper bound of `idleHours`, mirroring src/config.ts. */
|
|
431
|
+
export declare const IDLE_HOURS_MAX = 8760;
|
|
432
|
+
/** The row fields the settings page edits, in render order. */
|
|
433
|
+
export declare const SETTINGS_FIELDS: readonly SettingsFieldSpec[];
|
|
434
|
+
/**
|
|
435
|
+
* Whether the settings page's server-local operations (pairing, device
|
|
436
|
+
* management, the push probe) are usable: only once a status load answered
|
|
437
|
+
* AND it answered as the local machine (`viaGateway: false`). Everything
|
|
438
|
+
* else — no answer yet, or the page opened through the gateway — keeps them
|
|
439
|
+
* disabled. Pure so the check script pins all three cases.
|
|
440
|
+
*/
|
|
441
|
+
export declare function localOpsAllowed(view: SettingsView | undefined): boolean;
|
|
442
|
+
/** Per-field presentation facts the block renders next to each control. */
|
|
443
|
+
export interface SettingsFieldView {
|
|
444
|
+
/** Effective value (the resolved `config.values[field]`). */
|
|
445
|
+
value: unknown;
|
|
446
|
+
/** Layer that supplied the effective value. */
|
|
447
|
+
source: ConfigSource;
|
|
448
|
+
/** `source === 'env'`: shown locked, edits would be shadowed. */
|
|
449
|
+
locked: boolean;
|
|
450
|
+
/** `source === 'file'`: the legacy lan-gate.config.json supplies this. */
|
|
451
|
+
fromFile: boolean;
|
|
452
|
+
/** The row layer stores this field but another layer supplied the value —
|
|
453
|
+
* i.e. the saved value was illegal and resolveConfig skipped it. */
|
|
454
|
+
savedRowInvalid: boolean;
|
|
455
|
+
}
|
|
456
|
+
/** One device row the block renders. */
|
|
457
|
+
export interface SettingsDeviceView {
|
|
458
|
+
id: string;
|
|
459
|
+
name: string;
|
|
460
|
+
role: 'web' | 'desktop-client';
|
|
461
|
+
kind: 'auto' | 'phone' | 'desktop';
|
|
462
|
+
lastSeen: number;
|
|
463
|
+
hasPush: boolean;
|
|
464
|
+
}
|
|
465
|
+
/** The live pairing code with its countdown, or null when none is active. */
|
|
466
|
+
export interface SettingsPairingView {
|
|
467
|
+
code: string;
|
|
468
|
+
role: 'web' | 'desktop-client';
|
|
469
|
+
remainingSeconds: number;
|
|
470
|
+
}
|
|
471
|
+
/** Everything the settings block renders, derived from one status body. */
|
|
472
|
+
export interface SettingsView {
|
|
473
|
+
/** The status route answered ok. */
|
|
474
|
+
available: boolean;
|
|
475
|
+
/** Which half this DSH process runs as (`client` shows only the role group). */
|
|
476
|
+
role: 'host' | 'client';
|
|
477
|
+
/** Opened through the gateway (a remote device): server-local operations disabled. */
|
|
478
|
+
viaGateway: boolean;
|
|
479
|
+
/** Whether the gateway child answers. */
|
|
480
|
+
gatewayReachable: boolean;
|
|
481
|
+
gatewayPort: number | undefined;
|
|
482
|
+
gatewayTarget: string | undefined;
|
|
483
|
+
/** The probe status the admin route saw from the gateway; null = no answer. */
|
|
484
|
+
gatewayStatus: number | null;
|
|
485
|
+
/** `gatewayStatus` is a non-2xx reply: the status line shows the abnormal message instead. */
|
|
486
|
+
gatewayAbnormal: boolean;
|
|
487
|
+
fields: Record<SettingsFieldName, SettingsFieldView>;
|
|
488
|
+
devices: SettingsDeviceView[];
|
|
489
|
+
pairing: SettingsPairingView | null;
|
|
490
|
+
}
|
|
491
|
+
export interface DeriveSettingsOptions {
|
|
492
|
+
/** Clock for the pairing countdown; defaults to `Date.now()`. */
|
|
493
|
+
now?: number;
|
|
494
|
+
/** The shared form snapshot's raw user layer; field presence marks a row-layer override. */
|
|
495
|
+
rowUser?: unknown;
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Map one `admin/status` body into the view the settings block renders.
|
|
499
|
+
* Tolerant by design: every wire field is optional, an unexpected shape
|
|
500
|
+
* degrades to the conservative rendering (status unavailable, gateway down,
|
|
501
|
+
* no devices, no pairing) instead of throwing into the plugin page.
|
|
502
|
+
*/
|
|
503
|
+
export declare function deriveSettingsView(status: AdminStatusBody, options?: DeriveSettingsOptions): SettingsView;
|
|
504
|
+
/** Card-level state the shared form frame renders. */
|
|
505
|
+
export interface SettingsFormShellState {
|
|
506
|
+
available: boolean;
|
|
507
|
+
writable: boolean;
|
|
508
|
+
dirty: boolean;
|
|
509
|
+
invalid: boolean;
|
|
510
|
+
saving: boolean;
|
|
511
|
+
failed: boolean;
|
|
512
|
+
}
|
|
513
|
+
/** One control's state as its field renders it. */
|
|
514
|
+
export interface SettingsFieldState {
|
|
515
|
+
text: string;
|
|
516
|
+
overridden: boolean;
|
|
517
|
+
invalid: boolean;
|
|
518
|
+
/** A clear is staged: the box keeps showing the CURRENT value — the next
|
|
519
|
+
* layer's value only exists after the save — and the field renders the
|
|
520
|
+
* "reverts on save" hint instead of a fake preview. */
|
|
521
|
+
cleared: boolean;
|
|
522
|
+
/** `admin/status` reported this field's value locked by an environment variable. */
|
|
523
|
+
locked: boolean;
|
|
524
|
+
}
|
|
525
|
+
/** The row secret's face: presence only, never a value. */
|
|
526
|
+
export interface SettingsSecretFieldState {
|
|
527
|
+
/** The describe view's secrets sidecar reports `deviceToken` set. */
|
|
528
|
+
configured: boolean;
|
|
529
|
+
}
|
|
530
|
+
/** The whole staged-form snapshot the page renders. */
|
|
531
|
+
export interface ZenRemoteFormState extends SettingsFormShellState {
|
|
532
|
+
/** The shared form's scope sync state, reactively exposed so the page's
|
|
533
|
+
* role probe can wait out the mirror's loading phase. */
|
|
534
|
+
scopeStatus: 'loading' | 'ready' | 'unavailable';
|
|
535
|
+
role: SettingsFieldState;
|
|
536
|
+
host: SettingsFieldState;
|
|
537
|
+
port: SettingsFieldState;
|
|
538
|
+
trustedProxies: SettingsFieldState;
|
|
539
|
+
rateLimit: SettingsFieldState;
|
|
540
|
+
vapidSubject: SettingsFieldState;
|
|
541
|
+
pushSummary: SettingsFieldState;
|
|
542
|
+
pushTurnEnd: SettingsFieldState;
|
|
543
|
+
pushTool: SettingsFieldState;
|
|
544
|
+
pushDebounceMs: SettingsFieldState;
|
|
545
|
+
lang: SettingsFieldState;
|
|
546
|
+
serverName: SettingsFieldState;
|
|
547
|
+
idleHours: SettingsFieldState;
|
|
548
|
+
autoShareNewSessions: SettingsFieldState;
|
|
549
|
+
deviceToken: SettingsSecretFieldState;
|
|
550
|
+
/** A restart-required field ({@link RESTART_FIELDS}) has a staged change a
|
|
551
|
+
* save would actually write: the save will move the row and the host
|
|
552
|
+
* reloads it, briefly restarting the gateway — the page shows the reload
|
|
553
|
+
* note while this is true. Since T17b it is not merely "a draft exists":
|
|
554
|
+
* a draft equal to the displayed effective value is no change at all, an
|
|
555
|
+
* invalid draft blocks the save instead of saving anything, and an
|
|
556
|
+
* env-locked field never stages (nor counts if locked after staging). */
|
|
557
|
+
restartPending: boolean;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Stages one page's edits over the plugin row's shared form and writes them on
|
|
561
|
+
* save as one atomic, revision-fenced mutation. Fields whose `admin/status`
|
|
562
|
+
* source is `env` are locked via {@link setLockedFields}: they cannot be
|
|
563
|
+
* staged, because a written value would be shadowed by the environment anyway.
|
|
564
|
+
*/
|
|
565
|
+
export declare class ZenRemoteSettingsForm {
|
|
566
|
+
private readonly listeners;
|
|
567
|
+
private readonly staged;
|
|
568
|
+
private readonly lockedFields;
|
|
569
|
+
/** Fields whose saved row value `admin/status` reported invalid
|
|
570
|
+
* (savedRowInvalid): those display the RAW stored value instead of the
|
|
571
|
+
* resolved one, so the user can see (and fix) what they actually wrote. */
|
|
572
|
+
private readonly rowInvalidFields;
|
|
573
|
+
private readonly scope;
|
|
574
|
+
/** Reads the describe view's secrets sidecar for `deviceToken`'s
|
|
575
|
+
* configured flag; injectable so tests run without a describe mirror. */
|
|
576
|
+
private readonly secretConfigured;
|
|
577
|
+
/** Effective values from `admin/status`'s `config.values` — what a field
|
|
578
|
+
* displays while the row layer does not carry it. Empty until the page's
|
|
579
|
+
* first status load feeds it via {@link setBaseline}. Consulted only while
|
|
580
|
+
* the page is NOT a client page (T55): see {@link displayValue}. */
|
|
581
|
+
private baseline;
|
|
582
|
+
/** The effective role the page probed from the client-config route (T17):
|
|
583
|
+
* the fallback for {@link savedRoleIsClient} / {@link statusPoll} when the
|
|
584
|
+
* row document cannot answer the saved role (role only in
|
|
585
|
+
* lan-gate.config.json). Undefined until that probe answers. */
|
|
586
|
+
private probedRole;
|
|
587
|
+
private saving;
|
|
588
|
+
private failed;
|
|
589
|
+
/** Revision the drafts started from; the save's `expectedRevision` fence. */
|
|
590
|
+
private stagedRevision;
|
|
591
|
+
private cache;
|
|
592
|
+
private readonly unsubscribe;
|
|
593
|
+
/**
|
|
594
|
+
* @param scope - the shared configuration form for the plugin row entry.
|
|
595
|
+
* (`scope` and `secretConfigured` are assigned in the body rather than as
|
|
596
|
+
* parameter properties: scripts/check-settings-form.mjs imports this module
|
|
597
|
+
* through Node's strip-only type stripping, which rejects that syntax.)
|
|
598
|
+
* @param secretConfigured - whether the row's device token is set, read
|
|
599
|
+
* from the describe view's secrets sidecar; defaults to "not set".
|
|
600
|
+
*/
|
|
601
|
+
constructor(scope: SettingsFormScope, secretConfigured?: () => boolean);
|
|
602
|
+
/** @returns the current form snapshot (stable reference until the next change). */
|
|
603
|
+
getSnapshot(): ZenRemoteFormState;
|
|
604
|
+
/** Observe snapshot replacements (the renderer binds this as its store). */
|
|
605
|
+
subscribe(listener: () => void): () => void;
|
|
606
|
+
/** Republish from the current reads (admin/status moved underneath). */
|
|
607
|
+
refresh(): void;
|
|
608
|
+
/** Replace the set of environment-locked fields reported by admin/status. */
|
|
609
|
+
setLockedFields(fields: Iterable<string>): void;
|
|
610
|
+
/**
|
|
611
|
+
* Replace the set of fields whose saved row value resolveConfig rejected
|
|
612
|
+
* (admin/status's savedRowInvalid). Those keep showing the RAW stored
|
|
613
|
+
* value — the resolved value belongs to another layer and would hide the
|
|
614
|
+
* mistake the user needs to fix.
|
|
615
|
+
*/
|
|
616
|
+
setRowInvalidFields(fields: Iterable<string>): void;
|
|
617
|
+
/**
|
|
618
|
+
* The entry document's stored value for one field (the shared form
|
|
619
|
+
* snapshot's `value` — the redacted section: secrets never ride it). What
|
|
620
|
+
* the client group prefills the server address from, and what the page
|
|
621
|
+
* reads the SAVED role from (the role field's display text also carries
|
|
622
|
+
* staged drafts, which must not flip the page's mode).
|
|
623
|
+
*/
|
|
624
|
+
rowValue(field: string): unknown;
|
|
625
|
+
/**
|
|
626
|
+
* The scope sync state as the shared form sees it. The page waits it out
|
|
627
|
+
* before choosing a status source: a 'loading' snapshot cannot answer the
|
|
628
|
+
* role yet (T16-fix 3).
|
|
629
|
+
*/
|
|
630
|
+
scopeStatus(): 'loading' | 'ready' | 'unavailable';
|
|
631
|
+
/**
|
|
632
|
+
* Which status source the page should poll right now ({@link settingsPollOf}
|
|
633
|
+
* over the live snapshot): `none` while the mirror loads — and while a row
|
|
634
|
+
* without a stored role waits for the client-config probe — `client` for a
|
|
635
|
+
* client row — never admin there — and `admin` otherwise.
|
|
636
|
+
*/
|
|
637
|
+
statusPoll(): 'none' | 'admin' | 'client';
|
|
638
|
+
/**
|
|
639
|
+
* Feed the fallback role the page probed from the client-config route
|
|
640
|
+
* (T17). Only consulted when the row document carries no `role` — a stored
|
|
641
|
+
* row role always wins, so a staged-then-saved switch is never masked by a
|
|
642
|
+
* stale probe.
|
|
643
|
+
*/
|
|
644
|
+
setProbedRole(role: 'host' | 'client' | undefined): void;
|
|
645
|
+
/**
|
|
646
|
+
* Whether the SAVED row role is client ({@link savedRowRole} over the live
|
|
647
|
+
* snapshot, falling back to the probed role) — the page mode's single
|
|
648
|
+
* source of truth.
|
|
649
|
+
*/
|
|
650
|
+
savedRoleIsClient(): boolean;
|
|
651
|
+
/**
|
|
652
|
+
* Whether the row document answers the saved role at all (T17): false when
|
|
653
|
+
* neither snapshot layer carries one — the page then probes the
|
|
654
|
+
* client-config route for the effective role instead of assuming host.
|
|
655
|
+
*/
|
|
656
|
+
rowRoleKnown(): boolean;
|
|
657
|
+
/**
|
|
658
|
+
* One direct write for the pairing flow (T16): the normalized server
|
|
659
|
+
* address, the token just redeemed from the server, and (T60) the name
|
|
660
|
+
* actually registered with — the SERVER's confirmed echo when the claim
|
|
661
|
+
* carried one, else the name the claim sent — ride ONE revision-fenced
|
|
662
|
+
* mutate. The name lands in the row's `serverName` (the role card's 设备
|
|
663
|
+
* 名称 field), so a pairing that used an UNSAVED draft can never leave the
|
|
664
|
+
* local row empty while the server records the name. Not a staged edit —
|
|
665
|
+
* all three fields are volatile row settings and apply immediately. The
|
|
666
|
+
* caller drops the field's stale draft afterwards (see
|
|
667
|
+
* {@link discardField}). @returns whether the write landed.
|
|
668
|
+
*/
|
|
669
|
+
writeClientPairing(serverUrl: string, token: string, deviceName: string): Promise<boolean>;
|
|
670
|
+
/**
|
|
671
|
+
* One direct write for unpairing: forget the token, keep the address so
|
|
672
|
+
* the next pairing only needs a fresh code.
|
|
673
|
+
*/
|
|
674
|
+
clearDeviceToken(): Promise<boolean>;
|
|
675
|
+
/**
|
|
676
|
+
* Drop ONE field's staged draft (T60): after the pairing write landed the
|
|
677
|
+
* name in the row, the draft that produced it is stale — it now equals the
|
|
678
|
+
* displayed value, and keeping it would show a phantom "overridden" badge.
|
|
679
|
+
* The revision fence goes with the last draft (there is nothing staged
|
|
680
|
+
* left to fence); an in-flight save-failure flag is not touched — that is
|
|
681
|
+
* the staged-save surface's own story.
|
|
682
|
+
*/
|
|
683
|
+
discardField(field: string): void;
|
|
684
|
+
/**
|
|
685
|
+
* Whether one field currently has a STAGED edit (a typed draft or a staged
|
|
686
|
+
* clear) — the "the user is mid-edit" fact the T59 server-name follow
|
|
687
|
+
* reads before it may write. Reads the live draft map, so it answers per
|
|
688
|
+
* call; the page re-runs its effect on every republished snapshot.
|
|
689
|
+
*/
|
|
690
|
+
hasDraft(field: string): boolean;
|
|
691
|
+
/**
|
|
692
|
+
* One direct write for the T59 name follow: the SERVER's record of this
|
|
693
|
+
* device's name landing in the row's `serverName` (the role card's 设备
|
|
694
|
+
* 名称 field). The page only calls it when the user is not mid-edit —
|
|
695
|
+
* see {@link shouldFollowServerDeviceName} — and the write's own volatile
|
|
696
|
+
* update echoes back to the backend with both sides now equal, so the
|
|
697
|
+
* push path stays silent (no overwrite loop).
|
|
698
|
+
*/
|
|
699
|
+
writeDeviceName(name: string): Promise<boolean>;
|
|
700
|
+
/**
|
|
701
|
+
* The shared write path of the two pairing flows. Deliberately does NOT
|
|
702
|
+
* touch the frame's `failed` flag — a refused pairing write surfaces in
|
|
703
|
+
* the client group's own copy, not as a staged-save failure.
|
|
704
|
+
*/
|
|
705
|
+
private directWrite;
|
|
706
|
+
/**
|
|
707
|
+
* Feed the effective values (`admin/status`'s `config.values`) the fields
|
|
708
|
+
* display while the row layer does not carry them; also the baseline the
|
|
709
|
+
* "did the user change anything" comparison reads. The page clears it
|
|
710
|
+
* (`undefined`) whenever the poll source is not the admin one (T55): a
|
|
711
|
+
* stale host baseline must not survive a role switch on a client page.
|
|
712
|
+
*/
|
|
713
|
+
setBaseline(values: unknown): void;
|
|
714
|
+
/** Stage draft text for one row field; ignored for env-locked fields. */
|
|
715
|
+
stage(field: string, text: string): void;
|
|
716
|
+
/** Stage a clear so the field re-inherits the composition layer; ignored for env-locked fields. */
|
|
717
|
+
resetField(field: string): void;
|
|
718
|
+
/** Drop every staged edit. */
|
|
719
|
+
discard(): void;
|
|
720
|
+
/**
|
|
721
|
+
* The raw user layer as the shared form stores it; a field's presence here
|
|
722
|
+
* marks a row-layer override, which is what `savedRowInvalid` compares
|
|
723
|
+
* against.
|
|
724
|
+
*/
|
|
725
|
+
rowUser(): Record<string, unknown>;
|
|
726
|
+
/** Whether a save would do anything and is allowed to run right now. */
|
|
727
|
+
canSave(): boolean;
|
|
728
|
+
/**
|
|
729
|
+
* Write every staged edit as one mutation fenced by the revision the drafts
|
|
730
|
+
* started from. The Host is the only authority on acceptance: a refused
|
|
731
|
+
* save keeps its drafts for correction.
|
|
732
|
+
* @returns whether the save landed.
|
|
733
|
+
*/
|
|
734
|
+
save(): Promise<boolean>;
|
|
735
|
+
/** Release the scope subscription. */
|
|
736
|
+
dispose(): void;
|
|
737
|
+
private noteStage;
|
|
738
|
+
/** Whether the user layer currently carries an entry for one field. */
|
|
739
|
+
private stored;
|
|
740
|
+
/**
|
|
741
|
+
* The value one field DISPLAYS, which is also the baseline a draft must
|
|
742
|
+
* differ from to count as a change: an env-locked field shows the effective
|
|
743
|
+
* value (a written one would be shadowed anyway); a field the row layer
|
|
744
|
+
* stores shows the RESOLVED value from admin/status — resolveConfig
|
|
745
|
+
* normalizes stored shapes (a trustedProxies array becomes the comma
|
|
746
|
+
* string, pushTool 1 becomes true) and the box must match what a save
|
|
747
|
+
* writes — EXCEPT when the saved row value was rejected (savedRowInvalid):
|
|
748
|
+
* then the raw stored value shows, so the user sees the mistake; before
|
|
749
|
+
* the first status load the stored raw value stands in, and otherwise the
|
|
750
|
+
* shared form's own effective layer does, so drafts behave sensibly even
|
|
751
|
+
* with no admin/status yet.
|
|
752
|
+
*
|
|
753
|
+
* T55: the admin/status baseline counts only while the page is not a CLIENT
|
|
754
|
+
* page. A role switch leaves the host page's kept status load (and with it
|
|
755
|
+
* the last-fed baseline) in place forever — a client page never refreshes
|
|
756
|
+
* admin/status again — so reading the baseline there would pin every field,
|
|
757
|
+
* the role dropdown included, to the old role. Under a client poll the
|
|
758
|
+
* fields fall back to the row's stored values (the page stops feeding the
|
|
759
|
+
* baseline too, {@link setBaseline}).
|
|
760
|
+
*/
|
|
761
|
+
private displayValue;
|
|
762
|
+
private anyInvalid;
|
|
763
|
+
/**
|
|
764
|
+
* Every staged edit a save would send, in field order, mirroring the
|
|
765
|
+
* upstream form model's plan: a draft equal to the field's current value is
|
|
766
|
+
* no change at all; a reset only writes when the user layer actually
|
|
767
|
+
* carries the field; an invalid draft produces none — the save refuses
|
|
768
|
+
* rather than dropping the edit; a locked field never produces one.
|
|
769
|
+
*/
|
|
770
|
+
private plan;
|
|
771
|
+
private project;
|
|
772
|
+
private publish;
|
|
773
|
+
}
|
|
774
|
+
//# sourceMappingURL=settings-form.d.ts.map
|