beauticode-dsh 1.0.18 → 1.0.19
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/client.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
const IMAGE_ATTEMPT_TIMEOUT_MS = 3_000;
|
|
17
17
|
const IMAGE_MAX_ATTEMPTS = 2;
|
|
18
18
|
const VIDEO_STARTUP_TIMEOUT_MS = CLIENT_APPLY_DEADLINE_MS;
|
|
19
|
+
const VIDEO_PROBE_TIMEOUT_MS = 2_000;
|
|
19
20
|
const DSH_STRUCTURE_TIMEOUT_MS = CLIENT_APPLY_DEADLINE_MS;
|
|
20
21
|
const FRAME_FALLBACK_MS = 120;
|
|
21
22
|
const VIDEO_STABLE_FRAMES = 3;
|
|
@@ -82,7 +83,7 @@ html[data-bc-fish="true"] #beauticode-bg-stage::after{background:transparent!imp
|
|
|
82
83
|
#beauticode-bg-stage[data-bc-transitioning="true"] .beauticode-media-slot[data-bc-role="candidate"]{opacity:1}
|
|
83
84
|
#beauticode-bg-stage .beauticode-media-slot img,#beauticode-bg-stage .beauticode-media-slot video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;pointer-events:none;transition:opacity 120ms ease}
|
|
84
85
|
#beauticode-bg-stage .beauticode-media-slot img{z-index:0;opacity:1}
|
|
85
|
-
#beauticode-bg-stage .beauticode-media-slot video{z-index:1;opacity
|
|
86
|
+
#beauticode-bg-stage .beauticode-media-slot video{z-index:1;opacity:.001}
|
|
86
87
|
#beauticode-bg-stage .beauticode-media-slot[data-bc-video-ready="true"] img{opacity:0}
|
|
87
88
|
#beauticode-bg-stage .beauticode-media-slot[data-bc-video-ready="true"] video{opacity:1}
|
|
88
89
|
@media (prefers-reduced-motion:reduce){#beauticode-bg-stage .beauticode-media-slot,#beauticode-bg-stage .beauticode-media-slot img,#beauticode-bg-stage .beauticode-media-slot video{transition:none!important}}
|
|
@@ -496,7 +497,65 @@ html[data-bc-fish="true"] #root{opacity:0!important;visibility:hidden!important;
|
|
|
496
497
|
}
|
|
497
498
|
}
|
|
498
499
|
|
|
499
|
-
function
|
|
500
|
+
function safeOrigin(value) {
|
|
501
|
+
try {
|
|
502
|
+
return new URL(value, globalThis.location?.href).origin;
|
|
503
|
+
} catch {
|
|
504
|
+
return "unknown";
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function videoRequestContext(url) {
|
|
509
|
+
return `页面Origin=${safeOrigin(globalThis.location?.href)};媒体Origin=${safeOrigin(url)};页面=${document.visibilityState || "unknown"}`;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
async function probeVideoSource(url, signal, timeoutMs = VIDEO_PROBE_TIMEOUT_MS) {
|
|
513
|
+
throwIfAborted(signal);
|
|
514
|
+
const controller = new AbortController();
|
|
515
|
+
const parentAborted = () => controller.abort();
|
|
516
|
+
signal?.addEventListener?.("abort", parentAborted, { once: true });
|
|
517
|
+
let timedOut = false;
|
|
518
|
+
const timer = setTimeout(() => {
|
|
519
|
+
timedOut = true;
|
|
520
|
+
controller.abort();
|
|
521
|
+
}, Math.max(1, timeoutMs));
|
|
522
|
+
try {
|
|
523
|
+
const response = await fetch(url, {
|
|
524
|
+
method: "GET",
|
|
525
|
+
headers: { Range: "bytes=0-1" },
|
|
526
|
+
cache: "no-store",
|
|
527
|
+
credentials: "omit",
|
|
528
|
+
mode: "cors",
|
|
529
|
+
signal: controller.signal,
|
|
530
|
+
});
|
|
531
|
+
if (response.status !== 206) {
|
|
532
|
+
if (response.body) {
|
|
533
|
+
await response.body.cancel().catch(() => {});
|
|
534
|
+
}
|
|
535
|
+
throw new Error(`视频媒体 Range 探针返回 HTTP ${response.status}`);
|
|
536
|
+
}
|
|
537
|
+
const bytes = await response.arrayBuffer();
|
|
538
|
+
if (bytes.byteLength !== 2) {
|
|
539
|
+
throw new Error(`视频媒体 Range 探针返回 ${bytes.byteLength} 字节`);
|
|
540
|
+
}
|
|
541
|
+
} catch (error) {
|
|
542
|
+
if (signal?.aborted) throw abortError();
|
|
543
|
+
const context = videoRequestContext(url);
|
|
544
|
+
if (timedOut || isAbortError(error)) {
|
|
545
|
+
throw new Error(`视频媒体 Range 探针超时;${context}`);
|
|
546
|
+
}
|
|
547
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
548
|
+
if (error instanceof TypeError) {
|
|
549
|
+
throw new Error(`视频媒体不可达或被 CORS 拒绝;${context};${detail}`);
|
|
550
|
+
}
|
|
551
|
+
throw new Error(`${detail};${context}`);
|
|
552
|
+
} finally {
|
|
553
|
+
clearTimeout(timer);
|
|
554
|
+
signal?.removeEventListener?.("abort", parentAborted);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function describeVideoState(video, phase) {
|
|
500
559
|
const mediaErrorNames = {
|
|
501
560
|
1: "MEDIA_ERR_ABORTED",
|
|
502
561
|
2: "MEDIA_ERR_NETWORK",
|
|
@@ -505,7 +564,8 @@ html[data-bc-fish="true"] #root{opacity:0!important;visibility:hidden!important;
|
|
|
505
564
|
};
|
|
506
565
|
const code = Number(video?.error?.code) || 0;
|
|
507
566
|
const mediaError = code ? mediaErrorNames[code] || `MEDIA_ERR_${code}` : "none";
|
|
508
|
-
|
|
567
|
+
const src = video?.currentSrc || video?.src || "";
|
|
568
|
+
return `${phase};mediaError=${mediaError};readyState=${video?.readyState ?? -1};networkState=${video?.networkState ?? -1};paused=${Boolean(video?.paused)};${videoRequestContext(src)}`;
|
|
509
569
|
}
|
|
510
570
|
|
|
511
571
|
function waitForVideo(video, signal, timeoutMs = VIDEO_STARTUP_TIMEOUT_MS) {
|
|
@@ -1090,6 +1150,12 @@ html[data-bc-fish="true"] #root{opacity:0!important;visibility:hidden!important;
|
|
|
1090
1150
|
if (payload.media !== "video" || typeof payload.videoUrl !== "string") {
|
|
1091
1151
|
throw new Error("视频载荷无效");
|
|
1092
1152
|
}
|
|
1153
|
+
await probeVideoSource(
|
|
1154
|
+
payload.videoUrl,
|
|
1155
|
+
signal,
|
|
1156
|
+
Math.min(VIDEO_PROBE_TIMEOUT_MS, remaining()),
|
|
1157
|
+
);
|
|
1158
|
+
throwIfAborted(signal);
|
|
1093
1159
|
const video = document.createElement("video");
|
|
1094
1160
|
video.autoplay = true;
|
|
1095
1161
|
video.loop = true;
|
package/package.json
CHANGED
|
@@ -13,6 +13,18 @@ export function normalizeDshBaseUrl(value) {
|
|
|
13
13
|
url.pathname = url.pathname.replace(/\/+$/, "") || "/";
|
|
14
14
|
return url;
|
|
15
15
|
}
|
|
16
|
+
export function dshTrustedOrigins(value) {
|
|
17
|
+
const baseUrl = normalizeDshBaseUrl(String(value));
|
|
18
|
+
const port = baseUrl.port ? `:${baseUrl.port}` : "";
|
|
19
|
+
return [
|
|
20
|
+
...new Set([
|
|
21
|
+
baseUrl.origin,
|
|
22
|
+
new URL(`http://127.0.0.1${port}`).origin,
|
|
23
|
+
new URL(`http://localhost${port}`).origin,
|
|
24
|
+
new URL(`http://[::1]${port}`).origin,
|
|
25
|
+
]),
|
|
26
|
+
];
|
|
27
|
+
}
|
|
16
28
|
function delay(ms) {
|
|
17
29
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
18
30
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { DshHostApplier, normalizeDshBaseUrl, } from "./bridge.js";
|
|
1
|
+
export { DshHostApplier, dshTrustedOrigins, normalizeDshBaseUrl, } from "./bridge.js";
|
|
2
2
|
export { DSH_HOST_DESCRIPTOR } from "./host-descriptor.js";
|
|
3
3
|
export { DshSession } from "./session.js";
|
|
4
4
|
export { DSH_BRIDGE_TOKEN_FILE, bridgeTokenPath, ensureBridgeToken, } from "./token.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { ApplyTransaction, BackgroundStore, MediaServerController, buildHostApplyPayload, defaultDataRoot, isLocalBackgroundSource, resolveSessionBundledThemes, resolveBackgroundImagePath, } from "../core/index.js";
|
|
4
|
-
import { DshHostApplier, normalizeDshBaseUrl } from "./bridge.js";
|
|
4
|
+
import { DshHostApplier, dshTrustedOrigins, normalizeDshBaseUrl } from "./bridge.js";
|
|
5
5
|
import { DSH_HOST_DESCRIPTOR } from "./host-descriptor.js";
|
|
6
6
|
import { acquireDshInjectorLock } from "./injector-lock.js";
|
|
7
7
|
import { ensureBridgeToken } from "./token.js";
|
|
@@ -54,7 +54,7 @@ export class DshSession {
|
|
|
54
54
|
});
|
|
55
55
|
this.media = new MediaServerController({
|
|
56
56
|
enabled: true,
|
|
57
|
-
trustedOrigins:
|
|
57
|
+
trustedOrigins: dshTrustedOrigins(this.baseUrl),
|
|
58
58
|
});
|
|
59
59
|
this.onError = opts.onError ?? null;
|
|
60
60
|
this.onStatus = opts.onStatus ?? null;
|