From 5d741df77b065cf64f022013db30f55e12d5c04a Mon Sep 17 00:00:00 2001 From: eric Date: Fri, 4 Sep 2026 07:39:52 -0500 Subject: [PATCH] Fix live room iframe CSP blocking and add open-in-new-tab fallback. Co-authored-by: Cursor --- backend/app/routers/api.py | 13 ++++++++--- deploy/caddy/lounge.nomadro.com.caddy | 3 +++ deploy/caddy/mirotalk.nomadro.com.caddy | 4 ++++ frontend/src/app/changelog/page.tsx | 7 ++++++ frontend/src/app/globals.css | 15 +++++++++++- frontend/src/components/MeetupLiveClient.tsx | 24 ++++++++++++++++++-- frontend/src/lib/i18n/dictionaries.ts | 6 +++++ 7 files changed, 66 insertions(+), 6 deletions(-) diff --git a/backend/app/routers/api.py b/backend/app/routers/api.py index ea13582..7fbec0f 100644 --- a/backend/app/routers/api.py +++ b/backend/app/routers/api.py @@ -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 diff --git a/deploy/caddy/lounge.nomadro.com.caddy b/deploy/caddy/lounge.nomadro.com.caddy index f8c84f4..12ca283 100644 --- a/deploy/caddy/lounge.nomadro.com.caddy +++ b/deploy/caddy/lounge.nomadro.com.caddy @@ -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 } } diff --git a/deploy/caddy/mirotalk.nomadro.com.caddy b/deploy/caddy/mirotalk.nomadro.com.caddy index 2d18c5f..e6b4c7e 100644 --- a/deploy/caddy/mirotalk.nomadro.com.caddy +++ b/deploy/caddy/mirotalk.nomadro.com.caddy @@ -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 } } diff --git a/frontend/src/app/changelog/page.tsx b/frontend/src/app/changelog/page.tsx index 9e73783..a4c5e16 100644 --- a/frontend/src/app/changelog/page.tsx +++ b/frontend/src/app/changelog/page.tsx @@ -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: "匹配资料 · 报名日历", diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 2e6b3ec..0e5af84 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -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; } diff --git a/frontend/src/components/MeetupLiveClient.tsx b/frontend/src/components/MeetupLiveClient.tsx index 564954e..f1240ca 100644 --- a/frontend/src/components/MeetupLiveClient.tsx +++ b/frontend/src/components/MeetupLiveClient.tsx @@ -112,17 +112,37 @@ export default function MeetupLiveClient({ meetupId, title }: { meetupId: string ))} +
+ {session.videoUrl && ( + + {t.live.openVideo} + + )} + {session.chatUrl && ( + + {t.live.openChat} + + )} + {t.live.iframeHint} +
{(layout === "split" || layout === "video") && session.videoUrl && (