Fix live room iframe CSP blocking and add open-in-new-tab fallback.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
eric 2026-09-04 07:39:52 -05:00
parent f9ab758926
commit 5d741df77b
7 changed files with 66 additions and 6 deletions

View File

@ -13,7 +13,8 @@ from app.data import mock_data
from app.data import community_data
from app.services.recommendations import recommend_destinations
from app.services.meetup_live import (
build_lounge_chat_url, build_mirotalk_join_url, can_access_meetup, create_mirotalk_room_slug,
build_lounge_chat_url, build_mirotalk_join_url, can_access_meetup,
create_lounge_channel, create_mirotalk_room_slug,
)
from app.services.auth import get_user_by_token
from app.services import community_store, social_store
@ -368,17 +369,23 @@ async def meetup_session(meetup_id: str, authorization: str | None = Header(None
mode = meetup.get("mode", "offline")
display = (user or {}).get("name", "访客")
if mode not in ("online", "hybrid"):
can_join = False
lock = lock or "offline"
session = MeetupSession(
canJoin=can_join and mode in ("online", "hybrid"),
canJoin=can_join,
lockReason=lock if not can_join else "",
displayName=display,
mode=mode,
mirotalkRoom=meetup.get("mirotalkRoom") or create_mirotalk_room_slug(meetup),
loungeChannel=meetup.get("loungeChannel", ""),
loungeChannel=meetup.get("loungeChannel") or "",
)
if session.canJoin:
session.videoUrl = build_mirotalk_join_url(meetup, display)
session.chatUrl = build_lounge_chat_url(meetup)
if not session.loungeChannel:
session.loungeChannel = create_lounge_channel(meetup)
return session

View File

@ -2,11 +2,14 @@ lounge.nomadro.com {
encode gzip
reverse_proxy https://lounge.nomadro.cn {
header_up Host lounge.nomadro.cn
header_down -Content-Security-Policy
header_down -X-Frame-Options
transport http {
tls_insecure_skip_verify
}
}
header {
Content-Security-Policy "frame-ancestors 'self' https://nomadweb.nomadro.com https://nomadro.cn https://www.nomadro.cn http://localhost:3001 http://127.0.0.1:3001"
-X-Frame-Options
}
}

View File

@ -2,11 +2,15 @@ mirotalk.nomadro.com {
encode gzip
reverse_proxy https://mirotalk.nomadro.cn {
header_up Host mirotalk.nomadro.cn
# Upstream CSP omits nomadweb.nomadro.com; strip so our frame-ancestors win.
header_down -Content-Security-Policy
header_down -X-Frame-Options
transport http {
tls_insecure_skip_verify
}
}
header {
Content-Security-Policy "frame-ancestors 'self' https://nomadweb.nomadro.com https://nomadro.cn https://www.nomadro.cn http://localhost:3001 http://127.0.0.1:3001"
-X-Frame-Options
}
}

View File

@ -8,6 +8,13 @@ export const metadata: Metadata = {
};
const LOGS = [
{
date: "2026-09-04",
tag: "直播 iframe 修复",
items: [
"修复 MiroTalk/Lounge 被上游 CSP 拦截导致直播页空白;可新窗口打开视频/聊天",
],
},
{
date: "2026-09-04",
tag: "匹配资料 · 报名日历",

View File

@ -11044,10 +11044,23 @@ a.weather-card:hover {
flex-wrap: wrap;
}
.live-layout-btns { margin-left: auto; display: flex; gap: 6px; }
.live-external-links {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
padding: 10px 20px;
background: var(--bg-card);
border-bottom: var(--border-glass);
}
.live-iframe-hint {
font-size: 0.8rem;
color: var(--text-muted);
}
.live-stage {
flex: 1;
display: grid;
min-height: calc(100vh - 52px);
min-height: calc(100vh - 110px);
}
.live-stage.layout-split { grid-template-columns: 1fr 380px; }
.live-stage.layout-video { grid-template-columns: 1fr; }

View File

@ -112,17 +112,37 @@ export default function MeetupLiveClient({ meetupId, title }: { meetupId: string
))}
</div>
</div>
<div className="live-external-links">
{session.videoUrl && (
<a href={session.videoUrl} target="_blank" rel="noopener noreferrer" className="btn btn-sm btn-primary">
{t.live.openVideo}
</a>
)}
{session.chatUrl && (
<a href={session.chatUrl} target="_blank" rel="noopener noreferrer" className="btn btn-sm">
{t.live.openChat}
</a>
)}
<span className="live-iframe-hint">{t.live.iframeHint}</span>
</div>
<div className={`live-stage layout-${layout}`}>
{(layout === "split" || layout === "video") && session.videoUrl && (
<iframe
title="video"
src={session.videoUrl}
className="live-iframe live-video"
allow="camera; microphone; display-capture"
allow="camera; microphone; display-capture; autoplay; fullscreen"
referrerPolicy="no-referrer-when-downgrade"
/>
)}
{(layout === "split" || layout === "chat") && session.chatUrl && (
<iframe title="chat" src={session.chatUrl} className="live-iframe live-chat" />
<iframe
title="chat"
src={session.chatUrl}
className="live-iframe live-chat"
allow="clipboard-write"
referrerPolicy="no-referrer-when-downgrade"
/>
)}
{!session.videoUrl && !session.chatUrl && (
<div className="notif-empty">

View File

@ -520,6 +520,9 @@ export const zh = {
layoutVideo: "视频",
layoutChat: "聊天",
linksMissing: "直播链接尚未配置",
openVideo: "新窗口打开视频",
openChat: "新窗口打开聊天",
iframeHint: "若画面空白,请用上方按钮在新窗口打开",
},
digital: {
tag: "🎓 ACADEMY",
@ -1795,6 +1798,9 @@ export const en: { [K in keyof typeof zh]: typeof zh[K] extends string ? string
layoutVideo: "Video",
layoutChat: "Chat",
linksMissing: "Live links are not configured yet",
openVideo: "Open video in new tab",
openChat: "Open chat in new tab",
iframeHint: "If the embed is blank, open the rooms in a new tab above",
},
digital: {
tag: "🎓 ACADEMY",