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:0}
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 describeVideoState(video, phase) {
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
- return `${phase};mediaError=${mediaError};readyState=${video?.readyState ?? -1};networkState=${video?.networkState ?? -1};paused=${Boolean(video?.paused)}`;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beauticode-dsh",
3
- "version": "1.0.18",
3
+ "version": "1.0.19",
4
4
  "description": "Cordis plugin: image/video backgrounds for DeepSeek Harness web.",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",
@@ -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: [this.baseUrl.origin],
57
+ trustedOrigins: dshTrustedOrigins(this.baseUrl),
58
58
  });
59
59
  this.onError = opts.onError ?? null;
60
60
  this.onStatus = opts.onStatus ?? null;