@linxin666/dsh-remote-web-ui 0.3.6 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +77 -468
- package/README.zh.md +83 -113
- package/lib/client.js +993 -203
- package/lib/client.js.map +1 -1
- package/lib/index.js +1136 -1466
- package/lib/types/client/FooterRemoteEntry.d.ts +6 -7
- package/lib/types/client/FooterRemoteEntry.d.ts.map +1 -1
- package/lib/types/client/FooterRemoteEntry.js +1 -1
- package/lib/types/client/RemoteEntry.d.ts +7 -4
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +7 -10
- package/lib/types/client/RemotePanel.d.ts +2 -2
- package/lib/types/client/RemotePanel.d.ts.map +1 -1
- package/lib/types/client/RemotePanel.js +2 -2
- package/lib/types/client/RemoteSettingsCard.d.ts +9 -5
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +56 -3
- package/lib/types/client/deep-link.d.ts +10 -13
- package/lib/types/client/deep-link.d.ts.map +1 -1
- package/lib/types/client/deep-link.js +14 -74
- package/lib/types/client/index.d.ts +2 -6
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +79 -39
- package/lib/types/client/locales.d.ts +32 -12
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +42 -22
- package/lib/types/client/mobile-adapt.d.ts +49 -0
- package/lib/types/client/mobile-adapt.d.ts.map +1 -0
- package/lib/types/client/mobile-adapt.js +1002 -0
- package/lib/types/client/pair-api.d.ts +24 -6
- package/lib/types/client/pair-api.d.ts.map +1 -1
- package/lib/types/client/pair-api.js +9 -14
- package/lib/types/client/remote-channel.d.ts +3 -0
- package/lib/types/client/remote-channel.d.ts.map +1 -1
- package/lib/types/client/remote-channel.js +35 -3
- package/lib/types/client/settings-form.d.ts +35 -43
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +60 -53
- package/lib/types/firewall.d.ts +75 -0
- package/lib/types/firewall.d.ts.map +1 -0
- package/lib/types/firewall.js +184 -0
- package/lib/types/gate.d.ts +11 -5
- package/lib/types/gate.d.ts.map +1 -1
- package/lib/types/gate.js +11 -5
- package/lib/types/index.d.ts +39 -10
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +206 -58
- package/lib/types/inner-auth.d.ts +32 -0
- package/lib/types/inner-auth.d.ts.map +1 -0
- package/lib/types/inner-auth.js +72 -0
- package/lib/types/lan-bind-plan.d.ts +55 -0
- package/lib/types/lan-bind-plan.d.ts.map +1 -0
- package/lib/types/lan-bind-plan.js +60 -0
- package/lib/types/lan-bind.d.ts +69 -0
- package/lib/types/lan-bind.d.ts.map +1 -0
- package/lib/types/lan-bind.js +128 -0
- package/lib/types/loopback-proxy.d.ts +9 -2
- package/lib/types/loopback-proxy.d.ts.map +1 -1
- package/lib/types/loopback-proxy.js +24 -2
- package/lib/types/pairing.d.ts +15 -3
- package/lib/types/pairing.d.ts.map +1 -1
- package/lib/types/pairing.js +20 -6
- package/lib/types/remote-api.d.ts +30 -13
- package/lib/types/remote-api.d.ts.map +1 -1
- package/lib/types/remote-api.js +55 -42
- package/lib/types/remote-channel-boot.d.ts +21 -2
- package/lib/types/remote-channel-boot.d.ts.map +1 -1
- package/lib/types/remote-channel-boot.js +65 -3
- package/lib/types/remote-channel-rules.d.ts +8 -0
- package/lib/types/remote-channel-rules.d.ts.map +1 -1
- package/lib/types/remote-channel-rules.js +9 -2
- package/lib/types/remote-methods.d.ts +45 -11
- package/lib/types/remote-methods.d.ts.map +1 -1
- package/lib/types/remote-methods.js +56 -27
- package/lib/types/remote-presence-pet.d.ts +51 -0
- package/lib/types/remote-presence-pet.d.ts.map +1 -0
- package/lib/types/remote-presence-pet.js +101 -0
- package/lib/types/routes.d.ts +76 -4
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +336 -19
- package/lib/types/tunnel.d.ts.map +1 -1
- package/lib/types/tunnel.js +7 -1
- package/package.json +20 -18
- package/src/client/FooterRemoteEntry.tsx +9 -10
- package/src/client/RemoteEntry.tsx +13 -14
- package/src/client/RemotePanel.tsx +5 -16
- package/src/client/RemoteSettingsCard.tsx +72 -13
- package/src/client/deep-link.ts +14 -75
- package/src/client/index.ts +82 -39
- package/src/client/locales.ts +42 -22
- package/src/client/mobile-adapt.ts +954 -0
- package/src/client/pair-api.ts +26 -15
- package/src/client/remote-channel.ts +31 -3
- package/src/client/settings-form.ts +78 -92
- package/src/dsh-home.ts +1 -1
- package/src/firewall.ts +230 -0
- package/src/gate.ts +11 -5
- package/src/index.ts +235 -65
- package/src/inner-auth.ts +85 -0
- package/src/lan-bind-plan.ts +81 -0
- package/src/lan-bind.ts +145 -0
- package/src/loopback-proxy.ts +34 -0
- package/src/pairing.persist.test.ts +1 -1
- package/src/pairing.ts +20 -6
- package/src/remote-api.ts +67 -45
- package/src/remote-channel-boot.ts +70 -3
- package/src/remote-channel-rules.ts +17 -2
- package/src/remote-methods.ts +58 -27
- package/src/remote-presence-pet.ts +129 -0
- package/src/routes.ts +358 -19
- package/src/tunnel.ts +7 -1
- package/assets/apple-touch-icon.png +0 -0
- package/assets/mobile-icon-192.png +0 -0
- package/assets/mobile-icon-512.png +0 -0
- package/assets/mobile-service-worker.js +0 -63
- package/lib/mobile.js +0 -17658
- package/lib/mobile.js.map +0 -1
- package/lib/types/mobile/PairRequiredView.d.ts +0 -7
- package/lib/types/mobile/PairRequiredView.d.ts.map +0 -1
- package/lib/types/mobile/PairRequiredView.js +0 -28
- package/lib/types/mobile/PairRequiredView.test.d.ts +0 -2
- package/lib/types/mobile/PairRequiredView.test.d.ts.map +0 -1
- package/lib/types/mobile/PairRequiredView.test.js +0 -24
- package/lib/types/mobile/api.d.ts +0 -145
- package/lib/types/mobile/api.d.ts.map +0 -1
- package/lib/types/mobile/api.js +0 -103
- package/lib/types/mobile/index.d.ts +0 -10
- package/lib/types/mobile/index.d.ts.map +0 -1
- package/lib/types/mobile/index.js +0 -38
- package/lib/types/mobile/markdown.d.ts +0 -34
- package/lib/types/mobile/markdown.d.ts.map +0 -1
- package/lib/types/mobile/markdown.js +0 -285
- package/lib/types/mobile/markdown.test.d.ts +0 -2
- package/lib/types/mobile/markdown.test.d.ts.map +0 -1
- package/lib/types/mobile/markdown.test.js +0 -79
- package/lib/types/mobile/messages.d.ts +0 -149
- package/lib/types/mobile/messages.d.ts.map +0 -1
- package/lib/types/mobile/messages.js +0 -561
- package/lib/types/mobile/messages.test.d.ts +0 -2
- package/lib/types/mobile/messages.test.d.ts.map +0 -1
- package/lib/types/mobile/messages.test.js +0 -356
- package/lib/types/mobile/mobile-styles.d.ts +0 -6
- package/lib/types/mobile/mobile-styles.d.ts.map +0 -1
- package/lib/types/mobile/mobile-styles.js +0 -1438
- package/lib/types/mobile/mobile-theme.d.ts +0 -22
- package/lib/types/mobile/mobile-theme.d.ts.map +0 -1
- package/lib/types/mobile/mobile-theme.js +0 -70
- package/lib/types/mobile/mobile-theme.test.d.ts +0 -2
- package/lib/types/mobile/mobile-theme.test.d.ts.map +0 -1
- package/lib/types/mobile/mobile-theme.test.js +0 -71
- package/lib/types/mobile/mux.d.ts +0 -128
- package/lib/types/mobile/mux.d.ts.map +0 -1
- package/lib/types/mobile/mux.js +0 -305
- package/lib/types/mobile/mux.test.d.ts +0 -2
- package/lib/types/mobile/mux.test.d.ts.map +0 -1
- package/lib/types/mobile/mux.test.js +0 -267
- package/lib/types/mobile/pairing.d.ts +0 -31
- package/lib/types/mobile/pairing.d.ts.map +0 -1
- package/lib/types/mobile/pairing.js +0 -62
- package/lib/types/mobile/rpc.d.ts +0 -38
- package/lib/types/mobile/rpc.d.ts.map +0 -1
- package/lib/types/mobile/rpc.js +0 -83
- package/lib/types/mobile/theme-toggle.d.ts +0 -12
- package/lib/types/mobile/theme-toggle.d.ts.map +0 -1
- package/lib/types/mobile/theme-toggle.js +0 -26
- package/lib/types/mobile/views/App.d.ts +0 -57
- package/lib/types/mobile/views/App.d.ts.map +0 -1
- package/lib/types/mobile/views/App.js +0 -150
- package/lib/types/mobile/views/App.test.d.ts +0 -2
- package/lib/types/mobile/views/App.test.d.ts.map +0 -1
- package/lib/types/mobile/views/App.test.js +0 -52
- package/lib/types/mobile/views/ChatView.d.ts +0 -63
- package/lib/types/mobile/views/ChatView.d.ts.map +0 -1
- package/lib/types/mobile/views/ChatView.js +0 -821
- package/lib/types/mobile/views/ChatView.stream.test.d.ts +0 -2
- package/lib/types/mobile/views/ChatView.stream.test.d.ts.map +0 -1
- package/lib/types/mobile/views/ChatView.stream.test.js +0 -236
- package/lib/types/mobile/views/ChatView.test.d.ts +0 -2
- package/lib/types/mobile/views/ChatView.test.d.ts.map +0 -1
- package/lib/types/mobile/views/ChatView.test.js +0 -718
- package/lib/types/mobile/views/SessionListView.d.ts +0 -27
- package/lib/types/mobile/views/SessionListView.d.ts.map +0 -1
- package/lib/types/mobile/views/SessionListView.js +0 -139
- package/lib/types/mobile/views/SessionListView.test.d.ts +0 -2
- package/lib/types/mobile/views/SessionListView.test.d.ts.map +0 -1
- package/lib/types/mobile/views/SessionListView.test.js +0 -167
- package/lib/types/mobile/views/WorkspaceView.d.ts +0 -21
- package/lib/types/mobile/views/WorkspaceView.d.ts.map +0 -1
- package/lib/types/mobile/views/WorkspaceView.js +0 -93
- package/lib/types/mobile/views/WorkspaceView.test.d.ts +0 -2
- package/lib/types/mobile/views/WorkspaceView.test.d.ts.map +0 -1
- package/lib/types/mobile/views/WorkspaceView.test.js +0 -183
- package/lib/types/mobile-api.d.ts +0 -65
- package/lib/types/mobile-api.d.ts.map +0 -1
- package/lib/types/mobile-api.js +0 -404
- package/lib/types/mobile-pending.d.ts +0 -38
- package/lib/types/mobile-pending.d.ts.map +0 -1
- package/lib/types/mobile-pending.js +0 -59
- package/lib/types/mobile-pwa.d.ts +0 -6
- package/lib/types/mobile-pwa.d.ts.map +0 -1
- package/lib/types/mobile-pwa.js +0 -17
- package/lib/types/mobile-routes.d.ts +0 -13
- package/lib/types/mobile-routes.d.ts.map +0 -1
- package/lib/types/mobile-routes.js +0 -217
- package/lib/types/paired-model-catalog.d.ts +0 -17
- package/lib/types/paired-model-catalog.d.ts.map +0 -1
- package/lib/types/paired-model-catalog.js +0 -311
- package/src/mobile/PairRequiredView.test.tsx +0 -28
- package/src/mobile/PairRequiredView.tsx +0 -58
- package/src/mobile/api.ts +0 -229
- package/src/mobile/index.tsx +0 -41
- package/src/mobile/markdown.test.ts +0 -91
- package/src/mobile/markdown.ts +0 -291
- package/src/mobile/messages.test.ts +0 -402
- package/src/mobile/messages.ts +0 -685
- package/src/mobile/mobile-styles.ts +0 -1439
- package/src/mobile/mobile-theme.test.ts +0 -84
- package/src/mobile/mobile-theme.ts +0 -76
- package/src/mobile/mux.test.ts +0 -314
- package/src/mobile/mux.ts +0 -336
- package/src/mobile/pairing.ts +0 -77
- package/src/mobile/rpc.ts +0 -90
- package/src/mobile/theme-toggle.tsx +0 -47
- package/src/mobile/views/App.test.tsx +0 -62
- package/src/mobile/views/App.tsx +0 -213
- package/src/mobile/views/ChatView.stream.test.tsx +0 -268
- package/src/mobile/views/ChatView.test.tsx +0 -816
- package/src/mobile/views/ChatView.tsx +0 -1342
- package/src/mobile/views/SessionListView.test.tsx +0 -207
- package/src/mobile/views/SessionListView.tsx +0 -244
- package/src/mobile/views/WorkspaceView.test.tsx +0 -227
- package/src/mobile/views/WorkspaceView.tsx +0 -245
- package/src/mobile-api.ts +0 -418
- package/src/mobile-pending.ts +0 -88
- package/src/mobile-pwa.ts +0 -19
- package/src/mobile-routes.ts +0 -231
- package/src/paired-model-catalog.ts +0 -272
package/lib/types/remote-api.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The remote desktop data channel: `/remote` is this plugin's own prefix, so
|
|
3
|
-
* the paired-device cookie is the access control
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* the paired-device cookie is the access control. After that gate, every
|
|
4
|
+
* fenced same-origin path the browser rewrote here is re-issued to 127.0.0.1
|
|
5
|
+
* as a loopback-shaped request so sibling plugin fences (and the connection
|
|
6
|
+
* plugin's `/api`) accept it — no `--trusted-host` and no per-plugin pairing
|
|
7
|
+
* consult.
|
|
8
8
|
*
|
|
9
9
|
* Security model:
|
|
10
10
|
* - While `requirePairingForLan` is on (default), every request must carry a
|
|
11
11
|
* live paired-device cookie, enforced before any bytes are forwarded and
|
|
12
12
|
* before any host call. With the policy off, the cookie gate is skipped
|
|
13
|
-
* (the
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
13
|
+
* (the local-only denials below still apply).
|
|
14
|
+
* - A paired remote desktop is a full-control credential: the browser half
|
|
15
|
+
* flips the official UI into host mode (the transport ownsHost hook), so
|
|
16
|
+
* the configuration plane (settings, credentials, presets, deliverables)
|
|
17
|
+
* rides this channel like every other call. The four control planes in
|
|
18
|
+
* LOCAL_ONLY_PREFIXES stay physically local.
|
|
19
19
|
* - Everything else is HTTP- or WebSocket-proxied to the local port with
|
|
20
20
|
* Host rewritten, Origin and cookies dropped, and a synthetic same-origin
|
|
21
21
|
* browser marker added after authentication. Plugin loopback fences then
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
import { readCookie } from "./gate.js";
|
|
25
25
|
import { writeJson } from "./http.js";
|
|
26
26
|
import { proxyLoopbackHttp, proxyLoopbackUpgrade } from "./loopback-proxy.js";
|
|
27
|
-
import {
|
|
28
|
-
export { DESKTOP_LAUNCHER_PATH,
|
|
27
|
+
import { REMOTE_DEVICE_HEADER, REMOTE_DEVICE_QUERY, REMOTE_PREFIX, REMOTE_UPGRADE_PATHS, localOnlyDenial, } from "./remote-methods.js";
|
|
28
|
+
export { DESKTOP_LAUNCHER_PATH, LOCAL_ONLY_PREFIXES, PLUGIN_MANAGER_PATH, REMOTE_API_PATHS, REMOTE_DEVICE_HEADER, REMOTE_DEVICE_QUERY, REMOTE_PREFIX, REMOTE_UPGRADE_PATHS, WEB_UI_SETTINGS_BRIDGE_PATH, localOnlyDenial, } from "./remote-methods.js";
|
|
29
29
|
export { REMOTE_API_PREFIX } from "./remote-methods.js";
|
|
30
30
|
const ALLOWED_METHODS = new Set(['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']);
|
|
31
31
|
/** Reject traversal and empty segments; allow plugin file-path characters. */
|
|
@@ -68,32 +68,28 @@ export function innerPathOf(pathname) {
|
|
|
68
68
|
return rest;
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
|
-
* Whether a paired inner path must stay physically local
|
|
71
|
+
* Whether a paired inner path must stay physically local (delegates to the
|
|
72
|
+
* shared LOCAL_ONLY_PREFIXES table).
|
|
72
73
|
* @returns a denial message, or undefined when the path may be proxied.
|
|
73
74
|
*/
|
|
74
75
|
export function loopbackOnlyDenial(innerPath) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (!innerPath.startsWith('/api/'))
|
|
76
|
+
return localOnlyDenial(innerPath);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Resolve a live device credential for a gated HTTP request: the pairing
|
|
80
|
+
* cookie first, then the cookieless header the boot patch attaches. Unknown
|
|
81
|
+
* or revoked ids are a no-op - a stale id never re-arms a device.
|
|
82
|
+
* @returns the touched device id, or undefined when neither credential is live.
|
|
83
|
+
*/
|
|
84
|
+
export function pairedDeviceIdOf(req, service) {
|
|
85
|
+
const cookieDevice = readCookie(req.headers.cookie, service.config.cookieName);
|
|
86
|
+
const headerDevice = typeof req.headers[REMOTE_DEVICE_HEADER] === 'string'
|
|
87
|
+
? req.headers[REMOTE_DEVICE_HEADER]
|
|
88
|
+
: undefined;
|
|
89
|
+
const id = cookieDevice ?? headerDevice;
|
|
90
|
+
if (id === undefined)
|
|
91
91
|
return undefined;
|
|
92
|
-
|
|
93
|
-
if (method !== '' && !method.includes('/') && LOOPBACK_ONLY_METHODS.has(method)) {
|
|
94
|
-
return `${method} is loopback-only and stays unreachable from a paired remote desktop`;
|
|
95
|
-
}
|
|
96
|
-
return undefined;
|
|
92
|
+
return service.touchDevice(id) ? id : undefined;
|
|
97
93
|
}
|
|
98
94
|
/**
|
|
99
95
|
* Build the remote desktop channel HTTP routes.
|
|
@@ -108,9 +104,8 @@ export function makeRemoteApiRoutes(deps) {
|
|
|
108
104
|
// (a stale client rewrite must not 403); loopback-only denials stay below.
|
|
109
105
|
const require = typeof requirePairingForLan === 'function' ? requirePairingForLan() : requirePairingForLan;
|
|
110
106
|
if (require) {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
if (!paired) {
|
|
107
|
+
const paired = pairedDeviceIdOf(req, service);
|
|
108
|
+
if (paired === undefined) {
|
|
114
109
|
req.resume();
|
|
115
110
|
envelopeError(res, 403, 'invalid-request', 'unpaired', 'this device is not paired with the desktop');
|
|
116
111
|
return;
|
|
@@ -135,7 +130,7 @@ export function makeRemoteApiRoutes(deps) {
|
|
|
135
130
|
envelopeError(res, 403, 'invalid-request', 'forbidden', denied);
|
|
136
131
|
return;
|
|
137
132
|
}
|
|
138
|
-
proxyLoopbackHttp(req, res, port, `${inner}${url.search}
|
|
133
|
+
proxyLoopbackHttp(req, res, port, `${inner}${url.search}`, deps.auth);
|
|
139
134
|
};
|
|
140
135
|
return [{ kind: 'prefix', path: REMOTE_PREFIX, handler }];
|
|
141
136
|
}
|
|
@@ -168,8 +163,16 @@ export function makeRemoteApiUpgradeRoutes(deps) {
|
|
|
168
163
|
const handlerFor = (fallbackPath) => (req, socket, head) => {
|
|
169
164
|
const require = typeof requirePairingForLan === 'function' ? requirePairingForLan() : requirePairingForLan;
|
|
170
165
|
if (require) {
|
|
171
|
-
|
|
172
|
-
|
|
166
|
+
// WebSocket handshakes cannot carry headers from the Web API, so the
|
|
167
|
+
// cookieless credential rides the query; the cookie stays the primary.
|
|
168
|
+
let queryDevice;
|
|
169
|
+
try {
|
|
170
|
+
queryDevice = new URL(req.url ?? '/', 'http://127.0.0.1').searchParams.get(REMOTE_DEVICE_QUERY) ?? undefined;
|
|
171
|
+
}
|
|
172
|
+
catch { /* fall through to the cookie */ }
|
|
173
|
+
const deviceId = pairedDeviceIdOf(req, service);
|
|
174
|
+
const paired = deviceId ?? (queryDevice !== undefined && service.touchDevice(queryDevice) ? queryDevice : undefined);
|
|
175
|
+
if (paired === undefined) {
|
|
173
176
|
socket.write('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
|
|
174
177
|
socket.destroy();
|
|
175
178
|
return;
|
|
@@ -182,7 +185,17 @@ export function makeRemoteApiUpgradeRoutes(deps) {
|
|
|
182
185
|
socket.destroy();
|
|
183
186
|
return;
|
|
184
187
|
}
|
|
185
|
-
|
|
188
|
+
// The inner handshake needs the browser-auth credential (the gateway
|
|
189
|
+
// event-stream route enforces it); resolving it is async, so the
|
|
190
|
+
// handshake bytes are written once it settles. A missing credential
|
|
191
|
+
// proceeds as before — the gateway route then refuses.
|
|
192
|
+
void Promise.resolve(deps.auth?.ready())
|
|
193
|
+
.catch(() => undefined)
|
|
194
|
+
.then((cookie) => {
|
|
195
|
+
if (socket.destroyed)
|
|
196
|
+
return;
|
|
197
|
+
proxyLoopbackUpgrade(req, socket, head, port, inner, typeof cookie === 'string' ? cookie : undefined);
|
|
198
|
+
});
|
|
186
199
|
};
|
|
187
200
|
return REMOTE_UPGRADE_PATHS.map((path) => ({
|
|
188
201
|
path,
|
|
@@ -10,8 +10,15 @@
|
|
|
10
10
|
* (hooks + pending unpaired signal) instead of patching twice.
|
|
11
11
|
*
|
|
12
12
|
* The rewrite decisions are generated from REMOTE_CHANNEL_RULES, the same
|
|
13
|
-
* data the browser patch consumes — the two cannot drift apart.
|
|
14
|
-
*
|
|
13
|
+
* data the browser patch consumes — the two cannot drift apart. On this
|
|
14
|
+
* 0.1.2-alpha.2 line the "configuration plane is local" behavior lives in
|
|
15
|
+
* the browser (client plugins branch on connection.isLoopback), so the
|
|
16
|
+
* script also flips the official UI into host mode by installing the
|
|
17
|
+
* transport hook `__DSH_TRANSPORT__ = { ownsHost: true }` before the
|
|
18
|
+
* connection plugin reads it: the paired remote desktop gets the full
|
|
19
|
+
* settings/credentials/presets surface, and every call still rides the
|
|
20
|
+
* gated /remote channel. The script self-skips on loopback origins and
|
|
21
|
+
* never throws.
|
|
15
22
|
* @module @linxin666/dsh-remote-web-ui/remote-channel-boot
|
|
16
23
|
*/
|
|
17
24
|
import { type RemoteChannelRules } from './remote-channel-rules.ts';
|
|
@@ -21,6 +28,18 @@ import { type RemoteChannelRules } from './remote-channel-rules.ts';
|
|
|
21
28
|
* the window global.
|
|
22
29
|
*/
|
|
23
30
|
export declare function buildRemoteChannelBootScript(rules?: RemoteChannelRules): string;
|
|
31
|
+
/** Boot-watchdog latch: one self-reload per session while the boot is broken. */
|
|
32
|
+
export declare const BOOT_WATCHDOG_KEY = "dsh-remote-boot-reload";
|
|
33
|
+
/**
|
|
34
|
+
* Build the boot-watchdog script fragment. A remote boot has no recovery
|
|
35
|
+
* path of its own: the SPA mounts nothing when a boot-critical request dies
|
|
36
|
+
* (a tunnel-edge 429 under burst, a dropped stream, a boot-order race), and
|
|
37
|
+
* the phone then shows a permanently blank shell. The watchdog polls for the
|
|
38
|
+
* app's conversation surface and reloads once when it never appears; the
|
|
39
|
+
* sessionStorage latch keeps a genuinely broken deployment from looping,
|
|
40
|
+
* and a successful boot clears the latch so a later failure can recover.
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildBootWatchdogScript(waitMs?: number, pollMs?: number): string;
|
|
24
43
|
/** The script this plugin contributes; built once from the live rules. */
|
|
25
44
|
export declare const REMOTE_CHANNEL_BOOT_SCRIPT: string;
|
|
26
45
|
//# sourceMappingURL=remote-channel-boot.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-channel-boot.d.ts","sourceRoot":"","sources":["../../src/remote-channel-boot.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"remote-channel-boot.d.ts","sourceRoot":"","sources":["../../src/remote-channel-boot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAoD,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAErH;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,GAAE,kBAAyC,GAAG,MAAM,CA+HrG;AAED,iFAAiF;AACjF,eAAO,MAAM,iBAAiB,2BAA2B,CAAA;AAQzD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,SAAwB,EAAE,MAAM,SAAwB,GAAG,MAAM,CAgB9G;AAED,0EAA0E;AAC1E,eAAO,MAAM,0BAA0B,EAAE,MAAuC,CAAA"}
|
|
@@ -10,8 +10,15 @@
|
|
|
10
10
|
* (hooks + pending unpaired signal) instead of patching twice.
|
|
11
11
|
*
|
|
12
12
|
* The rewrite decisions are generated from REMOTE_CHANNEL_RULES, the same
|
|
13
|
-
* data the browser patch consumes — the two cannot drift apart.
|
|
14
|
-
*
|
|
13
|
+
* data the browser patch consumes — the two cannot drift apart. On this
|
|
14
|
+
* 0.1.2-alpha.2 line the "configuration plane is local" behavior lives in
|
|
15
|
+
* the browser (client plugins branch on connection.isLoopback), so the
|
|
16
|
+
* script also flips the official UI into host mode by installing the
|
|
17
|
+
* transport hook `__DSH_TRANSPORT__ = { ownsHost: true }` before the
|
|
18
|
+
* connection plugin reads it: the paired remote desktop gets the full
|
|
19
|
+
* settings/credentials/presets surface, and every call still rides the
|
|
20
|
+
* gated /remote channel. The script self-skips on loopback origins and
|
|
21
|
+
* never throws.
|
|
15
22
|
* @module @linxin666/dsh-remote-web-ui/remote-channel-boot
|
|
16
23
|
*/
|
|
17
24
|
import { REMOTE_CHANNEL_BOOT_GLOBAL, REMOTE_CHANNEL_RULES } from "./remote-channel-rules.js";
|
|
@@ -30,7 +37,23 @@ export function buildRemoteChannelBootScript(rules = REMOTE_CHANNEL_RULES) {
|
|
|
30
37
|
'var w=window,loc=w.location,h=loc.hostname;' +
|
|
31
38
|
// Loopback origins keep the original paths (mirrors isLoopbackHostname).
|
|
32
39
|
"if(h==='localhost'||h==='::1'||/^127(\\.\\d{1,3}){3}$/.test(h))return;" +
|
|
40
|
+
// Host mode: the paired remote desktop presents itself as the machine
|
|
41
|
+
// owner, so the official UI keeps its full configuration surface (the
|
|
42
|
+
// settings mirror, document controller, and deliverables open actions
|
|
43
|
+
// all branch on connection.isLoopback). Must run before any boot entry.
|
|
44
|
+
'try{if(w.__DSH_TRANSPORT__===undefined)w.__DSH_TRANSPORT__={};w.__DSH_TRANSPORT__.ownsHost=true}catch(e){}' +
|
|
33
45
|
'var R=' + json + ';' +
|
|
46
|
+
// The cookieless device credential: read lazily per call - the
|
|
47
|
+
// /pair-app capture script sets it in head AFTER this boot script ran,
|
|
48
|
+
// so a parse-time read would always see null.
|
|
49
|
+
'function rdv(){try{return w.sessionStorage.getItem(R.deviceKey)}catch(e){return null}}' +
|
|
50
|
+
'function att(init){' +
|
|
51
|
+
'var dv=rdv();' +
|
|
52
|
+
'if(dv===null)return init;' +
|
|
53
|
+
'var h=init&&init.headers;' +
|
|
54
|
+
'if(typeof Headers!=="undefined"&&h instanceof Headers){try{h.set(R.deviceHeader,dv)}catch(e){}return init}' +
|
|
55
|
+
'if(typeof h==="object"&&h!==null){var o={};for(var k in h)o[k]=h[k];o[R.deviceHeader]=dv;return Object.assign({},init,{headers:o})}' +
|
|
56
|
+
'return init}' +
|
|
34
57
|
'function sf(p){' +
|
|
35
58
|
'if(p.indexOf(R.pairPrefix)===0)return false;' +
|
|
36
59
|
'if(p.indexOf(R.updatePrefix)===0)return false;' +
|
|
@@ -78,7 +101,7 @@ export function buildRemoteChannelBootScript(rules = REMOTE_CHANNEL_RULES) {
|
|
|
78
101
|
'var next=new URL(url);' +
|
|
79
102
|
'next.pathname=rp(url.pathname);' +
|
|
80
103
|
'var target=typeof input==="string"||input instanceof URL?next.toString():new Request(next,input);' +
|
|
81
|
-
'return Promise.resolve(of.call(w,target,init)).then(function(res){' +
|
|
104
|
+
'return Promise.resolve(of.call(w,target,att(init))).then(function(res){' +
|
|
82
105
|
// denied() is sync-false for non-403 and a promise otherwise.
|
|
83
106
|
'void Promise.resolve(denied(res)).then(signal);' +
|
|
84
107
|
'return res})}' +
|
|
@@ -90,6 +113,8 @@ export function buildRemoteChannelBootScript(rules = REMOTE_CHANNEL_RULES) {
|
|
|
90
113
|
'if(o!==""&&o===loc.origin&&sw(p.pathname)){' +
|
|
91
114
|
'var nx=new URL(p);' +
|
|
92
115
|
'nx.pathname=rp(p.pathname);' +
|
|
116
|
+
'var dvv=rdv();' +
|
|
117
|
+
'if(dvv!==null)nx.searchParams.set(R.deviceQuery,dvv);' +
|
|
93
118
|
'return protocols!==undefined?new OW(nx,protocols):new OW(nx)}' +
|
|
94
119
|
'return protocols!==undefined?new OW(url,protocols):new OW(url)};' +
|
|
95
120
|
'w.WebSocket.prototype=OW.prototype;' +
|
|
@@ -101,6 +126,8 @@ export function buildRemoteChannelBootScript(rules = REMOTE_CHANNEL_RULES) {
|
|
|
101
126
|
'if(so(p)&&sf(p.pathname)){' +
|
|
102
127
|
'var nx=new URL(p);' +
|
|
103
128
|
'nx.pathname=rp(p.pathname);' +
|
|
129
|
+
'var dvv=rdv();' +
|
|
130
|
+
'if(dvv!==null)nx.searchParams.set(R.deviceQuery,dvv);' +
|
|
104
131
|
'return new OE(nx,cfg)}' +
|
|
105
132
|
'return new OE(url,cfg)};' +
|
|
106
133
|
'w.EventSource.prototype=OE.prototype}' +
|
|
@@ -121,8 +148,43 @@ export function buildRemoteChannelBootScript(rules = REMOTE_CHANNEL_RULES) {
|
|
|
121
148
|
'for(var i=0;i<restores.length;i++)restores[i]();' +
|
|
122
149
|
'try{delete w[' + seat + ']}catch(e){w[' + seat + ']=undefined}};' +
|
|
123
150
|
'w[' + seat + ']=seat' +
|
|
151
|
+
// The boot watchdog rides the same IIFE and skip conditions: it needs
|
|
152
|
+
// the non-loopback gate above (the desktop boots over loopback without
|
|
153
|
+
// a tunnel in the path) and the try/catch fail-closed envelope.
|
|
154
|
+
buildBootWatchdogScript() +
|
|
124
155
|
'}catch(e){}' +
|
|
125
156
|
'})();';
|
|
126
157
|
}
|
|
158
|
+
/** Boot-watchdog latch: one self-reload per session while the boot is broken. */
|
|
159
|
+
export const BOOT_WATCHDOG_KEY = 'dsh-remote-boot-reload';
|
|
160
|
+
/** Total wait for the app's first conversation surface before the reload. */
|
|
161
|
+
const BOOT_WATCHDOG_WAIT_MS = 15_000;
|
|
162
|
+
/** Watchdog poll cadence. */
|
|
163
|
+
const BOOT_WATCHDOG_POLL_MS = 1_000;
|
|
164
|
+
/**
|
|
165
|
+
* Build the boot-watchdog script fragment. A remote boot has no recovery
|
|
166
|
+
* path of its own: the SPA mounts nothing when a boot-critical request dies
|
|
167
|
+
* (a tunnel-edge 429 under burst, a dropped stream, a boot-order race), and
|
|
168
|
+
* the phone then shows a permanently blank shell. The watchdog polls for the
|
|
169
|
+
* app's conversation surface and reloads once when it never appears; the
|
|
170
|
+
* sessionStorage latch keeps a genuinely broken deployment from looping,
|
|
171
|
+
* and a successful boot clears the latch so a later failure can recover.
|
|
172
|
+
*/
|
|
173
|
+
export function buildBootWatchdogScript(waitMs = BOOT_WATCHDOG_WAIT_MS, pollMs = BOOT_WATCHDOG_POLL_MS) {
|
|
174
|
+
const key = JSON.stringify(BOOT_WATCHDOG_KEY);
|
|
175
|
+
return (
|
|
176
|
+
// Leading semicolon: the preceding seat assignment ends without one,
|
|
177
|
+
// and the fragment must parse when spliced into the IIFE.
|
|
178
|
+
';function wBoot(){try{return !!(w.document&&w.document.querySelector&&(w.document.querySelector("[data-conversation-scroll]")||w.document.querySelector("[data-slot=\\"conversation\\"]")))}catch(e){return false}}' +
|
|
179
|
+
'function wTick(n){try{' +
|
|
180
|
+
'if(wBoot()){try{w.sessionStorage.removeItem(' + key + ')}catch(e){}return}' +
|
|
181
|
+
'if(n<' + waitMs + '){w.setTimeout(function(){wTick(n+' + pollMs + ')},' + pollMs + ');return}' +
|
|
182
|
+
'var done=false;try{done=w.sessionStorage.getItem(' + key + ')==="1"}catch(e){}' +
|
|
183
|
+
'if(done)return;' +
|
|
184
|
+
'try{w.sessionStorage.setItem(' + key + ',"1")}catch(e){}' +
|
|
185
|
+
'w.location.reload()' +
|
|
186
|
+
'}catch(e){}}' +
|
|
187
|
+
'if(typeof w.setTimeout==="function")w.setTimeout(function(){wTick(0)},' + pollMs + ');');
|
|
188
|
+
}
|
|
127
189
|
/** The script this plugin contributes; built once from the live rules. */
|
|
128
190
|
export const REMOTE_CHANNEL_BOOT_SCRIPT = buildRemoteChannelBootScript();
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/** The gated mirror prefix (must match src/remote-methods.ts). */
|
|
9
9
|
export declare const REMOTE_PREFIX = "/remote";
|
|
10
|
+
import { REMOTE_DEVICE_HEADER, REMOTE_DEVICE_QUERY } from './remote-methods.ts';
|
|
11
|
+
export { REMOTE_DEVICE_HEADER, REMOTE_DEVICE_QUERY };
|
|
10
12
|
/** Connection-plugin method prefix under the gated channel. */
|
|
11
13
|
export declare const REMOTE_API_PREFIX = "/remote/api";
|
|
12
14
|
/** Every decision input of the remote-channel rewrite, JSON-serializable. */
|
|
@@ -21,6 +23,12 @@ export interface RemoteChannelRules {
|
|
|
21
23
|
readonly gitPrefix: string;
|
|
22
24
|
readonly petPrefix: string;
|
|
23
25
|
readonly wsPaths: readonly string[];
|
|
26
|
+
/** Header carrying the cookieless device credential on gated fetches. */
|
|
27
|
+
readonly deviceHeader: string;
|
|
28
|
+
/** sessionStorage key holding the device credential (set by /pair-app). */
|
|
29
|
+
readonly deviceKey: string;
|
|
30
|
+
/** Query parameter carrying it on WebSocket upgrades. */
|
|
31
|
+
readonly deviceQuery: string;
|
|
24
32
|
}
|
|
25
33
|
/** The live rule set. */
|
|
26
34
|
export declare const REMOTE_CHANNEL_RULES: RemoteChannelRules;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-channel-rules.d.ts","sourceRoot":"","sources":["../../src/remote-channel-rules.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,kEAAkE;AAClE,eAAO,MAAM,aAAa,YAAY,CAAA;AAEtC,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,gBAAyB,CAAA;AAEvD,6EAA6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAA;IACtC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAA;IACrC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"remote-channel-rules.d.ts","sourceRoot":"","sources":["../../src/remote-channel-rules.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,kEAAkE;AAClE,eAAO,MAAM,aAAa,YAAY,CAAA;AAEtC,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAE/E,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,CAAA;AAEpD,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,gBAAyB,CAAA;AAEvD,6EAA6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAA;IACtC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAA;IACrC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,yEAAyE;IACzE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;AAED,yBAAyB;AACzB,eAAO,MAAM,oBAAoB,EAAE,kBAsBlC,CAAA;AAED,iEAAiE;AACjE,eAAO,MAAM,0BAA0B,gCAAgC,CAAA;AAEvE;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IAC/B,QAAQ,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IAC7B,eAAe,EAAE,OAAO,CAAA;IACxB,OAAO,IAAI,IAAI,CAAA;CAChB"}
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/** The gated mirror prefix (must match src/remote-methods.ts). */
|
|
9
9
|
export const REMOTE_PREFIX = '/remote';
|
|
10
|
+
import { REMOTE_DEVICE_HEADER, REMOTE_DEVICE_QUERY } from "./remote-methods.js";
|
|
11
|
+
export { REMOTE_DEVICE_HEADER, REMOTE_DEVICE_QUERY };
|
|
10
12
|
/** Connection-plugin method prefix under the gated channel. */
|
|
11
13
|
export const REMOTE_API_PREFIX = `${REMOTE_PREFIX}/api`;
|
|
12
14
|
/** The live rule set. */
|
|
@@ -20,13 +22,18 @@ export const REMOTE_CHANNEL_RULES = {
|
|
|
20
22
|
sidebarPrefix: '/sidebar/',
|
|
21
23
|
gitPrefix: '/git/',
|
|
22
24
|
petPrefix: '/pet/',
|
|
25
|
+
// The official 0.1.2-alpha.2 line opens exactly one stream socket (the
|
|
26
|
+
// Typert gateway mux, /api/remote.mux); the legacy /api/events.* paths
|
|
27
|
+
// were removed by the SDK and stay absent here.
|
|
23
28
|
wsPaths: [
|
|
24
|
-
'/api/
|
|
25
|
-
'/api/events.host',
|
|
29
|
+
'/api/remote.mux',
|
|
26
30
|
'/sidebar/ws/terminal',
|
|
27
31
|
'/sidebar/ws/agent-terminals',
|
|
28
32
|
'/api/dsh-ssh/terminal',
|
|
29
33
|
],
|
|
34
|
+
deviceHeader: REMOTE_DEVICE_HEADER,
|
|
35
|
+
deviceKey: 'dsh-remote-device',
|
|
36
|
+
deviceQuery: REMOTE_DEVICE_QUERY,
|
|
30
37
|
};
|
|
31
38
|
/** The window global the boot patch publishes its seat under. */
|
|
32
39
|
export const REMOTE_CHANNEL_BOOT_GLOBAL = '__DSH_REMOTE_CHANNEL_BOOT__';
|
|
@@ -1,33 +1,67 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Remote desktop channel constants — SDK-independent so tests and the
|
|
3
3
|
* client half can pin them without importing the host SDK graph.
|
|
4
|
+
*
|
|
5
|
+
* Access model on the 0.1.2-alpha.2 line: the host /api surface has no
|
|
6
|
+
* per-method privilege pin — the "configuration plane is local" behavior
|
|
7
|
+
* lives in the browser (client plugins branch on connection.isLoopback), and
|
|
8
|
+
* the paired remote desktop flips into host mode via the transport hook
|
|
9
|
+
* (ownsHost) while every call rides this gated channel as a loopback-shaped
|
|
10
|
+
* request. A paired device is therefore a full-control credential by
|
|
11
|
+
* design; the only paths that stay physically local are the control planes
|
|
12
|
+
* below (pairing control, self-update, plugin install/remove, host power).
|
|
4
13
|
*/
|
|
5
14
|
/** Gated mirror of same-origin fenced paths (`/remote` + original pathname). */
|
|
6
15
|
export declare const REMOTE_PREFIX = "/remote";
|
|
7
16
|
/** Connection-plugin method prefix under the gated channel. */
|
|
8
17
|
export declare const REMOTE_API_PREFIX = "/remote/api";
|
|
9
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* The gated mirrors of the official client WebSocket paths. On the pinned
|
|
20
|
+
* 0.1.2-alpha.2 line the client opens ONE persistent stream socket — the
|
|
21
|
+
* Typert gateway mux at `/api/remote.mux` — and every Remote stream
|
|
22
|
+
* (workspace follow, session feed, ...) rides it. If that socket is not
|
|
23
|
+
* rewritten onto the gated channel the phone's streams die at the connection
|
|
24
|
+
* fence and the UI shows an empty workspace/session mirror, so the mux is
|
|
25
|
+
* the one path that must never be missing here.
|
|
26
|
+
*/
|
|
10
27
|
export declare const REMOTE_API_PATHS: {
|
|
11
|
-
readonly mux: "/remote/api/
|
|
12
|
-
readonly host: "/remote/api/events.host";
|
|
28
|
+
readonly mux: "/remote/api/remote.mux";
|
|
13
29
|
};
|
|
14
30
|
/**
|
|
15
31
|
* Exact upgrade paths registered on webServer (the SDK matches upgrades by
|
|
16
32
|
* exact path, not prefix). Query strings ride on the request URL.
|
|
17
33
|
*/
|
|
18
|
-
export declare const REMOTE_UPGRADE_PATHS: readonly ["/remote/api/
|
|
34
|
+
export declare const REMOTE_UPGRADE_PATHS: readonly ["/remote/api/remote.mux", "/remote/sidebar/ws/terminal", "/remote/sidebar/ws/agent-terminals", "/remote/api/dsh-ssh/terminal"];
|
|
19
35
|
/** Plugin-manager HTTP prefix: install/remove stay physically local. */
|
|
20
36
|
export declare const PLUGIN_MANAGER_PATH = "/api/plugin-manager";
|
|
21
37
|
/** Desktop-launcher HTTP prefix: shortcut create and host shutdown stay physically local. */
|
|
22
38
|
export declare const DESKTOP_LAUNCHER_PATH = "/api/dsh-desktop-launcher";
|
|
23
|
-
/** Family settings-bridge HTTP prefix
|
|
39
|
+
/** Family settings-bridge HTTP prefix — re-exposed to paired devices (plain settings parity). */
|
|
24
40
|
export declare const WEB_UI_SETTINGS_BRIDGE_PATH = "/api/dsh-web-ui-settings";
|
|
25
41
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
42
|
+
* The cookieless device credential: the boot patch reads the device id from
|
|
43
|
+
* the /pair-app URL, keeps it in sessionStorage, and attaches it to every
|
|
44
|
+
* gated HTTP call as this header (and to WebSocket upgrades as the `device`
|
|
45
|
+
* query parameter - WS handshakes cannot carry headers from the Web API).
|
|
46
|
+
* The channel gate accepts it exactly like the device cookie, so the mobile
|
|
47
|
+
* flow works even with browser cookies fully blocked; the cookie remains the
|
|
48
|
+
* primary credential on normal browsers.
|
|
49
|
+
*/
|
|
50
|
+
export declare const REMOTE_DEVICE_HEADER = "x-dsh-remote-device";
|
|
51
|
+
export declare const REMOTE_DEVICE_QUERY = "device";
|
|
52
|
+
/**
|
|
53
|
+
* Path prefixes that stay physically local even for a paired device. A
|
|
54
|
+
* paired remote desktop may use the full host API (chat, sessions,
|
|
55
|
+
* settings, credentials, presets — it is a full-control credential), but it
|
|
56
|
+
* must not reach the machine-control planes: pairing control itself, the
|
|
57
|
+
* dsh-web self-update installer, plugin install/remove, and desktop
|
|
58
|
+
* launcher actions (host shutdown, shortcuts).
|
|
59
|
+
*/
|
|
60
|
+
export declare const LOCAL_ONLY_PREFIXES: readonly string[];
|
|
61
|
+
/**
|
|
62
|
+
* Whether a paired inner path must stay physically local.
|
|
63
|
+
* @param innerPath - the rewritten inner path (e.g. `/api/session.list`).
|
|
64
|
+
* @returns a denial message, or undefined when the path may be proxied.
|
|
31
65
|
*/
|
|
32
|
-
export declare
|
|
66
|
+
export declare function localOnlyDenial(innerPath: string): string | undefined;
|
|
33
67
|
//# sourceMappingURL=remote-methods.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-methods.d.ts","sourceRoot":"","sources":["../../src/remote-methods.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"remote-methods.d.ts","sourceRoot":"","sources":["../../src/remote-methods.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,gFAAgF;AAChF,eAAO,MAAM,aAAa,YAAY,CAAA;AAEtC,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,gBAAyB,CAAA;AAEvD;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB;;CAEnB,CAAA;AAEV;;;GAGG;AACH,eAAO,MAAM,oBAAoB,0IAKvB,CAAA;AAEV,wEAAwE;AACxE,eAAO,MAAM,mBAAmB,wBAAwB,CAAA;AAExD,6FAA6F;AAC7F,eAAO,MAAM,qBAAqB,8BAA8B,CAAA;AAEhE,iGAAiG;AACjG,eAAO,MAAM,2BAA2B,6BAA6B,CAAA;AAErE;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,wBAAwB,CAAA;AACzD,eAAO,MAAM,mBAAmB,WAAW,CAAA;AAE3C;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAKvC,CAAA;AAEV;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOrE"}
|
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Remote desktop channel constants — SDK-independent so tests and the
|
|
3
3
|
* client half can pin them without importing the host SDK graph.
|
|
4
|
+
*
|
|
5
|
+
* Access model on the 0.1.2-alpha.2 line: the host /api surface has no
|
|
6
|
+
* per-method privilege pin — the "configuration plane is local" behavior
|
|
7
|
+
* lives in the browser (client plugins branch on connection.isLoopback), and
|
|
8
|
+
* the paired remote desktop flips into host mode via the transport hook
|
|
9
|
+
* (ownsHost) while every call rides this gated channel as a loopback-shaped
|
|
10
|
+
* request. A paired device is therefore a full-control credential by
|
|
11
|
+
* design; the only paths that stay physically local are the control planes
|
|
12
|
+
* below (pairing control, self-update, plugin install/remove, host power).
|
|
4
13
|
*/
|
|
5
14
|
/** Gated mirror of same-origin fenced paths (`/remote` + original pathname). */
|
|
6
15
|
export const REMOTE_PREFIX = '/remote';
|
|
7
16
|
/** Connection-plugin method prefix under the gated channel. */
|
|
8
17
|
export const REMOTE_API_PREFIX = `${REMOTE_PREFIX}/api`;
|
|
9
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* The gated mirrors of the official client WebSocket paths. On the pinned
|
|
20
|
+
* 0.1.2-alpha.2 line the client opens ONE persistent stream socket — the
|
|
21
|
+
* Typert gateway mux at `/api/remote.mux` — and every Remote stream
|
|
22
|
+
* (workspace follow, session feed, ...) rides it. If that socket is not
|
|
23
|
+
* rewritten onto the gated channel the phone's streams die at the connection
|
|
24
|
+
* fence and the UI shows an empty workspace/session mirror, so the mux is
|
|
25
|
+
* the one path that must never be missing here.
|
|
26
|
+
*/
|
|
10
27
|
export const REMOTE_API_PATHS = {
|
|
11
|
-
mux: `${REMOTE_API_PREFIX}/
|
|
12
|
-
host: `${REMOTE_API_PREFIX}/events.host`,
|
|
28
|
+
mux: `${REMOTE_API_PREFIX}/remote.mux`,
|
|
13
29
|
};
|
|
14
30
|
/**
|
|
15
31
|
* Exact upgrade paths registered on webServer (the SDK matches upgrades by
|
|
@@ -17,7 +33,6 @@ export const REMOTE_API_PATHS = {
|
|
|
17
33
|
*/
|
|
18
34
|
export const REMOTE_UPGRADE_PATHS = [
|
|
19
35
|
REMOTE_API_PATHS.mux,
|
|
20
|
-
REMOTE_API_PATHS.host,
|
|
21
36
|
`${REMOTE_PREFIX}/sidebar/ws/terminal`,
|
|
22
37
|
`${REMOTE_PREFIX}/sidebar/ws/agent-terminals`,
|
|
23
38
|
`${REMOTE_API_PREFIX}/dsh-ssh/terminal`,
|
|
@@ -26,29 +41,43 @@ export const REMOTE_UPGRADE_PATHS = [
|
|
|
26
41
|
export const PLUGIN_MANAGER_PATH = '/api/plugin-manager';
|
|
27
42
|
/** Desktop-launcher HTTP prefix: shortcut create and host shutdown stay physically local. */
|
|
28
43
|
export const DESKTOP_LAUNCHER_PATH = '/api/dsh-desktop-launcher';
|
|
29
|
-
/** Family settings-bridge HTTP prefix
|
|
44
|
+
/** Family settings-bridge HTTP prefix — re-exposed to paired devices (plain settings parity). */
|
|
30
45
|
export const WEB_UI_SETTINGS_BRIDGE_PATH = '/api/dsh-web-ui-settings';
|
|
31
46
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
47
|
+
* The cookieless device credential: the boot patch reads the device id from
|
|
48
|
+
* the /pair-app URL, keeps it in sessionStorage, and attaches it to every
|
|
49
|
+
* gated HTTP call as this header (and to WebSocket upgrades as the `device`
|
|
50
|
+
* query parameter - WS handshakes cannot carry headers from the Web API).
|
|
51
|
+
* The channel gate accepts it exactly like the device cookie, so the mobile
|
|
52
|
+
* flow works even with browser cookies fully blocked; the cookie remains the
|
|
53
|
+
* primary credential on normal browsers.
|
|
54
|
+
*/
|
|
55
|
+
export const REMOTE_DEVICE_HEADER = 'x-dsh-remote-device';
|
|
56
|
+
export const REMOTE_DEVICE_QUERY = 'device';
|
|
57
|
+
/**
|
|
58
|
+
* Path prefixes that stay physically local even for a paired device. A
|
|
59
|
+
* paired remote desktop may use the full host API (chat, sessions,
|
|
60
|
+
* settings, credentials, presets — it is a full-control credential), but it
|
|
61
|
+
* must not reach the machine-control planes: pairing control itself, the
|
|
62
|
+
* dsh-web self-update installer, plugin install/remove, and desktop
|
|
63
|
+
* launcher actions (host shutdown, shortcuts).
|
|
64
|
+
*/
|
|
65
|
+
export const LOCAL_ONLY_PREFIXES = [
|
|
66
|
+
'/api/pair',
|
|
67
|
+
'/api/update',
|
|
68
|
+
PLUGIN_MANAGER_PATH,
|
|
69
|
+
DESKTOP_LAUNCHER_PATH,
|
|
70
|
+
];
|
|
71
|
+
/**
|
|
72
|
+
* Whether a paired inner path must stay physically local.
|
|
73
|
+
* @param innerPath - the rewritten inner path (e.g. `/api/session.list`).
|
|
74
|
+
* @returns a denial message, or undefined when the path may be proxied.
|
|
37
75
|
*/
|
|
38
|
-
export
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
'settings.openDocument',
|
|
47
|
-
'settings.update',
|
|
48
|
-
'settings.replace',
|
|
49
|
-
'settings.mutate',
|
|
50
|
-
'credentials.describe',
|
|
51
|
-
'credentials.set',
|
|
52
|
-
'credentials.unset',
|
|
53
|
-
'llm.discoverModels',
|
|
54
|
-
]);
|
|
76
|
+
export function localOnlyDenial(innerPath) {
|
|
77
|
+
for (const prefix of LOCAL_ONLY_PREFIXES) {
|
|
78
|
+
if (innerPath === prefix || innerPath.startsWith(`${prefix}/`)) {
|
|
79
|
+
return `${prefix.slice(1)} stays physically local and stays unreachable from a paired remote desktop`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote-presence to pet-visibility link (user requirement): while at least
|
|
3
|
+
* one paired device is online (an active phone mirror session), the
|
|
4
|
+
* host-global pet is hidden through the pet's OWN hide switch (the
|
|
5
|
+
* PetService.setVisible RPC, the same one the pet's hide button uses);
|
|
6
|
+
* once the last device stays offline past a grace window, the pet is
|
|
7
|
+
* restored. The pet plugin is optional: without it (or on any pet failure)
|
|
8
|
+
* the link is a no-op - the remote-control feature never depends on the
|
|
9
|
+
* pet being installed.
|
|
10
|
+
*
|
|
11
|
+
* The visibility is host-global by design (the pet is host-global, no
|
|
12
|
+
* session dimension): the desktop and the phone share one pet, so the link
|
|
13
|
+
* hides it while the operator is away on a phone mirror and restores it
|
|
14
|
+
* when the mirror ends. A manual user hide is respected: if the pet is
|
|
15
|
+
* already hidden when the first device comes online, nothing is recorded
|
|
16
|
+
* and nothing is restored later.
|
|
17
|
+
*/
|
|
18
|
+
/** The pet service seam the link needs (structural slice, no package edge). */
|
|
19
|
+
export interface PresencePetSeam {
|
|
20
|
+
/** RPC: show or hide the pet. */
|
|
21
|
+
setVisible(visible: boolean): Promise<unknown>;
|
|
22
|
+
/** Read the current display state (the visibility the user sees). */
|
|
23
|
+
state(): Promise<{
|
|
24
|
+
display?: {
|
|
25
|
+
visible?: boolean;
|
|
26
|
+
};
|
|
27
|
+
}>;
|
|
28
|
+
}
|
|
29
|
+
/** Dependencies of the presence link (all injectable for tests). */
|
|
30
|
+
export interface PresencePetDeps {
|
|
31
|
+
/** Subscribe to pairing snapshot changes (fires with every state change). */
|
|
32
|
+
onState(listener: (snapshot: {
|
|
33
|
+
phase: string;
|
|
34
|
+
onlineCount: number;
|
|
35
|
+
}) => void): () => void;
|
|
36
|
+
/** Resolve the pet service seam (undefined when the pet plugin is absent). */
|
|
37
|
+
pet(): PresencePetSeam | undefined;
|
|
38
|
+
/** Grace before restoring the pet after the last device went offline. */
|
|
39
|
+
restoreAfterMs?: number;
|
|
40
|
+
timers?: {
|
|
41
|
+
setTimeout(fn: () => void, ms: number): unknown;
|
|
42
|
+
clearTimeout(t: unknown): void;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Start the presence link. Returns the disposer (withdraws the state
|
|
47
|
+
* subscription and cancels a pending restore).
|
|
48
|
+
* @param deps - pairing stream + pet seam (+ test seams).
|
|
49
|
+
*/
|
|
50
|
+
export declare function startRemotePresencePet(deps: PresencePetDeps): () => void;
|
|
51
|
+
//# sourceMappingURL=remote-presence-pet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-presence-pet.d.ts","sourceRoot":"","sources":["../../src/remote-presence-pet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,+EAA+E;AAC/E,MAAM,WAAW,eAAe;IAC9B,iCAAiC;IACjC,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC9C,qEAAqE;IACrE,KAAK,IAAI,OAAO,CAAC;QAAE,OAAO,CAAC,EAAE;YAAE,OAAO,CAAC,EAAE,OAAO,CAAA;SAAE,CAAA;KAAE,CAAC,CAAA;CACtD;AAED,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,OAAO,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAAG,MAAM,IAAI,CAAA;IACzF,8EAA8E;IAC9E,GAAG,IAAI,eAAe,GAAG,SAAS,CAAA;IAClC,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE;QACP,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAA;QAC/C,YAAY,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;KAC/B,CAAA;CACF;AASD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,IAAI,CA4ExE"}
|