@linxin666/dsh-remote-web-ui 0.3.24 → 0.4.1
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 +13 -10
- package/README.zh.md +13 -10
- package/icon.svg +3 -0
- package/lib/client.js +263 -129
- package/lib/client.js.map +1 -1
- package/lib/index.js +565 -256
- package/lib/types/client/FenceNotice.d.ts.map +1 -1
- package/lib/types/client/FenceNotice.js +0 -3
- package/lib/types/client/PluginSettingsCard.d.ts +4 -0
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +3 -2
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +14 -9
- package/lib/types/client/RemotePanel.d.ts.map +1 -1
- package/lib/types/client/RemotePanel.js +13 -4
- package/lib/types/client/RemoteSettingsCard.d.ts +6 -6
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +14 -8
- package/lib/types/client/UpdateEntry.js +2 -2
- package/lib/types/client/UpdatePanel.js +2 -2
- package/lib/types/client/deep-link.js +2 -2
- package/lib/types/client/index.d.ts +16 -8
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +47 -18
- package/lib/types/client/locales.d.ts +0 -2
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +6 -8
- package/lib/types/client/mobile-adapt.d.ts.map +1 -1
- package/lib/types/client/mobile-adapt.js +103 -22
- package/lib/types/client/pair-api.d.ts +32 -3
- package/lib/types/client/pair-api.d.ts.map +1 -1
- package/lib/types/client/pair-api.js +44 -6
- package/lib/types/client/plugin-card-seat.d.ts +23 -16
- package/lib/types/client/plugin-card-seat.d.ts.map +1 -1
- package/lib/types/client/plugin-card-seat.js +16 -14
- package/lib/types/client/remote-channel.d.ts.map +1 -1
- package/lib/types/client/remote-channel.js +18 -5
- package/lib/types/client/settings-form.d.ts +17 -17
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +30 -27
- package/lib/types/http.d.ts +18 -1
- package/lib/types/http.d.ts.map +1 -1
- package/lib/types/http.js +22 -1
- package/lib/types/index.d.ts +19 -11
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +91 -76
- package/lib/types/inner-auth.d.ts +3 -2
- package/lib/types/inner-auth.d.ts.map +1 -1
- package/lib/types/inner-auth.js +3 -2
- package/lib/types/pair-grant.d.ts +66 -0
- package/lib/types/pair-grant.d.ts.map +1 -0
- package/lib/types/pair-grant.js +95 -0
- package/lib/types/pairing.d.ts +44 -12
- package/lib/types/pairing.d.ts.map +1 -1
- package/lib/types/pairing.js +69 -20
- package/lib/types/pairing.persist.test.js +26 -0
- package/lib/types/posture.d.ts +11 -3
- package/lib/types/posture.d.ts.map +1 -1
- package/lib/types/posture.js +8 -3
- package/lib/types/remote-api.d.ts +10 -13
- package/lib/types/remote-api.d.ts.map +1 -1
- package/lib/types/remote-api.js +34 -34
- package/lib/types/remote-channel-boot.d.ts +6 -1
- package/lib/types/remote-channel-boot.d.ts.map +1 -1
- package/lib/types/remote-channel-boot.js +16 -8
- package/lib/types/remote-channel-rules.d.ts +14 -0
- package/lib/types/remote-channel-rules.d.ts.map +1 -1
- package/lib/types/remote-channel-rules.js +8 -0
- package/lib/types/remote-methods.d.ts +1 -1
- package/lib/types/remote-methods.d.ts.map +1 -1
- package/lib/types/remote-methods.js +1 -0
- package/lib/types/routes.d.ts +47 -5
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +160 -58
- package/lib/types/tunnel.d.ts +21 -6
- package/lib/types/tunnel.d.ts.map +1 -1
- package/lib/types/tunnel.js +58 -11
- package/lib/types/update.d.ts +4 -0
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +74 -26
- package/package.json +23 -20
- package/src/client/FenceNotice.tsx +0 -2
- package/src/client/PluginSettingsCard.tsx +7 -2
- package/src/client/RemoteEntry.tsx +14 -9
- package/src/client/RemotePanel.tsx +17 -7
- package/src/client/RemoteSettingsCard.tsx +16 -9
- package/src/client/UpdateEntry.tsx +2 -2
- package/src/client/UpdatePanel.tsx +3 -3
- package/src/client/deep-link.ts +2 -2
- package/src/client/index.ts +61 -27
- package/src/client/locales.ts +6 -8
- package/src/client/mobile-adapt.ts +97 -20
- package/src/client/pair-api.ts +47 -7
- package/src/client/plugin-card-seat.ts +24 -17
- package/src/client/remote-channel.ts +19 -4
- package/src/client/remote.module.css +38 -0
- package/src/client/settings-form.ts +34 -31
- package/src/http.ts +23 -1
- package/src/index.ts +102 -79
- package/src/inner-auth.ts +3 -2
- package/src/pair-grant.ts +131 -0
- package/src/pairing.persist.test.ts +36 -0
- package/src/pairing.ts +75 -24
- package/src/posture.ts +12 -4
- package/src/remote-api.ts +34 -41
- package/src/remote-channel-boot.ts +16 -8
- package/src/remote-channel-rules.ts +17 -0
- package/src/remote-methods.ts +1 -0
- package/src/routes.ts +165 -57
- package/src/tunnel.ts +56 -11
- package/src/update.ts +74 -24
|
@@ -43,7 +43,11 @@ export function remoteChannelRequired(hostname, snapshot, hostPairingPolicy) {
|
|
|
43
43
|
* @returns true for localhost, IPv6 loopback, or any 127/8 literal.
|
|
44
44
|
*/
|
|
45
45
|
export function isLoopbackHostname(hostname) {
|
|
46
|
-
|
|
46
|
+
// WHATWG location.hostname keeps IPv6 literals bracketed ("[::1]"); the
|
|
47
|
+
// host-side shared predicate accepts that spelling, so this browser copy must
|
|
48
|
+
// too, or an IPv6-loopback origin is judged remote and rewrites every call
|
|
49
|
+
// onto the gated channel where it can never pair.
|
|
50
|
+
if (hostname === 'localhost' || hostname === '::1' || hostname === '[::1]')
|
|
47
51
|
return true;
|
|
48
52
|
const parts = hostname.split('.');
|
|
49
53
|
return parts.length === 4 && parts[0] === '127' && parts.every(part => /^\d{1,3}$/.test(part) && Number(part) <= 255);
|
|
@@ -193,11 +197,17 @@ export function installRemoteChannel(window, options = {}) {
|
|
|
193
197
|
return init;
|
|
194
198
|
const headers = init?.headers;
|
|
195
199
|
if (typeof Headers !== 'undefined' && headers instanceof Headers) {
|
|
200
|
+
// Copy instead of mutating: the instance belongs to the caller, which may
|
|
201
|
+
// reuse it for a request this channel does not rewrite (the device
|
|
202
|
+
// credential must not ride along there).
|
|
196
203
|
try {
|
|
197
|
-
headers
|
|
204
|
+
const copy = new Headers(headers);
|
|
205
|
+
copy.set(RULES.deviceHeader, device);
|
|
206
|
+
return { ...init, headers: copy };
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return init;
|
|
198
210
|
}
|
|
199
|
-
catch { /* ignore */ }
|
|
200
|
-
return init;
|
|
201
211
|
}
|
|
202
212
|
if (typeof headers === 'object' && headers !== null) {
|
|
203
213
|
return { ...init, headers: { ...headers, [RULES.deviceHeader]: device } };
|
|
@@ -218,7 +228,10 @@ export function installRemoteChannel(window, options = {}) {
|
|
|
218
228
|
? rewritten.toString()
|
|
219
229
|
: new Request(rewritten, input);
|
|
220
230
|
return Promise.resolve(originalFetch.call(window, next, attach(init))).then(async (response) => {
|
|
221
|
-
|
|
231
|
+
// The clone tees the response body; only the 403 branch inspects it, so
|
|
232
|
+
// every other status skips the copy (a large upload or session dump
|
|
233
|
+
// would otherwise be duplicated per gated request).
|
|
234
|
+
if (response.status === 403 && await isUnpairedDenied(response.clone()))
|
|
222
235
|
options.onUnpaired?.();
|
|
223
236
|
else
|
|
224
237
|
options.onPaired?.();
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* each plugin's client bundle; mirrors the official ui-plugin-config
|
|
7
7
|
* card-store pattern.
|
|
8
8
|
*/
|
|
9
|
-
import type {
|
|
9
|
+
import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
10
10
|
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
11
11
|
/** The write one field's staged text performs when the card is saved. */
|
|
12
12
|
export type FieldWrite = {
|
|
@@ -86,7 +86,7 @@ export interface CardActions {
|
|
|
86
86
|
/** Drop every staged edit. */
|
|
87
87
|
discard: () => void;
|
|
88
88
|
}
|
|
89
|
-
/** One durable write inside the save's atomic
|
|
89
|
+
/** One durable write inside the save's atomic form mutation. */
|
|
90
90
|
export interface BatchedWrite {
|
|
91
91
|
/** Field this entry writes. */
|
|
92
92
|
field: string;
|
|
@@ -130,20 +130,20 @@ export declare class CardForm<T> {
|
|
|
130
130
|
private readonly specs;
|
|
131
131
|
private readonly staged;
|
|
132
132
|
private readonly listeners;
|
|
133
|
-
/** The
|
|
134
|
-
private readonly
|
|
133
|
+
/** The form subscription installed in the constructor; released by dispose(). */
|
|
134
|
+
private readonly disposeForm;
|
|
135
135
|
private disposed;
|
|
136
136
|
private saving;
|
|
137
137
|
private failed;
|
|
138
138
|
private failedReason;
|
|
139
|
-
/** @param scope - the bound
|
|
140
|
-
constructor(scope:
|
|
139
|
+
/** @param scope - the bound configuration form for this card's namespace. */
|
|
140
|
+
constructor(scope: ConfigForm<T>, specs: FieldSpec[]);
|
|
141
141
|
/**
|
|
142
|
-
* Release the
|
|
142
|
+
* Release the form subscription and every bound store listener. The card
|
|
143
143
|
* must call this on teardown; later calls are no-ops.
|
|
144
144
|
*/
|
|
145
145
|
dispose(): void;
|
|
146
|
-
/** Publish a projection of this form, rebuilt whenever the
|
|
146
|
+
/** Publish a projection of this form, rebuilt whenever the form or a draft changes. */
|
|
147
147
|
bind<S>(project: () => S): SnapshotStore<S>;
|
|
148
148
|
/** Read the card-level state: what the Host serves, and what a save would do. */
|
|
149
149
|
shell(): CardShell;
|
|
@@ -152,19 +152,19 @@ export declare class CardForm<T> {
|
|
|
152
152
|
/** The actions the card's slot registration injects. */
|
|
153
153
|
actions(): CardActions;
|
|
154
154
|
/**
|
|
155
|
-
* Write every staged edit in one atomic
|
|
155
|
+
* Write every staged edit in one atomic form mutation, then re-seed from
|
|
156
156
|
* what the Host accepted.
|
|
157
157
|
*
|
|
158
158
|
* The whole batch rides one mutate, so cross-field validate hooks
|
|
159
159
|
* (baseURL+model) judge it as a unit: the Host either applies every write
|
|
160
|
-
* or refuses the batch. The
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* settled snapshot back
|
|
164
|
-
* fails the whole save. A
|
|
165
|
-
*
|
|
166
|
-
* message. A save that did not land keeps its drafts, so
|
|
167
|
-
* correct them instead of retyping.
|
|
160
|
+
* or refuses the batch. The form contract answers a refusal or a skipped
|
|
161
|
+
* write with `false` (it recovers with a fresh Host view instead of
|
|
162
|
+
* throwing), so the outcome is judged twice: the answer itself, and then the
|
|
163
|
+
* settled snapshot read back one planned write at a time. One missed write
|
|
164
|
+
* fails the whole save. A transport that rejects instead (the dsh-web bridge
|
|
165
|
+
* controller on a dead connection) reports through the same failure path
|
|
166
|
+
* with its rejection message. A save that did not land keeps its drafts, so
|
|
167
|
+
* the user can correct them instead of retyping.
|
|
168
168
|
* @returns settlement after the mutation and the read-back.
|
|
169
169
|
*/
|
|
170
170
|
save(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAsB,MAAM,4CAA4C,CAAA;AAChG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAGlE,yEAAyE;AACzE,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAA;AAErB,0EAA0E;AAC1E,MAAM,WAAW,SAAS;IACxB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,2FAA2F;IAC3F,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAA;IAClC;;;OAGG;IACH,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAA;CAChD;AAED,wCAAwC;AACxC,MAAM,WAAW,UAAU;IACzB,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAA;IACZ,oEAAoE;IACpE,UAAU,EAAE,OAAO,CAAA;IACnB,gFAAgF;IAChF,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,oDAAoD;AACpD,MAAM,WAAW,SAAS;IACxB,4EAA4E;IAC5E,SAAS,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,OAAO,EAAE,OAAO,CAAA;IAChB,gDAAgD;IAChD,QAAQ,EAAE,OAAO,CAAA;IACjB,4DAA4D;IAC5D,KAAK,EAAE,OAAO,CAAA;IACd,kEAAkE;IAClE,OAAO,EAAE,OAAO,CAAA;IAChB,2CAA2C;IAC3C,MAAM,EAAE,OAAO,CAAA;IACf,sFAAsF;IACtF,MAAM,EAAE,OAAO,CAAA;IACf;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,uDAAuD;AACvD,MAAM,WAAW,WAAW;IAC1B,sCAAsC;IACtC,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3C,gFAAgF;IAChF,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,yEAAyE;IACzE,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAwBD,gEAAgE;AAChE,MAAM,WAAW,YAAY;IAC3B,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,gDAAgD;IAChD,EAAE,EAAE,KAAK,GAAG,OAAO,CAAA;IACnB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,6FAA6F;AAC7F,MAAM,WAAW,iBAAiB;IAChC,iDAAiD;IACjD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,6JAA6J;AAC7J,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,GAAE,iBAAsB,GAAG,SAAS,CAezF;AAED,0DAA0D;AAC1D,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CASlD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAEpD;AAED,6DAA6D;AAC7D,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAYrD;AAED,yGAAyG;AACzG,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAShF;AAED;;;;;;;GAOG;AACH,qBAAa,QAAQ,CAAC,CAAC;IAanB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAZxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwB;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;IACvD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAClD,iFAAiF;IACjF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAY;IACxC,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,YAAY,CAAoB;IAExC,6EAA6E;gBAE1D,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EACrC,KAAK,EAAE,SAAS,EAAE;IAMpB;;;OAGG;IACH,OAAO,IAAI,IAAI;IAOf,uFAAuF;IACvF,IAAI,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC;IAM3C,iFAAiF;IACjF,KAAK,IAAI,SAAS;IAelB,8EAA8E;IAC9E,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU;IAchC,wDAAwD;IACxD,OAAO,IAAI,WAAW;IAiBtB;;;;;;;;;;;;;;;OAeG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA4C3B;;;;;;OAMG;IACH,OAAO,CAAC,IAAI;IAiBZ;;;OAGG;IACH,OAAO,CAAC,SAAS;IAUjB;;;OAGG;IACH,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,KAAK;IAOb,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,MAAM;IAKd,OAAO,CAAC,OAAO;CAGhB"}
|
|
@@ -91,30 +91,30 @@ export class CardForm {
|
|
|
91
91
|
specs;
|
|
92
92
|
staged = new Map();
|
|
93
93
|
listeners = new Set();
|
|
94
|
-
/** The
|
|
95
|
-
|
|
94
|
+
/** The form subscription installed in the constructor; released by dispose(). */
|
|
95
|
+
disposeForm;
|
|
96
96
|
disposed = false;
|
|
97
97
|
saving = false;
|
|
98
98
|
failed = false;
|
|
99
99
|
failedReason;
|
|
100
|
-
/** @param scope - the bound
|
|
100
|
+
/** @param scope - the bound configuration form for this card's namespace. */
|
|
101
101
|
constructor(scope, specs) {
|
|
102
102
|
this.scope = scope;
|
|
103
103
|
this.specs = new Map(specs.map(spec => [spec.field, spec]));
|
|
104
|
-
this.
|
|
104
|
+
this.disposeForm = scope.subscribe(() => { this.publish(); });
|
|
105
105
|
}
|
|
106
106
|
/**
|
|
107
|
-
* Release the
|
|
107
|
+
* Release the form subscription and every bound store listener. The card
|
|
108
108
|
* must call this on teardown; later calls are no-ops.
|
|
109
109
|
*/
|
|
110
110
|
dispose() {
|
|
111
111
|
if (this.disposed)
|
|
112
112
|
return;
|
|
113
113
|
this.disposed = true;
|
|
114
|
-
this.
|
|
114
|
+
this.disposeForm();
|
|
115
115
|
this.listeners.clear();
|
|
116
116
|
}
|
|
117
|
-
/** Publish a projection of this form, rebuilt whenever the
|
|
117
|
+
/** Publish a projection of this form, rebuilt whenever the form or a draft changes. */
|
|
118
118
|
bind(project) {
|
|
119
119
|
const store = createSnapshotStore(project());
|
|
120
120
|
this.listeners.add(() => { store.set(project()); });
|
|
@@ -168,19 +168,19 @@ export class CardForm {
|
|
|
168
168
|
};
|
|
169
169
|
}
|
|
170
170
|
/**
|
|
171
|
-
* Write every staged edit in one atomic
|
|
171
|
+
* Write every staged edit in one atomic form mutation, then re-seed from
|
|
172
172
|
* what the Host accepted.
|
|
173
173
|
*
|
|
174
174
|
* The whole batch rides one mutate, so cross-field validate hooks
|
|
175
175
|
* (baseURL+model) judge it as a unit: the Host either applies every write
|
|
176
|
-
* or refuses the batch. The
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
* settled snapshot back
|
|
180
|
-
* fails the whole save. A
|
|
181
|
-
*
|
|
182
|
-
* message. A save that did not land keeps its drafts, so
|
|
183
|
-
* correct them instead of retyping.
|
|
176
|
+
* or refuses the batch. The form contract answers a refusal or a skipped
|
|
177
|
+
* write with `false` (it recovers with a fresh Host view instead of
|
|
178
|
+
* throwing), so the outcome is judged twice: the answer itself, and then the
|
|
179
|
+
* settled snapshot read back one planned write at a time. One missed write
|
|
180
|
+
* fails the whole save. A transport that rejects instead (the dsh-web bridge
|
|
181
|
+
* controller on a dead connection) reports through the same failure path
|
|
182
|
+
* with its rejection message. A save that did not land keeps its drafts, so
|
|
183
|
+
* the user can correct them instead of retyping.
|
|
184
184
|
* @returns settlement after the mutation and the read-back.
|
|
185
185
|
*/
|
|
186
186
|
async save() {
|
|
@@ -198,32 +198,35 @@ export class CardForm {
|
|
|
198
198
|
this.failed = false;
|
|
199
199
|
this.failedReason = undefined;
|
|
200
200
|
this.publish();
|
|
201
|
-
// One atomic namespace mutation: the
|
|
202
|
-
//
|
|
201
|
+
// One atomic namespace mutation: the form contract takes ordered path
|
|
202
|
+
// operations, so the whole staged batch is validated, persisted, and
|
|
203
203
|
// recovered together — either every write lands or none does.
|
|
204
204
|
const ops = valid.map(item => item.op.op === 'set'
|
|
205
205
|
? { op: 'set', path: [item.field], value: item.op.value }
|
|
206
206
|
: { op: 'unset', path: [item.field] });
|
|
207
207
|
let failedReason;
|
|
208
|
+
let accepted = false;
|
|
208
209
|
try {
|
|
209
|
-
await this.scope.mutate(ops);
|
|
210
|
+
accepted = await this.scope.mutate(ops);
|
|
210
211
|
}
|
|
211
212
|
catch (error) {
|
|
212
213
|
failedReason = error instanceof Error ? error.message : String(error);
|
|
213
214
|
}
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
// judge every planned write against the settled
|
|
217
|
-
// atomic, so one missed write fails the
|
|
218
|
-
|
|
215
|
+
// `false` is the contract's refusal/skip answer, and the form still
|
|
216
|
+
// resolves after a refusal (it recovers with a fresh view), so the answer
|
|
217
|
+
// alone is not enough: judge every planned write against the settled
|
|
218
|
+
// snapshot as well. The mutation is atomic, so one missed write fails the
|
|
219
|
+
// whole save and keeps the drafts.
|
|
220
|
+
const landed = accepted && failedReason === undefined && valid.every(item => item.judge());
|
|
219
221
|
for (const [field, before] of pending) {
|
|
220
222
|
if (landed && this.staged.get(field) === before)
|
|
221
223
|
this.staged.delete(field);
|
|
222
224
|
}
|
|
223
225
|
this.saving = false;
|
|
224
226
|
this.failed = !landed;
|
|
225
|
-
// A
|
|
226
|
-
// generic failure copy; a rejecting
|
|
227
|
+
// A refused or unlanded batch carries no server reason: the card surfaces
|
|
228
|
+
// its generic failure copy; a rejecting transport (the bridge) adds its
|
|
229
|
+
// message.
|
|
227
230
|
this.failedReason = failedReason;
|
|
228
231
|
this.publish();
|
|
229
232
|
}
|
|
@@ -261,7 +264,7 @@ export class CardForm {
|
|
|
261
264
|
*/
|
|
262
265
|
landedSet(field, value) {
|
|
263
266
|
// A redacted secret never appears in any wire view layer: the Host strips
|
|
264
|
-
// role('secret') fields and reports them through a sidecar the
|
|
267
|
+
// role('secret') fields and reports them through a sidecar the form
|
|
265
268
|
// snapshot does not expose, so there is nothing to compare the draft
|
|
266
269
|
// against. Settling is the only signal the form has; the rest of the
|
|
267
270
|
// batch, when one exists, still carries the atomic verdict by read-back.
|
package/lib/types/http.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared JSON body/response helpers for the host route families: one strict
|
|
3
3
|
* bounded body reader, one lenient bounded body reader, one JSON object
|
|
4
|
-
* narrow,
|
|
4
|
+
* narrow, one JSON writer, and the outbound request-init helper that keeps a
|
|
5
|
+
* host fetch body decodable (withIdentityEncoding). Previously the readers
|
|
6
|
+
* were copy-pasted across the
|
|
5
7
|
* package route files (routes.ts, update-routes.ts, mobile-api.ts, and each
|
|
6
8
|
* family's route module) with drifting contracts: body caps ranging 4 KiB to
|
|
7
9
|
* 1 MiB and four distinct overflow behaviors (reject, undefined, null, throw).
|
|
@@ -34,6 +36,21 @@ export declare function readJsonBody(req: IncomingMessage, opts?: {
|
|
|
34
36
|
}): Promise<unknown | null>;
|
|
35
37
|
/** Narrow a value to a JSON object, or undefined when it is not one. */
|
|
36
38
|
export declare function asJsonObject(value: unknown): Record<string, unknown> | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Request init that asks a remote origin for an uncompressed body.
|
|
41
|
+
*
|
|
42
|
+
* The DSH host boots `@deepseek-ai/dsh-http-proxy`, whose top-level import of
|
|
43
|
+
* the npm `undici` copy replaces the legacy `undici.globalDispatcher.1` slot
|
|
44
|
+
* Node's built-in `fetch()` reads. That cross-major wrapper drops the
|
|
45
|
+
* `content-encoding` header and automatic decompression, so a plain fetch
|
|
46
|
+
* resolves to raw gzip/brotli/zstd bytes — compressed noise no JSON or text
|
|
47
|
+
* consumer can read. Every host fetch that parses a remote body must request
|
|
48
|
+
* identity encoding. Caller headers survive; `accept-encoding` is forced to
|
|
49
|
+
* `identity` because a caller value has no decoder behind it in this host.
|
|
50
|
+
* @param init - the caller's request init (signal, headers, method, ...).
|
|
51
|
+
* @returns a copy with `accept-encoding: identity` merged in.
|
|
52
|
+
*/
|
|
53
|
+
export declare function withIdentityEncoding(init?: RequestInit): RequestInit;
|
|
37
54
|
/**
|
|
38
55
|
* Write one JSON response. Default headers are the family defaults
|
|
39
56
|
* (content-type and referrer-policy); caller headers are appended or
|
package/lib/types/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/http.ts"],"names":[],"mappings":"AACA
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/http.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;GAeG;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;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,WAAgB,GAAG,WAAW,CAIxE;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
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Shared JSON body/response helpers for the host route families: one strict
|
|
4
4
|
* bounded body reader, one lenient bounded body reader, one JSON object
|
|
5
|
-
* narrow,
|
|
5
|
+
* narrow, one JSON writer, and the outbound request-init helper that keeps a
|
|
6
|
+
* host fetch body decodable (withIdentityEncoding). Previously the readers
|
|
7
|
+
* were copy-pasted across the
|
|
6
8
|
* package route files (routes.ts, update-routes.ts, mobile-api.ts, and each
|
|
7
9
|
* family's route module) with drifting contracts: body caps ranging 4 KiB to
|
|
8
10
|
* 1 MiB and four distinct overflow behaviors (reject, undefined, null, throw).
|
|
@@ -80,6 +82,25 @@ function isJsonObject(value) {
|
|
|
80
82
|
export function asJsonObject(value) {
|
|
81
83
|
return isJsonObject(value) ? value : undefined;
|
|
82
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Request init that asks a remote origin for an uncompressed body.
|
|
87
|
+
*
|
|
88
|
+
* The DSH host boots `@deepseek-ai/dsh-http-proxy`, whose top-level import of
|
|
89
|
+
* the npm `undici` copy replaces the legacy `undici.globalDispatcher.1` slot
|
|
90
|
+
* Node's built-in `fetch()` reads. That cross-major wrapper drops the
|
|
91
|
+
* `content-encoding` header and automatic decompression, so a plain fetch
|
|
92
|
+
* resolves to raw gzip/brotli/zstd bytes — compressed noise no JSON or text
|
|
93
|
+
* consumer can read. Every host fetch that parses a remote body must request
|
|
94
|
+
* identity encoding. Caller headers survive; `accept-encoding` is forced to
|
|
95
|
+
* `identity` because a caller value has no decoder behind it in this host.
|
|
96
|
+
* @param init - the caller's request init (signal, headers, method, ...).
|
|
97
|
+
* @returns a copy with `accept-encoding: identity` merged in.
|
|
98
|
+
*/
|
|
99
|
+
export function withIdentityEncoding(init = {}) {
|
|
100
|
+
const headers = new Headers(init.headers);
|
|
101
|
+
headers.set('accept-encoding', 'identity');
|
|
102
|
+
return { ...init, headers };
|
|
103
|
+
}
|
|
83
104
|
/**
|
|
84
105
|
* Write one JSON response. Default headers are the family defaults
|
|
85
106
|
* (content-type and referrer-policy); caller headers are appended or
|
package/lib/types/index.d.ts
CHANGED
|
@@ -34,9 +34,12 @@ export declare const name = "remote-web-ui";
|
|
|
34
34
|
/** Services required before the pairing surfaces can mount. */
|
|
35
35
|
export declare const inject: string[];
|
|
36
36
|
/**
|
|
37
|
-
* Settings namespace of the remote-control capability
|
|
38
|
-
* settings
|
|
39
|
-
*
|
|
37
|
+
* Settings namespace of the remote-control capability. Under the 0.1.7
|
|
38
|
+
* settings model the namespace IS the Host profile entry id, so this is also
|
|
39
|
+
* the id the browser half asks `ctx.configForms` for (the family binder
|
|
40
|
+
* resolves the same value to the owning entry). Spelled here rather than
|
|
41
|
+
* imported: the browser half spells the same value and must not depend on a
|
|
42
|
+
* Host package.
|
|
40
43
|
*/
|
|
41
44
|
export declare const REMOTE_WEB_UI_SETTINGS_NAMESPACE: SettingsNamespace;
|
|
42
45
|
/** Plugin config, validated by the same-named schemastery schema. */
|
|
@@ -49,22 +52,28 @@ export interface Config {
|
|
|
49
52
|
maxDevices?: number;
|
|
50
53
|
/**
|
|
51
54
|
* Idle sessions older than this (ms) are deleted from memory and disk.
|
|
52
|
-
* Default is
|
|
55
|
+
* Default is 30 days; a leftover cookie no longer authorizes after expiry.
|
|
53
56
|
*/
|
|
54
57
|
idleExpireMs?: number;
|
|
55
58
|
/** Cookie name carrying the paired device id. */
|
|
56
59
|
cookieName?: string;
|
|
57
60
|
/**
|
|
58
61
|
* When true (default), a desktop Web GUI opened at a non-loopback origin
|
|
59
|
-
* rides the gated `/remote/api` channel
|
|
60
|
-
* cookie — the QR is the only way into remote desktop, and
|
|
61
|
-
* cut the
|
|
62
|
+
* rides the gated `/remote/api` channel, whose requests must carry a live
|
|
63
|
+
* paired-device cookie — the QR is the only way into remote desktop, and
|
|
64
|
+
* stop()/revoke() cut the channel and the pairing cookie off immediately.
|
|
65
|
+
* This policy selects the transport; it never loosens the channel, whose
|
|
66
|
+
* gate is unconditional (issue #1665). Scope
|
|
62
67
|
* note for this cohort: direct /api is governed by the harness fence +
|
|
63
68
|
* browser-auth cookie (the api/gate seam has no emitter on 0.1.2-alpha.2),
|
|
64
69
|
* so a harness browser credential a device has already redeemed is not
|
|
65
70
|
* invalidated by stop() — see the README security model. Set false to keep
|
|
66
71
|
* the desktop on plain `/api` (only useful when that origin is already
|
|
67
|
-
* trusted for `/api`).
|
|
72
|
+
* trusted for `/api`). Turning the policy off does NOT open the channel:
|
|
73
|
+
* the /remote proxy attaches the process's own browser-auth credential only
|
|
74
|
+
* when the request itself presents a live paired-device credential, so an
|
|
75
|
+
* unpaired LAN/tunnel caller is forwarded without it and the inner route
|
|
76
|
+
* answers 401.
|
|
68
77
|
*/
|
|
69
78
|
requirePairingForLan?: boolean;
|
|
70
79
|
/**
|
|
@@ -164,9 +173,8 @@ type ResolvedConfig = Required<Omit<Config, 'publicBaseUrl' | 'trustedHosts' | '
|
|
|
164
173
|
};
|
|
165
174
|
/**
|
|
166
175
|
* The single mapping from resolved plugin config to the pairing service
|
|
167
|
-
* config. Both the constructed service and every
|
|
168
|
-
* it, so no field can be silently dropped
|
|
169
|
-
* pushes a new value into the running service.
|
|
176
|
+
* config. Both the constructed service and every later apply of this row
|
|
177
|
+
* reuse it, so no field can be silently dropped.
|
|
170
178
|
*/
|
|
171
179
|
export declare function pairingConfigOf(resolved: Pick<ResolvedConfig, 'tokenTtlMs' | 'offlineAfterMs' | 'maxDevices' | 'idleExpireMs' | 'cookieName' | 'devicesFile'>): PairingConfig;
|
|
172
180
|
/** Default paired-session store: `$DSH_HOME/remote-web-ui-devices.json`. */
|
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;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAClE,OAAO,CAAC,MAAM,aAAa,CAAA;AAO3B,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;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAClE,OAAO,CAAC,MAAM,aAAa,CAAA;AAO3B,OAAO,EAA0C,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AAkCzF,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,MAAM;QACd;;;;;;;;;;;WAWG;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,UAA+C,CAAA;AAElE;;;;;;;GAOG;AACH,eAAO,MAAM,gCAAgC,EAAsB,iBAAiB,CAAA;AAEpF,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;;;;;;;;;;;;;;;;;OAiBG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAgB3B,CAAA;AAYF;;;;GAIG;AACH,KAAK,cAAc,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,GAAG,cAAc,GAAG,aAAa,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,CAAC,CAAC,GAAG;IACvI,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,YAAY,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;IAClC,WAAW,EAAE,MAAM,CAAA;IACnB,wFAAwF;IACxF,OAAO,EAAE,OAAO,GAAG,SAAS,CAAA;IAC5B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED;;;;GAIG;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;AAqBD;;;;GAIG;AACH,eAAO,MAAM,KAAK,kBAAuD,CAAA;AAEzE,iBAAS,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CA2mBtD"}
|