@linxin666/dsh-remote-web-ui 0.2.8 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/LICENSE +202 -28
  2. package/README.i18n.yaml +2 -2
  3. package/README.md +51 -15
  4. package/README.zh.md +9 -4
  5. package/lib/client.js +65 -34
  6. package/lib/client.js.map +1 -1
  7. package/lib/index.js +667 -77
  8. package/lib/mobile.js +874 -64
  9. package/lib/mobile.js.map +1 -1
  10. package/lib/types/client/index.d.ts.map +1 -1
  11. package/lib/types/client/index.js +35 -5
  12. package/lib/types/client/remote-channel.d.ts +4 -5
  13. package/lib/types/client/remote-channel.d.ts.map +1 -1
  14. package/lib/types/client/remote-channel.js +13 -28
  15. package/lib/types/http.d.ts +37 -8
  16. package/lib/types/http.d.ts.map +1 -1
  17. package/lib/types/http.js +76 -11
  18. package/lib/types/index.d.ts.map +1 -1
  19. package/lib/types/index.js +40 -5
  20. package/lib/types/mobile/api.d.ts +57 -0
  21. package/lib/types/mobile/api.d.ts.map +1 -1
  22. package/lib/types/mobile/api.js +29 -0
  23. package/lib/types/mobile/markdown.d.ts +7 -1
  24. package/lib/types/mobile/markdown.d.ts.map +1 -1
  25. package/lib/types/mobile/markdown.js +16 -7
  26. package/lib/types/mobile/markdown.test.js +8 -0
  27. package/lib/types/mobile/mobile-styles.d.ts +1 -1
  28. package/lib/types/mobile/mobile-styles.d.ts.map +1 -1
  29. package/lib/types/mobile/mobile-styles.js +325 -6
  30. package/lib/types/mobile/views/ChatView.d.ts +10 -0
  31. package/lib/types/mobile/views/ChatView.d.ts.map +1 -1
  32. package/lib/types/mobile/views/ChatView.js +205 -8
  33. package/lib/types/mobile/views/ChatView.stream.test.d.ts +2 -0
  34. package/lib/types/mobile/views/ChatView.stream.test.d.ts.map +1 -0
  35. package/lib/types/mobile/views/ChatView.stream.test.js +205 -0
  36. package/lib/types/mobile/views/WorkspaceView.d.ts.map +1 -1
  37. package/lib/types/mobile/views/WorkspaceView.js +46 -5
  38. package/lib/types/mobile/views/WorkspaceView.test.js +129 -2
  39. package/lib/types/mobile-api.d.ts +9 -3
  40. package/lib/types/mobile-api.d.ts.map +1 -1
  41. package/lib/types/mobile-api.js +61 -16
  42. package/lib/types/mobile-pending.d.ts +38 -0
  43. package/lib/types/mobile-pending.d.ts.map +1 -0
  44. package/lib/types/mobile-pending.js +59 -0
  45. package/lib/types/paired-model-catalog.d.ts +17 -0
  46. package/lib/types/paired-model-catalog.d.ts.map +1 -0
  47. package/lib/types/paired-model-catalog.js +252 -0
  48. package/lib/types/pairing.d.ts +13 -4
  49. package/lib/types/pairing.d.ts.map +1 -1
  50. package/lib/types/pairing.js +13 -5
  51. package/lib/types/remote-api.d.ts +13 -4
  52. package/lib/types/remote-api.d.ts.map +1 -1
  53. package/lib/types/remote-api.js +27 -17
  54. package/lib/types/remote-channel-boot.d.ts +26 -0
  55. package/lib/types/remote-channel-boot.d.ts.map +1 -0
  56. package/lib/types/remote-channel-boot.js +128 -0
  57. package/lib/types/remote-channel-rules.d.ts +40 -0
  58. package/lib/types/remote-channel-rules.d.ts.map +1 -0
  59. package/lib/types/remote-channel-rules.js +32 -0
  60. package/lib/types/routes.d.ts.map +1 -1
  61. package/lib/types/routes.js +12 -23
  62. package/lib/types/uuid-polyfill.d.ts +10 -0
  63. package/lib/types/uuid-polyfill.d.ts.map +1 -0
  64. package/lib/types/uuid-polyfill.js +27 -0
  65. package/package.json +1 -1
  66. package/src/client/index.ts +41 -5
  67. package/src/client/remote-channel.ts +18 -28
  68. package/src/http.ts +87 -12
  69. package/src/index.ts +42 -6
  70. package/src/mobile/api.ts +91 -0
  71. package/src/mobile/markdown.test.ts +9 -0
  72. package/src/mobile/markdown.ts +17 -7
  73. package/src/mobile/mobile-styles.ts +326 -6
  74. package/src/mobile/views/ChatView.stream.test.tsx +231 -0
  75. package/src/mobile/views/ChatView.tsx +328 -10
  76. package/src/mobile/views/WorkspaceView.test.tsx +163 -2
  77. package/src/mobile/views/WorkspaceView.tsx +152 -26
  78. package/src/mobile-api.ts +59 -17
  79. package/src/mobile-pending.ts +88 -0
  80. package/src/paired-model-catalog.ts +224 -0
  81. package/src/pairing.ts +13 -5
  82. package/src/remote-api.ts +34 -18
  83. package/src/remote-channel-boot.ts +131 -0
  84. package/src/remote-channel-rules.ts +62 -0
  85. package/src/routes.ts +13 -24
  86. package/src/uuid-polyfill.ts +28 -0
