dsh-any-background 0.2.2 → 0.2.4
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.md +141 -140
- package/README.zh.md +141 -140
- package/lib/client.js +215 -38
- package/lib/client.js.map +1 -1
- package/lib/index.js +243 -54
- package/package.json +11 -14
package/lib/index.js
CHANGED
|
@@ -24,6 +24,8 @@ const WALLPAPER_FILE = "wallpaper.jpg";
|
|
|
24
24
|
const VIDEO_ROUTE = "/dsh-any-background/video";
|
|
25
25
|
const UPLOAD_ROUTE = "/dsh-any-background/video/upload";
|
|
26
26
|
const UPLOAD_TMP = "wallpaper.upload.tmp";
|
|
27
|
+
const WALLPAPER_FETCH_MAX = 26214400;
|
|
28
|
+
const WALLPAPER_FETCH_TIMEOUT = 2e4;
|
|
27
29
|
function videoFileName(mime) {
|
|
28
30
|
switch (mime) {
|
|
29
31
|
case "video/mp4": return "wallpaper.mp4";
|
|
@@ -278,6 +280,94 @@ async function writeWallpaper(dataUrl) {
|
|
|
278
280
|
return false;
|
|
279
281
|
}
|
|
280
282
|
}
|
|
283
|
+
/** Sniff an image's MIME from its leading magic bytes (defaults to JPEG). */
|
|
284
|
+
function sniffImageMime(buf) {
|
|
285
|
+
if (buf.length >= 4 && buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
|
|
286
|
+
if (buf.length >= 3 && buf[0] === 255 && buf[1] === 216 && buf[2] === 255) return "image/jpeg";
|
|
287
|
+
if (buf.length >= 6 && buf[0] === 71 && buf[1] === 73 && buf[2] === 70) return "image/gif";
|
|
288
|
+
if (buf.length >= 12 && buf[0] === 82 && buf[1] === 73 && buf[2] === 70 && buf[3] === 70 && buf[8] === 87 && buf[9] === 69 && buf[10] === 66 && buf[11] === 80) return "image/webp";
|
|
289
|
+
return "image/jpeg";
|
|
290
|
+
}
|
|
291
|
+
/** Download a wallpaper from a network URL and persist it into the local
|
|
292
|
+
* wallpaper.jpg slot (replacing whatever was stored), so type switches,
|
|
293
|
+
* export and import keep working through the existing data-URL path.
|
|
294
|
+
* null removes the wallpaper. Returns { ok, dataUrl?, error? }. */
|
|
295
|
+
async function writeWallpaperFromUrl(url) {
|
|
296
|
+
if (url === null) {
|
|
297
|
+
const ok = await writeWallpaper(null);
|
|
298
|
+
return {
|
|
299
|
+
ok,
|
|
300
|
+
dataUrl: null,
|
|
301
|
+
error: ok ? void 0 : "remove failed"
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
let u;
|
|
305
|
+
try {
|
|
306
|
+
u = new URL(url);
|
|
307
|
+
} catch {
|
|
308
|
+
return {
|
|
309
|
+
ok: false,
|
|
310
|
+
error: "invalid url"
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return {
|
|
314
|
+
ok: false,
|
|
315
|
+
error: "unsupported scheme"
|
|
316
|
+
};
|
|
317
|
+
let res;
|
|
318
|
+
try {
|
|
319
|
+
const ctl = new AbortController();
|
|
320
|
+
const timer = setTimeout(() => ctl.abort(), WALLPAPER_FETCH_TIMEOUT);
|
|
321
|
+
try {
|
|
322
|
+
res = await fetch(url, {
|
|
323
|
+
redirect: "follow",
|
|
324
|
+
signal: ctl.signal
|
|
325
|
+
});
|
|
326
|
+
} finally {
|
|
327
|
+
clearTimeout(timer);
|
|
328
|
+
}
|
|
329
|
+
} catch (e) {
|
|
330
|
+
return {
|
|
331
|
+
ok: false,
|
|
332
|
+
error: e instanceof Error && e.name === "AbortError" ? "timeout" : "network error"
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
if (!res.ok) return {
|
|
336
|
+
ok: false,
|
|
337
|
+
error: `http ${res.status}`
|
|
338
|
+
};
|
|
339
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
340
|
+
if (ct && !/^image\//.test(ct)) return {
|
|
341
|
+
ok: false,
|
|
342
|
+
error: "not an image"
|
|
343
|
+
};
|
|
344
|
+
let buf;
|
|
345
|
+
try {
|
|
346
|
+
const arr = await res.arrayBuffer();
|
|
347
|
+
if (arr.byteLength === 0) return {
|
|
348
|
+
ok: false,
|
|
349
|
+
error: "empty response"
|
|
350
|
+
};
|
|
351
|
+
if (arr.byteLength > WALLPAPER_FETCH_MAX) return {
|
|
352
|
+
ok: false,
|
|
353
|
+
error: "too large"
|
|
354
|
+
};
|
|
355
|
+
buf = Buffer.from(arr);
|
|
356
|
+
} catch {
|
|
357
|
+
return {
|
|
358
|
+
ok: false,
|
|
359
|
+
error: "read failed"
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
const dataUrl = `data:${sniffImageMime(buf)};base64,${buf.toString("base64")}`;
|
|
363
|
+
return await writeWallpaper(dataUrl) ? {
|
|
364
|
+
ok: true,
|
|
365
|
+
dataUrl
|
|
366
|
+
} : {
|
|
367
|
+
ok: false,
|
|
368
|
+
error: "write failed"
|
|
369
|
+
};
|
|
370
|
+
}
|
|
281
371
|
async function videoUrl() {
|
|
282
372
|
return await findVideoFile() ? VIDEO_ROUTE : null;
|
|
283
373
|
}
|
|
@@ -447,65 +537,164 @@ async function handleVideoUpload(req, res) {
|
|
|
447
537
|
}
|
|
448
538
|
}
|
|
449
539
|
const NS = "dshAnyBackground";
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}
|
|
484
|
-
} catch (e) {
|
|
485
|
-
return {
|
|
540
|
+
const RPC_CHANNEL = "/dsh-any-background";
|
|
541
|
+
const RPC_BODY_MAX = 314572800;
|
|
542
|
+
/** Dispatch one decoded RPC method to the matching persistence routine and
|
|
543
|
+
* return the wire `result` half of the server-response envelope. */
|
|
544
|
+
async function handleRpcMethod(endpoint, payload) {
|
|
545
|
+
const method = endpoint.slice(`${NS}/`.length);
|
|
546
|
+
try {
|
|
547
|
+
switch (method) {
|
|
548
|
+
case "read": return {
|
|
549
|
+
ok: true,
|
|
550
|
+
value: {
|
|
551
|
+
config: await readConfig(),
|
|
552
|
+
wallpaper: await readWallpaper(),
|
|
553
|
+
videoUrl: await videoUrl()
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
case "writeConfig": return {
|
|
557
|
+
ok: true,
|
|
558
|
+
value: await writeConfig(payload?.config ?? {})
|
|
559
|
+
};
|
|
560
|
+
case "setWallpaper": return {
|
|
561
|
+
ok: true,
|
|
562
|
+
value: await writeWallpaper(payload?.dataUrl ?? null)
|
|
563
|
+
};
|
|
564
|
+
case "setVideo": return {
|
|
565
|
+
ok: true,
|
|
566
|
+
value: await writeVideo(payload?.dataUrl ?? null)
|
|
567
|
+
};
|
|
568
|
+
case "setWallpaperUrl": return {
|
|
569
|
+
ok: true,
|
|
570
|
+
value: await writeWallpaperFromUrl(payload?.url ?? null)
|
|
571
|
+
};
|
|
572
|
+
default: return {
|
|
486
573
|
ok: false,
|
|
487
574
|
error: {
|
|
488
|
-
code: "
|
|
489
|
-
message:
|
|
490
|
-
details: {}
|
|
575
|
+
code: "dsh-any-background/bad-request",
|
|
576
|
+
message: `unknown endpoint ${endpoint}`,
|
|
577
|
+
details: { issues: [] }
|
|
491
578
|
}
|
|
492
579
|
};
|
|
493
580
|
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
581
|
+
} catch (e) {
|
|
582
|
+
return {
|
|
583
|
+
ok: false,
|
|
584
|
+
error: {
|
|
585
|
+
code: "dsh-any-background/internal",
|
|
586
|
+
message: e instanceof Error ? e.message : String(e),
|
|
587
|
+
details: {}
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function apply(ctx) {
|
|
593
|
+
ctx.inject(["connection", "webServer"], (webCtx) => {
|
|
594
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
595
|
+
kind: "prefix",
|
|
596
|
+
path: RPC_CHANNEL,
|
|
597
|
+
handler: async (req, res) => {
|
|
598
|
+
const rejection = webCtx.connection.requestRejection(req);
|
|
599
|
+
if (rejection !== void 0) {
|
|
600
|
+
res.writeHead(rejection);
|
|
601
|
+
res.end(rejection === 401 ? "unauthorized" : "forbidden");
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (req.method !== "POST") {
|
|
605
|
+
res.writeHead(405, { "Content-Type": "application/json" });
|
|
606
|
+
res.end(JSON.stringify({
|
|
607
|
+
ok: false,
|
|
608
|
+
error: {
|
|
609
|
+
code: "dsh-any-background/bad-request",
|
|
610
|
+
message: "expected POST",
|
|
611
|
+
details: {}
|
|
612
|
+
}
|
|
613
|
+
}));
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
const pathname = new URL(req.url ?? "/", "http://dsh.internal").pathname;
|
|
617
|
+
const endpoint = pathname.startsWith(`${RPC_CHANNEL}/`) ? pathname.slice(20) : void 0;
|
|
618
|
+
if (endpoint === void 0 || endpoint.length === 0) {
|
|
619
|
+
res.writeHead(404);
|
|
620
|
+
res.end();
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
const chunks = [];
|
|
624
|
+
let received = 0;
|
|
625
|
+
for await (const chunk of req) {
|
|
626
|
+
const buf = chunk;
|
|
627
|
+
received += buf.byteLength;
|
|
628
|
+
if (received > RPC_BODY_MAX) {
|
|
629
|
+
res.writeHead(413, { connection: "close" });
|
|
630
|
+
res.end();
|
|
631
|
+
req.destroy();
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
chunks.push(buf);
|
|
635
|
+
}
|
|
636
|
+
let env;
|
|
637
|
+
try {
|
|
638
|
+
env = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
639
|
+
} catch {
|
|
640
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
641
|
+
res.end(JSON.stringify({
|
|
642
|
+
ok: false,
|
|
643
|
+
error: {
|
|
644
|
+
code: "dsh-any-background/bad-request",
|
|
645
|
+
message: "body is not JSON",
|
|
646
|
+
details: {}
|
|
647
|
+
}
|
|
648
|
+
}));
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (env === null || typeof env !== "object" || env.type !== "client-request" || typeof env.rpcId !== "string" || typeof env.method !== "string") {
|
|
652
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
653
|
+
res.end(JSON.stringify({
|
|
654
|
+
ok: false,
|
|
655
|
+
error: {
|
|
656
|
+
code: "dsh-any-background/bad-request",
|
|
657
|
+
message: "invalid client-request envelope",
|
|
658
|
+
details: {}
|
|
659
|
+
}
|
|
660
|
+
}));
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
if (env.method !== endpoint) {
|
|
664
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
665
|
+
res.end(JSON.stringify({
|
|
666
|
+
type: "server-response",
|
|
667
|
+
rpcId: env.rpcId,
|
|
668
|
+
result: {
|
|
669
|
+
ok: false,
|
|
670
|
+
error: {
|
|
671
|
+
code: "dsh-any-background/bad-request",
|
|
672
|
+
message: `method ${env.method} does not match endpoint ${endpoint}`,
|
|
673
|
+
details: { issues: [] }
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}));
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
const result = await handleRpcMethod(endpoint, env.payload);
|
|
680
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
681
|
+
res.end(JSON.stringify({
|
|
682
|
+
type: "server-response",
|
|
683
|
+
rpcId: env.rpcId,
|
|
684
|
+
result
|
|
685
|
+
}));
|
|
686
|
+
}
|
|
687
|
+
}), "dsh-any-background: rpc channel");
|
|
688
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
689
|
+
kind: "prefix",
|
|
690
|
+
path: VIDEO_ROUTE,
|
|
691
|
+
handler: serveVideo
|
|
692
|
+
}), "dsh-any-background: video route");
|
|
693
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
694
|
+
kind: "exact",
|
|
695
|
+
path: UPLOAD_ROUTE,
|
|
696
|
+
handler: handleVideoUpload
|
|
697
|
+
}), "dsh-any-background: upload route");
|
|
509
698
|
});
|
|
510
699
|
}
|
|
511
700
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-any-background",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Appearance plugin for DeepSeek Harness: custom theme color (PS-style color wheel), background wallpaper with opacity/blur controls, and separate main/settings interface opacity.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
"license": "MIT",
|
|
23
23
|
"author": "Tkingxiao",
|
|
24
24
|
"type": "module",
|
|
25
|
+
"engines": {
|
|
26
|
+
"dsh": ">=0.1.2-rc.1"
|
|
27
|
+
},
|
|
25
28
|
"exports": {
|
|
26
29
|
".": {
|
|
27
30
|
"default": "./lib/index.js"
|
|
@@ -63,14 +66,13 @@
|
|
|
63
66
|
"@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6"
|
|
64
67
|
},
|
|
65
68
|
"peerDependencies": {
|
|
66
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
67
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.
|
|
68
|
-
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
|
|
69
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
70
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.2",
|
|
69
71
|
"@deepseek-ai/dsh-client-store": "^0.1.2-alpha.2",
|
|
70
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.
|
|
71
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.
|
|
72
|
-
"@deepseek-ai/dsh-client-ui-theme": "^0.1.
|
|
73
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
72
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.2-alpha.2",
|
|
73
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.2-alpha.2",
|
|
74
|
+
"@deepseek-ai/dsh-client-ui-theme": "^0.1.2-alpha.2",
|
|
75
|
+
"@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
|
|
74
76
|
"react": "^18.2.0"
|
|
75
77
|
},
|
|
76
78
|
"peerDependenciesMeta": {
|
|
@@ -89,9 +91,6 @@
|
|
|
89
91
|
"@deepseek-ai/dsh-client-ui-settings": {
|
|
90
92
|
"optional": true
|
|
91
93
|
},
|
|
92
|
-
"@deepseek-ai/dsh-client-runtime": {
|
|
93
|
-
"optional": true
|
|
94
|
-
},
|
|
95
94
|
"@deepseek-ai/dsh-client-ui-theme": {
|
|
96
95
|
"optional": true
|
|
97
96
|
},
|
|
@@ -109,13 +108,11 @@
|
|
|
109
108
|
"client": {
|
|
110
109
|
"platform": "web",
|
|
111
110
|
"inject": [
|
|
112
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
113
111
|
"@deepseek-ai/dsh-client-store",
|
|
114
112
|
"@deepseek-ai/dsh-client-locale",
|
|
115
113
|
"@deepseek-ai/dsh-client-connection",
|
|
116
114
|
"@deepseek-ai/dsh-client-ui-theme",
|
|
117
|
-
"@deepseek-ai/dsh-client-ui-settings"
|
|
118
|
-
"@deepseek-ai/dsh-client-ui-renderer"
|
|
115
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
119
116
|
],
|
|
120
117
|
"immediately": true
|
|
121
118
|
}
|