fix: make student demo handshake reliable

dev-QQq
chenyuan 4 weeks ago
parent b6be9d0eac
commit 3b44d0174b

@ -7,11 +7,17 @@ import { exchangeStudentDemoSession } from "@/api/studentDemo";
import { saveStudentDemoSession } from "@/utils/studentDemo";
const message = ref("正在连接教师演示会话…");
let readyTimer;
function notifyReady() {
if (window.opener) window.opener.postMessage({ type: "STUDENT_DEMO_READY" }, window.location.origin);
}
function receiveTicket(event) {
if (event.origin !== window.location.origin || event.source !== window.opener || event.data?.type !== "STUDENT_DEMO_TICKET") return;
exchangeStudentDemoSession(event.data.ticket).then((res) => {
saveStudentDemoSession(res.data);
window.clearInterval(readyTimer);
window.removeEventListener("message", receiveTicket);
window.location.replace("/index");
}).catch(() => { message.value = "演示链接已失效,请返回教师端重新打开。"; });
@ -20,9 +26,13 @@ function receiveTicket(event) {
onMounted(() => {
if (!window.opener) { message.value = "演示链接已失效,请返回教师端重新打开。"; return; }
window.addEventListener("message", receiveTicket);
window.opener.postMessage({ type: "STUDENT_DEMO_READY" }, window.location.origin);
notifyReady();
readyTimer = window.setInterval(notifyReady, 500);
});
onBeforeUnmount(() => {
window.clearInterval(readyTimer);
window.removeEventListener("message", receiveTicket);
});
onBeforeUnmount(() => window.removeEventListener("message", receiveTicket));
</script>
<style scoped>.student-demo-entry{display:grid;min-height:100vh;place-items:center;background:#06111d;color:#dff7ff;font-size:18px}</style>

@ -573,17 +573,29 @@ async function openStudentDemo() {
if (!selectedTeachingClass.value) return;
const child = window.open("/student-demo", "_blank");
if (!child) { proxy?.$modal?.msgError?.("浏览器拦截了演示窗口,请允许弹窗后重试"); return; }
let ticket = "";
let childReady = false;
const cleanup = () => window.removeEventListener("message", listener);
const sendTicket = () => {
if (!childReady || !ticket) return;
child.postMessage({ type: "STUDENT_DEMO_TICKET", ticket }, window.location.origin);
cleanup();
};
const listener = (event) => {
if (event.origin !== window.location.origin || event.source !== child || event.data?.type !== "STUDENT_DEMO_READY") return;
childReady = true;
sendTicket();
};
window.addEventListener("message", listener);
const timeout = window.setTimeout(cleanup, 60 * 1000);
try {
const res = await createStudentDemoSession(selectedTeachingClassId.value);
const ticket = res?.data?.ticket;
const listener = (event) => {
if (event.origin !== window.location.origin || event.source !== child || event.data?.type !== "STUDENT_DEMO_READY") return;
child.postMessage({ type: "STUDENT_DEMO_TICKET", ticket }, window.location.origin);
window.removeEventListener("message", listener);
};
window.addEventListener("message", listener);
window.setTimeout(() => window.removeEventListener("message", listener), 60 * 1000);
ticket = res?.data?.ticket || "";
if (!ticket) throw new Error("missing demo ticket");
sendTicket();
} catch (error) {
window.clearTimeout(timeout);
cleanup();
child.close();
proxy?.$modal?.msgError?.("无法创建学生端讲解会话");
}

@ -19,7 +19,12 @@ assert(api.includes("/api/student-demo-sessions/exchange"), "ticket exchange API
assert(teacherPage.includes("学生端讲解"), "teacher entry missing");
assert(teacherPage.includes("window.open("), "teacher entry must open a new window");
assert(teacherPage.includes("STUDENT_DEMO_READY"), "teacher must wait for child readiness");
assert(
teacherPage.indexOf('window.addEventListener("message", listener)') < teacherPage.indexOf("createStudentDemoSession("),
"teacher must listen for child readiness before requesting the demo ticket"
);
assert(demoPage.includes("STUDENT_DEMO_TICKET"), "demo page must receive ticket via postMessage");
assert(demoPage.includes("window.setInterval"), "demo page must retry its readiness notification until it receives the ticket");
assert(demoPage.includes("window.location.origin"), "postMessage must validate origin");
assert(!teacherPage.includes("ticket="), "ticket must not be sent in URL");
assert(navbar.includes("isStudentDemo()"), "demo mode must not request a profile for its temporary user");

Loading…
Cancel
Save