@linxin666/dsh-remote-web-ui 0.3.6 → 0.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +77 -468
  3. package/README.zh.md +83 -113
  4. package/lib/client.js +993 -203
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +1136 -1466
  7. package/lib/types/client/FooterRemoteEntry.d.ts +6 -7
  8. package/lib/types/client/FooterRemoteEntry.d.ts.map +1 -1
  9. package/lib/types/client/FooterRemoteEntry.js +1 -1
  10. package/lib/types/client/RemoteEntry.d.ts +7 -4
  11. package/lib/types/client/RemoteEntry.d.ts.map +1 -1
  12. package/lib/types/client/RemoteEntry.js +7 -10
  13. package/lib/types/client/RemotePanel.d.ts +2 -2
  14. package/lib/types/client/RemotePanel.d.ts.map +1 -1
  15. package/lib/types/client/RemotePanel.js +2 -2
  16. package/lib/types/client/RemoteSettingsCard.d.ts +9 -5
  17. package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
  18. package/lib/types/client/RemoteSettingsCard.js +56 -3
  19. package/lib/types/client/deep-link.d.ts +10 -13
  20. package/lib/types/client/deep-link.d.ts.map +1 -1
  21. package/lib/types/client/deep-link.js +14 -74
  22. package/lib/types/client/index.d.ts +2 -6
  23. package/lib/types/client/index.d.ts.map +1 -1
  24. package/lib/types/client/index.js +79 -39
  25. package/lib/types/client/locales.d.ts +32 -12
  26. package/lib/types/client/locales.d.ts.map +1 -1
  27. package/lib/types/client/locales.js +42 -22
  28. package/lib/types/client/mobile-adapt.d.ts +49 -0
  29. package/lib/types/client/mobile-adapt.d.ts.map +1 -0
  30. package/lib/types/client/mobile-adapt.js +1002 -0
  31. package/lib/types/client/pair-api.d.ts +24 -6
  32. package/lib/types/client/pair-api.d.ts.map +1 -1
  33. package/lib/types/client/pair-api.js +9 -14
  34. package/lib/types/client/remote-channel.d.ts +3 -0
  35. package/lib/types/client/remote-channel.d.ts.map +1 -1
  36. package/lib/types/client/remote-channel.js +35 -3
  37. package/lib/types/client/settings-form.d.ts +35 -43
  38. package/lib/types/client/settings-form.d.ts.map +1 -1
  39. package/lib/types/client/settings-form.js +60 -53
  40. package/lib/types/firewall.d.ts +75 -0
  41. package/lib/types/firewall.d.ts.map +1 -0
  42. package/lib/types/firewall.js +184 -0
  43. package/lib/types/gate.d.ts +11 -5
  44. package/lib/types/gate.d.ts.map +1 -1
  45. package/lib/types/gate.js +11 -5
  46. package/lib/types/index.d.ts +39 -10
  47. package/lib/types/index.d.ts.map +1 -1
  48. package/lib/types/index.js +206 -58
  49. package/lib/types/inner-auth.d.ts +32 -0
  50. package/lib/types/inner-auth.d.ts.map +1 -0
  51. package/lib/types/inner-auth.js +72 -0
  52. package/lib/types/lan-bind-plan.d.ts +55 -0
  53. package/lib/types/lan-bind-plan.d.ts.map +1 -0
  54. package/lib/types/lan-bind-plan.js +60 -0
  55. package/lib/types/lan-bind.d.ts +69 -0
  56. package/lib/types/lan-bind.d.ts.map +1 -0
  57. package/lib/types/lan-bind.js +128 -0
  58. package/lib/types/loopback-proxy.d.ts +9 -2
  59. package/lib/types/loopback-proxy.d.ts.map +1 -1
  60. package/lib/types/loopback-proxy.js +24 -2
  61. package/lib/types/pairing.d.ts +15 -3
  62. package/lib/types/pairing.d.ts.map +1 -1
  63. package/lib/types/pairing.js +20 -6
  64. package/lib/types/remote-api.d.ts +30 -13
  65. package/lib/types/remote-api.d.ts.map +1 -1
  66. package/lib/types/remote-api.js +55 -42
  67. package/lib/types/remote-channel-boot.d.ts +21 -2
  68. package/lib/types/remote-channel-boot.d.ts.map +1 -1
  69. package/lib/types/remote-channel-boot.js +65 -3
  70. package/lib/types/remote-channel-rules.d.ts +8 -0
  71. package/lib/types/remote-channel-rules.d.ts.map +1 -1
  72. package/lib/types/remote-channel-rules.js +9 -2
  73. package/lib/types/remote-methods.d.ts +45 -11
  74. package/lib/types/remote-methods.d.ts.map +1 -1
  75. package/lib/types/remote-methods.js +56 -27
  76. package/lib/types/remote-presence-pet.d.ts +51 -0
  77. package/lib/types/remote-presence-pet.d.ts.map +1 -0
  78. package/lib/types/remote-presence-pet.js +101 -0
  79. package/lib/types/routes.d.ts +76 -4
  80. package/lib/types/routes.d.ts.map +1 -1
  81. package/lib/types/routes.js +336 -19
  82. package/lib/types/tunnel.d.ts.map +1 -1
  83. package/lib/types/tunnel.js +7 -1
  84. package/package.json +20 -18
  85. package/src/client/FooterRemoteEntry.tsx +9 -10
  86. package/src/client/RemoteEntry.tsx +13 -14
  87. package/src/client/RemotePanel.tsx +5 -16
  88. package/src/client/RemoteSettingsCard.tsx +72 -13
  89. package/src/client/deep-link.ts +14 -75
  90. package/src/client/index.ts +82 -39
  91. package/src/client/locales.ts +42 -22
  92. package/src/client/mobile-adapt.ts +954 -0
  93. package/src/client/pair-api.ts +26 -15
  94. package/src/client/remote-channel.ts +31 -3
  95. package/src/client/settings-form.ts +78 -92
  96. package/src/dsh-home.ts +1 -1
  97. package/src/firewall.ts +230 -0
  98. package/src/gate.ts +11 -5
  99. package/src/index.ts +235 -65
  100. package/src/inner-auth.ts +85 -0
  101. package/src/lan-bind-plan.ts +81 -0
  102. package/src/lan-bind.ts +145 -0
  103. package/src/loopback-proxy.ts +34 -0
  104. package/src/pairing.persist.test.ts +1 -1
  105. package/src/pairing.ts +20 -6
  106. package/src/remote-api.ts +67 -45
  107. package/src/remote-channel-boot.ts +70 -3
  108. package/src/remote-channel-rules.ts +17 -2
  109. package/src/remote-methods.ts +58 -27
  110. package/src/remote-presence-pet.ts +129 -0
  111. package/src/routes.ts +358 -19
  112. package/src/tunnel.ts +7 -1
  113. package/assets/apple-touch-icon.png +0 -0
  114. package/assets/mobile-icon-192.png +0 -0
  115. package/assets/mobile-icon-512.png +0 -0
  116. package/assets/mobile-service-worker.js +0 -63
  117. package/lib/mobile.js +0 -17658
  118. package/lib/mobile.js.map +0 -1
  119. package/lib/types/mobile/PairRequiredView.d.ts +0 -7
  120. package/lib/types/mobile/PairRequiredView.d.ts.map +0 -1
  121. package/lib/types/mobile/PairRequiredView.js +0 -28
  122. package/lib/types/mobile/PairRequiredView.test.d.ts +0 -2
  123. package/lib/types/mobile/PairRequiredView.test.d.ts.map +0 -1
  124. package/lib/types/mobile/PairRequiredView.test.js +0 -24
  125. package/lib/types/mobile/api.d.ts +0 -145
  126. package/lib/types/mobile/api.d.ts.map +0 -1
  127. package/lib/types/mobile/api.js +0 -103
  128. package/lib/types/mobile/index.d.ts +0 -10
  129. package/lib/types/mobile/index.d.ts.map +0 -1
  130. package/lib/types/mobile/index.js +0 -38
  131. package/lib/types/mobile/markdown.d.ts +0 -34
  132. package/lib/types/mobile/markdown.d.ts.map +0 -1
  133. package/lib/types/mobile/markdown.js +0 -285
  134. package/lib/types/mobile/markdown.test.d.ts +0 -2
  135. package/lib/types/mobile/markdown.test.d.ts.map +0 -1
  136. package/lib/types/mobile/markdown.test.js +0 -79
  137. package/lib/types/mobile/messages.d.ts +0 -149
  138. package/lib/types/mobile/messages.d.ts.map +0 -1
  139. package/lib/types/mobile/messages.js +0 -561
  140. package/lib/types/mobile/messages.test.d.ts +0 -2
  141. package/lib/types/mobile/messages.test.d.ts.map +0 -1
  142. package/lib/types/mobile/messages.test.js +0 -356
  143. package/lib/types/mobile/mobile-styles.d.ts +0 -6
  144. package/lib/types/mobile/mobile-styles.d.ts.map +0 -1
  145. package/lib/types/mobile/mobile-styles.js +0 -1438
  146. package/lib/types/mobile/mobile-theme.d.ts +0 -22
  147. package/lib/types/mobile/mobile-theme.d.ts.map +0 -1
  148. package/lib/types/mobile/mobile-theme.js +0 -70
  149. package/lib/types/mobile/mobile-theme.test.d.ts +0 -2
  150. package/lib/types/mobile/mobile-theme.test.d.ts.map +0 -1
  151. package/lib/types/mobile/mobile-theme.test.js +0 -71
  152. package/lib/types/mobile/mux.d.ts +0 -128
  153. package/lib/types/mobile/mux.d.ts.map +0 -1
  154. package/lib/types/mobile/mux.js +0 -305
  155. package/lib/types/mobile/mux.test.d.ts +0 -2
  156. package/lib/types/mobile/mux.test.d.ts.map +0 -1
  157. package/lib/types/mobile/mux.test.js +0 -267
  158. package/lib/types/mobile/pairing.d.ts +0 -31
  159. package/lib/types/mobile/pairing.d.ts.map +0 -1
  160. package/lib/types/mobile/pairing.js +0 -62
  161. package/lib/types/mobile/rpc.d.ts +0 -38
  162. package/lib/types/mobile/rpc.d.ts.map +0 -1
  163. package/lib/types/mobile/rpc.js +0 -83
  164. package/lib/types/mobile/theme-toggle.d.ts +0 -12
  165. package/lib/types/mobile/theme-toggle.d.ts.map +0 -1
  166. package/lib/types/mobile/theme-toggle.js +0 -26
  167. package/lib/types/mobile/views/App.d.ts +0 -57
  168. package/lib/types/mobile/views/App.d.ts.map +0 -1
  169. package/lib/types/mobile/views/App.js +0 -150
  170. package/lib/types/mobile/views/App.test.d.ts +0 -2
  171. package/lib/types/mobile/views/App.test.d.ts.map +0 -1
  172. package/lib/types/mobile/views/App.test.js +0 -52
  173. package/lib/types/mobile/views/ChatView.d.ts +0 -63
  174. package/lib/types/mobile/views/ChatView.d.ts.map +0 -1
  175. package/lib/types/mobile/views/ChatView.js +0 -821
  176. package/lib/types/mobile/views/ChatView.stream.test.d.ts +0 -2
  177. package/lib/types/mobile/views/ChatView.stream.test.d.ts.map +0 -1
  178. package/lib/types/mobile/views/ChatView.stream.test.js +0 -236
  179. package/lib/types/mobile/views/ChatView.test.d.ts +0 -2
  180. package/lib/types/mobile/views/ChatView.test.d.ts.map +0 -1
  181. package/lib/types/mobile/views/ChatView.test.js +0 -718
  182. package/lib/types/mobile/views/SessionListView.d.ts +0 -27
  183. package/lib/types/mobile/views/SessionListView.d.ts.map +0 -1
  184. package/lib/types/mobile/views/SessionListView.js +0 -139
  185. package/lib/types/mobile/views/SessionListView.test.d.ts +0 -2
  186. package/lib/types/mobile/views/SessionListView.test.d.ts.map +0 -1
  187. package/lib/types/mobile/views/SessionListView.test.js +0 -167
  188. package/lib/types/mobile/views/WorkspaceView.d.ts +0 -21
  189. package/lib/types/mobile/views/WorkspaceView.d.ts.map +0 -1
  190. package/lib/types/mobile/views/WorkspaceView.js +0 -93
  191. package/lib/types/mobile/views/WorkspaceView.test.d.ts +0 -2
  192. package/lib/types/mobile/views/WorkspaceView.test.d.ts.map +0 -1
  193. package/lib/types/mobile/views/WorkspaceView.test.js +0 -183
  194. package/lib/types/mobile-api.d.ts +0 -65
  195. package/lib/types/mobile-api.d.ts.map +0 -1
  196. package/lib/types/mobile-api.js +0 -404
  197. package/lib/types/mobile-pending.d.ts +0 -38
  198. package/lib/types/mobile-pending.d.ts.map +0 -1
  199. package/lib/types/mobile-pending.js +0 -59
  200. package/lib/types/mobile-pwa.d.ts +0 -6
  201. package/lib/types/mobile-pwa.d.ts.map +0 -1
  202. package/lib/types/mobile-pwa.js +0 -17
  203. package/lib/types/mobile-routes.d.ts +0 -13
  204. package/lib/types/mobile-routes.d.ts.map +0 -1
  205. package/lib/types/mobile-routes.js +0 -217
  206. package/lib/types/paired-model-catalog.d.ts +0 -17
  207. package/lib/types/paired-model-catalog.d.ts.map +0 -1
  208. package/lib/types/paired-model-catalog.js +0 -311
  209. package/src/mobile/PairRequiredView.test.tsx +0 -28
  210. package/src/mobile/PairRequiredView.tsx +0 -58
  211. package/src/mobile/api.ts +0 -229
  212. package/src/mobile/index.tsx +0 -41
  213. package/src/mobile/markdown.test.ts +0 -91
  214. package/src/mobile/markdown.ts +0 -291
  215. package/src/mobile/messages.test.ts +0 -402
  216. package/src/mobile/messages.ts +0 -685
  217. package/src/mobile/mobile-styles.ts +0 -1439
  218. package/src/mobile/mobile-theme.test.ts +0 -84
  219. package/src/mobile/mobile-theme.ts +0 -76
  220. package/src/mobile/mux.test.ts +0 -314
  221. package/src/mobile/mux.ts +0 -336
  222. package/src/mobile/pairing.ts +0 -77
  223. package/src/mobile/rpc.ts +0 -90
  224. package/src/mobile/theme-toggle.tsx +0 -47
  225. package/src/mobile/views/App.test.tsx +0 -62
  226. package/src/mobile/views/App.tsx +0 -213
  227. package/src/mobile/views/ChatView.stream.test.tsx +0 -268
  228. package/src/mobile/views/ChatView.test.tsx +0 -816
  229. package/src/mobile/views/ChatView.tsx +0 -1342
  230. package/src/mobile/views/SessionListView.test.tsx +0 -207
  231. package/src/mobile/views/SessionListView.tsx +0 -244
  232. package/src/mobile/views/WorkspaceView.test.tsx +0 -227
  233. package/src/mobile/views/WorkspaceView.tsx +0 -245
  234. package/src/mobile-api.ts +0 -418
  235. package/src/mobile-pending.ts +0 -88
  236. package/src/mobile-pwa.ts +0 -19
  237. package/src/mobile-routes.ts +0 -231
  238. package/src/paired-model-catalog.ts +0 -272
