beauticode-dsh 1.0.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/README.zh-CN.md +48 -0
- package/agent.mjs +595 -0
- package/atmosphere.js +306 -0
- package/bin/beauticode-dsh +219 -0
- package/cli.js +219 -0
- package/client.js +370 -0
- package/console.js +303 -0
- package/control-client.mjs +445 -0
- package/cordis.patch.yml +4 -0
- package/host-apply.mjs +163 -0
- package/index.mjs +570 -0
- package/package.json +56 -0
- package/presets.mjs +73 -0
- package/themes/internal-beyond/NOTICE.md +9 -0
- package/themes/internal-beyond/bg-canvas-4k.png +0 -0
- package/themes/internal-beyond/bg-canvas.png +0 -0
- package/themes/internal-beyond/bg-infernal.jpg +0 -0
- package/themes/internal-beyond/bg-internal.jpg +0 -0
- package/ui-host.mjs +292 -0
- package/vendor/adapter-dsh/bridge.js +215 -0
- package/vendor/adapter-dsh/host-descriptor.js +15 -0
- package/vendor/adapter-dsh/index.js +6 -0
- package/vendor/adapter-dsh/injector-lock.js +27 -0
- package/vendor/adapter-dsh/package.json +5 -0
- package/vendor/adapter-dsh/session.js +527 -0
- package/vendor/adapter-dsh/token.js +46 -0
- package/vendor/adapter-dsh/tray-handoff.js +54 -0
- package/vendor/core/apply-transaction.js +287 -0
- package/vendor/core/background-store.js +1116 -0
- package/vendor/core/bundled-gallery.js +53 -0
- package/vendor/core/constants.js +47 -0
- package/vendor/core/error-message.js +208 -0
- package/vendor/core/file-lock.js +151 -0
- package/vendor/core/host-session.js +2 -0
- package/vendor/core/index.js +12 -0
- package/vendor/core/media-server.js +544 -0
- package/vendor/core/media-validation.js +244 -0
- package/vendor/core/package.json +5 -0
- package/vendor/core/paths.js +168 -0
- package/vendor/core/types.js +15 -0
package/cli.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* One-line installer: npx beauticode-dsh
|
|
4
|
+
*
|
|
5
|
+
* Copies this package to a stable local folder and writes the DSH patch so
|
|
6
|
+
* `dsh web` loads beautiCode. Does not start DeepSeek Harness.
|
|
7
|
+
*/
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import fsp from "node:fs/promises";
|
|
10
|
+
import os from "node:os";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
13
|
+
|
|
14
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const pluginName = "beauticode-dsh";
|
|
16
|
+
const bridgeId = "beauticode-bridge";
|
|
17
|
+
|
|
18
|
+
function argValue(argv, name) {
|
|
19
|
+
const idx = argv.indexOf(name);
|
|
20
|
+
if (idx === -1) return null;
|
|
21
|
+
const value = argv[idx + 1];
|
|
22
|
+
return value && !value.startsWith("--") ? value : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function defaultDataRoot() {
|
|
26
|
+
if (process.env.BEAUTICODE_DATA_ROOT) return process.env.BEAUTICODE_DATA_ROOT;
|
|
27
|
+
if (process.env.LOCALAPPDATA) {
|
|
28
|
+
return path.join(process.env.LOCALAPPDATA, "beautiCode");
|
|
29
|
+
}
|
|
30
|
+
return path.join(os.homedir(), ".beauticode");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function defaultDshHome() {
|
|
34
|
+
return process.env.DSH_HOME || path.join(os.homedir(), ".dsh");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function toFileUri(filePath) {
|
|
38
|
+
const full = path.resolve(filePath).replaceAll("\\", "/");
|
|
39
|
+
if (/^[A-Za-z]:/.test(full)) return `file:///${full}`;
|
|
40
|
+
return pathToFileURL(full).href;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function fileUriInsert(uri) {
|
|
44
|
+
return [
|
|
45
|
+
"# beauticode-bridge (installer)",
|
|
46
|
+
"- insert:",
|
|
47
|
+
" - id: beauticode-bridge",
|
|
48
|
+
` name: '${uri}'`,
|
|
49
|
+
" inject: [webServer]",
|
|
50
|
+
"",
|
|
51
|
+
].join("\n");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function packageInsert() {
|
|
55
|
+
return [
|
|
56
|
+
"# beauticode-bridge (installer)",
|
|
57
|
+
"- insert:",
|
|
58
|
+
" - id: beauticode-bridge",
|
|
59
|
+
` name: '${pluginName}'`,
|
|
60
|
+
" inject: [webServer]",
|
|
61
|
+
"",
|
|
62
|
+
].join("\n");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function hasBridge(text) {
|
|
66
|
+
return new RegExp(`^\\s*-\\s*id:\\s*${bridgeId}\\s*$`, "m").test(text);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function stripBridge(text) {
|
|
70
|
+
const patterns = [
|
|
71
|
+
/(?:^|\r?\n)# beauticode-bridge \(installer\)\r?\n- insert:\r?\n(?:[ \t]+.*\r?\n)*/g,
|
|
72
|
+
/(?:^|\r?\n)- insert:\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+-\s*id:\s*beauticode-bridge\r?\n(?:[ \t]+.*\r?\n)*/g,
|
|
73
|
+
];
|
|
74
|
+
let next = text;
|
|
75
|
+
for (const pattern of patterns) next = next.replace(pattern, "\n");
|
|
76
|
+
return next;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function writePatch(filePath, body) {
|
|
80
|
+
await fsp.mkdir(path.dirname(filePath), { recursive: true });
|
|
81
|
+
if (!fs.existsSync(filePath)) {
|
|
82
|
+
await fsp.writeFile(filePath, body, "utf8");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const raw = await fsp.readFile(filePath, "utf8");
|
|
86
|
+
if (hasBridge(raw)) {
|
|
87
|
+
const replaced = raw.replace(
|
|
88
|
+
/(?:# beauticode-bridge \(installer\)\r?\n)?- insert:\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+-\s*id:\s*beauticode-bridge\r?\n(?:[ \t]+.*\r?\n)*/,
|
|
89
|
+
body,
|
|
90
|
+
);
|
|
91
|
+
await fsp.writeFile(filePath, replaced.endsWith("\n") ? replaced : `${replaced}\n`, "utf8");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const stripped = raw.trim();
|
|
95
|
+
if (stripped === "" || stripped === "[]") {
|
|
96
|
+
await fsp.writeFile(filePath, body, "utf8");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
await fsp.writeFile(filePath, `${stripped}\n\n${body}`, "utf8");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function removePatch(filePath) {
|
|
103
|
+
if (!fs.existsSync(filePath)) return false;
|
|
104
|
+
const raw = await fsp.readFile(filePath, "utf8");
|
|
105
|
+
if (!hasBridge(raw)) return false;
|
|
106
|
+
const cleaned = stripBridge(raw).trim();
|
|
107
|
+
if (cleaned === "" || cleaned === "[]") {
|
|
108
|
+
await fsp.writeFile(
|
|
109
|
+
filePath,
|
|
110
|
+
"# Your patch layer for this dsh profile.\n[]\n",
|
|
111
|
+
"utf8",
|
|
112
|
+
);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
await fsp.writeFile(filePath, `${cleaned}\n`, "utf8");
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function shouldCopy(source) {
|
|
120
|
+
const relative = path.relative(here, source);
|
|
121
|
+
if (relative.startsWith("test") || relative.includes(`${path.sep}test${path.sep}`)) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
if (relative.endsWith(".test.mjs")) return false;
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function copyPackage(dest) {
|
|
129
|
+
await fsp.rm(dest, { recursive: true, force: true });
|
|
130
|
+
await fsp.cp(here, dest, {
|
|
131
|
+
recursive: true,
|
|
132
|
+
filter: (source) => shouldCopy(source),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function ensureEngine(dest) {
|
|
137
|
+
const vendor = path.join(dest, "vendor", "adapter-dsh", "index.js");
|
|
138
|
+
if (fs.existsSync(vendor)) return;
|
|
139
|
+
const packPath = path.resolve(here, "../../scripts/pack-dsh-plugin.mjs");
|
|
140
|
+
if (!fs.existsSync(packPath)) {
|
|
141
|
+
throw new Error("插件包不完整:缺少本机导入引擎。请重新执行 npx beauticode-dsh。");
|
|
142
|
+
}
|
|
143
|
+
const { stageEngineInto } = await import(pathToFileURL(packPath).href);
|
|
144
|
+
await stageEngineInto(dest);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function install(opts) {
|
|
148
|
+
const dest = path.resolve(opts.pluginHome);
|
|
149
|
+
const dshHome = path.resolve(opts.dshHome);
|
|
150
|
+
const webProfile = path.join(dshHome, "profiles", "web");
|
|
151
|
+
const webPatch = path.join(webProfile, "cordis.patch.yml");
|
|
152
|
+
const webPackage = path.join(webProfile, "package.json");
|
|
153
|
+
const homePatch = path.join(dshHome, "cordis.patch.yml");
|
|
154
|
+
|
|
155
|
+
await fsp.mkdir(dest, { recursive: true });
|
|
156
|
+
await copyPackage(dest);
|
|
157
|
+
await ensureEngine(dest);
|
|
158
|
+
const indexFile = path.join(dest, "index.mjs");
|
|
159
|
+
if (!fs.existsSync(indexFile)) {
|
|
160
|
+
throw new Error(`缺少插件入口:${indexFile}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (fs.existsSync(webPackage)) {
|
|
164
|
+
await writePatch(webPatch, packageInsert());
|
|
165
|
+
if (fs.existsSync(homePatch)) {
|
|
166
|
+
const homeRaw = await fsp.readFile(homePatch, "utf8");
|
|
167
|
+
if (hasBridge(homeRaw)) await removePatch(homePatch);
|
|
168
|
+
}
|
|
169
|
+
console.log(`已写入 ${webPatch}`);
|
|
170
|
+
console.log(`插件已复制到 ${dest}`);
|
|
171
|
+
console.log("请自己运行:npx @deepseek-ai/dsh web");
|
|
172
|
+
return { dest, patch: webPatch };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
await fsp.mkdir(dshHome, { recursive: true });
|
|
176
|
+
await writePatch(homePatch, fileUriInsert(toFileUri(indexFile)));
|
|
177
|
+
console.log(`DSH web profile 还不存在,已写入 ${homePatch}`);
|
|
178
|
+
console.log(`插件已复制到 ${dest}`);
|
|
179
|
+
console.log("请自己运行:npx @deepseek-ai/dsh web");
|
|
180
|
+
return { dest, patch: homePatch };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function uninstall(opts) {
|
|
184
|
+
const dshHome = path.resolve(opts.dshHome);
|
|
185
|
+
const removed = [];
|
|
186
|
+
if (await removePatch(path.join(dshHome, "profiles", "web", "cordis.patch.yml"))) {
|
|
187
|
+
removed.push("web patch");
|
|
188
|
+
}
|
|
189
|
+
if (await removePatch(path.join(dshHome, "cordis.patch.yml"))) {
|
|
190
|
+
removed.push("home patch");
|
|
191
|
+
}
|
|
192
|
+
const dest = path.resolve(opts.pluginHome);
|
|
193
|
+
if (fs.existsSync(dest)) {
|
|
194
|
+
await fsp.rm(dest, { recursive: true, force: true });
|
|
195
|
+
removed.push(dest);
|
|
196
|
+
}
|
|
197
|
+
console.log(removed.length ? `已移除:${removed.join("、")}` : "没有可移除的 beautiCode 插件接线。");
|
|
198
|
+
return { removed };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export async function runCli(argv = process.argv.slice(2)) {
|
|
202
|
+
const dshHome = argValue(argv, "--dsh-home") || defaultDshHome();
|
|
203
|
+
const pluginHome =
|
|
204
|
+
argValue(argv, "--plugin-home") || path.join(defaultDataRoot(), "plugin");
|
|
205
|
+
const opts = { dshHome, pluginHome };
|
|
206
|
+
if (argv.includes("--remove")) return uninstall(opts);
|
|
207
|
+
return install(opts);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const launchedDirectly =
|
|
211
|
+
Boolean(process.argv[1]) &&
|
|
212
|
+
pathToFileURL(path.resolve(process.argv[1])).href.toLowerCase() ===
|
|
213
|
+
import.meta.url.toLowerCase();
|
|
214
|
+
if (launchedDirectly) {
|
|
215
|
+
runCli().catch((error) => {
|
|
216
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
217
|
+
process.exit(1);
|
|
218
|
+
});
|
|
219
|
+
}
|
package/client.js
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
"use strict";
|
|
3
|
+
if (window.__beauticodeBridgeLoaded) return;
|
|
4
|
+
window.__beauticodeBridgeLoaded = true;
|
|
5
|
+
window.__beauticodeBridgeVersion = 4;
|
|
6
|
+
|
|
7
|
+
const clientId =
|
|
8
|
+
globalThis.crypto?.randomUUID?.() ||
|
|
9
|
+
`bc-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
10
|
+
const desiredModes = { fish: false, muted: true, tone: "auto" };
|
|
11
|
+
let activePayload = null;
|
|
12
|
+
let playbackBlocked = false;
|
|
13
|
+
const systemDarkMedia = globalThis.matchMedia?.("(prefers-color-scheme: dark)") ?? null;
|
|
14
|
+
let themeSyncQueued = false;
|
|
15
|
+
|
|
16
|
+
const style = document.createElement("style");
|
|
17
|
+
style.dataset.beauticodeBridge = "true";
|
|
18
|
+
style.textContent = `
|
|
19
|
+
html[data-bc-active="true"],html[data-bc-active="true"] body{background:transparent!important}
|
|
20
|
+
html[data-bc-active="true"] body{
|
|
21
|
+
--dsw-alias-bg-base:rgba(17,20,27,.10);
|
|
22
|
+
--dsw-alias-bg-layer-1:rgba(26,30,39,.28);
|
|
23
|
+
--dsw-alias-bg-layer-2:rgba(35,40,51,.32);
|
|
24
|
+
--dsw-alias-bg-overlay:rgba(17,20,27,.12);
|
|
25
|
+
--dsw-specific-sidebar-fill:rgba(23,27,35,.28);
|
|
26
|
+
}
|
|
27
|
+
html[data-bc-resolved-tone="light"][data-bc-active="true"] body{
|
|
28
|
+
--dsw-alias-bg-base:rgba(248,250,252,.12);
|
|
29
|
+
--dsw-alias-bg-layer-1:rgba(255,255,255,.28);
|
|
30
|
+
--dsw-alias-bg-layer-2:rgba(248,250,252,.32);
|
|
31
|
+
--dsw-alias-bg-overlay:rgba(255,255,255,.14);
|
|
32
|
+
--dsw-specific-sidebar-fill:rgba(255,255,255,.28);
|
|
33
|
+
}
|
|
34
|
+
html[data-bc-active="true"]:has(#root [data-phase="active"]) body,
|
|
35
|
+
html[data-bc-active="true"]:has(#root [data-phase="settling"]) body{
|
|
36
|
+
--dsw-alias-bg-base:rgba(17,20,27,.42);
|
|
37
|
+
--dsw-alias-bg-layer-1:rgba(26,30,39,.72);
|
|
38
|
+
--dsw-alias-bg-layer-2:rgba(35,40,51,.80);
|
|
39
|
+
--dsw-alias-bg-overlay:rgba(17,20,27,.86);
|
|
40
|
+
--dsw-specific-sidebar-fill:rgba(23,27,35,.78);
|
|
41
|
+
}
|
|
42
|
+
html[data-bc-resolved-tone="light"][data-bc-active="true"]:has(#root [data-phase="active"]) body,
|
|
43
|
+
html[data-bc-resolved-tone="light"][data-bc-active="true"]:has(#root [data-phase="settling"]) body{
|
|
44
|
+
--dsw-alias-bg-base:rgba(248,250,252,.48);
|
|
45
|
+
--dsw-alias-bg-layer-1:rgba(255,255,255,.74);
|
|
46
|
+
--dsw-alias-bg-layer-2:rgba(248,250,252,.82);
|
|
47
|
+
--dsw-alias-bg-overlay:rgba(255,255,255,.86);
|
|
48
|
+
--dsw-specific-sidebar-fill:rgba(255,255,255,.78);
|
|
49
|
+
}
|
|
50
|
+
#beauticode-bg-stage{position:fixed;inset:0;z-index:0;overflow:hidden;pointer-events:none;background:#11141b}
|
|
51
|
+
#beauticode-bg-stage::after{content:"";position:absolute;inset:0;z-index:2;background:transparent;pointer-events:none}
|
|
52
|
+
html[data-bc-resolved-tone="light"] #beauticode-bg-stage{background:#f8fafc}
|
|
53
|
+
html[data-bc-active="true"]:has(#root [data-phase="active"]) #beauticode-bg-stage::after,
|
|
54
|
+
html[data-bc-active="true"]:has(#root [data-phase="settling"]) #beauticode-bg-stage::after{background:rgba(0,0,0,.42)}
|
|
55
|
+
html[data-bc-resolved-tone="light"][data-bc-active="true"]:has(#root [data-phase="active"]) #beauticode-bg-stage::after,
|
|
56
|
+
html[data-bc-resolved-tone="light"][data-bc-active="true"]:has(#root [data-phase="settling"]) #beauticode-bg-stage::after{background:rgba(255,255,255,.22)}
|
|
57
|
+
html[data-bc-fish="true"] #beauticode-bg-stage::after{background:transparent!important}
|
|
58
|
+
#beauticode-bg-stage img,#beauticode-bg-stage video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;pointer-events:none}
|
|
59
|
+
#beauticode-bg-stage img{z-index:0}
|
|
60
|
+
#beauticode-bg-stage video{z-index:1}
|
|
61
|
+
html[data-bc-active="true"] #root{position:relative;z-index:1;background:transparent!important}
|
|
62
|
+
html[data-bc-active="true"] [class*="_fade"]{display:none!important}
|
|
63
|
+
html[data-bc-fish="true"] #root{opacity:0!important;visibility:hidden!important;pointer-events:none!important}
|
|
64
|
+
`;
|
|
65
|
+
document.head.append(style);
|
|
66
|
+
|
|
67
|
+
function dshAppearance() {
|
|
68
|
+
const body = document.body;
|
|
69
|
+
if (body?.hasAttribute("data-ds-dark-theme")) return "dark";
|
|
70
|
+
const scheme = document.documentElement.style.colorScheme;
|
|
71
|
+
if (scheme === "dark" || scheme === "light") return scheme;
|
|
72
|
+
return systemDarkMedia?.matches ? "dark" : "light";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function resolvedTone() {
|
|
76
|
+
return dshAppearance();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isDshThemeSynced(tone = resolvedTone()) {
|
|
80
|
+
return document.documentElement.dataset.bcResolvedTone === tone;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function syncDshTheme() {
|
|
84
|
+
const tone = resolvedTone();
|
|
85
|
+
document.documentElement.dataset.bcResolvedTone = tone;
|
|
86
|
+
return isDshThemeSynced(tone);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function scheduleDshThemeSync() {
|
|
90
|
+
if (themeSyncQueued) return;
|
|
91
|
+
themeSyncQueued = true;
|
|
92
|
+
queueMicrotask(() => {
|
|
93
|
+
themeSyncQueued = false;
|
|
94
|
+
syncDshTheme();
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const themeObserver = new MutationObserver(scheduleDshThemeSync);
|
|
99
|
+
themeObserver.observe(document.documentElement, {
|
|
100
|
+
attributes: true,
|
|
101
|
+
attributeFilter: ["style"],
|
|
102
|
+
});
|
|
103
|
+
if (document.body) {
|
|
104
|
+
themeObserver.observe(document.body, {
|
|
105
|
+
attributes: true,
|
|
106
|
+
attributeFilter: ["data-ds-dark-theme"],
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
systemDarkMedia?.addEventListener("change", () => {
|
|
110
|
+
if (desiredModes.tone === "auto") {
|
|
111
|
+
syncDshTheme();
|
|
112
|
+
void acknowledgeMode();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
syncDshTheme();
|
|
116
|
+
|
|
117
|
+
function stage() {
|
|
118
|
+
let node = document.getElementById("beauticode-bg-stage");
|
|
119
|
+
if (!node) {
|
|
120
|
+
node = document.createElement("div");
|
|
121
|
+
node.id = "beauticode-bg-stage";
|
|
122
|
+
document.body.prepend(node);
|
|
123
|
+
}
|
|
124
|
+
return node;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Fail closed on DSH DOM drift: the injected CSS depends on `#root`.
|
|
129
|
+
* If the shell no longer exposes it, report a clear error through the
|
|
130
|
+
* render ack instead of silently painting a broken page.
|
|
131
|
+
*/
|
|
132
|
+
function dshStructureIssue() {
|
|
133
|
+
if (!document.getElementById("root")) {
|
|
134
|
+
return "DSH 页面结构不兼容:未找到 #root。";
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function activeVideo() {
|
|
140
|
+
return document.querySelector("#beauticode-bg-stage video");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function postAck(body) {
|
|
144
|
+
await fetch("/__beauticode/ack", {
|
|
145
|
+
method: "POST",
|
|
146
|
+
headers: { "content-type": "application/json" },
|
|
147
|
+
body: JSON.stringify({ clientId, ...body }),
|
|
148
|
+
}).catch(() => {});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function playbackSnapshot(video) {
|
|
152
|
+
if (!(video instanceof HTMLVideoElement)) return null;
|
|
153
|
+
return {
|
|
154
|
+
currentTime: Number.isFinite(video.currentTime) ? Math.max(0, video.currentTime) : 0,
|
|
155
|
+
duration: Number.isFinite(video.duration) ? Math.max(0, video.duration) : 0,
|
|
156
|
+
hasVideo: true,
|
|
157
|
+
muted: video.muted,
|
|
158
|
+
paused: video.paused,
|
|
159
|
+
blocked: playbackBlocked,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function acknowledgeRender(payload, ok, visible, error = null) {
|
|
164
|
+
await postAck({
|
|
165
|
+
kind: "render",
|
|
166
|
+
generation: payload.generation,
|
|
167
|
+
media: payload.media,
|
|
168
|
+
ok,
|
|
169
|
+
visible,
|
|
170
|
+
error,
|
|
171
|
+
playback: payload.media === "video" ? playbackSnapshot(activeVideo()) : null,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function acknowledgeMode() {
|
|
176
|
+
const video = activeVideo();
|
|
177
|
+
const effectiveTone = resolvedTone();
|
|
178
|
+
await postAck({
|
|
179
|
+
kind: "mode",
|
|
180
|
+
fish: document.documentElement.dataset.bcFish === "true",
|
|
181
|
+
muted: video instanceof HTMLVideoElement ? video.muted : desiredModes.muted,
|
|
182
|
+
tone: document.documentElement.dataset.bcTone || "dark",
|
|
183
|
+
resolvedTone: effectiveTone,
|
|
184
|
+
themeSynced: isDshThemeSynced(effectiveTone),
|
|
185
|
+
blocked: playbackBlocked,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function loadImage(url) {
|
|
190
|
+
const image = new Image();
|
|
191
|
+
image.alt = "";
|
|
192
|
+
image.crossOrigin = "anonymous";
|
|
193
|
+
image.decoding = "async";
|
|
194
|
+
return new Promise((resolve, reject) => {
|
|
195
|
+
image.onload = () => resolve(image);
|
|
196
|
+
image.onerror = () => reject(new Error("图片加载失败"));
|
|
197
|
+
image.src = url;
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function waitForVideo(video) {
|
|
202
|
+
if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) return Promise.resolve();
|
|
203
|
+
return new Promise((resolve, reject) => {
|
|
204
|
+
const done = () => {
|
|
205
|
+
cleanup();
|
|
206
|
+
resolve();
|
|
207
|
+
};
|
|
208
|
+
const failed = () => {
|
|
209
|
+
cleanup();
|
|
210
|
+
reject(new Error("MP4 加载或解码失败"));
|
|
211
|
+
};
|
|
212
|
+
const cleanup = () => {
|
|
213
|
+
video.removeEventListener("loadeddata", done);
|
|
214
|
+
video.removeEventListener("error", failed);
|
|
215
|
+
};
|
|
216
|
+
video.addEventListener("loadeddata", done, { once: true });
|
|
217
|
+
video.addEventListener("error", failed, { once: true });
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function seekVideo(video, value) {
|
|
222
|
+
const requested = Number(value);
|
|
223
|
+
const duration = Number(video.duration);
|
|
224
|
+
const safe =
|
|
225
|
+
Number.isFinite(requested) &&
|
|
226
|
+
requested >= 0 &&
|
|
227
|
+
(!Number.isFinite(duration) || duration <= 0 || requested < duration)
|
|
228
|
+
? requested
|
|
229
|
+
: 0;
|
|
230
|
+
try {
|
|
231
|
+
video.currentTime = safe;
|
|
232
|
+
} catch {
|
|
233
|
+
video.currentTime = 0;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function playWithPreference(video) {
|
|
238
|
+
playbackBlocked = false;
|
|
239
|
+
video.muted = desiredModes.muted;
|
|
240
|
+
try {
|
|
241
|
+
await video.play();
|
|
242
|
+
} catch (error) {
|
|
243
|
+
if (desiredModes.muted) throw error;
|
|
244
|
+
playbackBlocked = true;
|
|
245
|
+
video.muted = true;
|
|
246
|
+
await video.play();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function syncGallery(payload) {
|
|
251
|
+
const on = payload?.atmosphere?.preset === "gallery";
|
|
252
|
+
globalThis.BeauticodeAtmosphere?.setWindowMode?.(on ? "on" : "closed");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function applyBackground(payload) {
|
|
256
|
+
if (!Number.isSafeInteger(payload?.generation)) return;
|
|
257
|
+
activePayload = payload;
|
|
258
|
+
document.documentElement.dataset.bcGeneration = String(payload.generation);
|
|
259
|
+
syncGallery(payload);
|
|
260
|
+
if (payload.media === "clear") {
|
|
261
|
+
desiredModes.fish = false;
|
|
262
|
+
playbackBlocked = false;
|
|
263
|
+
document.documentElement.removeAttribute("data-bc-active");
|
|
264
|
+
document.documentElement.removeAttribute("data-bc-media");
|
|
265
|
+
document.documentElement.removeAttribute("data-bc-fish");
|
|
266
|
+
document.getElementById("beauticode-bg-stage")?.remove();
|
|
267
|
+
if (document.documentElement.dataset.bcGallery === "true") {
|
|
268
|
+
document.documentElement.dataset.bcActive = "true";
|
|
269
|
+
}
|
|
270
|
+
await acknowledgeRender(payload, true, false);
|
|
271
|
+
await acknowledgeMode();
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (typeof payload.imageUrl !== "string") return;
|
|
275
|
+
const structureIssue = dshStructureIssue();
|
|
276
|
+
if (structureIssue) {
|
|
277
|
+
await acknowledgeRender(payload, false, false, structureIssue);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
const image = await loadImage(payload.imageUrl);
|
|
282
|
+
if (activePayload !== payload) return;
|
|
283
|
+
const node = stage();
|
|
284
|
+
if (payload.media === "image") {
|
|
285
|
+
playbackBlocked = false;
|
|
286
|
+
node.replaceChildren(image);
|
|
287
|
+
document.documentElement.dataset.bcActive = "true";
|
|
288
|
+
document.documentElement.dataset.bcMedia = "image";
|
|
289
|
+
const visible = image.naturalWidth > 0 && image.naturalHeight > 0;
|
|
290
|
+
await acknowledgeRender(payload, visible, visible, visible ? null : "图片尺寸无效");
|
|
291
|
+
await acknowledgeMode();
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (payload.media !== "video" || typeof payload.videoUrl !== "string") return;
|
|
295
|
+
const video = document.createElement("video");
|
|
296
|
+
video.autoplay = true;
|
|
297
|
+
video.loop = true;
|
|
298
|
+
video.playsInline = true;
|
|
299
|
+
video.preload = "auto";
|
|
300
|
+
video.poster = payload.imageUrl;
|
|
301
|
+
video.crossOrigin = "anonymous";
|
|
302
|
+
video.src = payload.videoUrl;
|
|
303
|
+
node.replaceChildren(image, video);
|
|
304
|
+
await waitForVideo(video);
|
|
305
|
+
if (activePayload !== payload) return;
|
|
306
|
+
seekVideo(video, payload.startAt);
|
|
307
|
+
await playWithPreference(video);
|
|
308
|
+
document.documentElement.dataset.bcActive = "true";
|
|
309
|
+
document.documentElement.dataset.bcMedia = "video";
|
|
310
|
+
const visible = video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA && !video.paused;
|
|
311
|
+
await acknowledgeRender(payload, visible, visible, visible ? null : "视频未开始播放");
|
|
312
|
+
await acknowledgeMode();
|
|
313
|
+
} catch (error) {
|
|
314
|
+
if (activePayload !== payload) return;
|
|
315
|
+
await acknowledgeRender(
|
|
316
|
+
payload,
|
|
317
|
+
false,
|
|
318
|
+
false,
|
|
319
|
+
error instanceof Error ? error.message : String(error),
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function applyModes(payload) {
|
|
325
|
+
if (typeof payload.fish === "boolean") desiredModes.fish = payload.fish;
|
|
326
|
+
if (typeof payload.muted === "boolean") desiredModes.muted = payload.muted;
|
|
327
|
+
if (["dark", "light", "auto"].includes(payload.tone)) desiredModes.tone = payload.tone;
|
|
328
|
+
document.documentElement.dataset.bcTone = desiredModes.tone;
|
|
329
|
+
syncDshTheme();
|
|
330
|
+
if (desiredModes.fish && document.documentElement.dataset.bcActive === "true") {
|
|
331
|
+
document.documentElement.dataset.bcFish = "true";
|
|
332
|
+
} else {
|
|
333
|
+
document.documentElement.removeAttribute("data-bc-fish");
|
|
334
|
+
}
|
|
335
|
+
const video = activeVideo();
|
|
336
|
+
if (video instanceof HTMLVideoElement) {
|
|
337
|
+
try {
|
|
338
|
+
await playWithPreference(video);
|
|
339
|
+
} catch {
|
|
340
|
+
playbackBlocked = desiredModes.muted === false;
|
|
341
|
+
}
|
|
342
|
+
if (activePayload?.media === "video") {
|
|
343
|
+
await acknowledgeRender(activePayload, video.readyState >= 2 && !video.paused, true);
|
|
344
|
+
}
|
|
345
|
+
} else {
|
|
346
|
+
playbackBlocked = false;
|
|
347
|
+
}
|
|
348
|
+
await acknowledgeMode();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const events = new EventSource(
|
|
352
|
+
`/__beauticode/events?clientId=${encodeURIComponent(clientId)}`,
|
|
353
|
+
);
|
|
354
|
+
events.onmessage = (event) => {
|
|
355
|
+
try {
|
|
356
|
+
const payload = JSON.parse(event.data);
|
|
357
|
+
if (payload?.type === "mode") void applyModes(payload);
|
|
358
|
+
else if (payload?.type === "apply") void applyBackground(payload);
|
|
359
|
+
} catch {
|
|
360
|
+
/* EventSource will continue with the next valid frame. */
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
setInterval(() => {
|
|
365
|
+
const video = activeVideo();
|
|
366
|
+
if (activePayload?.media === "video" && video instanceof HTMLVideoElement) {
|
|
367
|
+
void acknowledgeRender(activePayload, video.readyState >= 2 && !video.paused, true);
|
|
368
|
+
}
|
|
369
|
+
}, 1_000);
|
|
370
|
+
})();
|