@linxin666/dsh-remote-web-ui 0.2.8 → 0.3.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/LICENSE +202 -28
- package/README.i18n.yaml +2 -2
- package/README.md +51 -15
- package/README.zh.md +9 -4
- package/lib/client.js +65 -34
- package/lib/client.js.map +1 -1
- package/lib/index.js +667 -77
- package/lib/mobile.js +874 -64
- package/lib/mobile.js.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +35 -5
- package/lib/types/client/remote-channel.d.ts +4 -5
- package/lib/types/client/remote-channel.d.ts.map +1 -1
- package/lib/types/client/remote-channel.js +13 -28
- package/lib/types/http.d.ts +37 -8
- package/lib/types/http.d.ts.map +1 -1
- package/lib/types/http.js +76 -11
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +40 -5
- package/lib/types/mobile/api.d.ts +57 -0
- package/lib/types/mobile/api.d.ts.map +1 -1
- package/lib/types/mobile/api.js +29 -0
- package/lib/types/mobile/markdown.d.ts +7 -1
- package/lib/types/mobile/markdown.d.ts.map +1 -1
- package/lib/types/mobile/markdown.js +16 -7
- package/lib/types/mobile/markdown.test.js +8 -0
- package/lib/types/mobile/mobile-styles.d.ts +1 -1
- package/lib/types/mobile/mobile-styles.d.ts.map +1 -1
- package/lib/types/mobile/mobile-styles.js +325 -6
- package/lib/types/mobile/views/ChatView.d.ts +10 -0
- package/lib/types/mobile/views/ChatView.d.ts.map +1 -1
- package/lib/types/mobile/views/ChatView.js +205 -8
- package/lib/types/mobile/views/ChatView.stream.test.d.ts +2 -0
- package/lib/types/mobile/views/ChatView.stream.test.d.ts.map +1 -0
- package/lib/types/mobile/views/ChatView.stream.test.js +205 -0
- package/lib/types/mobile/views/WorkspaceView.d.ts.map +1 -1
- package/lib/types/mobile/views/WorkspaceView.js +46 -5
- package/lib/types/mobile/views/WorkspaceView.test.js +129 -2
- package/lib/types/mobile-api.d.ts +9 -3
- package/lib/types/mobile-api.d.ts.map +1 -1
- package/lib/types/mobile-api.js +61 -16
- package/lib/types/mobile-pending.d.ts +38 -0
- package/lib/types/mobile-pending.d.ts.map +1 -0
- package/lib/types/mobile-pending.js +59 -0
- package/lib/types/paired-model-catalog.d.ts +17 -0
- package/lib/types/paired-model-catalog.d.ts.map +1 -0
- package/lib/types/paired-model-catalog.js +252 -0
- package/lib/types/pairing.d.ts +13 -4
- package/lib/types/pairing.d.ts.map +1 -1
- package/lib/types/pairing.js +13 -5
- package/lib/types/remote-api.d.ts +13 -4
- package/lib/types/remote-api.d.ts.map +1 -1
- package/lib/types/remote-api.js +27 -17
- package/lib/types/remote-channel-boot.d.ts +26 -0
- package/lib/types/remote-channel-boot.d.ts.map +1 -0
- package/lib/types/remote-channel-boot.js +128 -0
- package/lib/types/remote-channel-rules.d.ts +40 -0
- package/lib/types/remote-channel-rules.d.ts.map +1 -0
- package/lib/types/remote-channel-rules.js +32 -0
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +12 -23
- package/lib/types/uuid-polyfill.d.ts +10 -0
- package/lib/types/uuid-polyfill.d.ts.map +1 -0
- package/lib/types/uuid-polyfill.js +27 -0
- package/package.json +1 -1
- package/src/client/index.ts +41 -5
- package/src/client/remote-channel.ts +18 -28
- package/src/http.ts +87 -12
- package/src/index.ts +42 -6
- package/src/mobile/api.ts +91 -0
- package/src/mobile/markdown.test.ts +9 -0
- package/src/mobile/markdown.ts +17 -7
- package/src/mobile/mobile-styles.ts +326 -6
- package/src/mobile/views/ChatView.stream.test.tsx +231 -0
- package/src/mobile/views/ChatView.tsx +328 -10
- package/src/mobile/views/WorkspaceView.test.tsx +163 -2
- package/src/mobile/views/WorkspaceView.tsx +152 -26
- package/src/mobile-api.ts +59 -17
- package/src/mobile-pending.ts +88 -0
- package/src/paired-model-catalog.ts +224 -0
- package/src/pairing.ts +13 -5
- package/src/remote-api.ts +34 -18
- package/src/remote-channel-boot.ts +131 -0
- package/src/remote-channel-rules.ts +62 -0
- package/src/routes.ts +13 -24
- package/src/uuid-polyfill.ts +28 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAA;AAa7G,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAA;AAa7G,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AAarD,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACrE,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AACnE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,YAAY,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAC/F,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAErE,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,0CAA0C;QAC1C,MAAM,EAAE,SAAS,CAAA;KAClB;IAED,UAAU,OAAO;QACf;;;;WAIG;QACH,gBAAgB,EAAE;YAAE,IAAI,EAAE,QAAQ,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,uBAAuB,CAAA;SAAE,CAAA;QACnF;;;;;WAKG;QACH,oBAAoB,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,4BAA4B,CAAA;SAAE,CAAA;KAC3F;CACF;AAED,4GAA4G;AAC5G,MAAM,WAAW,uBAAuB;IACtC,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf;;;;WAIG;QACH,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;SAAE,CAAA;KAC1E;CACF;AAYD,wCAAwC;AACxC,eAAO,MAAM,MAAM,UAA+D,CAAA;AAElF;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAkN9C"}
|
|
@@ -15,7 +15,7 @@ import { RemoteSettingsCard, RemoteSettingsCardController } from "./RemoteSettin
|
|
|
15
15
|
import { en, zh } from "./locales.js";
|
|
16
16
|
import { PAIR_FAILED_MARKER, runPairBootFlow } from "./deep-link.js";
|
|
17
17
|
import { readPairGatePolicy, sendHeartbeat } from "./pair-api.js";
|
|
18
|
-
import { channelTransition, installRemoteChannel, isLoopbackHostname, remoteChannelRequired } from "./remote-channel.js";
|
|
18
|
+
import { channelTransition, installRemoteChannel, isLoopbackHostname, remoteChannelRequired, REMOTE_CHANNEL_BOOT_GLOBAL, } from "./remote-channel.js";
|
|
19
19
|
import { FenceNotice } from "./FenceNotice.js";
|
|
20
20
|
/** Dictionary namespace owned by this plugin. */
|
|
21
21
|
const NS = 'remote';
|
|
@@ -154,17 +154,42 @@ export function apply(ctx) {
|
|
|
154
154
|
showFenceNotice();
|
|
155
155
|
};
|
|
156
156
|
const channelActive = () => remoteChannelRequired(window.location.hostname, settingsScope.getSnapshot(), hostPairingPolicy);
|
|
157
|
+
// The parse-time boot patch (issue #987), when the served index carried
|
|
158
|
+
// it: already installed before any boot entry ran, so adopting its seat
|
|
159
|
+
// beats patching a second time (which would double-rewrite onto
|
|
160
|
+
// /remote/remote/...).
|
|
161
|
+
const bootSeat = () => window[REMOTE_CHANNEL_BOOT_GLOBAL];
|
|
157
162
|
const syncChannel = () => {
|
|
158
163
|
const transition = channelTransition(channelActive(), disposeChannel !== undefined);
|
|
159
164
|
if (transition === 'install') {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
165
|
+
const seat = bootSeat();
|
|
166
|
+
if (seat !== undefined) {
|
|
167
|
+
seat.onUnpaired = handleUnpaired;
|
|
168
|
+
seat.onPaired = hideFenceNotice;
|
|
169
|
+
// Replay a signal raised before adoption (early unpaired responses).
|
|
170
|
+
if (seat.pendingUnpaired) {
|
|
171
|
+
seat.pendingUnpaired = false;
|
|
172
|
+
handleUnpaired();
|
|
173
|
+
}
|
|
174
|
+
disposeChannel = ctx.effect(() => () => {
|
|
175
|
+
seat.onUnpaired = null;
|
|
176
|
+
seat.onPaired = null;
|
|
177
|
+
}, 'remote-web-ui: remote desktop channel (boot patch)');
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
disposeChannel = ctx.effect(() => {
|
|
181
|
+
const restore = installRemoteChannel(window, { onUnpaired: handleUnpaired, onPaired: hideFenceNotice });
|
|
182
|
+
return restore;
|
|
183
|
+
}, 'remote-web-ui: remote desktop channel');
|
|
184
|
+
}
|
|
164
185
|
}
|
|
165
186
|
else if (transition === 'retire' && disposeChannel !== undefined) {
|
|
166
187
|
disposeChannel();
|
|
167
188
|
disposeChannel = undefined;
|
|
189
|
+
// Retire the provisional parse-time install with the channel: the
|
|
190
|
+
// desktop now rides plain /api, so the rewrite must go (its seat
|
|
191
|
+
// removes the global; a later re-activation patches afresh).
|
|
192
|
+
bootSeat()?.restore();
|
|
168
193
|
// Retire the notice with the channel: once requirePairingForLan turns
|
|
169
194
|
// off (or the plugin is disabled) the desktop rides plain /api again,
|
|
170
195
|
// so an unpaired notice raised while the channel was briefly active
|
|
@@ -173,6 +198,11 @@ export function apply(ctx) {
|
|
|
173
198
|
// the channel gone nothing can re-raise it (issue #808).
|
|
174
199
|
hideFenceNotice();
|
|
175
200
|
}
|
|
201
|
+
else if (transition === 'none' && !channelActive()) {
|
|
202
|
+
// The channel was never adopted (policy settled to off before apply
|
|
203
|
+
// ran): the provisional boot patch still retires.
|
|
204
|
+
bootSeat()?.restore();
|
|
205
|
+
}
|
|
176
206
|
};
|
|
177
207
|
settingsScope.subscribe(syncChannel);
|
|
178
208
|
syncChannel();
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
* Pure helpers are exported for unit tests; `installRemoteChannel` patches
|
|
24
24
|
* the given window and returns their restore.
|
|
25
25
|
*/
|
|
26
|
-
|
|
27
|
-
export
|
|
28
|
-
|
|
29
|
-
export
|
|
26
|
+
import { REMOTE_API_PREFIX, REMOTE_PREFIX } from '../remote-channel-rules.ts';
|
|
27
|
+
export { REMOTE_API_PREFIX, REMOTE_PREFIX };
|
|
28
|
+
export type { RemoteChannelBootSeat } from '../remote-channel-rules.ts';
|
|
29
|
+
export { REMOTE_CHANNEL_BOOT_GLOBAL } from '../remote-channel-rules.ts';
|
|
30
30
|
/** Minimal settings snapshot used by the remote channel decision. */
|
|
31
31
|
export interface RemoteChannelSettingsSnapshot {
|
|
32
32
|
status: 'ready' | 'loading' | 'unavailable' | string;
|
|
@@ -114,5 +114,4 @@ export type ChannelTransition = 'install' | 'retire' | 'none';
|
|
|
114
114
|
* @returns the transition to apply.
|
|
115
115
|
*/
|
|
116
116
|
export declare function channelTransition(active: boolean, installed: boolean): ChannelTransition;
|
|
117
|
-
export {};
|
|
118
117
|
//# sourceMappingURL=remote-channel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-channel.d.ts","sourceRoot":"","sources":["../../../src/client/remote-channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,
|
|
1
|
+
{"version":3,"file":"remote-channel.d.ts","sourceRoot":"","sources":["../../../src/client/remote-channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EACL,iBAAiB,EAEjB,aAAa,EACd,MAAM,4BAA4B,CAAA;AAEnC,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,CAAA;AAC3C,YAAY,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AACvE,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAA;AAIvE,qEAAqE;AACrE,MAAM,WAAW,6BAA6B;IAC5C,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,aAAa,GAAG,MAAM,CAAA;IACpD,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,oBAAoB,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAC9D;AAED,qFAAqF;AACrF,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,6BAA6B,EACvC,iBAAiB,EAAE,OAAO,GAAG,SAAS,GACrC,OAAO,CAQT;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAI5D;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAUhE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAcnF;AAED,wEAAwE;AACxE,UAAU,cAAc;IACtB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,qEAAqE;AACrE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;IAC9B,SAAS,EAAE,OAAO,SAAS,CAAA;IAC3B,WAAW,CAAC,EAAE,OAAO,WAAW,CAAA;IAChC,gBAAgB,CAAC,EAAE,cAAc,CAAA;IACjC,iBAAiB,CAAC,EAAE,cAAc,CAAA;IAClC,iBAAiB,CAAC,EAAE,cAAc,CAAA;IAClC,QAAQ,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAC3C;AAED,gDAAgD;AAChD,MAAM,WAAW,oBAAoB;IACnC,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB;AAoBD;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAO3E;AA0BD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,GAAE,oBAAyB,GAAG,MAAM,IAAI,CAwE1G;AAED;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAA;AAE7D;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,GAAG,iBAAiB,CAIxF"}
|
|
@@ -23,25 +23,10 @@
|
|
|
23
23
|
* Pure helpers are exported for unit tests; `installRemoteChannel` patches
|
|
24
24
|
* the given window and returns their restore.
|
|
25
25
|
*/
|
|
26
|
-
|
|
27
|
-
export
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const API_PREFIX = '/api/';
|
|
31
|
-
const PAIR_PREFIX = '/api/pair/';
|
|
32
|
-
const UPDATE_PREFIX = '/api/update/';
|
|
33
|
-
const DESKTOP_LAUNCHER_PREFIX = '/api/dsh-desktop-launcher';
|
|
34
|
-
const SETTINGS_BRIDGE_PREFIX = '/api/dsh-web-ui-settings';
|
|
35
|
-
const SIDEBAR_PREFIX = '/sidebar/';
|
|
36
|
-
const GIT_PREFIX = '/git/';
|
|
37
|
-
const PET_PREFIX = '/pet/';
|
|
38
|
-
const WS_PATHS = new Set([
|
|
39
|
-
'/api/events.mux',
|
|
40
|
-
'/api/events.host',
|
|
41
|
-
'/sidebar/ws/terminal',
|
|
42
|
-
'/sidebar/ws/agent-terminals',
|
|
43
|
-
'/api/dsh-ssh/terminal',
|
|
44
|
-
]);
|
|
26
|
+
import { REMOTE_API_PREFIX, REMOTE_CHANNEL_RULES, REMOTE_PREFIX, } from "../remote-channel-rules.js";
|
|
27
|
+
export { REMOTE_API_PREFIX, REMOTE_PREFIX };
|
|
28
|
+
export { REMOTE_CHANNEL_BOOT_GLOBAL } from "../remote-channel-rules.js";
|
|
29
|
+
const RULES = REMOTE_CHANNEL_RULES;
|
|
45
30
|
/** Decide whether a remote desktop channel is required from local or host policy. */
|
|
46
31
|
export function remoteChannelRequired(hostname, snapshot, hostPairingPolicy) {
|
|
47
32
|
if (isLoopbackHostname(hostname))
|
|
@@ -71,21 +56,21 @@ export function isLoopbackHostname(hostname) {
|
|
|
71
56
|
* @param pathname - the request URL pathname.
|
|
72
57
|
*/
|
|
73
58
|
export function shouldRewriteFetchPath(pathname) {
|
|
74
|
-
if (pathname.startsWith(
|
|
59
|
+
if (pathname.startsWith(RULES.pairPrefix))
|
|
75
60
|
return false;
|
|
76
|
-
if (pathname.startsWith(
|
|
61
|
+
if (pathname.startsWith(RULES.updatePrefix))
|
|
77
62
|
return false;
|
|
78
|
-
if (pathname ===
|
|
63
|
+
if (pathname === RULES.desktopLauncherPrefix || pathname.startsWith(`${RULES.desktopLauncherPrefix}/`))
|
|
79
64
|
return false;
|
|
80
|
-
if (pathname ===
|
|
65
|
+
if (pathname === RULES.settingsBridgePrefix || pathname.startsWith(`${RULES.settingsBridgePrefix}/`))
|
|
81
66
|
return false;
|
|
82
|
-
if (pathname.startsWith(
|
|
67
|
+
if (pathname.startsWith(RULES.apiPrefix))
|
|
83
68
|
return true;
|
|
84
|
-
if (pathname.startsWith(
|
|
69
|
+
if (pathname.startsWith(RULES.sidebarPrefix) || pathname === '/sidebar')
|
|
85
70
|
return true;
|
|
86
|
-
if (pathname.startsWith(
|
|
71
|
+
if (pathname.startsWith(RULES.gitPrefix) || pathname === '/git')
|
|
87
72
|
return true;
|
|
88
|
-
if (pathname.startsWith(
|
|
73
|
+
if (pathname.startsWith(RULES.petPrefix) || pathname === '/pet')
|
|
89
74
|
return true;
|
|
90
75
|
return false;
|
|
91
76
|
}
|
|
@@ -94,7 +79,7 @@ export function shouldRewriteFetchPath(pathname) {
|
|
|
94
79
|
* @param pathname - the WebSocket URL pathname.
|
|
95
80
|
*/
|
|
96
81
|
export function shouldRewriteWsPath(pathname) {
|
|
97
|
-
return
|
|
82
|
+
return RULES.wsPaths.includes(pathname);
|
|
98
83
|
}
|
|
99
84
|
/** The gated twin of one fenced path (`/remote` + original pathname). */
|
|
100
85
|
export function rewritePath(pathname) {
|
package/lib/types/http.d.ts
CHANGED
|
@@ -1,14 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared
|
|
3
|
-
* bounded
|
|
4
|
-
*
|
|
2
|
+
* Shared JSON body/response helpers for the host route families: one strict
|
|
3
|
+
* bounded body reader, one lenient bounded body reader, one JSON object
|
|
4
|
+
* narrow, and one JSON writer. Previously these were copy-pasted across the
|
|
5
|
+
* package route files (routes.ts, update-routes.ts, mobile-api.ts, and each
|
|
6
|
+
* family's route module) with drifting contracts: body caps ranging 4 KiB to
|
|
7
|
+
* 1 MiB and four distinct overflow behaviors (reject, undefined, null, throw).
|
|
8
|
+
*
|
|
9
|
+
* Packages receive this file as a generated copy via scripts/sync-shared.mjs;
|
|
10
|
+
* edit this shared source and re-run the sync instead of editing a copy.
|
|
11
|
+
* Consumer code is migrated onto it in follow-up waves; no call site changes
|
|
12
|
+
* belong in the same change as its introduction.
|
|
13
|
+
* @module dsh-web-ui-shared/host/http
|
|
5
14
|
*/
|
|
6
|
-
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
7
|
-
/** One JSON response. */
|
|
8
|
-
export declare function writeJson(res: ServerResponse, status: number, body: unknown): void;
|
|
15
|
+
import type { IncomingMessage, OutgoingHttpHeaders, ServerResponse } from 'node:http';
|
|
9
16
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
17
|
+
* Strict bounded body reader: parse a request body of at most maxBytes as
|
|
18
|
+
* JSON.
|
|
19
|
+
* @throws 'body too large' past the cap, or the JSON.parse error for an
|
|
20
|
+
* invalid or empty payload.
|
|
12
21
|
*/
|
|
13
22
|
export declare function readBoundedJson(req: IncomingMessage, maxBytes: number): Promise<unknown>;
|
|
23
|
+
/**
|
|
24
|
+
* Lenient bounded body reader: parse a request body as JSON, or null on an
|
|
25
|
+
* empty body, invalid JSON, or a body past maxBytes (default 64 KiB).
|
|
26
|
+
* Overflow destroys the request instead of draining the remainder (no drain
|
|
27
|
+
* call, matching the current repo-wide behavior); callers must not keep
|
|
28
|
+
* reading the request afterwards. With objectOnly, non-JSON-object payloads
|
|
29
|
+
* also yield null.
|
|
30
|
+
*/
|
|
31
|
+
export declare function readJsonBody(req: IncomingMessage, opts?: {
|
|
32
|
+
maxBytes?: number;
|
|
33
|
+
objectOnly?: boolean;
|
|
34
|
+
}): Promise<unknown | null>;
|
|
35
|
+
/** Narrow a value to a JSON object, or undefined when it is not one. */
|
|
36
|
+
export declare function asJsonObject(value: unknown): Record<string, unknown> | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Write one JSON response. Default headers are the family defaults
|
|
39
|
+
* (content-type and referrer-policy); caller headers are appended or
|
|
40
|
+
* override them.
|
|
41
|
+
*/
|
|
42
|
+
export declare function writeJson(res: ServerResponse, status: number, body: unknown, headers?: OutgoingHttpHeaders): void;
|
|
14
43
|
//# sourceMappingURL=http.d.ts.map
|
package/lib/types/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/http.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/http.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAWrF;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAU9F;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,eAAe,EACpB,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GACrD,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAsBzB;AAOD,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAEhF;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,OAAO,GAAE,mBAAwB,GAChC,IAAI,CAIN"}
|
package/lib/types/http.js
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
|
+
// Generated by scripts/sync-shared.mjs from shared/host/http.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
|
|
1
2
|
/**
|
|
2
|
-
* Shared
|
|
3
|
-
* bounded
|
|
4
|
-
*
|
|
3
|
+
* Shared JSON body/response helpers for the host route families: one strict
|
|
4
|
+
* bounded body reader, one lenient bounded body reader, one JSON object
|
|
5
|
+
* narrow, and one JSON writer. Previously these were copy-pasted across the
|
|
6
|
+
* package route files (routes.ts, update-routes.ts, mobile-api.ts, and each
|
|
7
|
+
* family's route module) with drifting contracts: body caps ranging 4 KiB to
|
|
8
|
+
* 1 MiB and four distinct overflow behaviors (reject, undefined, null, throw).
|
|
9
|
+
*
|
|
10
|
+
* Packages receive this file as a generated copy via scripts/sync-shared.mjs;
|
|
11
|
+
* edit this shared source and re-run the sync instead of editing a copy.
|
|
12
|
+
* Consumer code is migrated onto it in follow-up waves; no call site changes
|
|
13
|
+
* belong in the same change as its introduction.
|
|
14
|
+
* @module dsh-web-ui-shared/host/http
|
|
5
15
|
*/
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
/** Default body cap for readJsonBody: 64 KiB. */
|
|
17
|
+
const DEFAULT_JSON_BODY_MAX_BYTES = 64 * 1024;
|
|
18
|
+
/** Family-default JSON response headers; callers may append or override. */
|
|
19
|
+
const JSON_HEADERS = {
|
|
20
|
+
'content-type': 'application/json; charset=utf-8',
|
|
21
|
+
'referrer-policy': 'no-referrer',
|
|
22
|
+
};
|
|
12
23
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
24
|
+
* Strict bounded body reader: parse a request body of at most maxBytes as
|
|
25
|
+
* JSON.
|
|
26
|
+
* @throws 'body too large' past the cap, or the JSON.parse error for an
|
|
27
|
+
* invalid or empty payload.
|
|
15
28
|
*/
|
|
16
29
|
export async function readBoundedJson(req, maxBytes) {
|
|
17
30
|
const chunks = [];
|
|
@@ -25,3 +38,55 @@ export async function readBoundedJson(req, maxBytes) {
|
|
|
25
38
|
}
|
|
26
39
|
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
27
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Lenient bounded body reader: parse a request body as JSON, or null on an
|
|
43
|
+
* empty body, invalid JSON, or a body past maxBytes (default 64 KiB).
|
|
44
|
+
* Overflow destroys the request instead of draining the remainder (no drain
|
|
45
|
+
* call, matching the current repo-wide behavior); callers must not keep
|
|
46
|
+
* reading the request afterwards. With objectOnly, non-JSON-object payloads
|
|
47
|
+
* also yield null.
|
|
48
|
+
*/
|
|
49
|
+
export async function readJsonBody(req, opts = {}) {
|
|
50
|
+
const maxBytes = opts.maxBytes ?? DEFAULT_JSON_BODY_MAX_BYTES;
|
|
51
|
+
const chunks = [];
|
|
52
|
+
let size = 0;
|
|
53
|
+
for await (const chunk of req) {
|
|
54
|
+
const buffer = chunk;
|
|
55
|
+
size += buffer.length;
|
|
56
|
+
if (size > maxBytes) {
|
|
57
|
+
req.destroy();
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
chunks.push(buffer);
|
|
61
|
+
}
|
|
62
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
63
|
+
if (text === '')
|
|
64
|
+
return null;
|
|
65
|
+
try {
|
|
66
|
+
const parsed = JSON.parse(text);
|
|
67
|
+
if (opts.objectOnly && !isJsonObject(parsed))
|
|
68
|
+
return null;
|
|
69
|
+
return parsed;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Whether a value is a JSON object: typeof object, not null, not an array. */
|
|
76
|
+
function isJsonObject(value) {
|
|
77
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
78
|
+
}
|
|
79
|
+
/** Narrow a value to a JSON object, or undefined when it is not one. */
|
|
80
|
+
export function asJsonObject(value) {
|
|
81
|
+
return isJsonObject(value) ? value : undefined;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Write one JSON response. Default headers are the family defaults
|
|
85
|
+
* (content-type and referrer-policy); caller headers are appended or
|
|
86
|
+
* override them.
|
|
87
|
+
*/
|
|
88
|
+
export function writeJson(res, status, body, headers = {}) {
|
|
89
|
+
const payload = JSON.stringify(body);
|
|
90
|
+
res.writeHead(status, { ...JSON_HEADERS, ...headers });
|
|
91
|
+
res.end(payload);
|
|
92
|
+
}
|
package/lib/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,CAAC,MAAM,aAAa,CAAA;AAE3B,OAAO,EAA0C,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,CAAC,MAAM,aAAa,CAAA;AAE3B,OAAO,EAA0C,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AA0BzF,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,MAAM;QACd;;;;;WAKG;QACH,UAAU,CACR,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,IAAI,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GACrC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAC9B;CACF;AAED,iCAAiC;AACjC,eAAO,MAAM,IAAI,kBAAkB,CAAA;AAEnC,+DAA+D;AAC/D,eAAO,MAAM,MAAM,UAA4B,CAAA;AAE/C;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,uDAAqC,CAAA;AAElF,qEAAqE;AACrE,MAAM,WAAW,MAAM;IACrB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAY3B,CAAA;AAKF;;;;GAIG;AACH,KAAK,cAAc,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,GAAG,aAAa,CAAC,CAAC,GAAG;IAC9E,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,IAAI,CAC5C,cAAc,EACd,YAAY,GAAG,gBAAgB,GAAG,YAAY,GAAG,cAAc,GAAG,YAAY,GAAG,aAAa,CAC/F,GAAG,aAAa,CAShB;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,MAAkB,GAAG,MAAM,CAEnE;AAiBD;;;;GAIG;AACH,eAAO,MAAM,KAAK,kBAAuD,CAAA;AAEzE,iBAAS,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAuTtD"}
|
package/lib/types/index.js
CHANGED
|
@@ -19,6 +19,8 @@ import { RemoteWebUiPairing } from "./pairing-access.js";
|
|
|
19
19
|
import { isTrustedApiRequest, makeRoutes } from "./routes.js";
|
|
20
20
|
import { makeMobileRoutes } from "./mobile-routes.js";
|
|
21
21
|
import { makeMobileApiRoutes } from "./mobile-api.js";
|
|
22
|
+
import { PendingTracker } from "./mobile-pending.js";
|
|
23
|
+
import { makePairedModelCatalogRoutes } from "./paired-model-catalog.js";
|
|
22
24
|
import { makeRemoteApiRoutes, makeRemoteApiUpgradeRoutes } from "./remote-api.js";
|
|
23
25
|
import { claimPostureKey, postureTargets, probePosture, releasePostureKey } from "./posture.js";
|
|
24
26
|
import { lanIPv4Addresses } from "./lan.js";
|
|
@@ -26,6 +28,8 @@ import { TunnelManager } from "./tunnel.js";
|
|
|
26
28
|
import { checkUpdates, fetchLatestVersion, resolveAnchorManifest, resolveUpdateTarget, runUpdateVerified, } from "./update.js";
|
|
27
29
|
import { makeUpdateRoutes } from "./update-routes.js";
|
|
28
30
|
import { mountOnce } from "./mount-once.js";
|
|
31
|
+
import { REMOTE_CHANNEL_BOOT_SCRIPT } from "./remote-channel-boot.js";
|
|
32
|
+
import { UUID_POLYFILL_SCRIPT } from "./uuid-polyfill.js";
|
|
29
33
|
/** Stable cordis plugin name. */
|
|
30
34
|
export const name = 'remote-web-ui';
|
|
31
35
|
/** Services required before the pairing surfaces can mount. */
|
|
@@ -251,14 +255,26 @@ function applyImpl(ctx, config) {
|
|
|
251
255
|
...makeRoutes({ service, lanAddresses, requirePairingForLan: () => resolve().requirePairingForLan }),
|
|
252
256
|
...makeMobileRoutes(),
|
|
253
257
|
...(apiProxy !== undefined
|
|
254
|
-
? makeMobileApiRoutes({ service, apiProxy, mobileEnterToSend: () => resolve().mobileEnterToSend })
|
|
258
|
+
? makeMobileApiRoutes({ service, apiProxy, pendingTracker: new PendingTracker(), mobileEnterToSend: () => resolve().mobileEnterToSend })
|
|
255
259
|
: []),
|
|
256
|
-
|
|
257
|
-
//
|
|
258
|
-
|
|
260
|
+
...(apiProxy !== undefined ? makePairedModelCatalogRoutes({ service, apiProxy, lanAddresses }) : []),
|
|
261
|
+
// The remote desktop channel: policy-gated `/remote` prefix that
|
|
262
|
+
// re-issues fenced paths to loopback (see remote-api.ts). The live
|
|
263
|
+
// requirePairingForLan is re-read per request, same as the gate listener
|
|
264
|
+
// and routes above, so a stale client rewrite on an open-LAN deployment
|
|
265
|
+
// proxies instead of 403ing.
|
|
266
|
+
...makeRemoteApiRoutes({
|
|
267
|
+
service,
|
|
268
|
+
port: ctx.webServer.port,
|
|
269
|
+
requirePairingForLan: () => resolve().requirePairingForLan,
|
|
270
|
+
}),
|
|
259
271
|
...updateRoutes,
|
|
260
272
|
];
|
|
261
|
-
const upgrades = makeRemoteApiUpgradeRoutes({
|
|
273
|
+
const upgrades = makeRemoteApiUpgradeRoutes({
|
|
274
|
+
service,
|
|
275
|
+
port: ctx.webServer.port,
|
|
276
|
+
requirePairingForLan: () => resolve().requirePairingForLan,
|
|
277
|
+
});
|
|
262
278
|
const gate = makeGateListener(service, () => resolve().requirePairingForLan, () => resolve().enabled);
|
|
263
279
|
ctx.effect(() => ctx.on('api/gate', gate), 'remote-web-ui: api gate');
|
|
264
280
|
// ── posture probe ─────────────────────────────────────────────────────────
|
|
@@ -369,6 +385,25 @@ function applyImpl(ctx, config) {
|
|
|
369
385
|
// re-probe unless the target set is unchanged.
|
|
370
386
|
runPostureProbe();
|
|
371
387
|
};
|
|
388
|
+
// Inject the crypto.randomUUID polyfill before any other script runs, so that
|
|
389
|
+
// the main bundle doesn't crash on non-secure contexts (LAN HTTP)
|
|
390
|
+
ctx.effect(() => ctx.on('webserver/index-inject', (table) => {
|
|
391
|
+
table.push({ kind: 'script', placement: 'head', text: UUID_POLYFILL_SCRIPT });
|
|
392
|
+
}), 'remote-web-ui: uuid polyfill');
|
|
393
|
+
// Issue #987: the browser-half channel patch installs at this plugin's
|
|
394
|
+
// boot entry, but dsh-client-connection boots earlier and opens its event
|
|
395
|
+
// streams unrewritten — on a non-loopback origin the SDK fence rejects
|
|
396
|
+
// them and the workspace list never loads. Contribute the rewrite as a
|
|
397
|
+
// parse-time head script so it is active before ANY boot entry runs; the
|
|
398
|
+
// client apply adopts the installed seat instead of patching twice. The
|
|
399
|
+
// row follows the live steady-state decision (enabled + pairing gate); the
|
|
400
|
+
// script itself skips loopback origins.
|
|
401
|
+
ctx.effect(() => ctx.on('webserver/index-inject', (table) => {
|
|
402
|
+
const value = resolve();
|
|
403
|
+
if (!value.enabled || !value.requirePairingForLan)
|
|
404
|
+
return;
|
|
405
|
+
table.push({ kind: 'script', placement: 'head', text: REMOTE_CHANNEL_BOOT_SCRIPT });
|
|
406
|
+
}), 'remote-web-ui: remote channel boot patch');
|
|
372
407
|
installSettingsSection(ctx, REMOTE_WEB_UI_SETTINGS_NAMESPACE, Config, config ?? {}, {
|
|
373
408
|
setSource: (source) => {
|
|
374
409
|
current = source;
|
|
@@ -78,4 +78,61 @@ export declare function selectModel(sessionId: string, selection: {
|
|
|
78
78
|
reasoningEffort?: string;
|
|
79
79
|
};
|
|
80
80
|
}>;
|
|
81
|
+
/** One pending tool approval awaiting the user's decision. */
|
|
82
|
+
export interface PendingApproval {
|
|
83
|
+
approvalId: string;
|
|
84
|
+
toolName: string;
|
|
85
|
+
callId?: string;
|
|
86
|
+
reason?: string;
|
|
87
|
+
}
|
|
88
|
+
/** One pending question group awaiting the user's answer. */
|
|
89
|
+
export interface PendingQuestionItem {
|
|
90
|
+
id: string;
|
|
91
|
+
question: string;
|
|
92
|
+
detail?: string;
|
|
93
|
+
header?: string;
|
|
94
|
+
options?: Array<{
|
|
95
|
+
label: string;
|
|
96
|
+
description?: string;
|
|
97
|
+
}>;
|
|
98
|
+
multiSelect?: boolean;
|
|
99
|
+
}
|
|
100
|
+
/** The pending state for one session. */
|
|
101
|
+
export interface PendingState {
|
|
102
|
+
approvals: PendingApproval[];
|
|
103
|
+
questions: PendingQuestionItem[];
|
|
104
|
+
}
|
|
105
|
+
/** Fetch pending approvals and questions for one session (polling fallback data source). */
|
|
106
|
+
export declare function fetchPending(sessionId: string): Promise<PendingState>;
|
|
107
|
+
/** Submit an approval decision (allowed-once or rejected). */
|
|
108
|
+
export declare function respondApproval(sessionId: string, approvalId: string, outcome: 'allowed-once' | 'rejected'): Promise<void>;
|
|
109
|
+
/** Submit answers to a question group. */
|
|
110
|
+
export declare function respondQuestion(sessionId: string, answers: Array<{
|
|
111
|
+
id: string;
|
|
112
|
+
selected: string[];
|
|
113
|
+
custom?: string;
|
|
114
|
+
}>): Promise<void>;
|
|
115
|
+
/** One entry in a directory listing. */
|
|
116
|
+
export interface DirectoryEntry {
|
|
117
|
+
name: string;
|
|
118
|
+
path: string;
|
|
119
|
+
hidden: boolean;
|
|
120
|
+
}
|
|
121
|
+
/** The host directory listing result (one level, with ancestor breadcrumbs). */
|
|
122
|
+
export interface DirectoryListing {
|
|
123
|
+
path: string;
|
|
124
|
+
home: string;
|
|
125
|
+
crumbs: DirectoryEntry[];
|
|
126
|
+
entries: DirectoryEntry[];
|
|
127
|
+
truncated: boolean;
|
|
128
|
+
}
|
|
129
|
+
/** The result of creating a workspace from an existing directory. */
|
|
130
|
+
export interface WorkspaceCreateResult {
|
|
131
|
+
workspace: WorkspaceView;
|
|
132
|
+
created: boolean;
|
|
133
|
+
}
|
|
134
|
+
/** Browse one directory level on the host (defaults to home directory). */
|
|
135
|
+
export declare function listDirectory(path?: string): Promise<DirectoryListing>;
|
|
136
|
+
/** Create a workspace from an existing host directory (does not mkdir). */
|
|
137
|
+
export declare function createWorkspace(path: string): Promise<WorkspaceCreateResult>;
|
|
81
138
|
//# sourceMappingURL=api.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/mobile/api.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAA;AACjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kDAAkD,CAAA;AACxF,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,6CAA6C,CAAA;AAGzH,6BAA6B;AAC7B,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,cAAc,EAAE,CAAA;IACvB,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,4DAA4D;AAC5D,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAA;IACpC,UAAU,EAAE,OAAO,CAAA;IACnB,WAAW,EAAE,OAAO,CAAA;CACrB;AAED,wEAAwE;AACxE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,6CAA6C,EAAE,YAAY,EAAE,CAAA;IAC5E,OAAO,EAAE,OAAO,CAAA;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,uBAAuB,CAAA;CACtC;AAED,4EAA4E;AAC5E,MAAM,WAAW,iBAAiB;IAChC,6EAA6E;IAC7E,iBAAiB,EAAE,OAAO,CAAA;CAC3B;AAED,kEAAkE;AAClE,wBAAsB,cAAc,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAG/D;AAED,yFAAyF;AACzF,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAEzE;AAED,iEAAiE;AACjE,wBAAsB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAExE;AAED,+DAA+D;AAC/D,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAEnE;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GACzE,OAAO,CAAC,cAAc,CAAC,CAEzB;AAED,oFAAoF;AACpF,wBAAsB,OAAO,CAC3B,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,EAClB,WAAW,SAAK,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,WAAW,CAAC,CAMtB;AAED,qEAAqE;AACrE,wBAAsB,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM3E;AAED,wEAAwE;AACxE,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAMnF;AAED,oFAAoF;AACpF,wBAAsB,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAEtE;AAED,2FAA2F;AAC3F,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,GACvE,OAAO,CAAC;IAAE,QAAQ,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC,CAOtF"}
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/mobile/api.ts"],"names":[],"mappings":"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"}
|
package/lib/types/mobile/api.js
CHANGED
|
@@ -66,3 +66,32 @@ export async function selectModel(sessionId, selection) {
|
|
|
66
66
|
...(selection.reasoningEffort !== undefined ? { reasoningEffort: selection.reasoningEffort } : {}),
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
|
+
/** Fetch pending approvals and questions for one session (polling fallback data source). */
|
|
70
|
+
export async function fetchPending(sessionId) {
|
|
71
|
+
return await callUnary('mobile.pending', { sessionId });
|
|
72
|
+
}
|
|
73
|
+
/** Submit an approval decision (allowed-once or rejected). */
|
|
74
|
+
export async function respondApproval(sessionId, approvalId, outcome) {
|
|
75
|
+
await callUnary('mobile.respond', {
|
|
76
|
+
sessionId,
|
|
77
|
+
type: 'approval',
|
|
78
|
+
approvalId,
|
|
79
|
+
outcome,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/** Submit answers to a question group. */
|
|
83
|
+
export async function respondQuestion(sessionId, answers) {
|
|
84
|
+
await callUnary('mobile.respond', {
|
|
85
|
+
sessionId,
|
|
86
|
+
type: 'question',
|
|
87
|
+
answers,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/** Browse one directory level on the host (defaults to home directory). */
|
|
91
|
+
export async function listDirectory(path) {
|
|
92
|
+
return await callUnary('host.listDirectory', path === undefined ? {} : { path });
|
|
93
|
+
}
|
|
94
|
+
/** Create a workspace from an existing host directory (does not mkdir). */
|
|
95
|
+
export async function createWorkspace(path) {
|
|
96
|
+
return await callUnary('workspace.create', { path });
|
|
97
|
+
}
|
|
@@ -9,7 +9,13 @@
|
|
|
9
9
|
* Pure and exported for tests.
|
|
10
10
|
* @module dsh-remote-web-ui/mobile/markdown
|
|
11
11
|
*/
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Escape HTML special characters. One regex pass replaces all five
|
|
14
|
+
* characters through the map; the output is identical to five sequential
|
|
15
|
+
* passes (each replacement string contains none of the escaped characters),
|
|
16
|
+
* and the common no-special-character case scans the string only once
|
|
17
|
+
* instead of five times.
|
|
18
|
+
*/
|
|
13
19
|
export declare function escapeHtml(text: string): string;
|
|
14
20
|
/**
|
|
15
21
|
* Guard a raw link/image target against dangerous protocols. Returns the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../../src/mobile/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../../src/mobile/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAWH;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASlD;AAgBD,2EAA2E;AAC3E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAqFjD;AAED,uDAAuD;AACvD,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA+HrD"}
|
|
@@ -9,14 +9,23 @@
|
|
|
9
9
|
* Pure and exported for tests.
|
|
10
10
|
* @module dsh-remote-web-ui/mobile/markdown
|
|
11
11
|
*/
|
|
12
|
-
/**
|
|
12
|
+
/** HTML special-character map for {@link escapeHtml}. */
|
|
13
|
+
const HTML_ESCAPE_MAP = {
|
|
14
|
+
'&': '&',
|
|
15
|
+
'<': '<',
|
|
16
|
+
'>': '>',
|
|
17
|
+
'"': '"',
|
|
18
|
+
"'": ''',
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Escape HTML special characters. One regex pass replaces all five
|
|
22
|
+
* characters through the map; the output is identical to five sequential
|
|
23
|
+
* passes (each replacement string contains none of the escaped characters),
|
|
24
|
+
* and the common no-special-character case scans the string only once
|
|
25
|
+
* instead of five times.
|
|
26
|
+
*/
|
|
13
27
|
export function escapeHtml(text) {
|
|
14
|
-
return text
|
|
15
|
-
.replace(/&/g, '&')
|
|
16
|
-
.replace(/</g, '<')
|
|
17
|
-
.replace(/>/g, '>')
|
|
18
|
-
.replace(/"/g, '"')
|
|
19
|
-
.replace(/'/g, ''');
|
|
28
|
+
return text.replace(/[&<>"']/g, char => HTML_ESCAPE_MAP[char] ?? char);
|
|
20
29
|
}
|
|
21
30
|
/**
|
|
22
31
|
* Guard a raw link/image target against dangerous protocols. Returns the
|
|
@@ -68,4 +68,12 @@ describe('escapeHtml', () => {
|
|
|
68
68
|
it('escapes all five HTML-significant characters', () => {
|
|
69
69
|
expect(escapeHtml('<&>"\'')).toBe('<&>"'');
|
|
70
70
|
});
|
|
71
|
+
it('equals the five sequential passes on mixed, pre-escaped and plain input', () => {
|
|
72
|
+
// Single-pass map replacement must never diverge from the sequential
|
|
73
|
+
// five-pass escape (including double-escaping of literal entities).
|
|
74
|
+
expect(escapeHtml('a & b < c > d " e \' f')).toBe('a & b < c > d " e ' f');
|
|
75
|
+
expect(escapeHtml('& < >')).toBe('&amp; &lt; &gt;');
|
|
76
|
+
expect(escapeHtml('plain text 123')).toBe('plain text 123');
|
|
77
|
+
expect(escapeHtml('')).toBe('');
|
|
78
|
+
});
|
|
71
79
|
});
|