@@ -82,7 +82,6 @@ export interface DeviceFrame {
82
82
  /**
83
83
  * Mint a fresh pairing token (one active token at a time — this invalidates
84
84
  * any previous link).
85
- * @param workspaceId - optional current workspace to deep-link the phone into.
86
85
  * @param address - optional LAN IP literal the QR must be built from (the
87
86
  * default is the first interface); unknown literals refuse with
88
87
  * 'unknown-address'.
@@ -90,7 +89,7 @@ export interface DeviceFrame {
90
89
  * 0.0.0.0), or the forbidden refusal (the loopback-only fence rejected this
91
90
  * origin — the panel is a desktop control endpoint).
92
91
  */
93
- export declare function issuePair(workspaceId?: string, address?: string): Promise<IssueResponse>;
92
+ export declare function issuePair(address?: string): Promise<IssueResponse>;
94
93
  /**
95
94
  * Accept a pairing token (the phone's first open of the QR link). Success
96
95
  * sets the device cookie; the page then reloads to boot with it.
@@ -109,10 +108,9 @@ export declare function stopPair(): Promise<void>;
109
108
  export declare function revokePair(deviceId: string): Promise<void>;
110
109
  /** Presence heartbeat from a paired phone (unpaired heartbeats 401 harmlessly). */
111
110
  export declare function sendHeartbeat(): Promise<void>;
112
- /** Whether the current page URL carries a pairing token / workspace target. */
111
+ /** Whether the current page URL carries a pairing token. */
113
112
  export declare function readPairParams(search: string): {
114
113
  pair?: string;
115
- workspace?: string;
116
114
  };
117
115
  /**
118
116
  * Strip one query parameter from the current URL without reloading.
@@ -120,8 +118,28 @@ export declare function readPairParams(search: string): {
120
118
  * @returns the new search string ('' when empty).
121
119
  */
122
120
  export declare function stripParam(name: string): string;
123
- /** Convert an issued `/m/` link into the desktop pairing form. */
124
- export declare function desktopPairUrl(mobileUrl: string): string;
121
+ /** LAN-bind status frame from the loopback-only card endpoint. */
122
+ export interface LanBindFrame {
123
+ profile: string;
124
+ /** The config toggle: true/false once flipped, null while untouched. */
125
+ setting: boolean | null;
126
+ /** The bind default the managed patch block currently pins. */
127
+ blockHost: string | null;
128
+ /** The live bind of the running web server. */
129
+ bindHost: string;
130
+ port: number;
131
+ lanUrls: string[];
132
+ firewall: {
133
+ ok: boolean;
134
+ managed: boolean;
135
+ note?: string;
136
+ };
137
+ platform: string;
138
+ /** true when the running bind has not caught up with the toggle yet. */
139
+ pendingRestart?: boolean;
140
+ }
141
+ /** Read the LAN-bind facts (loopback-only endpoint). */
142
+ export declare function readLanBindStatus(): Promise<LanBindFrame>;
125
143
  /** Human-readable expiry clock, e.g. "10:35". */
126
144
  export declare function formatClock(epochMs: number): string;
127
145
  /** Calendar + clock for last-seen timestamps, e.g. "2026-08-19 10:35". */
@@ -1 +1 @@
1
- {"version":3,"file":"pair-api.d.ts","sourceRoot":"","sources":["../../../src/client/pair-api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pair-api.d.ts","sourceRoot":"","sources":["../../../src/client/pair-api.ts"],"names":[],"mappings":"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"}
@@ -17,7 +17,6 @@ export async function readPairGatePolicy() {
17
17
  /**
18
18
  * Mint a fresh pairing token (one active token at a time — this invalidates
19
19
  * any previous link).
20
- * @param workspaceId - optional current workspace to deep-link the phone into.
21
20
  * @param address - optional LAN IP literal the QR must be built from (the
22
21
  * default is the first interface); unknown literals refuse with
23
22
  * 'unknown-address'.
@@ -25,12 +24,11 @@ export async function readPairGatePolicy() {
25
24
  * 0.0.0.0), or the forbidden refusal (the loopback-only fence rejected this
26
25
  * origin — the panel is a desktop control endpoint).
27
26
  */
28
- export async function issuePair(workspaceId, address) {
27
+ export async function issuePair(address) {
29
28
  const response = await fetch('/api/pair/issue', {
30
29
  method: 'POST',
31
30
  headers: { 'content-type': 'application/json' },
32
31
  body: JSON.stringify({
33
- ...(workspaceId !== undefined ? { workspaceId } : {}),
34
32
  ...(address !== undefined ? { address } : {}),
35
33
  }),
36
34
  });
@@ -90,15 +88,11 @@ export async function revokePair(deviceId) {
90
88
  export async function sendHeartbeat() {
91
89
  await fetch('/api/pair/heartbeat', { method: 'POST' });
92
90
  }
93
- /** Whether the current page URL carries a pairing token / workspace target. */
91
+ /** Whether the current page URL carries a pairing token. */
94
92
  export function readPairParams(search) {
95
93
  const params = new URLSearchParams(search);
96
94
  const pair = params.get('pair');
97
- const workspace = params.get('workspace');
98
- return {
99
- ...(pair !== null && pair !== '' ? { pair } : {}),
100
- ...(workspace !== null && workspace !== '' ? { workspace } : {}),
101
- };
95
+ return pair !== null && pair !== '' ? { pair } : {};
102
96
  }
103
97
  /**
104
98
  * Strip one query parameter from the current URL without reloading.
@@ -110,11 +104,12 @@ export function stripParam(name) {
110
104
  url.searchParams.delete(name);
111
105
  return url.search;
112
106
  }
113
- /** Convert an issued `/m/` link into the desktop pairing form. */
114
- export function desktopPairUrl(mobileUrl) {
115
- const url = new URL(mobileUrl);
116
- url.pathname = '/';
117
- return url.href;
107
+ /** Read the LAN-bind facts (loopback-only endpoint). */
108
+ export async function readLanBindStatus() {
109
+ const response = await fetch('/api/pair/lan-bind');
110
+ if (!response.ok)
111
+ throw new Error(`remote-web-ui: lan-bind status failed with ${String(response.status)}`);
112
+ return await response.json();
118
113
  }
119
114
  /** Human-readable expiry clock, e.g. "10:35". */
120
115
  export function formatClock(epochMs) {
@@ -78,6 +78,9 @@ export interface ChannelWindow {
78
78
  origin: string;
79
79
  href: string;
80
80
  };
81
+ sessionStorage?: {
82
+ getItem(key: string): string | null;
83
+ };
81
84
  }
82
85
  /** Options for {@link installRemoteChannel}. */
83
86
  export interface RemoteChannelOptions {
@@ -1 +1 @@
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"}
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;IAC1C,cAAc,CAAC,EAAE;QAAE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;KAAE,CAAA;CACzD;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,CAmG1G;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"}
@@ -178,6 +178,38 @@ export function installRemoteChannel(window, options = {}) {
178
178
  const OriginalEventSource = window.EventSource;
179
179
  const sameOrigin = (url) => url.origin === window.location.origin;
180
180
  const rewrite = (raw) => rewriteRawUrl(raw, window.location.href, window.location.origin);
181
+ // The cookieless device credential (set by the /pair-app capture script):
182
+ // attaches as a header on gated fetches and as the device query on WS and
183
+ // EventSource handshakes, mirroring the boot patch exactly.
184
+ const device = (() => {
185
+ try {
186
+ return window.sessionStorage?.getItem(RULES.deviceKey) ?? null;
187
+ }
188
+ catch {
189
+ return null;
190
+ }
191
+ })();
192
+ const attach = (init) => {
193
+ if (device === null)
194
+ return init;
195
+ const headers = init?.headers;
196
+ if (typeof Headers !== 'undefined' && headers instanceof Headers) {
197
+ try {
198
+ headers.set(RULES.deviceHeader, device);
199
+ }
200
+ catch { /* ignore */ }
201
+ return init;
202
+ }
203
+ if (typeof headers === 'object' && headers !== null) {
204
+ return { ...init, headers: { ...headers, [RULES.deviceHeader]: device } };
205
+ }
206
+ return init;
207
+ };
208
+ const withDeviceQuery = (url) => {
209
+ if (device !== null)
210
+ url.searchParams.set(RULES.deviceQuery, device);
211
+ return url;
212
+ };
181
213
  const patchedFetch = (input, init) => {
182
214
  const url = new URL(typeof input === 'string' || input instanceof URL ? input.toString() : input.url, window.location.href);
183
215
  if (sameOrigin(url) && shouldRewriteFetchPath(url.pathname)) {
@@ -186,7 +218,7 @@ export function installRemoteChannel(window, options = {}) {
186
218
  const next = typeof input === 'string' || input instanceof URL
187
219
  ? rewritten.toString()
188
220
  : new Request(rewritten, input);
189
- return Promise.resolve(originalFetch.call(window, next, init)).then(async (response) => {
221
+ return Promise.resolve(originalFetch.call(window, next, attach(init))).then(async (response) => {
190
222
  if (await isUnpairedDenied(response.clone()))
191
223
  options.onUnpaired?.();
192
224
  else
@@ -203,7 +235,7 @@ export function installRemoteChannel(window, options = {}) {
203
235
  if (wsOrigin !== '' && wsOrigin === window.location.origin && shouldRewriteWsPath(parsed.pathname)) {
204
236
  const rewritten = new URL(parsed);
205
237
  rewritten.pathname = rewritePath(parsed.pathname);
206
- super(rewritten, protocols);
238
+ super(withDeviceQuery(rewritten), protocols);
207
239
  return;
208
240
  }
209
241
  super(url, protocols);
@@ -223,7 +255,7 @@ export function installRemoteChannel(window, options = {}) {
223
255
  if (sameOrigin(parsed) && shouldRewriteFetchPath(parsed.pathname)) {
224
256
  const rewritten = new URL(parsed);
225
257
  rewritten.pathname = rewritePath(parsed.pathname);
226
- super(rewritten, eventSourceInitDict);
258
+ super(withDeviceQuery(rewritten), eventSourceInitDict);
227
259
  return;
228
260
  }
229
261
  super(url, eventSourceInitDict);
@@ -6,7 +6,8 @@
6
6
  * each plugin's client bundle; mirrors the official ui-plugin-config
7
7
  * card-store pattern.
8
8
  */
9
- import type { SettingsScope, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
9
+ import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
10
+ import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
10
11
  /** The write one field's staged text performs when the card is saved. */
11
12
  export type FieldWrite = {
12
13
  kind: 'set';
@@ -21,9 +22,11 @@ export interface FieldSpec {
21
22
  /**
22
23
  * Whether the Host treats this field as a secret and redacts its value from
23
24
  * the read-back (role('secret') in the section schema). Redacted secrets are
24
- * never compared against the draft on save; the field lands when the scope
25
- * reports the write succeeded (its secret-set marker under the bridge), so
26
- * a successful secret save is not misreported as failed.
25
+ * never compared against the draft on save: the Host strips them from every
26
+ * wire view layer, so the settled snapshot carries nothing to read back. A
27
+ * staged secret set is judged by the mutation settling; the rest of its
28
+ * batch, when one exists, is still judged by read-back, and the atomic
29
+ * mutation lands every write or none.
27
30
  */
28
31
  secret?: boolean;
29
32
  /** Render a stored value as draft text; the empty string when the section carries none. */
@@ -49,8 +52,8 @@ export interface CardShell {
49
52
  available: boolean;
50
53
  /**
51
54
  * Whether the namespace is actually served to this client. False when the
52
- * Host deployment does not expose it (e.g. the official apiproxy settings
53
- * allowlist omits third-party namespaces): the card renders an explanation
55
+ * Host deployment does not expose it (e.g. the owning plugin's settings
56
+ * domain is not mounted): the card renders an explanation
54
57
  * instead of its form, so a missing namespace never looks like a missing
55
58
  * plugin.
56
59
  */
@@ -83,7 +86,7 @@ export interface CardActions {
83
86
  /** Drop every staged edit. */
84
87
  discard: () => void;
85
88
  }
86
- /** One durable write a batched settings scope performs. */
89
+ /** One durable write inside the save's atomic scope mutation. */
87
90
  export interface BatchedWrite {
88
91
  /** Field this entry writes. */
89
92
  field: string;
@@ -92,28 +95,6 @@ export interface BatchedWrite {
92
95
  /** Value for op set (absent for unset). */
93
96
  value?: unknown;
94
97
  }
95
- /** Per-field outcome of one batched scope write. */
96
- export interface BatchedFieldResult {
97
- /** Field this entry writes. */
98
- field: string;
99
- /** Whether the Host accepted this field's write (per the read-back view). */
100
- landed: boolean;
101
- }
102
- /**
103
- * Result of a batched scope write. The bridge scope posts every planned write
104
- * in one /mutate so the Host validate hook judges baseURL+model together; a
105
- * batched refusal fails the whole save rather than per-field.
106
- */
107
- export interface BatchResult {
108
- /** Whether the whole mutate was accepted. */
109
- ok: boolean;
110
- /** Per-field success, in the request order (always present when ok). */
111
- fields: BatchedFieldResult[];
112
- /** Host rejection code (mutate refused). */
113
- code?: string;
114
- /** Host rejection message (mutate refused). */
115
- message?: string;
116
- }
117
98
  /** Constraints a numeric field's accepted drafts must satisfy, mirroring the host schema. */
118
99
  export interface NumberConstraints {
119
100
  /** The accepted value must be a whole number. */
@@ -128,8 +109,8 @@ export declare function textField(field: string): FieldSpec;
128
109
  /**
129
110
  * A free-text field the Host treats as a secret and redacts from the read-back
130
111
  * (role('secret') in the section schema). The card still edits it like text,
131
- * but a save never compares the redacted value back and relies on the scope
132
- * reporting the write landed.
112
+ * but a save never compares the redacted value back: the staged set is judged
113
+ * by the mutation settling (see {@link FieldSpec.secret}).
133
114
  */
134
115
  export declare function secretField(field: string): FieldSpec;
135
116
  /** A boolean field, edited through true/false draft text. */
@@ -171,19 +152,22 @@ export declare class CardForm<T> {
171
152
  /** The actions the card's slot registration injects. */
172
153
  actions(): CardActions;
173
154
  /**
174
- * Write every staged edit, then re-seed from what the Host accepted.
155
+ * Write every staged edit in one atomic scope mutation, then re-seed from
156
+ * what the Host accepted.
175
157
  *
176
- * When the scope carries the optional batch surface (the dsh-web
177
- * bridge scope), every planned write rides one mutation so cross-field
178
- * validate hooks (baseURL+model) judge the batch as a unit instead of
179
- * deadlocking on per-field writes. Otherwise the per-field loop runs.
180
- * A field lands only when the Host reports it held the staged value; a
181
- * landed field's draft is dropped, a failed one stays staged for the user.
182
- * @returns settlement after every write and the read-back.
158
+ * The whole batch rides one mutate, so cross-field validate hooks
159
+ * (baseURL+model) judge it as a unit: the Host either applies every write
160
+ * or refuses the batch. The 0.1.2 scope contract never rejects a refused
161
+ * mutation — the scope recovers with a fresh Host view and resolves — so
162
+ * resolution alone proves nothing: the outcome is judged by reading the
163
+ * settled snapshot back, one planned write at a time, and one missed write
164
+ * fails the whole save. A scope that still rejects on refusal (the dsh-web
165
+ * bridge scope) reports through the same failure path with its rejection
166
+ * message. A save that did not land keeps its drafts, so the user can
167
+ * correct them instead of retyping.
168
+ * @returns settlement after the mutation and the read-back.
183
169
  */
184
170
  save(): Promise<void>;
185
- /** The scope's batch surface when it supports one; undefined conservatively otherwise. */
186
- private batchedScope;
187
171
  /**
188
172
  * Every staged edit a save would write. An entry whose draft is not a value
189
173
  * its field accepts carries no write: the form is still dirty, and the save
@@ -192,8 +176,16 @@ export declare class CardForm<T> {
192
176
  * @returns the planned writes, in the order the fields were staged.
193
177
  */
194
178
  private plan;
195
- private clear;
196
- private store;
179
+ /**
180
+ * Read-back judgment for a planned set: the user layer must hold the
181
+ * intended value once the mutation has settled.
182
+ */
183
+ private landedSet;
184
+ /**
185
+ * Read-back judgment for a planned unset: the field must be gone from the
186
+ * user layer once the mutation has settled.
187
+ */
188
+ private landedUnset;
197
189
  private stage;
198
190
  private specOf;
199
191
  private snapshotOf;
@@ -1 +1 @@
1
- {"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"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"}
@@ -7,7 +7,7 @@
7
7
  * each plugin's client bundle; mirrors the official ui-plugin-config
8
8
  * card-store pattern.
9
9
  */
10
- import { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
10
+ import { createSnapshotStore } from '@deepseek-ai/dsh-client-store';
11
11
  /** A whole- or decimal-number field. An empty draft clears the field; any other draft that is not a finite number within the constraints blocks the save. */
12
12
  export function numberField(field, constraints = {}) {
13
13
  const { integer = false, min } = constraints;
@@ -43,8 +43,8 @@ export function textField(field) {
43
43
  /**
44
44
  * A free-text field the Host treats as a secret and redacts from the read-back
45
45
  * (role('secret') in the section schema). The card still edits it like text,
46
- * but a save never compares the redacted value back and relies on the scope
47
- * reporting the write landed.
46
+ * but a save never compares the redacted value back: the staged set is judged
47
+ * by the mutation settling (see {@link FieldSpec.secret}).
48
48
  */
49
49
  export function secretField(field) {
50
50
  return { ...textField(field), secret: true };
@@ -129,7 +129,7 @@ export class CardForm {
129
129
  exposed: snapshot.status === 'ready',
130
130
  writable: snapshot.writable,
131
131
  dirty: plan.length > 0,
132
- invalid: plan.some(item => item.run === undefined),
132
+ invalid: plan.some(item => item.judge === undefined),
133
133
  saving: this.saving,
134
134
  failed: this.failed,
135
135
  ...this.failedReason === undefined ? {} : { failedReason: this.failedReason },
@@ -168,22 +168,26 @@ export class CardForm {
168
168
  };
169
169
  }
170
170
  /**
171
- * Write every staged edit, then re-seed from what the Host accepted.
171
+ * Write every staged edit in one atomic scope mutation, then re-seed from
172
+ * what the Host accepted.
172
173
  *
173
- * When the scope carries the optional batch surface (the dsh-web
174
- * bridge scope), every planned write rides one mutation so cross-field
175
- * validate hooks (baseURL+model) judge the batch as a unit instead of
176
- * deadlocking on per-field writes. Otherwise the per-field loop runs.
177
- * A field lands only when the Host reports it held the staged value; a
178
- * landed field's draft is dropped, a failed one stays staged for the user.
179
- * @returns settlement after every write and the read-back.
174
+ * The whole batch rides one mutate, so cross-field validate hooks
175
+ * (baseURL+model) judge it as a unit: the Host either applies every write
176
+ * or refuses the batch. The 0.1.2 scope contract never rejects a refused
177
+ * mutation — the scope recovers with a fresh Host view and resolves — so
178
+ * resolution alone proves nothing: the outcome is judged by reading the
179
+ * settled snapshot back, one planned write at a time, and one missed write
180
+ * fails the whole save. A scope that still rejects on refusal (the dsh-web
181
+ * bridge scope) reports through the same failure path with its rejection
182
+ * message. A save that did not land keeps its drafts, so the user can
183
+ * correct them instead of retyping.
184
+ * @returns settlement after the mutation and the read-back.
180
185
  */
181
186
  async save() {
182
187
  const plan = this.plan();
183
- const valid = plan.filter(item => item.run !== undefined);
188
+ const valid = plan.filter((item) => item.judge !== undefined);
184
189
  if (plan.length === 0 || this.saving || valid.length !== plan.length)
185
190
  return;
186
- const plannedWrites = valid.map(item => item.op);
187
191
  // Snapshot the staged entries this save writes, so an edit staged while it
188
192
  // is in flight (which replaces the same key) survives: only delete the key
189
193
  // when the entry is still the one this save started from.
@@ -194,39 +198,35 @@ export class CardForm {
194
198
  this.failed = false;
195
199
  this.failedReason = undefined;
196
200
  this.publish();
197
- const landed = new Set();
198
- const batch = this.batchedScope();
199
- if (batch !== undefined) {
200
- const result = await batch.mutate(plannedWrites);
201
- if (result.ok) {
202
- for (const field of result.fields) {
203
- if (field.landed)
204
- landed.add(field.field);
205
- }
206
- }
207
- else {
208
- this.failedReason = result.message;
209
- }
201
+ // One atomic namespace mutation: the 0.1.2 scope contract takes ordered
202
+ // path operations, so the whole staged batch is validated, persisted, and
203
+ // recovered together — either every write lands or none does.
204
+ const ops = valid.map(item => item.op.op === 'set'
205
+ ? { op: 'set', path: [item.field], value: item.op.value }
206
+ : { op: 'unset', path: [item.field] });
207
+ let failedReason;
208
+ try {
209
+ await this.scope.mutate(ops);
210
210
  }
211
- else {
212
- for (const item of valid) {
213
- if (await item.run())
214
- landed.add(item.field);
215
- }
211
+ catch (error) {
212
+ failedReason = error instanceof Error ? error.message : String(error);
216
213
  }
214
+ // The 0.1.2 scope resolves even a refused mutation (it recovers with a
215
+ // fresh view instead of throwing), so resolution alone proves nothing:
216
+ // judge every planned write against the settled snapshot. The mutation is
217
+ // atomic, so one missed write fails the whole save and keeps the drafts.
218
+ const landed = failedReason === undefined && valid.every(item => item.judge());
217
219
  for (const [field, before] of pending) {
218
- if (landed.has(field) && this.staged.get(field) === before)
220
+ if (landed && this.staged.get(field) === before)
219
221
  this.staged.delete(field);
220
222
  }
221
223
  this.saving = false;
222
- this.failed = landed.size !== pending.size;
224
+ this.failed = !landed;
225
+ // A read-back failure carries no server reason: the card surfaces its
226
+ // generic failure copy; a rejecting scope (the bridge) adds its message.
227
+ this.failedReason = failedReason;
223
228
  this.publish();
224
229
  }
225
- /** The scope's batch surface when it supports one; undefined conservatively otherwise. */
226
- batchedScope() {
227
- const candidate = this.scope;
228
- return typeof candidate?.mutate === 'function' ? candidate : undefined;
229
- }
230
230
  /**
231
231
  * Every staged edit a save would write. An entry whose draft is not a value
232
232
  * its field accepts carries no write: the form is still dirty, and the save
@@ -240,35 +240,42 @@ export class CardForm {
240
240
  const spec = this.specOf(field);
241
241
  if (staged.clear) {
242
242
  if (this.stored(field))
243
- plan.push({ field, op: { field, op: 'unset' }, run: () => this.clear(field) });
243
+ plan.push({ field, op: { field, op: 'unset' }, judge: () => this.landedUnset(field) });
244
244
  continue;
245
245
  }
246
246
  if (staged.text === spec.format(this.sectionValue(field)))
247
247
  continue;
248
248
  const write = spec.parse(staged.text);
249
249
  if (write === undefined)
250
- plan.push({ field, op: { field, op: 'unset' }, run: undefined });
250
+ plan.push({ field, op: { field, op: 'unset' }, judge: undefined });
251
251
  else if (write.kind === 'clear')
252
- plan.push({ field, op: { field, op: 'unset' }, run: () => this.clear(field) });
252
+ plan.push({ field, op: { field, op: 'unset' }, judge: () => this.landedUnset(field) });
253
253
  else
254
- plan.push({ field, op: { field, op: 'set', value: write.value }, run: () => this.store(field, write.value) });
254
+ plan.push({ field, op: { field, op: 'set', value: write.value }, judge: () => this.landedSet(field, write.value) });
255
255
  }
256
256
  return plan;
257
257
  }
258
- async clear(field) {
259
- await this.scope.unset(field);
260
- return !this.stored(field);
261
- }
262
- async store(field, value) {
263
- await this.scope.set(field, value);
264
- // A redacted secret never appears in the user layer read-back; judging it
265
- // by value would misreport a successful secret save as failed. The bridge
266
- // reports secret writes through its secret-set markers (batch path); on
267
- // the per-field path the scope resolved, so the write is landed.
258
+ /**
259
+ * Read-back judgment for a planned set: the user layer must hold the
260
+ * intended value once the mutation has settled.
261
+ */
262
+ landedSet(field, value) {
263
+ // A redacted secret never appears in any wire view layer: the Host strips
264
+ // role('secret') fields and reports them through a sidecar the scope
265
+ // snapshot does not expose, so there is nothing to compare the draft
266
+ // against. Settling is the only signal the form has; the rest of the
267
+ // batch, when one exists, still carries the atomic verdict by read-back.
268
268
  if (this.specOf(field).secret)
269
269
  return true;
270
270
  return this.userLayer()?.[field] === value;
271
271
  }
272
+ /**
273
+ * Read-back judgment for a planned unset: the field must be gone from the
274
+ * user layer once the mutation has settled.
275
+ */
276
+ landedUnset(field) {
277
+ return !this.stored(field);
278
+ }
272
279
  stage(field, edit) {
273
280
  this.staged.set(field, edit);
274
281
  this.failed = false;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Host firewall management for the LAN bind toggle: while LAN access is on,
3
+ * keep one inbound allow rule for the bound port; when it turns off, remove
4
+ * the rule. Windows manages the Windows Defender Firewall rule via netsh;
5
+ * Linux uses the first available manager among firewalld / ufw / iptables.
6
+ * On every other platform (macOS included) the plugin reports the firewall
7
+ * as unmanaged: the port usually needs no rule there, and managing pf or the
8
+ * application firewall is out of scope for a distributable plugin.
9
+ *
10
+ * Ported from the dsh-LAN reference implementation (MIT), adapted to this
11
+ * package's structure and test seams.
12
+ */
13
+ /** Rule name shared by every backend so re-runs recreate the same rule. */
14
+ export declare const FIREWALL_RULE_NAME = "remote-web-ui (auto)";
15
+ /** One command execution result (test seam replaces the runner). */
16
+ export interface ToolResult {
17
+ ok: boolean;
18
+ out: string;
19
+ err: string;
20
+ /** true when the binary could not be spawned at all (ENOENT et al). */
21
+ missing?: boolean;
22
+ }
23
+ /** The command runner seam: spawnSync in production, a stub in tests. */
24
+ export type CommandRunner = (cmd: string, args: readonly string[]) => ToolResult;
25
+ /** Production runner: spawn without a shell, capture text, 20s timeout. */
26
+ export declare const spawnRunner: CommandRunner;
27
+ /** A firewall backend manages one allow rule for a TCP port. */
28
+ export interface FirewallBackend {
29
+ /** Short label surfaced in the settings card (netsh, firewalld, ...). */
30
+ readonly label: string;
31
+ ruleExists(port: number): boolean;
32
+ addRule(port: number): boolean;
33
+ removeRule(port: number): boolean;
34
+ }
35
+ /**
36
+ * Detect the platform firewall manager. Returns undefined when the platform
37
+ * has no supported manager (macOS, unknown Linux without tools): the port
38
+ * then usually needs no rule and the UI reports "unmanaged".
39
+ */
40
+ export declare function detectFirewallBackend(platform: NodeJS.Platform, run?: CommandRunner): FirewallBackend | undefined;
41
+ /** Cached detection for the running platform (per process). */
42
+ export declare function firewallBackend(): FirewallBackend | undefined;
43
+ /** Firewall state reported to the settings card. */
44
+ export interface FirewallSummary {
45
+ /** true when the rule state matches the LAN toggle (or nothing to manage). */
46
+ ok: boolean;
47
+ /** false when no supported firewall manager exists on this platform. */
48
+ managed: boolean;
49
+ /** Backend label or an explanation for unmanaged platforms. */
50
+ note?: string;
51
+ }
52
+ /**
53
+ * Delete-and-add: recreating the rule is idempotent and locale-proof (netsh
54
+ * output is localized, so parsing the live rule's port is fragile; the Linux
55
+ * backends follow the same recreate pattern).
56
+ */
57
+ export declare function ensureFirewallRule(port: number): boolean;
58
+ export declare function removeFirewallRule(port: number): boolean;
59
+ /**
60
+ * Human-readable firewall state for the status endpoint. A missing backend
61
+ * means the platform has nothing to manage; detection stays at the call site
62
+ * so an explicit undefined cannot silently re-probe the real OS mid-test.
63
+ */
64
+ export declare function computeFirewallSummary(port: number, lanEnabled: boolean, backend: FirewallBackend | undefined): FirewallSummary;
65
+ /** How long a firewallSummary answer is reused before the backend is probed again. */
66
+ export declare const FIREWALL_SUMMARY_TTL_MS = 30000;
67
+ /** Forget the cached summary (rule mutations call this). */
68
+ export declare function invalidateFirewallSummary(): void;
69
+ /**
70
+ * The settings card polls this every ten seconds; the probes are blocking
71
+ * spawnSync subprocesses on managed platforms, so short-TTL memoization
72
+ * keeps one poll from freezing the host event loop per request.
73
+ */
74
+ export declare function firewallSummary(port: number, lanEnabled: boolean): FirewallSummary;
75
+ //# sourceMappingURL=firewall.d.ts.map