diff --git a/game-studio/src/host/GamePlayer.vue b/game-studio/src/host/GamePlayer.vue index 6621af94..3d39e07e 100644 --- a/game-studio/src/host/GamePlayer.vue +++ b/game-studio/src/host/GamePlayer.vue @@ -55,10 +55,24 @@ type LoadPhase = 'idle' | 'loading' | 'ready' | 'error'; const phase = ref('idle'); const errorMsg = ref(''); -/** iframe 元素引用与 srcdoc 内容 */ -const iframeRef = ref(null); +/** + * iframe 元素引用与 srcdoc 内容。 + * ⚠ 模板里 ref="iframeRef" 位于 v-for="slot in containers" 内部——Vue 3 对 v-for 内的 + * 模板 ref 填充的是【数组】而非元素(即使只渲染 1 个 iframe)。曾因按元素直接取 + * .contentWindow 得 undefined,致 HostBridge.targetWindow=undefined、post() 静默丢弃 + * 全部 host→game 消息(storage/ad/pay 回包与 init 全断;game→host 不受影响故两波未现形)。 + * 取元素一律走 currentIframe() 解包,勿直接读 iframeRef.value。 + */ +const iframeRef = ref(null); const srcdoc = ref(''); +/** 解包 v-for 数组形态的模板 ref,返回当前唯一 iframe 元素(无则 null)。 */ +function currentIframe(): HTMLIFrameElement | null { + const v = iframeRef.value; + if (Array.isArray(v)) return v[0] ?? null; + return v; +} + /** 宿主桥实例(onMounted 后创建,卸载时 dispose) */ let bridge: HostBridge | null = null; @@ -200,16 +214,23 @@ let pendingAllowOrigins: string[] | undefined; /** 在 iframe 渲染后建立桥(含双校验) */ function attachBridge(): void { - const iframe = iframeRef.value; + const iframe = currentIframe(); // v-for 模板 ref 是数组,必须解包(见 iframeRef 声明注释) if (!iframe) return; // 先销毁旧桥 bridge?.dispose(); - // origin 白名单:srcdoc 注入文档 origin='null',需 includeNull=true 才能收到 iframe 消息。 + // origin 白名单:srcdoc 沙箱按 sandbox 形态 origin 可能为 'null'(无 allow-same-origin) + // 或与宿主同源(现行 allow-same-origin),两种均入白名单(includeNull=true + 自身 origin)。 const allowed = buildOriginAllowlist(pendingAllowOrigins, /* includeNull */ true); + const win = iframe.contentWindow; + if (!win) { + // host→game 出口不可用是致命静默故障(storage/ad/pay 回包全断),必须可观测 + // eslint-disable-next-line no-console + console.warn('[GamePlayer] attachBridge:iframe.contentWindow 不可用,host→game 方向将不可达'); + } bridge = new HostBridge({ - targetWindow: iframe.contentWindow, + targetWindow: win, allowedOrigins: allowed, onMessage: handleEnvelope, onReject: handleReject,