dsh-any-background 0.2.5 → 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.md +15 -3
- package/README.zh.md +15 -3
- package/lib/client.js +812 -382
- package/lib/client.js.map +1 -1
- package/lib/index.js +347 -20
- package/package.json +133 -121
package/lib/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { access, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
2
2
|
import { createReadStream, createWriteStream } from "node:fs";
|
|
3
|
+
import { Readable } from "node:stream";
|
|
3
4
|
import { dshHomePath } from "@deepseek-ai/dsh-home-paths";
|
|
4
5
|
//#region src/index.ts
|
|
5
6
|
/**
|
|
@@ -24,9 +25,17 @@ const WALLPAPER_FILE = "wallpaper.jpg";
|
|
|
24
25
|
const ROTATION_DIR = "rotation";
|
|
25
26
|
const VIDEO_ROUTE = "/dsh-any-background/video";
|
|
26
27
|
const UPLOAD_ROUTE = "/dsh-any-background/video/upload";
|
|
28
|
+
const WALLPAPER_ROUTE = "/dsh-any-background/wallpaper";
|
|
29
|
+
const WALLPAPER_UPLOAD_ROUTE = "/dsh-any-background/wallpaper/upload";
|
|
27
30
|
const UPLOAD_TMP = "wallpaper.upload.tmp";
|
|
31
|
+
const VIDEO_UPLOAD_TMP = "video.upload.tmp";
|
|
32
|
+
const WALLPAPER_UPLOAD_MAX = 104857600;
|
|
33
|
+
const VIDEO_UPLOAD_MAX = 2147483648;
|
|
28
34
|
const WALLPAPER_FETCH_MAX = 26214400;
|
|
29
35
|
const WALLPAPER_FETCH_TIMEOUT = 2e4;
|
|
36
|
+
const VIDEO_FETCH_MAX = 2147483648;
|
|
37
|
+
const VIDEO_FETCH_TIMEOUT = 6e4;
|
|
38
|
+
const VIDEO_FETCH_IDLE_TIMEOUT = 6e4;
|
|
30
39
|
function videoFileName(mime) {
|
|
31
40
|
switch (mime) {
|
|
32
41
|
case "video/mp4": return "wallpaper.mp4";
|
|
@@ -236,7 +245,8 @@ function normalizeGeneratedBg(p) {
|
|
|
236
245
|
preset: [
|
|
237
246
|
"aurora",
|
|
238
247
|
"nebula",
|
|
239
|
-
"noise"
|
|
248
|
+
"noise",
|
|
249
|
+
"starfield"
|
|
240
250
|
].includes(p.preset) ? p.preset : "aurora",
|
|
241
251
|
speed: clamp(p.speed, 0, 2, .3),
|
|
242
252
|
scale: clamp(p.scale, .3, 3, 1),
|
|
@@ -247,7 +257,10 @@ function normalizeGeneratedBg(p) {
|
|
|
247
257
|
preset: [
|
|
248
258
|
"dots",
|
|
249
259
|
"waves",
|
|
250
|
-
"poly"
|
|
260
|
+
"poly",
|
|
261
|
+
"rain",
|
|
262
|
+
"contour",
|
|
263
|
+
"meta"
|
|
251
264
|
].includes(p.preset) ? p.preset : "dots",
|
|
252
265
|
density: clamp(p.density, 0, 1, .5),
|
|
253
266
|
scale: clamp(p.scale, .3, 3, 1),
|
|
@@ -376,10 +389,12 @@ async function writeConfig(config) {
|
|
|
376
389
|
return false;
|
|
377
390
|
}
|
|
378
391
|
}
|
|
379
|
-
|
|
392
|
+
/** The wallpaper slot is served over HTTP (never shipped as base64 inside the
|
|
393
|
+
* read RPC): the browser decodes it natively through the same pipeline as any
|
|
394
|
+
* <img>, so boot only transfers a tiny URL instead of the whole image. */
|
|
395
|
+
async function wallpaperServeUrl() {
|
|
380
396
|
try {
|
|
381
|
-
|
|
382
|
-
return `data:${sniffImageMime(buf)};base64,${buf.toString("base64")}`;
|
|
397
|
+
return (await stat(wallpaperPath())).size > 0 ? WALLPAPER_ROUTE : null;
|
|
383
398
|
} catch {
|
|
384
399
|
return null;
|
|
385
400
|
}
|
|
@@ -410,15 +425,16 @@ function sniffImageMime(buf) {
|
|
|
410
425
|
return "image/jpeg";
|
|
411
426
|
}
|
|
412
427
|
/** Download a wallpaper from a network URL and persist it into the local
|
|
413
|
-
* wallpaper.jpg slot (replacing whatever was stored), so type switches
|
|
414
|
-
*
|
|
415
|
-
*
|
|
428
|
+
* wallpaper.jpg slot (replacing whatever was stored), so type switches and
|
|
429
|
+
* rotation keep working through the single active slot. The response carries
|
|
430
|
+
* the serve URL, never the bytes. null removes the wallpaper.
|
|
431
|
+
* Returns { ok, wallpaperUrl?, error? }. */
|
|
416
432
|
async function writeWallpaperFromUrl(url) {
|
|
417
433
|
if (url === null) {
|
|
418
434
|
const ok = await writeWallpaper(null);
|
|
419
435
|
return {
|
|
420
436
|
ok,
|
|
421
|
-
|
|
437
|
+
wallpaperUrl: null,
|
|
422
438
|
error: ok ? void 0 : "remove failed"
|
|
423
439
|
};
|
|
424
440
|
}
|
|
@@ -480,13 +496,19 @@ async function writeWallpaperFromUrl(url) {
|
|
|
480
496
|
error: "read failed"
|
|
481
497
|
};
|
|
482
498
|
}
|
|
483
|
-
|
|
484
|
-
|
|
499
|
+
await ensureDir();
|
|
500
|
+
try {
|
|
501
|
+
await writeFile(wallpaperPath(), buf);
|
|
502
|
+
} catch (e) {
|
|
503
|
+
console.error("dsh-any-background: failed to write the downloaded wallpaper", e);
|
|
504
|
+
return {
|
|
505
|
+
ok: false,
|
|
506
|
+
error: "write failed"
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
return {
|
|
485
510
|
ok: true,
|
|
486
|
-
|
|
487
|
-
} : {
|
|
488
|
-
ok: false,
|
|
489
|
-
error: "write failed"
|
|
511
|
+
wallpaperUrl: WALLPAPER_ROUTE
|
|
490
512
|
};
|
|
491
513
|
}
|
|
492
514
|
const rotationDir = () => dshHomePath(DATA_DIR, ROTATION_DIR);
|
|
@@ -578,7 +600,8 @@ async function handleRotationRemove(payload) {
|
|
|
578
600
|
};
|
|
579
601
|
}
|
|
580
602
|
/** Activate a rotation item: copy its bytes over the active wallpaper slot and
|
|
581
|
-
* return the
|
|
603
|
+
* return the serve URL so the client applies it live (bytes never round-trip
|
|
604
|
+
* through the RPC response). */
|
|
582
605
|
async function handleRotationSet(payload) {
|
|
583
606
|
const idx = payload?.index;
|
|
584
607
|
if (typeof idx !== "number" || !isFinite(idx)) return {
|
|
@@ -612,12 +635,155 @@ async function handleRotationSet(payload) {
|
|
|
612
635
|
}
|
|
613
636
|
return {
|
|
614
637
|
ok: true,
|
|
615
|
-
|
|
638
|
+
wallpaperUrl: WALLPAPER_ROUTE
|
|
616
639
|
};
|
|
617
640
|
}
|
|
618
641
|
async function videoUrl() {
|
|
619
642
|
return await findVideoFile() ? VIDEO_ROUTE : null;
|
|
620
643
|
}
|
|
644
|
+
/** Guess a video MIME from the URL's path extension (fallback for servers that
|
|
645
|
+
* send no precise Content-Type). */
|
|
646
|
+
function videoMimeFromUrl(u) {
|
|
647
|
+
const p = u.pathname.toLowerCase();
|
|
648
|
+
if (/\.(mp4|m4v)$/.test(p)) return "video/mp4";
|
|
649
|
+
if (/\.webm$/.test(p)) return "video/webm";
|
|
650
|
+
if (/\.(ogg|ogv)$/.test(p)) return "video/ogg";
|
|
651
|
+
if (/\.(mov|qt)$/.test(p)) return "video/quicktime";
|
|
652
|
+
if (/\.(mkv|mk3d|mka)$/.test(p)) return "video/x-matroska";
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
/** Download a background video from a network URL and store it in the video
|
|
656
|
+
* slot (streamed to a temp file — never buffered whole), then record the MIME
|
|
657
|
+
* in the config so findVideoFile/serve resolve immediately. */
|
|
658
|
+
async function writeVideoFromUrl(url) {
|
|
659
|
+
if (url === null) return {
|
|
660
|
+
ok: false,
|
|
661
|
+
error: "invalid url"
|
|
662
|
+
};
|
|
663
|
+
let u;
|
|
664
|
+
try {
|
|
665
|
+
u = new URL(url);
|
|
666
|
+
} catch {
|
|
667
|
+
return {
|
|
668
|
+
ok: false,
|
|
669
|
+
error: "invalid url"
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return {
|
|
673
|
+
ok: false,
|
|
674
|
+
error: "unsupported scheme"
|
|
675
|
+
};
|
|
676
|
+
let res;
|
|
677
|
+
try {
|
|
678
|
+
const ctl = new AbortController();
|
|
679
|
+
const timer = setTimeout(() => ctl.abort(), VIDEO_FETCH_TIMEOUT);
|
|
680
|
+
try {
|
|
681
|
+
res = await fetch(url, {
|
|
682
|
+
redirect: "follow",
|
|
683
|
+
signal: ctl.signal
|
|
684
|
+
});
|
|
685
|
+
} finally {
|
|
686
|
+
clearTimeout(timer);
|
|
687
|
+
}
|
|
688
|
+
} catch (e) {
|
|
689
|
+
return {
|
|
690
|
+
ok: false,
|
|
691
|
+
error: e instanceof Error && e.name === "AbortError" ? "timeout" : "network error"
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
if (!res.ok) return {
|
|
695
|
+
ok: false,
|
|
696
|
+
error: `http ${res.status}`
|
|
697
|
+
};
|
|
698
|
+
let mime = (res.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
|
|
699
|
+
if (mime === "" || mime === "application/octet-stream" || mime === "binary/octet-stream") mime = videoMimeFromUrl(u) ?? "video/mp4";
|
|
700
|
+
if (!mime.startsWith("video/")) return {
|
|
701
|
+
ok: false,
|
|
702
|
+
error: "not a video"
|
|
703
|
+
};
|
|
704
|
+
const declared = Number(res.headers.get("content-length") ?? "");
|
|
705
|
+
if (Number.isFinite(declared) && declared > VIDEO_FETCH_MAX) return {
|
|
706
|
+
ok: false,
|
|
707
|
+
error: "too large"
|
|
708
|
+
};
|
|
709
|
+
await ensureDir();
|
|
710
|
+
const tmp = dshHomePath(DATA_DIR, "video.download.tmp");
|
|
711
|
+
const target = videoPathFor(mime);
|
|
712
|
+
try {
|
|
713
|
+
if (res.body === null) return {
|
|
714
|
+
ok: false,
|
|
715
|
+
error: "empty response"
|
|
716
|
+
};
|
|
717
|
+
const out = createWriteStream(tmp);
|
|
718
|
+
let received = 0;
|
|
719
|
+
let failed = false;
|
|
720
|
+
const fail = () => {
|
|
721
|
+
if (failed) return;
|
|
722
|
+
failed = true;
|
|
723
|
+
out.destroy();
|
|
724
|
+
rm(tmp, { force: true });
|
|
725
|
+
};
|
|
726
|
+
const nodeStream = Readable.fromWeb(res.body);
|
|
727
|
+
let idle = null;
|
|
728
|
+
const pokeIdle = () => {
|
|
729
|
+
if (idle) clearTimeout(idle);
|
|
730
|
+
idle = setTimeout(() => {
|
|
731
|
+
nodeStream.destroy();
|
|
732
|
+
fail();
|
|
733
|
+
}, VIDEO_FETCH_IDLE_TIMEOUT);
|
|
734
|
+
};
|
|
735
|
+
nodeStream.on("data", (chunk) => {
|
|
736
|
+
pokeIdle();
|
|
737
|
+
received += chunk.byteLength;
|
|
738
|
+
if (received > VIDEO_FETCH_MAX) {
|
|
739
|
+
nodeStream.destroy();
|
|
740
|
+
fail();
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
nodeStream.on("end", () => {
|
|
744
|
+
if (idle) clearTimeout(idle);
|
|
745
|
+
idle = null;
|
|
746
|
+
});
|
|
747
|
+
nodeStream.on("aborted", fail);
|
|
748
|
+
nodeStream.on("error", fail);
|
|
749
|
+
out.on("error", fail);
|
|
750
|
+
pokeIdle();
|
|
751
|
+
await new Promise((resolve, reject) => {
|
|
752
|
+
nodeStream.pipe(out);
|
|
753
|
+
out.on("finish", () => resolve());
|
|
754
|
+
out.on("close", () => {
|
|
755
|
+
if (failed) reject(/* @__PURE__ */ new Error("download failed"));
|
|
756
|
+
});
|
|
757
|
+
nodeStream.on("error", () => reject(/* @__PURE__ */ new Error("download failed")));
|
|
758
|
+
});
|
|
759
|
+
if (failed) return {
|
|
760
|
+
ok: false,
|
|
761
|
+
error: "read failed"
|
|
762
|
+
};
|
|
763
|
+
for (const name of VIDEO_CANDIDATES) {
|
|
764
|
+
const p = dshHomePath(DATA_DIR, name);
|
|
765
|
+
if (p !== target) await rm(p, { force: true });
|
|
766
|
+
}
|
|
767
|
+
await rm(target, { force: true });
|
|
768
|
+
await rename(tmp, target);
|
|
769
|
+
const cfg = await readConfig();
|
|
770
|
+
if (cfg.videoMime !== mime) {
|
|
771
|
+
cfg.videoMime = mime;
|
|
772
|
+
await writeConfig(cfg);
|
|
773
|
+
}
|
|
774
|
+
return {
|
|
775
|
+
ok: true,
|
|
776
|
+
mime
|
|
777
|
+
};
|
|
778
|
+
} catch (e) {
|
|
779
|
+
console.error("dsh-any-background: failed to download the background video", e);
|
|
780
|
+
rm(tmp, { force: true });
|
|
781
|
+
return {
|
|
782
|
+
ok: false,
|
|
783
|
+
error: e instanceof Error && e.message === "download failed" ? "read failed" : "write failed"
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
}
|
|
621
787
|
/** Persist a video from a data URL (null removes every variant); only used
|
|
622
788
|
* for removal and small legacy/import payloads. */
|
|
623
789
|
async function writeVideo(dataUrl) {
|
|
@@ -735,9 +901,10 @@ async function handleVideoUpload(req, res) {
|
|
|
735
901
|
}
|
|
736
902
|
try {
|
|
737
903
|
await ensureDir();
|
|
738
|
-
const tmp = dshHomePath(DATA_DIR,
|
|
904
|
+
const tmp = dshHomePath(DATA_DIR, VIDEO_UPLOAD_TMP);
|
|
739
905
|
const target = videoPathFor(mime);
|
|
740
906
|
const out = createWriteStream(tmp);
|
|
907
|
+
let received = 0;
|
|
741
908
|
let failed = false;
|
|
742
909
|
const fail = () => {
|
|
743
910
|
if (failed) return;
|
|
@@ -747,6 +914,13 @@ async function handleVideoUpload(req, res) {
|
|
|
747
914
|
};
|
|
748
915
|
req.on("aborted", fail);
|
|
749
916
|
req.on("error", fail);
|
|
917
|
+
req.on("data", (chunk) => {
|
|
918
|
+
received += chunk.byteLength;
|
|
919
|
+
if (received > VIDEO_UPLOAD_MAX) {
|
|
920
|
+
req.destroy();
|
|
921
|
+
fail();
|
|
922
|
+
}
|
|
923
|
+
});
|
|
750
924
|
out.on("error", () => {
|
|
751
925
|
fail();
|
|
752
926
|
try {
|
|
@@ -764,6 +938,11 @@ async function handleVideoUpload(req, res) {
|
|
|
764
938
|
}
|
|
765
939
|
await rm(target, { force: true });
|
|
766
940
|
await rename(tmp, target);
|
|
941
|
+
const cfg = await readConfig();
|
|
942
|
+
if (cfg.videoMime !== mime) {
|
|
943
|
+
cfg.videoMime = mime;
|
|
944
|
+
await writeConfig(cfg);
|
|
945
|
+
}
|
|
767
946
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
768
947
|
res.end(JSON.stringify({ ok: true }));
|
|
769
948
|
} catch (e) {
|
|
@@ -783,6 +962,131 @@ async function handleVideoUpload(req, res) {
|
|
|
783
962
|
} catch {}
|
|
784
963
|
}
|
|
785
964
|
}
|
|
965
|
+
/** Stream the stored wallpaper: sniffed MIME, no caching (uploads and rotation
|
|
966
|
+
* replace the file in place). */
|
|
967
|
+
async function serveWallpaper(req, res) {
|
|
968
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
969
|
+
res.writeHead(405, { "Content-Type": "application/json" });
|
|
970
|
+
res.end(JSON.stringify({
|
|
971
|
+
ok: false,
|
|
972
|
+
error: "wallpaper route only serves GET/HEAD"
|
|
973
|
+
}));
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
try {
|
|
977
|
+
const st = await stat(wallpaperPath());
|
|
978
|
+
if (st.size === 0) {
|
|
979
|
+
res.writeHead(404);
|
|
980
|
+
res.end();
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
const mime = sniffImageMime(await new Promise((resolve, reject) => {
|
|
984
|
+
const chunks = [];
|
|
985
|
+
const s = createReadStream(wallpaperPath(), {
|
|
986
|
+
start: 0,
|
|
987
|
+
end: 15
|
|
988
|
+
});
|
|
989
|
+
s.on("data", (c) => chunks.push(c));
|
|
990
|
+
s.on("end", () => resolve(Buffer.concat(chunks)));
|
|
991
|
+
s.on("error", reject);
|
|
992
|
+
}));
|
|
993
|
+
res.writeHead(200, {
|
|
994
|
+
"Content-Type": mime,
|
|
995
|
+
"Content-Length": st.size,
|
|
996
|
+
"Cache-Control": "no-store"
|
|
997
|
+
});
|
|
998
|
+
if (req.method === "HEAD") {
|
|
999
|
+
res.end();
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
createReadStream(wallpaperPath()).pipe(res);
|
|
1003
|
+
} catch {
|
|
1004
|
+
res.writeHead(404);
|
|
1005
|
+
res.end("no wallpaper stored");
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
/** Accept a raw wallpaper upload (POST): pipe the body straight into the
|
|
1009
|
+
* wallpaper slot — no base64 inflation, original pixels preserved. */
|
|
1010
|
+
async function handleWallpaperUpload(req, res) {
|
|
1011
|
+
if (req.method !== "POST") {
|
|
1012
|
+
res.writeHead(405);
|
|
1013
|
+
res.end();
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
if (!(typeof req.headers["content-type"] === "string" ? req.headers["content-type"] : "").split(";")[0].trim().startsWith("image/")) {
|
|
1017
|
+
req.resume();
|
|
1018
|
+
res.writeHead(415, { "Content-Type": "application/json" });
|
|
1019
|
+
res.end(JSON.stringify({
|
|
1020
|
+
ok: false,
|
|
1021
|
+
error: "unsupported media type, expected image/*"
|
|
1022
|
+
}));
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
try {
|
|
1026
|
+
await ensureDir();
|
|
1027
|
+
const tmp = dshHomePath(DATA_DIR, UPLOAD_TMP);
|
|
1028
|
+
const out = createWriteStream(tmp);
|
|
1029
|
+
let received = 0;
|
|
1030
|
+
let failed = false;
|
|
1031
|
+
const fail = () => {
|
|
1032
|
+
if (failed) return;
|
|
1033
|
+
failed = true;
|
|
1034
|
+
out.destroy();
|
|
1035
|
+
rm(tmp, { force: true });
|
|
1036
|
+
};
|
|
1037
|
+
req.on("aborted", fail);
|
|
1038
|
+
req.on("error", fail);
|
|
1039
|
+
out.on("error", () => {
|
|
1040
|
+
fail();
|
|
1041
|
+
try {
|
|
1042
|
+
res.writeHead(500);
|
|
1043
|
+
res.end();
|
|
1044
|
+
} catch {}
|
|
1045
|
+
});
|
|
1046
|
+
req.on("data", (chunk) => {
|
|
1047
|
+
received += chunk.byteLength;
|
|
1048
|
+
if (received > WALLPAPER_UPLOAD_MAX) {
|
|
1049
|
+
req.destroy();
|
|
1050
|
+
fail();
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
req.pipe(out);
|
|
1054
|
+
out.on("finish", async () => {
|
|
1055
|
+
if (failed) return;
|
|
1056
|
+
try {
|
|
1057
|
+
if (received === 0) {
|
|
1058
|
+
fail();
|
|
1059
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1060
|
+
res.end(JSON.stringify({
|
|
1061
|
+
ok: false,
|
|
1062
|
+
error: "empty upload"
|
|
1063
|
+
}));
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
await rm(wallpaperPath(), { force: true });
|
|
1067
|
+
await rename(tmp, wallpaperPath());
|
|
1068
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1069
|
+
res.end(JSON.stringify({
|
|
1070
|
+
ok: true,
|
|
1071
|
+
wallpaperUrl: WALLPAPER_ROUTE
|
|
1072
|
+
}));
|
|
1073
|
+
} catch (e) {
|
|
1074
|
+
console.error("dsh-any-background: failed to finalize the wallpaper upload", e);
|
|
1075
|
+
rm(tmp, { force: true });
|
|
1076
|
+
try {
|
|
1077
|
+
res.writeHead(500);
|
|
1078
|
+
res.end();
|
|
1079
|
+
} catch {}
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
} catch (e) {
|
|
1083
|
+
console.error("dsh-any-background: failed to accept the wallpaper upload", e);
|
|
1084
|
+
try {
|
|
1085
|
+
res.writeHead(500);
|
|
1086
|
+
res.end();
|
|
1087
|
+
} catch {}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
786
1090
|
const NS = "dshAnyBackground";
|
|
787
1091
|
const RPC_CHANNEL = "/dsh-any-background";
|
|
788
1092
|
const RPC_BODY_MAX = 314572800;
|
|
@@ -841,7 +1145,7 @@ async function handleRpcMethod(endpoint, payload) {
|
|
|
841
1145
|
ok: true,
|
|
842
1146
|
value: {
|
|
843
1147
|
config: await readConfig(),
|
|
844
|
-
|
|
1148
|
+
wallpaperUrl: await wallpaperServeUrl(),
|
|
845
1149
|
videoUrl: await videoUrl(),
|
|
846
1150
|
rotated
|
|
847
1151
|
}
|
|
@@ -863,6 +1167,10 @@ async function handleRpcMethod(endpoint, payload) {
|
|
|
863
1167
|
ok: true,
|
|
864
1168
|
value: await writeWallpaperFromUrl(payload?.url ?? null)
|
|
865
1169
|
};
|
|
1170
|
+
case "setVideoUrl": return {
|
|
1171
|
+
ok: true,
|
|
1172
|
+
value: await writeVideoFromUrl(payload?.url ?? null)
|
|
1173
|
+
};
|
|
866
1174
|
case "rotationAdd": return {
|
|
867
1175
|
ok: true,
|
|
868
1176
|
value: await handleRotationAdd(payload)
|
|
@@ -991,6 +1299,15 @@ function apply(ctx) {
|
|
|
991
1299
|
}));
|
|
992
1300
|
}
|
|
993
1301
|
}), "dsh-any-background: rpc channel");
|
|
1302
|
+
const fenceUpload = (handler) => (req, res) => {
|
|
1303
|
+
const rejection = webCtx.connection.requestRejection(req);
|
|
1304
|
+
if (rejection !== void 0) {
|
|
1305
|
+
res.writeHead(rejection);
|
|
1306
|
+
res.end(rejection === 401 ? "unauthorized" : "forbidden");
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
handler(req, res);
|
|
1310
|
+
};
|
|
994
1311
|
webCtx.effect(() => webCtx.webServer.register({
|
|
995
1312
|
kind: "prefix",
|
|
996
1313
|
path: VIDEO_ROUTE,
|
|
@@ -999,8 +1316,18 @@ function apply(ctx) {
|
|
|
999
1316
|
webCtx.effect(() => webCtx.webServer.register({
|
|
1000
1317
|
kind: "exact",
|
|
1001
1318
|
path: UPLOAD_ROUTE,
|
|
1002
|
-
handler: handleVideoUpload
|
|
1319
|
+
handler: fenceUpload(handleVideoUpload)
|
|
1003
1320
|
}), "dsh-any-background: upload route");
|
|
1321
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
1322
|
+
kind: "prefix",
|
|
1323
|
+
path: WALLPAPER_ROUTE,
|
|
1324
|
+
handler: serveWallpaper
|
|
1325
|
+
}), "dsh-any-background: wallpaper route");
|
|
1326
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
1327
|
+
kind: "exact",
|
|
1328
|
+
path: WALLPAPER_UPLOAD_ROUTE,
|
|
1329
|
+
handler: fenceUpload(handleWallpaperUpload)
|
|
1330
|
+
}), "dsh-any-background: wallpaper upload route");
|
|
1004
1331
|
});
|
|
1005
1332
|
}
|
|
1006
1333
|
//#endregion
|