package/src/routes.ts CHANGED
@@ -13,7 +13,7 @@ import type { WebRoute } from '@deepseek-ai/dsh-host-webserver'
13
13
  import { z, type ZodType } from 'zod'
14
14
  import { UnknownLanAddressError, type PairingService, type PairingSnapshot } from './pairing.ts'
15
15
  import { isLoopbackClient, readCookie } from './gate.ts'
16
- import { readBoundedJson, writeJson } from './http.ts'
16
+ import { readJsonBody, writeJson } from './http.ts'
17
17
 
18
18
  /**
19
19
  * Browser-trust fence for the /api/pair routes, mirroring the connection
@@ -115,26 +115,17 @@ export const revokePayloadSchema = z.object({
115
115
  export const pairActionPayloadSchema = z.object({}).passthrough()
116
116
 
117
117
  /**
118
- * Parse a pair request body through schema. A missing/empty or non-object
119
- * body is treated as an empty object (the same way the previous manual reads
120
- * yielded absent fields), and a value that fails the schema returns
121
- * `undefined` so the caller can answer with the existing error shape.
118
+ * Parse a pair request body through schema. A missing/empty, unparseable
119
+ * or non-object body (shared readJsonBody with objectOnly yields null for
120
+ * all of them) is treated as an empty object — the desktop stop/heartbeat
121
+ * send no body — and a value that fails the schema returns `undefined` so
122
+ * the caller can answer with the existing error shape.
122
123
  */
