@linxin666/dsh-remote-web-ui 0.3.23 → 0.4.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/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 +396 -132
- package/lib/client.js.map +1 -1
- package/lib/index.js +604 -262
- 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 +56 -32
- 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 +143 -28
- 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 +104 -0
- package/lib/types/client/plugin-card-seat.d.ts.map +1 -0
- package/lib/types/client/plugin-card-seat.js +143 -0
- 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/console-output.d.ts +41 -0
- package/lib/types/console-output.d.ts.map +1 -0
- package/lib/types/console-output.js +89 -0
- 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 -33
- 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 +70 -40
- package/src/client/locales.ts +6 -8
- package/src/client/mobile-adapt.ts +136 -25
- package/src/client/pair-api.ts +47 -7
- package/src/client/plugin-card-seat.ts +192 -0
- 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/console-output.ts +97 -0
- 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 -29
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Family plugin-card seat.
|
|
3
|
+
*
|
|
4
|
+
* A family plugin contributes its settings card to whichever plugin-card seat
|
|
5
|
+
* the running host actually renders:
|
|
6
|
+
*
|
|
7
|
+
* - `web-ui.plugin.item` — the list seat declared by the dsh-web-settings
|
|
8
|
+
* group section (this family's own first-level "Web UI plugins" section);
|
|
9
|
+
* - `plugins.bundle.config` — the official keyed seat of the harness's plugin
|
|
10
|
+
* manager page, keyed by the bundle's package name and rendered on that
|
|
11
|
+
* bundle's page. alpha.2 removed the `settings.plugin.item` keyed seat of the
|
|
12
|
+
* `ui-settings-plugins` tab that this helper used before, so a card keyed by
|
|
13
|
+
* its settings namespace has no seat to land in any more.
|
|
14
|
+
*
|
|
15
|
+
* SEAT SELECTION IS NOT A DECLARATION PROBE. The official plugin surface
|
|
16
|
+
* belongs to the harness bundle and declares its seats before any external
|
|
17
|
+
* plugin's `apply()` runs, so "is the official seat declared?" answers yes even
|
|
18
|
+
* in the deployment whose whole point is the family group. Choosing on that
|
|
19
|
+
* probe sends every family card to the official page and leaves the group's own
|
|
20
|
+
* section permanently empty — the family of reports where the section renders
|
|
21
|
+
* its heading and zero cards.
|
|
22
|
+
*
|
|
23
|
+
* The signal that actually distinguishes the two deployments is whether
|
|
24
|
+
* dsh-web-settings is loaded: it is the package that owns the group section and
|
|
25
|
+
* it publishes the `webUiSettings` service during `apply()`, which every
|
|
26
|
+
* family plugin already reads for its settings form. Group loaded -> the family
|
|
27
|
+
* seat; group absent -> the official seat.
|
|
28
|
+
*
|
|
29
|
+
* The decision is re-evaluated on every `slots/changed` because the group may
|
|
30
|
+
* apply after this plugin (the family aggregate orders it first, a profile that
|
|
31
|
+
* installs the group separately need not): the initial contribution goes to the
|
|
32
|
+
* official seat, then moves to the family seat the moment the group's section
|
|
33
|
+
* registers. The entry is disposed before the replacement is registered, so a
|
|
34
|
+
* card is never in two seats at once.
|
|
35
|
+
*
|
|
36
|
+
* The shared tree has no client-SDK dependency, so this module reads its
|
|
37
|
+
* context through the structural shape below; callers pass the plugin's own
|
|
38
|
+
* `ctx`.
|
|
39
|
+
*/
|
|
40
|
+
/** The family list seat key. */
|
|
41
|
+
export declare const FAMILY_PLUGIN_CARD_SEAT = "web-ui.plugin.item";
|
|
42
|
+
/** The official keyed plugin-card seat key (the alpha.2 bundle-configuration seat). */
|
|
43
|
+
export declare const OFFICIAL_PLUGIN_CARD_SEAT = "plugins.bundle.config";
|
|
44
|
+
/** The service dsh-web-settings publishes while it is loaded. */
|
|
45
|
+
export declare const FAMILY_GROUP_SERVICE = "webUiSettings";
|
|
46
|
+
/** The slot-registry member this helper uses (structurally satisfied by ctx.slots). */
|
|
47
|
+
export interface PluginCardSlots {
|
|
48
|
+
/** Contribution of one card entry. */
|
|
49
|
+
register(options: never, component: never): unknown;
|
|
50
|
+
}
|
|
51
|
+
/** The slice of the client context a card contribution needs. */
|
|
52
|
+
export interface PluginCardContext {
|
|
53
|
+
slots: PluginCardSlots;
|
|
54
|
+
/** Service lookup; absent on a context double that only models the slots. */
|
|
55
|
+
get?(name: string): unknown;
|
|
56
|
+
/** Event subscription seat; absent on an event-less test double. */
|
|
57
|
+
on?(event: string, listener: (...args: never[]) => void): unknown;
|
|
58
|
+
}
|
|
59
|
+
/** Owner share of a family list-seat card (that seat supplies nothing). */
|
|
60
|
+
export interface SettingsPluginItemOwnerProps {
|
|
61
|
+
/** Marker field: card owner props are intentionally empty. */
|
|
62
|
+
children?: never;
|
|
63
|
+
}
|
|
64
|
+
/** One family plugin's card contribution. */
|
|
65
|
+
export interface PluginCardSeat {
|
|
66
|
+
/**
|
|
67
|
+
* Npm package name of the bundle this card configures; the official keyed
|
|
68
|
+
* seat dispatches on it. Every family package ships its own bundle patch, so
|
|
69
|
+
* a standalone install lists that package as the bundle whose page renders
|
|
70
|
+
* this card.
|
|
71
|
+
*/
|
|
72
|
+
bundle: string;
|
|
73
|
+
/** Family list-seat entry id. */
|
|
74
|
+
id: string;
|
|
75
|
+
/** Family list-seat sort order. */
|
|
76
|
+
order?: number;
|
|
77
|
+
/** Family list-seat display label; the official keyed seat carries none. */
|
|
78
|
+
label?: () => string;
|
|
79
|
+
/** Locale namespace the card renders with. */
|
|
80
|
+
locale: string;
|
|
81
|
+
/** Business-face factory of the registration. */
|
|
82
|
+
inject?: () => object;
|
|
83
|
+
/**
|
|
84
|
+
* The card component. Its props type is the seat's composed shape, which is
|
|
85
|
+
* chosen at runtime, so the helper takes it erased; the registrant keeps its
|
|
86
|
+
* own precise typing at the call site.
|
|
87
|
+
*/
|
|
88
|
+
component: unknown;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Whether the family group (dsh-web-settings) is loaded in this page. The
|
|
92
|
+
* service is the group package's own contract, so the probe cannot be fooled
|
|
93
|
+
* by a harness release that starts declaring the official seat differently.
|
|
94
|
+
*/
|
|
95
|
+
export declare function familyGroupLoaded(ctx: PluginCardContext): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Contribute one family plugin card to the seat this host renders, following
|
|
98
|
+
* the group if it loads later. The entry is disposed and re-registered on a
|
|
99
|
+
* seat change, never duplicated.
|
|
100
|
+
* @param ctx - client context (its slot registry decides the seat).
|
|
101
|
+
* @param seat - the card contribution.
|
|
102
|
+
*/
|
|
103
|
+
export declare function installPluginCard(ctx: PluginCardContext, seat: PluginCardSeat): void;
|
|
104
|
+
//# sourceMappingURL=plugin-card-seat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-card-seat.d.ts","sourceRoot":"","sources":["../../../src/client/plugin-card-seat.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,gCAAgC;AAChC,eAAO,MAAM,uBAAuB,uBAAuB,CAAA;AAE3D,uFAAuF;AACvF,eAAO,MAAM,yBAAyB,0BAA0B,CAAA;AAEhE,iEAAiE;AACjE,eAAO,MAAM,oBAAoB,kBAAkB,CAAA;AAEnD,uFAAuF;AACvF,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,GAAG,OAAO,CAAA;CACpD;AAED,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,eAAe,CAAA;IACtB,6EAA6E;IAC7E,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B,oEAAoE;IACpE,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,IAAI,GAAG,OAAO,CAAA;CAClE;AAED,2EAA2E;AAC3E,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED,6CAA6C;AAC7C,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAA;IACd,iCAAiC;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;IACpB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,MAAM,CAAA;IACrB;;;;OAIG;IACH,SAAS,EAAE,OAAO,CAAA;CACnB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAUjE;AAWD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI,CA4DpF"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// Generated by scripts/sync-shared.mjs from shared/client/settings/plugin-card-seat.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
|
|
2
|
+
/**
|
|
3
|
+
* Family plugin-card seat.
|
|
4
|
+
*
|
|
5
|
+
* A family plugin contributes its settings card to whichever plugin-card seat
|
|
6
|
+
* the running host actually renders:
|
|
7
|
+
*
|
|
8
|
+
* - `web-ui.plugin.item` — the list seat declared by the dsh-web-settings
|
|
9
|
+
* group section (this family's own first-level "Web UI plugins" section);
|
|
10
|
+
* - `plugins.bundle.config` — the official keyed seat of the harness's plugin
|
|
11
|
+
* manager page, keyed by the bundle's package name and rendered on that
|
|
12
|
+
* bundle's page. alpha.2 removed the `settings.plugin.item` keyed seat of the
|
|
13
|
+
* `ui-settings-plugins` tab that this helper used before, so a card keyed by
|
|
14
|
+
* its settings namespace has no seat to land in any more.
|
|
15
|
+
*
|
|
16
|
+
* SEAT SELECTION IS NOT A DECLARATION PROBE. The official plugin surface
|
|
17
|
+
* belongs to the harness bundle and declares its seats before any external
|
|
18
|
+
* plugin's `apply()` runs, so "is the official seat declared?" answers yes even
|
|
19
|
+
* in the deployment whose whole point is the family group. Choosing on that
|
|
20
|
+
* probe sends every family card to the official page and leaves the group's own
|
|
21
|
+
* section permanently empty — the family of reports where the section renders
|
|
22
|
+
* its heading and zero cards.
|
|
23
|
+
*
|
|
24
|
+
* The signal that actually distinguishes the two deployments is whether
|
|
25
|
+
* dsh-web-settings is loaded: it is the package that owns the group section and
|
|
26
|
+
* it publishes the `webUiSettings` service during `apply()`, which every
|
|
27
|
+
* family plugin already reads for its settings form. Group loaded -> the family
|
|
28
|
+
* seat; group absent -> the official seat.
|
|
29
|
+
*
|
|
30
|
+
* The decision is re-evaluated on every `slots/changed` because the group may
|
|
31
|
+
* apply after this plugin (the family aggregate orders it first, a profile that
|
|
32
|
+
* installs the group separately need not): the initial contribution goes to the
|
|
33
|
+
* official seat, then moves to the family seat the moment the group's section
|
|
34
|
+
* registers. The entry is disposed before the replacement is registered, so a
|
|
35
|
+
* card is never in two seats at once.
|
|
36
|
+
*
|
|
37
|
+
* The shared tree has no client-SDK dependency, so this module reads its
|
|
38
|
+
* context through the structural shape below; callers pass the plugin's own
|
|
39
|
+
* `ctx`.
|
|
40
|
+
*/
|
|
41
|
+
/** The family list seat key. */
|
|
42
|
+
export const FAMILY_PLUGIN_CARD_SEAT = 'web-ui.plugin.item';
|
|
43
|
+
/** The official keyed plugin-card seat key (the alpha.2 bundle-configuration seat). */
|
|
44
|
+
export const OFFICIAL_PLUGIN_CARD_SEAT = 'plugins.bundle.config';
|
|
45
|
+
/** The service dsh-web-settings publishes while it is loaded. */
|
|
46
|
+
export const FAMILY_GROUP_SERVICE = 'webUiSettings';
|
|
47
|
+
/**
|
|
48
|
+
* Whether the family group (dsh-web-settings) is loaded in this page. The
|
|
49
|
+
* service is the group package's own contract, so the probe cannot be fooled
|
|
50
|
+
* by a harness release that starts declaring the official seat differently.
|
|
51
|
+
*/
|
|
52
|
+
export function familyGroupLoaded(ctx) {
|
|
53
|
+
const get = ctx.get;
|
|
54
|
+
if (typeof get !== 'function')
|
|
55
|
+
return false;
|
|
56
|
+
try {
|
|
57
|
+
return get.call(ctx, FAMILY_GROUP_SERVICE) !== undefined;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// A context that refuses the lookup: treat the group as absent, which
|
|
61
|
+
// falls back to the official seat the harness declares.
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Report a refused registration instead of leaving the user with no card. */
|
|
66
|
+
function warnRefusedSeat(seat, error) {
|
|
67
|
+
try {
|
|
68
|
+
console.warn(`[dsh-web] plugin card registration into "${seat}" was refused; the card will not render`, error);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Best-effort console write; a failed warning must not break the plugin.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Contribute one family plugin card to the seat this host renders, following
|
|
76
|
+
* the group if it loads later. The entry is disposed and re-registered on a
|
|
77
|
+
* seat change, never duplicated.
|
|
78
|
+
* @param ctx - client context (its slot registry decides the seat).
|
|
79
|
+
* @param seat - the card contribution.
|
|
80
|
+
*/
|
|
81
|
+
export function installPluginCard(ctx, seat) {
|
|
82
|
+
const slots = ctx.slots;
|
|
83
|
+
const component = seat.component;
|
|
84
|
+
const inject = seat.inject;
|
|
85
|
+
let dispose;
|
|
86
|
+
let current;
|
|
87
|
+
/**
|
|
88
|
+
* Re-entrancy latch. The registry emits a change event synchronously from
|
|
89
|
+
* inside both `register` and the previous entry's disposer, so an unguarded
|
|
90
|
+
* reconcile would re-enter itself mid-move and register the card twice into
|
|
91
|
+
* the seat it is leaving ("already has an entry for key ...").
|
|
92
|
+
*/
|
|
93
|
+
let reconciling = false;
|
|
94
|
+
/** Reconcile the contribution with the currently live seat (no-op when unchanged). */
|
|
95
|
+
const reconcile = () => {
|
|
96
|
+
if (reconciling)
|
|
97
|
+
return;
|
|
98
|
+
const target = familyGroupLoaded(ctx) ? FAMILY_PLUGIN_CARD_SEAT : OFFICIAL_PLUGIN_CARD_SEAT;
|
|
99
|
+
if (current === target)
|
|
100
|
+
return;
|
|
101
|
+
reconciling = true;
|
|
102
|
+
const previous = dispose;
|
|
103
|
+
dispose = undefined;
|
|
104
|
+
current = undefined;
|
|
105
|
+
previous?.();
|
|
106
|
+
try {
|
|
107
|
+
dispose = slots.register((target === FAMILY_PLUGIN_CARD_SEAT
|
|
108
|
+
? {
|
|
109
|
+
name: FAMILY_PLUGIN_CARD_SEAT,
|
|
110
|
+
id: seat.id,
|
|
111
|
+
...(seat.order === undefined ? {} : { order: seat.order }),
|
|
112
|
+
...(seat.label === undefined ? {} : { label: seat.label }),
|
|
113
|
+
locale: seat.locale,
|
|
114
|
+
...(seat.inject === undefined ? {} : { inject }),
|
|
115
|
+
}
|
|
116
|
+
: {
|
|
117
|
+
name: OFFICIAL_PLUGIN_CARD_SEAT,
|
|
118
|
+
key: seat.bundle,
|
|
119
|
+
locale: seat.locale,
|
|
120
|
+
...(seat.inject === undefined ? {} : { inject }),
|
|
121
|
+
}), component);
|
|
122
|
+
current = target;
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
warnRefusedSeat(target, error);
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
reconciling = false;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
if (typeof ctx.on === 'function') {
|
|
132
|
+
try {
|
|
133
|
+
ctx.on('slots/changed', () => { reconcile(); });
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// An event seat that refuses the subscription leaves the initial
|
|
137
|
+
// decision in place; the card still renders in the seat chosen below.
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// The initial decision: the group, when it is already loaded (the aggregate
|
|
141
|
+
// order), otherwise the official seat the harness declares.
|
|
142
|
+
reconcile();
|
|
143
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-channel.d.ts","sourceRoot":"","sources":["../../../src/client/remote-channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;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,
|
|
1
|
+
{"version":3,"file":"remote-channel.d.ts","sourceRoot":"","sources":["../../../src/client/remote-channel.ts"],"names":[],"mappings":"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"}
|
|
@@ -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":"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"}
|
|
@@ -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.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console-output capture shared by the family's Host halves.
|
|
3
|
+
*
|
|
4
|
+
* A child process's stdout/stderr are raw bytes, and on Windows a console
|
|
5
|
+
* program writes the OEM code page (CP936/GBK on a zh-CN system), not UTF-8.
|
|
6
|
+
* Decoding each chunk on its own corrupts the text twice over: the
|
|
7
|
+
* encoding is guessed wrong, and a multi-byte character split across two reads
|
|
8
|
+
* decodes into two replacement characters. This capture accumulates raw bytes
|
|
9
|
+
* under a byte budget and decodes exactly once, at read time.
|
|
10
|
+
*/
|
|
11
|
+
/** Bounded byte accumulator for one child process's interleaved stdout/stderr. */
|
|
12
|
+
export interface OutputCapture {
|
|
13
|
+
/** Append one raw chunk (the bytes are copied into the budget). */
|
|
14
|
+
push(chunk: Uint8Array): void;
|
|
15
|
+
/**
|
|
16
|
+
* Decode everything captured so far. Idempotent, so a caller may read the
|
|
17
|
+
* tail both after a failure branch and at settlement.
|
|
18
|
+
*/
|
|
19
|
+
read(): string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Decode one console byte buffer. Strict UTF-8 wins whenever it is valid;
|
|
23
|
+
* otherwise the bytes are not UTF-8 at all and the console wrote its own code
|
|
24
|
+
* page, so the candidate that loses the least text is taken.
|
|
25
|
+
* @param bytes - the captured bytes.
|
|
26
|
+
* @returns the decoded text.
|
|
27
|
+
*/
|
|
28
|
+
export declare function decodeConsoleBytes(bytes: Uint8Array): string;
|
|
29
|
+
/**
|
|
30
|
+
* Create a capture bounded to a byte budget. Overflow drops from the FRONT
|
|
31
|
+
* (the tail is what carries a failure reason), and the cut never lands inside
|
|
32
|
+
* a character: leading UTF-8 continuation bytes are dropped with it, and a
|
|
33
|
+
* legacy code page's dangling lead byte — indistinguishable from a real byte
|
|
34
|
+
* without knowing the encoding until the whole buffer is decoded — is removed
|
|
35
|
+
* with the one replacement character it produced, so an over-long stream still
|
|
36
|
+
* reads cleanly at the seam.
|
|
37
|
+
* @param maxBytes - maximum retained bytes.
|
|
38
|
+
* @returns the capture.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createOutputCapture(maxBytes: number): OutputCapture;
|
|
41
|
+
//# sourceMappingURL=console-output.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"console-output.d.ts","sourceRoot":"","sources":["../../src/console-output.ts"],"names":[],"mappings":"AACA;;;;;;;;;GASG;AAEH,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;IAC7B;;;OAGG;IACH,IAAI,IAAI,MAAM,CAAA;CACf;AAWD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CA2B5D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,CAiBnE"}
|