@linxin666/dsh-remote-web-ui 0.2.7 → 0.2.9

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 (48) hide show
  1. package/LICENSE +202 -28
  2. package/README.i18n.yaml +2 -2
  3. package/README.md +14 -7
  4. package/README.zh.md +3 -2
  5. package/lib/client.js +40 -8
  6. package/lib/client.js.map +1 -1
  7. package/lib/index.js +34 -24
  8. package/lib/mobile.js +39 -21
  9. package/lib/mobile.js.map +1 -1
  10. package/lib/types/client/RemoteEntry.js +3 -3
  11. package/lib/types/client/UpdateEntry.d.ts.map +1 -1
  12. package/lib/types/client/UpdateEntry.js +1 -1
  13. package/lib/types/client/index.d.ts.map +1 -1
  14. package/lib/types/client/index.js +28 -14
  15. package/lib/types/client/pair-api.d.ts +6 -0
  16. package/lib/types/client/pair-api.d.ts.map +1 -1
  17. package/lib/types/client/pair-api.js +11 -0
  18. package/lib/types/client/remote-channel.d.ts +10 -0
  19. package/lib/types/client/remote-channel.d.ts.map +1 -1
  20. package/lib/types/client/remote-channel.js +11 -0
  21. package/lib/types/index.d.ts.map +1 -1
  22. package/lib/types/index.js +16 -5
  23. package/lib/types/mobile/messages.d.ts.map +1 -1
  24. package/lib/types/mobile/messages.js +5 -3
  25. package/lib/types/mobile/messages.test.js +20 -0
  26. package/lib/types/mobile/mux.d.ts +7 -4
  27. package/lib/types/mobile/mux.d.ts.map +1 -1
  28. package/lib/types/mobile/mux.js +43 -15
  29. package/lib/types/mobile/mux.test.js +57 -0
  30. package/lib/types/remote-api.d.ts +13 -4
  31. package/lib/types/remote-api.d.ts.map +1 -1
  32. package/lib/types/remote-api.js +27 -17
  33. package/lib/types/routes.d.ts +2 -0
  34. package/lib/types/routes.d.ts.map +1 -1
  35. package/lib/types/routes.js +5 -2
  36. package/package.json +15 -12
  37. package/src/client/RemoteEntry.tsx +3 -2
  38. package/src/client/UpdateEntry.tsx +1 -0
  39. package/src/client/index.ts +30 -13
  40. package/src/client/pair-api.ts +16 -0
  41. package/src/client/remote-channel.ts +21 -0
  42. package/src/index.ts +16 -5
  43. package/src/mobile/messages.test.ts +23 -0
  44. package/src/mobile/messages.ts +5 -3
  45. package/src/mobile/mux.test.ts +64 -0
  46. package/src/mobile/mux.ts +44 -17
  47. package/src/remote-api.ts +34 -17
  48. package/src/routes.ts +7 -2
package/src/remote-api.ts CHANGED
@@ -7,8 +7,10 @@
7
7
  * and no per-plugin pairing consult.
8
8
  *
9
9
  * Security model:
10
- * - Every request must carry a live paired-device cookie, enforced before
11
- * any bytes are forwarded and before any host call.
10
+ * - While `requirePairingForLan` is on (default), every request must carry a
11
+ * live paired-device cookie, enforced before any bytes are forwarded and
12
+ * before any host call. With the policy off, the cookie gate is skipped
13
+ * (the loopback-only denials below still apply).
12
14
  * - The SDK's loopback-only privileged methods (native dialogs, the settings
13
15
  * plane, credentials — the `PRIVILEGED_METHODS` set of client-connection)
14
16
  * are denied here. The set is pinned by tests/remote-contract.spec.ts.
@@ -68,6 +70,13 @@ export interface RemoteApiDeps {
68
70
  service: PairingService
69
71
  /** The local webServer port the loopback proxy connects to. */
70
72
  port: number
73
+ /**
74
+ * Live policy: whether the paired-device cookie gates the /remote channel.
75
+ * When false, requests are proxied without a cookie (the loopback-only
76
+ * denials still apply). A function is re-read per request, so a settings
77
+ * edit takes effect without a restart. Defaults to true.
78
+ */
79
+ requirePairingForLan?: boolean | (() => boolean)
71
80
  }
72
81
 
73
82
  /** One SDK-shaped error envelope (keeps the desktop client's parse path intact). */