123
- function parsePairPayload<T>(schema: ZodType<T>, body: Record<string, unknown> | undefined): T | undefined {
124
+ function parsePairPayload<T>(schema: ZodType<T>, body: unknown | null): T | undefined {
124
125
  const result = schema.safeParse(body ?? {})
125
126
  return result.success ? result.data : undefined
126
127
  }
127
128
 
128
- /** Read a request body up to MAX_BODY_BYTES and parse it as JSON (undefined on failure). */
129
- async function readJsonBody(req: IncomingMessage): Promise<Record<string, unknown> | undefined> {
130
- try {
131
- const parsed: unknown = await readBoundedJson(req, MAX_BODY_BYTES)
132
- return typeof parsed === 'object' && parsed !== null ? parsed as Record<string, unknown> : undefined
133
- } catch {
134
- return undefined
135
- }
136
- }
137
-
138
129
  /** One open desktop status stream. */
139
130
  interface StatusStream {
140
131
  res: ServerResponse
@@ -265,7 +256,7 @@ export function makeRoutes(deps: PairRoutesDeps): WebRoute[] {
265
256
  writeJson(res, 403, { ok: false, code: 'forbidden' })
266
257
  return
267
258
  }
268
- const body = await readJsonBody(req)
259
+ const body = await readJsonBody(req, { maxBytes: MAX_BODY_BYTES, objectOnly: true })
269
260
  const payload = parsePairPayload(issuePayloadSchema, body)
270
261
  if (payload === undefined) {
271
262
  writeJson(res, 400, { ok: false, code: 'bad-payload' })
@@ -313,7 +304,7 @@ export function makeRoutes(deps: PairRoutesDeps): WebRoute[] {
313
304
  writeJson(res, 429, { ok: false, code: 'rate-limited' })
314
305
  return
315
306
  }
316
- const body = await readJsonBody(req)
307
+ const body = await readJsonBody(req, { maxBytes: MAX_BODY_BYTES, objectOnly: true })
317
308
  const payload = parsePairPayload(acceptPayloadSchema, body)
318
309
  if (payload === undefined) {
319
310
  writeJson(res, 400, { ok: false, code: 'bad-payload' })
@@ -328,13 +319,11 @@ export function makeRoutes(deps: PairRoutesDeps): WebRoute[] {
328
319
  // No Secure attribute: LAN pairing runs over plain HTTP (the cookie must
329
320
  // work there), and the same cookie rides HTTPS on the tunnel. Lax keeps
330
321
  // top-level navigations working while blocking cross-site subrequests.
331
- res.writeHead(200, {
332
- 'content-type': 'application/json; charset=utf-8',
322
+ writeJson(res, 200, { ok: true, deviceId: result.deviceId }, {
333
323
  'set-cookie': [
334
324
  `${service.config.cookieName}=${result.deviceId}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${String(COOKIE_MAX_AGE_SEC)}`,
335
325
  ],
336
326
  })
337
- res.end(JSON.stringify({ ok: true, deviceId: result.deviceId }))
338
327
  }
339
328
 
340
329
  const handleStop = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {
@@ -343,7 +332,7 @@ export function makeRoutes(deps: PairRoutesDeps): WebRoute[] {
343
332
  writeJson(res, 403, { ok: false, code: 'forbidden' })
344
333
  return
345
334
  }
346
- const body = await readJsonBody(req)
335
+ const body = await readJsonBody(req, { maxBytes: MAX_BODY_BYTES, objectOnly: true })
347
336
  if (parsePairPayload(pairActionPayloadSchema, body) === undefined) {
348
337
  writeJson(res, 400, { ok: false, code: 'bad-payload' })
349
338
  return
@@ -358,7 +347,7 @@ export function makeRoutes(deps: PairRoutesDeps): WebRoute[] {
358
347
  writeJson(res, 403, { ok: false, code: 'forbidden' })
359
348
  return
360
349
  }
361
- const body = await readJsonBody(req)
350
+ const body = await readJsonBody(req, { maxBytes: MAX_BODY_BYTES, objectOnly: true })
362
351
  const payload = parsePairPayload(revokePayloadSchema, body)
363
352
  if (payload === undefined) {
364
353
  writeJson(res, 400, { ok: false, code: 'bad-payload' })
@@ -378,7 +367,7 @@ export function makeRoutes(deps: PairRoutesDeps): WebRoute[] {
378
367
  writeJson(res, 403, { ok: false, code: 'forbidden' })
379
368
  return
380
369
  }
381
- const body = await readJsonBody(req)
370
+ const body = await readJsonBody(req, { maxBytes: MAX_BODY_BYTES, objectOnly: true })
382
371
  if (parsePairPayload(pairActionPayloadSchema, body) === undefined) {
383
372
  writeJson(res, 400, { ok: false, code: 'bad-payload' })
384
373
  return
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Polyfill for crypto.randomUUID for non-secure contexts (LAN HTTP)
3
+ * where the browser does not expose crypto.randomUUID.
4
+ *
5
+ * The script string is injected as an inline <script> in the desktop HTML
6
+ * head so the SDK's mintRpcId() sees a working randomUUID even on plain
7
+ * http:// origins (#1024).
8
+ */
9
+
10
+ export const UUID_POLYFILL_SCRIPT = [
11
+ '(function(){',
12
+ 'var g=typeof globalThis!=="undefined"?globalThis:typeof window!=="undefined"?window:self;',
13
+ 'if(typeof g.crypto==="undefined"){g.crypto={};}',
14
+ 'if(typeof g.crypto.randomUUID!=="function"){',
15
+ 'g.crypto.randomUUID=function(){',
16
+ 'var a=new Uint8Array(16);',
17
+ 'if(g.crypto.getRandomValues){g.crypto.getRandomValues(a);}',
18
+ 'else{for(var j=0;j<16;j++){a[j]=Math.random()*256|0;}}',
19
+ 'a[6]=(a[6]&0x0f)|0x40;',
20
+ 'a[8]=(a[8]&0x3f)|0x80;',
21
+ 'var h="",x="0123456789abcdef";',
22
+ 'for(var i=0;i<16;i++){h+=x[a[i]>>4]+x[a[i]&0x0f];',
23
+ 'if(i===3||i===5||i===7||i===9)h+="-";}',
24
+ 'return h;',
25
+ '};',
26
+ '}',
27
+ '})();',
28
+ ].join('')