@linxin666/dsh-pet 0.2.4 → 0.2.6
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 +1 -1
- package/README.zh.md +1 -1
- package/lib/client.js +110 -28
- package/lib/client.js.map +1 -1
- package/lib/index.js +164 -34
- package/lib/types/access.d.ts +16 -0
- package/lib/types/access.d.ts.map +1 -0
- package/lib/types/access.js +20 -0
- package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PetSettingsCard.js +8 -2
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +10 -2
- package/lib/types/client/renderers/live2d/Live2dVisualMount.d.ts.map +1 -1
- package/lib/types/client/renderers/live2d/Live2dVisualMount.js +1 -0
- package/lib/types/client/renderers/live2d/runtime.d.ts +13 -1
- package/lib/types/client/renderers/live2d/runtime.d.ts.map +1 -1
- package/lib/types/client/renderers/live2d.d.ts.map +1 -1
- package/lib/types/client/renderers/live2d.js +120 -24
- package/lib/types/image-dimensions.d.ts +26 -0
- package/lib/types/image-dimensions.d.ts.map +1 -0
- package/lib/types/image-dimensions.js +77 -0
- package/lib/types/index.js +1 -1
- package/lib/types/registry.d.ts.map +1 -1
- package/lib/types/registry.js +43 -1
- package/lib/types/routes.d.ts +6 -1
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +36 -33
- package/package.json +1 -1
- package/src/access.ts +40 -0
- package/src/client/PetSettingsCard.tsx +8 -2
- package/src/client/PetSprite.test.tsx +71 -0
- package/src/client/PetSprite.tsx +10 -2
- package/src/client/renderers/live2d/Live2dVisualMount.test.tsx +72 -0
- package/src/client/renderers/live2d/Live2dVisualMount.tsx +1 -0
- package/src/client/renderers/live2d/runtime.ts +8 -2
- package/src/client/renderers/live2d.test.ts +211 -8
- package/src/client/renderers/live2d.ts +129 -29
- package/src/client/settings-card.module.css +2 -0
- package/src/image-dimensions.test.ts +85 -0
- package/src/image-dimensions.ts +76 -0
- package/src/index.ts +1 -1
- package/src/registry.test.ts +58 -5
- package/src/registry.ts +40 -1
- package/src/routes.ts +38 -34
package/src/routes.ts
CHANGED
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
* domains are platform-registered, so the pet serves its own API and media —
|
|
6
6
|
* the same pattern as dsh-remote-web-ui's '/api/pair' family. The asset route
|
|
7
7
|
* is one prefix registration serving every registry entry (manifest, atlas,
|
|
8
|
-
* optional previews), so adding a pet never touches route wiring.
|
|
8
|
+
* optional previews), so adding a pet never touches route wiring. Both the
|
|
9
|
+
* JSON API, the asset prefix, and the Live2D runtime prefix are loopback-only
|
|
10
|
+
* by default; a live paired-device cookie is an extra allow path when
|
|
11
|
+
* remote-web-ui is loaded.
|
|
9
12
|
* @module @linxin666/dsh-pet/routes
|
|
10
13
|
*/
|
|
11
14
|
|
|
@@ -13,11 +16,12 @@ import { existsSync, realpathSync, statSync } from 'node:fs'
|
|
|
13
16
|
import { readFile } from 'node:fs/promises'
|
|
14
17
|
import { join, sep } from 'node:path'
|
|
15
18
|
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
19
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
16
20
|
import type { WebRoute } from '@deepseek-ai/dsh-host-webserver'
|
|
17
21
|
import type { PetService } from './service.ts'
|
|
18
22
|
import type { PetInteraction } from './affinity.ts'
|
|
19
23
|
import { DECORATION_ASSET_PREFIX, petEntryView, petPackageRoot, type PetEntry, type PetRegistry } from './registry.ts'
|
|
20
|
-
import {
|
|
24
|
+
import { isPetAllowed } from './access.ts'
|
|
21
25
|
import { dshHome } from './dsh-home.ts'
|
|
22
26
|
|
|
23
27
|
/** Browser-facing base path of the pet API. */
|
|
@@ -135,20 +139,20 @@ function readJsonBody(req: IncomingMessage): Promise<unknown> {
|
|
|
135
139
|
})
|
|
136
140
|
}
|
|
137
141
|
|
|
138
|
-
/** Shared route fence:
|
|
139
|
-
function guard(req: IncomingMessage, res: ServerResponse): boolean {
|
|
140
|
-
if (
|
|
142
|
+
/** Shared route fence: loopback always passes; a live paired-device cookie is an extra allow path. */
|
|
143
|
+
function guard(ctx: Context, req: IncomingMessage, res: ServerResponse): boolean {
|
|
144
|
+
if (isPetAllowed(ctx, req)) return true
|
|
141
145
|
json(res, 403, { ok: false, error: 'forbidden: loopback-only' })
|
|
142
146
|
return false
|
|
143
147
|
}
|
|
144
148
|
|
|
145
149
|
/** Wrap one async service call as a GET JSON route. */
|
|
146
|
-
function getRoute(path: string, run: () => Promise<unknown>): WebRoute {
|
|
150
|
+
function getRoute(ctx: Context, path: string, run: () => Promise<unknown>): WebRoute {
|
|
147
151
|
return {
|
|
148
152
|
kind: 'exact',
|
|
149
153
|
path,
|
|
150
154
|
handler: (req: IncomingMessage, res: ServerResponse): void => {
|
|
151
|
-
if (!guard(req, res)) return
|
|
155
|
+
if (!guard(ctx, req, res)) return
|
|
152
156
|
if (!requireMethod(req, res, 'GET')) return
|
|
153
157
|
run().then((value) => json(res, 200, value), (error) => {
|
|
154
158
|
json(res, 500, { ok: false, error: error instanceof Error ? error.message : String(error) })
|
|
@@ -158,12 +162,12 @@ function getRoute(path: string, run: () => Promise<unknown>): WebRoute {
|
|
|
158
162
|
}
|
|
159
163
|
|
|
160
164
|
/** Wrap one async service call as a POST JSON route (body passed through). */
|
|
161
|
-
function postRoute(path: string, run: (body: Record<string, unknown>) => Promise<unknown>): WebRoute {
|
|
165
|
+
function postRoute(ctx: Context, path: string, run: (body: Record<string, unknown>) => Promise<unknown>): WebRoute {
|
|
162
166
|
return {
|
|
163
167
|
kind: 'exact',
|
|
164
168
|
path,
|
|
165
169
|
handler: (req: IncomingMessage, res: ServerResponse): Promise<void> => {
|
|
166
|
-
if (!guard(req, res)) return Promise.resolve()
|
|
170
|
+
if (!guard(ctx, req, res)) return Promise.resolve()
|
|
167
171
|
if (!requireMethod(req, res, 'POST')) return Promise.resolve()
|
|
168
172
|
return readJsonBody(req).then((body) => {
|
|
169
173
|
const record = (typeof body === 'object' && body !== null) ? body as Record<string, unknown> : {}
|
|
@@ -200,10 +204,10 @@ function dirAliases(registry: PetRegistry): Map<string, PetEntry> {
|
|
|
200
204
|
* match; containedRealpath stays as the second layer. Composed pets without
|
|
201
205
|
* a manifest file get a synthesized pet.json.
|
|
202
206
|
*/
|
|
203
|
-
function assetHandler(registry: PetRegistry, caps: PetAssetCaps): WebRoute['handler'] {
|
|
207
|
+
function assetHandler(ctx: Context, registry: PetRegistry, caps: PetAssetCaps): WebRoute['handler'] {
|
|
204
208
|
const aliases = dirAliases(registry)
|
|
205
|
-
return (req: IncomingMessage, res: ServerResponse)
|
|
206
|
-
if (!guard(req, res)) return
|
|
209
|
+
return ((req: IncomingMessage, res: ServerResponse) => {
|
|
210
|
+
if (!guard(ctx, req, res)) return
|
|
207
211
|
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
208
212
|
res.writeHead(405)
|
|
209
213
|
res.end()
|
|
@@ -303,7 +307,7 @@ function assetHandler(registry: PetRegistry, caps: PetAssetCaps): WebRoute['hand
|
|
|
303
307
|
res.end()
|
|
304
308
|
return
|
|
305
309
|
}
|
|
306
|
-
readFile(resolved).then((body) => {
|
|
310
|
+
return readFile(resolved).then((body) => {
|
|
307
311
|
res.writeHead(200, {
|
|
308
312
|
'content-type': mimeFor(resolved),
|
|
309
313
|
'content-length': String(body.byteLength),
|
|
@@ -318,7 +322,7 @@ function assetHandler(registry: PetRegistry, caps: PetAssetCaps): WebRoute['hand
|
|
|
318
322
|
res.writeHead(404)
|
|
319
323
|
res.end()
|
|
320
324
|
})
|
|
321
|
-
}
|
|
325
|
+
}) as WebRoute['handler']
|
|
322
326
|
}
|
|
323
327
|
|
|
324
328
|
/** Browser-facing base path of the plugin runtime files (pet-center M3). */
|
|
@@ -354,9 +358,9 @@ export interface PetRuntimeRoots {
|
|
|
354
358
|
* guidance (the Cubism Core is user-supplied, so its absence is a normal
|
|
355
359
|
* state, not an error).
|
|
356
360
|
*/
|
|
357
|
-
function runtimeHandler(roots: { runtimeDir: string; vendorDir: string }): WebRoute['handler'] {
|
|
358
|
-
return (req: IncomingMessage, res: ServerResponse)
|
|
359
|
-
if (!guard(req, res)) return
|
|
361
|
+
function runtimeHandler(ctx: Context, roots: { runtimeDir: string; vendorDir: string }): WebRoute['handler'] {
|
|
362
|
+
return ((req: IncomingMessage, res: ServerResponse) => {
|
|
363
|
+
if (!guard(ctx, req, res)) return
|
|
360
364
|
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
361
365
|
res.writeHead(405)
|
|
362
366
|
res.end()
|
|
@@ -409,7 +413,7 @@ function runtimeHandler(roots: { runtimeDir: string; vendorDir: string }): WebRo
|
|
|
409
413
|
res.end()
|
|
410
414
|
return
|
|
411
415
|
}
|
|
412
|
-
readFile(resolved).then((body) => {
|
|
416
|
+
return readFile(resolved).then((body) => {
|
|
413
417
|
res.writeHead(200, {
|
|
414
418
|
'content-type': name.endsWith('.map') ? 'application/json' : 'application/javascript; charset=utf-8',
|
|
415
419
|
'content-length': String(body.byteLength),
|
|
@@ -424,7 +428,7 @@ function runtimeHandler(roots: { runtimeDir: string; vendorDir: string }): WebRo
|
|
|
424
428
|
res.writeHead(404)
|
|
425
429
|
res.end()
|
|
426
430
|
})
|
|
427
|
-
}
|
|
431
|
+
}) as WebRoute['handler']
|
|
428
432
|
}
|
|
429
433
|
|
|
430
434
|
/**
|
|
@@ -434,9 +438,9 @@ function runtimeHandler(roots: { runtimeDir: string; vendorDir: string }): WebRo
|
|
|
434
438
|
* match, with realpath containment and the same size ceilings as pet
|
|
435
439
|
* assets. Crafted '..' or '.' segments never match the normalized closure.
|
|
436
440
|
*/
|
|
437
|
-
function decorationHandler(registry: PetRegistry, caps: PetAssetCaps): WebRoute['handler'] {
|
|
441
|
+
function decorationHandler(ctx: Context, registry: PetRegistry, caps: PetAssetCaps): WebRoute['handler'] {
|
|
438
442
|
return (req: IncomingMessage, res: ServerResponse): void => {
|
|
439
|
-
if (!guard(req, res)) return
|
|
443
|
+
if (!guard(ctx, req, res)) return
|
|
440
444
|
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
441
445
|
res.writeHead(405)
|
|
442
446
|
res.end()
|
|
@@ -547,34 +551,34 @@ function decorationHandler(registry: PetRegistry, caps: PetAssetCaps): WebRoute[
|
|
|
547
551
|
}
|
|
548
552
|
|
|
549
553
|
/** Build the full route family (API + assets + runtime) for one service. */
|
|
550
|
-
export function makePetRoutes(deps: { service: PetService; assetCaps?: PetAssetCaps } & PetRuntimeRoots): WebRoute[] {
|
|
551
|
-
const { service } = deps
|
|
554
|
+
export function makePetRoutes(deps: { service: PetService; ctx: Context; assetCaps?: PetAssetCaps } & PetRuntimeRoots): WebRoute[] {
|
|
555
|
+
const { service, ctx } = deps
|
|
552
556
|
const apiRoutes: WebRoute[] = [
|
|
553
|
-
getRoute(PET_API_PREFIX + '/state', () => service.state()),
|
|
554
|
-
getRoute(PET_API_PREFIX + '/pets', () => service.pets()),
|
|
555
|
-
getRoute(PET_API_PREFIX + '/diagnostics', () => service.diagnostics()),
|
|
556
|
-
postRoute(PET_API_PREFIX + '/interact', (body) => {
|
|
557
|
+
getRoute(ctx, PET_API_PREFIX + '/state', () => service.state()),
|
|
558
|
+
getRoute(ctx, PET_API_PREFIX + '/pets', () => service.pets()),
|
|
559
|
+
getRoute(ctx, PET_API_PREFIX + '/diagnostics', () => service.diagnostics()),
|
|
560
|
+
postRoute(ctx, PET_API_PREFIX + '/interact', (body) => {
|
|
557
561
|
const kind = body.kind as PetInteraction | undefined
|
|
558
562
|
if (kind !== 'pet' && kind !== 'feed') return Promise.reject(new Error('invalid-kind'))
|
|
559
563
|
return service.interact(kind)
|
|
560
564
|
}),
|
|
561
|
-
postRoute(PET_API_PREFIX + '/set-visible', (body) => {
|
|
565
|
+
postRoute(ctx, PET_API_PREFIX + '/set-visible', (body) => {
|
|
562
566
|
const visible = body.visible
|
|
563
567
|
if (typeof visible !== 'boolean') return Promise.reject(new Error('invalid-visible'))
|
|
564
568
|
return service.setVisible(visible)
|
|
565
569
|
}),
|
|
566
|
-
postRoute(PET_API_PREFIX + '/set-config', (body) => service.setConfig({
|
|
570
|
+
postRoute(ctx, PET_API_PREFIX + '/set-config', (body) => service.setConfig({
|
|
567
571
|
...(typeof body.size === 'number' ? { size: body.size } : {}),
|
|
568
572
|
...(typeof body.right === 'number' ? { right: body.right } : {}),
|
|
569
573
|
...(typeof body.bottom === 'number' ? { bottom: body.bottom } : {}),
|
|
570
574
|
...(typeof body.visible === 'boolean' ? { visible: body.visible } : {}),
|
|
571
575
|
})),
|
|
572
|
-
postRoute(PET_API_PREFIX + '/set-name', (body) => {
|
|
576
|
+
postRoute(ctx, PET_API_PREFIX + '/set-name', (body) => {
|
|
573
577
|
const name = body.name
|
|
574
578
|
if (typeof name !== 'string') return Promise.reject(new Error('invalid-name'))
|
|
575
579
|
return service.setName(name)
|
|
576
580
|
}),
|
|
577
|
-
postRoute(PET_API_PREFIX + '/set-pet', (body) => {
|
|
581
|
+
postRoute(ctx, PET_API_PREFIX + '/set-pet', (body) => {
|
|
578
582
|
const petId = body.petId
|
|
579
583
|
if (typeof petId !== 'string') return Promise.reject(new Error('invalid-pet'))
|
|
580
584
|
return service.setPetId(petId)
|
|
@@ -584,13 +588,13 @@ export function makePetRoutes(deps: { service: PetService; assetCaps?: PetAssetC
|
|
|
584
588
|
const assetRoute: WebRoute = {
|
|
585
589
|
kind: 'prefix',
|
|
586
590
|
path: PET_ASSET_PREFIX,
|
|
587
|
-
handler: assetHandler(service.registrySnapshot(), deps.assetCaps ?? PET_ASSET_CAPS),
|
|
591
|
+
handler: assetHandler(ctx, service.registrySnapshot(), deps.assetCaps ?? PET_ASSET_CAPS),
|
|
588
592
|
}
|
|
589
593
|
|
|
590
594
|
const runtimeRoute: WebRoute = {
|
|
591
595
|
kind: 'prefix',
|
|
592
596
|
path: PET_RUNTIME_PREFIX,
|
|
593
|
-
handler: runtimeHandler({
|
|
597
|
+
handler: runtimeHandler(ctx, {
|
|
594
598
|
runtimeDir: deps.runtimeDir ?? join(dshHome(), 'pets', '.runtime'),
|
|
595
599
|
vendorDir: deps.vendorDir ?? join(petPackageRoot(import.meta.url), 'lib'),
|
|
596
600
|
}),
|
|
@@ -599,7 +603,7 @@ export function makePetRoutes(deps: { service: PetService; assetCaps?: PetAssetC
|
|
|
599
603
|
const decorationRoute: WebRoute = {
|
|
600
604
|
kind: 'prefix',
|
|
601
605
|
path: DECORATION_ASSET_PREFIX,
|
|
602
|
-
handler: decorationHandler(service.registrySnapshot(), deps.assetCaps ?? PET_ASSET_CAPS),
|
|
606
|
+
handler: decorationHandler(ctx, service.registrySnapshot(), deps.assetCaps ?? PET_ASSET_CAPS),
|
|
603
607
|
}
|
|
604
608
|
|
|
605
609
|
return [...apiRoutes, assetRoute, runtimeRoute, decorationRoute]
|