@@ -125,20 +134,25 @@ export function loopbackOnlyDenial(innerPath: string): string | undefined {
125
134
 
126
135
  /**
127
136
  * Build the remote desktop channel HTTP routes.
128
- * @param deps - pairing service + local port.
137
+ * @param deps - pairing service + local port + live pairing policy.
129
138
  * @returns the routes to register on webServer.
130
139
  */
131
140
  export function makeRemoteApiRoutes(deps: RemoteApiDeps): WebRoute[] {
132
- const { service, port } = deps
141
+ const { service, port, requirePairingForLan = true } = deps
133
142
 
134
143
  const handler = (req: IncomingMessage, res: ServerResponse): void => {
135
144
  // Cookie gate first — same order as /m/api. Do not buffer an unpaired body.
136
- const deviceId = readCookie(req.headers.cookie, service.config.cookieName)
137
- const paired = deviceId !== undefined && service.touchDevice(deviceId)
138
- if (!paired) {
139
- req.resume()
140
- envelopeError(res, 403, 'invalid-request', 'unpaired', 'this device is not paired with the desktop')
141
- return
145
+ // With the live policy off, untrusted-but-policy-open callers are proxied
146
+ // (a stale client rewrite must not 403); loopback-only denials stay below.
147
+ const require = typeof requirePairingForLan === 'function' ? requirePairingForLan() : requirePairingForLan
148
+ if (require) {
149
+ const deviceId = readCookie(req.headers.cookie, service.config.cookieName)
150
+ const paired = deviceId !== undefined && service.touchDevice(deviceId)
151
+ if (!paired) {
152
+ req.resume()
153
+ envelopeError(res, 403, 'invalid-request', 'unpaired', 'this device is not paired with the desktop')
154
+ return
155
+ }
142
156
  }
143
157
 
144
158
  const method = req.method ?? 'GET'
@@ -187,18 +201,21 @@ export function upgradeInnerPath(reqUrl: string | undefined, fallbackPath: strin
187
201
  /**
188
202
  * Build the WebSocket upgrade routes for the event streams and known plugin
189
203
  * sockets. webServer matches upgrades by exact path.
190
- * @param deps - pairing service + local port.
204
+ * @param deps - pairing service + local port + live pairing policy.
191
205
  * @returns the upgrade routes to register on webServer.
192
206
  */
193
207
  export function makeRemoteApiUpgradeRoutes(deps: RemoteApiDeps): WebUpgradeRoute[] {
194
- const { service, port } = deps
208
+ const { service, port, requirePairingForLan = true } = deps
195
209
 
196
210
  const handlerFor = (fallbackPath: string) => (req: IncomingMessage, socket: Duplex, head: Buffer): void => {
197
- const deviceId = readCookie(req.headers.cookie, service.config.cookieName)
198
- if (deviceId === undefined || !service.touchDevice(deviceId)) {
199
- socket.write('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n')
200
- socket.destroy()
201
- return
211
+ const require = typeof requirePairingForLan === 'function' ? requirePairingForLan() : requirePairingForLan
212
+ if (require) {
213
+ const deviceId = readCookie(req.headers.cookie, service.config.cookieName)
214
+ if (deviceId === undefined || !service.touchDevice(deviceId)) {
215
+ socket.write('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n')
216
+ socket.destroy()
217
+ return
218
+ }
202
219
  }
203
220
  const inner = upgradeInnerPath(req.url, fallbackPath)
204
221
  const denied = loopbackOnlyDenial(inner.split('?')[0] ?? inner)
package/src/routes.ts CHANGED
@@ -195,6 +195,8 @@ export interface PairRoutesDeps {
195
195
  service: PairingService
196
196
  /** The LAN IP literals the fence accepts (derived from the bind host). */
197
197
  lanAddresses: readonly string[]
198
+ /** Current desktop gate policy, re-read for every status response. */
199
+ requirePairingForLan?: boolean | (() => boolean)
198
200
  }
199
201
 
200
202
  /**
@@ -203,7 +205,10 @@ export interface PairRoutesDeps {
203
205
  * @returns the exact routes to register on webServer.
204
206
  */
205
207
  export function makeRoutes(deps: PairRoutesDeps): WebRoute[] {
206
- const { service, lanAddresses } = deps
208
+ const { service, lanAddresses, requirePairingForLan = true } = deps
209
+ const pairingRequired = (): boolean => typeof requirePairingForLan === 'function'
210
+ ? requirePairingForLan()
211
+ : requirePairingForLan
207
212
  const events = new PairingEventsStream(service)
208
213
 
209
214
  /** Loopback-only fence: the desktop panel's control endpoints. */
@@ -404,7 +409,7 @@ export function makeRoutes(deps: PairRoutesDeps): WebRoute[] {
404
409
  const visible = paired
405
410
  ? rest
406
411
  : { phase: snapshot.phase, lanAvailable: snapshot.lanAvailable, lanAddresses: snapshot.lanAddresses }
407
- writeJson(res, 200, { ok: true, paired, ...visible })
412
+ writeJson(res, 200, { ok: true, paired, requirePairingForLan: pairingRequired(), ...visible })
408
413
  }
409
414
 
410
415
  const handleEvents = (req: IncomingMessage, res: ServerResponse): void => {