@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.
- package/LICENSE +202 -28
- package/README.i18n.yaml +2 -2
- package/README.md +51 -15
- package/README.zh.md +9 -4
- package/lib/client.js +65 -34
- package/lib/client.js.map +1 -1
- package/lib/index.js +667 -77
- package/lib/mobile.js +874 -64
- package/lib/mobile.js.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +35 -5
- package/lib/types/client/remote-channel.d.ts +4 -5
- package/lib/types/client/remote-channel.d.ts.map +1 -1
- package/lib/types/client/remote-channel.js +13 -28
- package/lib/types/http.d.ts +37 -8
- package/lib/types/http.d.ts.map +1 -1
- package/lib/types/http.js +76 -11
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +40 -5
- package/lib/types/mobile/api.d.ts +57 -0
- package/lib/types/mobile/api.d.ts.map +1 -1
- package/lib/types/mobile/api.js +29 -0
- package/lib/types/mobile/markdown.d.ts +7 -1
- package/lib/types/mobile/markdown.d.ts.map +1 -1
- package/lib/types/mobile/markdown.js +16 -7
- package/lib/types/mobile/markdown.test.js +8 -0
- package/lib/types/mobile/mobile-styles.d.ts +1 -1
- package/lib/types/mobile/mobile-styles.d.ts.map +1 -1
- package/lib/types/mobile/mobile-styles.js +325 -6
- package/lib/types/mobile/views/ChatView.d.ts +10 -0
- package/lib/types/mobile/views/ChatView.d.ts.map +1 -1
- package/lib/types/mobile/views/ChatView.js +205 -8
- package/lib/types/mobile/views/ChatView.stream.test.d.ts +2 -0
- package/lib/types/mobile/views/ChatView.stream.test.d.ts.map +1 -0
- package/lib/types/mobile/views/ChatView.stream.test.js +205 -0
- package/lib/types/mobile/views/WorkspaceView.d.ts.map +1 -1
- package/lib/types/mobile/views/WorkspaceView.js +46 -5
- package/lib/types/mobile/views/WorkspaceView.test.js +129 -2
- package/lib/types/mobile-api.d.ts +9 -3
- package/lib/types/mobile-api.d.ts.map +1 -1
- package/lib/types/mobile-api.js +61 -16
- package/lib/types/mobile-pending.d.ts +38 -0
- package/lib/types/mobile-pending.d.ts.map +1 -0
- package/lib/types/mobile-pending.js +59 -0
- package/lib/types/paired-model-catalog.d.ts +17 -0
- package/lib/types/paired-model-catalog.d.ts.map +1 -0
- package/lib/types/paired-model-catalog.js +252 -0
- package/lib/types/pairing.d.ts +13 -4
- package/lib/types/pairing.d.ts.map +1 -1
- package/lib/types/pairing.js +13 -5
- package/lib/types/remote-api.d.ts +13 -4
- package/lib/types/remote-api.d.ts.map +1 -1
- package/lib/types/remote-api.js +27 -17
- package/lib/types/remote-channel-boot.d.ts +26 -0
- package/lib/types/remote-channel-boot.d.ts.map +1 -0
- package/lib/types/remote-channel-boot.js +128 -0
- package/lib/types/remote-channel-rules.d.ts +40 -0
- package/lib/types/remote-channel-rules.d.ts.map +1 -0
- package/lib/types/remote-channel-rules.js +32 -0
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +12 -23
- package/lib/types/uuid-polyfill.d.ts +10 -0
- package/lib/types/uuid-polyfill.d.ts.map +1 -0
- package/lib/types/uuid-polyfill.js +27 -0
- package/package.json +1 -1
- package/src/client/index.ts +41 -5
- package/src/client/remote-channel.ts +18 -28
- package/src/http.ts +87 -12
- package/src/index.ts +42 -6
- package/src/mobile/api.ts +91 -0
- package/src/mobile/markdown.test.ts +9 -0
- package/src/mobile/markdown.ts +17 -7
- package/src/mobile/mobile-styles.ts +326 -6
- package/src/mobile/views/ChatView.stream.test.tsx +231 -0
- package/src/mobile/views/ChatView.tsx +328 -10
- package/src/mobile/views/WorkspaceView.test.tsx +163 -2
- package/src/mobile/views/WorkspaceView.tsx +152 -26
- package/src/mobile-api.ts +59 -17
- package/src/mobile-pending.ts +88 -0
- package/src/paired-model-catalog.ts +224 -0
- package/src/pairing.ts +13 -5
- package/src/remote-api.ts +34 -18
- package/src/remote-channel-boot.ts +131 -0
- package/src/remote-channel-rules.ts +62 -0
- package/src/routes.ts +13 -24
- 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 {
|
|
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
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
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:
|
|
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
|
|
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('')
|