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:
parent
f9ab758926
commit
5d741df77b
@ -13,7 +13,8 @@ from app.data import mock_data
|
|||||||
from app.data import community_data
|
from app.data import community_data
|
||||||
from app.services.recommendations import recommend_destinations
|
from app.services.recommendations import recommend_destinations
|
||||||
from app.services.meetup_live import (
|
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.auth import get_user_by_token
|
||||||
from app.services import community_store, social_store
|
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")
|
mode = meetup.get("mode", "offline")
|
||||||
display = (user or {}).get("name", "访客")
|
display = (user or {}).get("name", "访客")
|
||||||
|
|
||||||
|
if mode not in ("online", "hybrid"):
|
||||||
|
can_join = False
|
||||||
|
lock = lock or "offline"
|
||||||
|
|
||||||
session = MeetupSession(
|
session = MeetupSession(
|
||||||
canJoin=can_join and mode in ("online", "hybrid"),
|
canJoin=can_join,
|
||||||
lockReason=lock if not can_join else "",
|
lockReason=lock if not can_join else "",
|
||||||
displayName=display,
|
displayName=display,
|
||||||
mode=mode,
|
mode=mode,
|
||||||
mirotalkRoom=meetup.get("mirotalkRoom") or create_mirotalk_room_slug(meetup),
|
mirotalkRoom=meetup.get("mirotalkRoom") or create_mirotalk_room_slug(meetup),
|
||||||
loungeChannel=meetup.get("loungeChannel", ""),
|
loungeChannel=meetup.get("loungeChannel") or "",
|
||||||
)
|
)
|
||||||
if session.canJoin:
|
if session.canJoin:
|
||||||
session.videoUrl = build_mirotalk_join_url(meetup, display)
|
session.videoUrl = build_mirotalk_join_url(meetup, display)
|
||||||
session.chatUrl = build_lounge_chat_url(meetup)
|
session.chatUrl = build_lounge_chat_url(meetup)
|
||||||
|
if not session.loungeChannel:
|
||||||
|
session.loungeChannel = create_lounge_channel(meetup)
|
||||||
return session
|
return session
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -2,11 +2,14 @@ lounge.nomadro.com {
|
|||||||
encode gzip
|
encode gzip
|
||||||
reverse_proxy https://lounge.nomadro.cn {
|
reverse_proxy https://lounge.nomadro.cn {
|
||||||
header_up Host lounge.nomadro.cn
|
header_up Host lounge.nomadro.cn
|
||||||
|
header_down -Content-Security-Policy
|
||||||
|
header_down -X-Frame-Options
|
||||||
transport http {
|
transport http {
|
||||||
tls_insecure_skip_verify
|
tls_insecure_skip_verify
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
header {
|
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"
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,15 @@ mirotalk.nomadro.com {
|
|||||||
encode gzip
|
encode gzip
|
||||||
reverse_proxy https://mirotalk.nomadro.cn {
|
reverse_proxy https://mirotalk.nomadro.cn {
|
||||||
header_up Host 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 {
|
transport http {
|
||||||
tls_insecure_skip_verify
|
tls_insecure_skip_verify
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
header {
|
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"
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,13 @@ export const metadata: Metadata = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const LOGS = [
|
const LOGS = [
|
||||||
|
{
|
||||||
|
date: "2026-09-04",
|
||||||
|
tag: "直播 iframe 修复",
|
||||||
|
items: [
|
||||||
|
"修复 MiroTalk/Lounge 被上游 CSP 拦截导致直播页空白;可新窗口打开视频/聊天",
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
date: "2026-09-04",
|
date: "2026-09-04",
|
||||||
tag: "匹配资料 · 报名日历",
|
tag: "匹配资料 · 报名日历",
|
||||||
|
|||||||
@ -11044,10 +11044,23 @@ a.weather-card:hover {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.live-layout-btns { margin-left: auto; display: flex; gap: 6px; }
|
.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 {
|
.live-stage {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: grid;
|
display: grid;
|
||||||
min-height: calc(100vh - 52px);
|
min-height: calc(100vh - 110px);
|
||||||
}
|
}
|
||||||
.live-stage.layout-split { grid-template-columns: 1fr 380px; }
|
.live-stage.layout-split { grid-template-columns: 1fr 380px; }
|
||||||
.live-stage.layout-video { grid-template-columns: 1fr; }
|
.live-stage.layout-video { grid-template-columns: 1fr; }
|
||||||
|
|||||||
@ -112,17 +112,37 @@ export default function MeetupLiveClient({ meetupId, title }: { meetupId: string
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</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}`}>
|
<div className={`live-stage layout-${layout}`}>
|
||||||
{(layout === "split" || layout === "video") && session.videoUrl && (
|
{(layout === "split" || layout === "video") && session.videoUrl && (
|
||||||
<iframe
|
<iframe
|
||||||
title="video"
|
title="video"
|
||||||
src={session.videoUrl}
|
src={session.videoUrl}
|
||||||
className="live-iframe live-video"
|
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 && (
|
{(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 && (
|
{!session.videoUrl && !session.chatUrl && (
|
||||||
<div className="notif-empty">
|
<div className="notif-empty">
|
||||||
|
|||||||
@ -520,6 +520,9 @@ export const zh = {
|
|||||||
layoutVideo: "视频",
|
layoutVideo: "视频",
|
||||||
layoutChat: "聊天",
|
layoutChat: "聊天",
|
||||||
linksMissing: "直播链接尚未配置",
|
linksMissing: "直播链接尚未配置",
|
||||||
|
openVideo: "新窗口打开视频",
|
||||||
|
openChat: "新窗口打开聊天",
|
||||||
|
iframeHint: "若画面空白,请用上方按钮在新窗口打开",
|
||||||
},
|
},
|
||||||
digital: {
|
digital: {
|
||||||
tag: "🎓 ACADEMY",
|
tag: "🎓 ACADEMY",
|
||||||
@ -1795,6 +1798,9 @@ export const en: { [K in keyof typeof zh]: typeof zh[K] extends string ? string
|
|||||||
layoutVideo: "Video",
|
layoutVideo: "Video",
|
||||||
layoutChat: "Chat",
|
layoutChat: "Chat",
|
||||||
linksMissing: "Live links are not configured yet",
|
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: {
|
digital: {
|
||||||
tag: "🎓 ACADEMY",
|
tag: "🎓 ACADEMY",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user