import { useUserStore } from "../store/user" import type { EditorView } from "@codemirror/view" import { Compartment } from "@codemirror/state" import type { WebrtcProvider } from "y-webrtc" import type { Doc, Text } from "yjs" // 常量定义 const SYNC_CONSTANTS = { MAX_ROOM_USERS: 2, AWARENESS_SYNC_DELAY: 500, INIT_SYNC_TIMEOUT: 500, SUPER_ADMIN_COLOR: "#ff6b6b", REGULAR_USER_COLOR: "#4dabf7", } as const // 类型定义 type SyncState = "waiting" | "active" | "error" interface UserInfo { name: string isSuperAdmin: boolean } interface PeersEvent { added: string[] removed: string[] webrtcPeers: string[] } interface StatusEvent { connected: boolean } interface SyncedEvent { synced: boolean } interface SyncOptions { problemId: string editorView: EditorView onStatusChange?: (status: SyncStatus) => void } export interface SyncStatus { connected: boolean roomUsers: number canSync: boolean message: string error?: string otherUser?: UserInfo } export function useCodeSync() { const userStore = useUserStore() const message = useMessage() // 状态变量 let ydoc: Doc | null = null let provider: WebrtcProvider | null = null let ytext: Text | null = null const collabCompartment = new Compartment() let currentEditorView: EditorView | null = null let lastSyncState: SyncState | null = null let roomUserInfo = new Map() let hasShownSuperAdminLeftMessage = false const updateStatus = ( status: SyncStatus, onStatusChange?: (status: SyncStatus) => void, ) => { onStatusChange?.(status) } const normalizeClientId = (clientId: number | string): number => { return typeof clientId === "string" ? parseInt(clientId, 10) : clientId } const checkHasSuperAdmin = (awarenessStates: Map): boolean => { if (userStore.isSuperAdmin) return true return Array.from(awarenessStates.values()).some( (state) => state.user?.isSuperAdmin, ) } const getOtherUserInfo = ( awarenessStates: Map, ): UserInfo | undefined => { if (!provider) return undefined const localClientId = provider.awareness.clientID for (const [clientId, state] of awarenessStates) { if (clientId !== localClientId && state.user) { return { name: state.user.name, isSuperAdmin: state.user.isSuperAdmin, } } } return undefined } const checkIfSuperAdminLeft = ( removedClientIds: number[], onStatusChange?: (status: SyncStatus) => void, ) => { if (userStore.isSuperAdmin || hasShownSuperAdminLeftMessage) return const superAdminInfo = removedClientIds .map((id) => roomUserInfo.get(id)) .find((info) => info?.isSuperAdmin) if (superAdminInfo) { hasShownSuperAdminLeftMessage = true updateStatus( { connected: false, roomUsers: 0, canSync: false, message: `超管 ${superAdminInfo.name} 已退出`, error: "超管已离开", }, onStatusChange, ) message.warning(`超管 ${superAdminInfo.name} 已退出`) stopSync() } } const checkRoomPermissions = ( roomUsers: number, onStatusChange?: (status: SyncStatus) => void, ) => { const awarenessStates = provider?.awareness.getStates() if (!awarenessStates) return const hasSuperAdmin = checkHasSuperAdmin(awarenessStates) const canSync = roomUsers === SYNC_CONSTANTS.MAX_ROOM_USERS && hasSuperAdmin const otherUser = getOtherUserInfo(awarenessStates) if (roomUsers === SYNC_CONSTANTS.MAX_ROOM_USERS && !hasSuperAdmin) { updateStatus( { connected: true, roomUsers, canSync: false, message: "房间内必须有一个超级管理员", error: "缺少超级管理员", otherUser, }, onStatusChange, ) if (lastSyncState !== "error") { message.warning("协同编辑需要至少一个超级管理员") lastSyncState = "error" } } else if (canSync) { updateStatus( { connected: true, roomUsers, canSync: true, message: "协同编辑已激活,可以开始协作!", otherUser, }, onStatusChange, ) if (lastSyncState !== "active") { message.success("协同编辑已激活,可以开始协作!") lastSyncState = "active" } } else { updateStatus( { connected: true, roomUsers, canSync: false, message: roomUsers === 1 ? "正在等待加入" : "等待超级管理员加入...", otherUser, }, onStatusChange, ) lastSyncState = "waiting" } } const setupContentSync = ( editorView: EditorView, ytext: Text, provider: WebrtcProvider, savedContent: string, ) => { let hasInitialized = false const initTimeout = setTimeout(() => { if (!hasInitialized && ytext.length === 0 && savedContent) { ytext.insert(0, savedContent) } hasInitialized = true }, SYNC_CONSTANTS.INIT_SYNC_TIMEOUT) provider.on("synced", (event: SyncedEvent) => { if (!event.synced || hasInitialized) return clearTimeout(initTimeout) if (ytext.length === 0 && savedContent) { ytext.insert(0, savedContent) } hasInitialized = true }) } async function startSync(options: SyncOptions): Promise<() => void> { const { problemId, editorView, onStatusChange } = options if (!userStore.isAuthed) { updateStatus( { connected: false, roomUsers: 0, canSync: false, message: "请先登录后再使用同步功能", error: "用户未登录", }, onStatusChange, ) message.error("请先登录后再使用同步功能") return () => {} } // 动态导入 yjs 相关模块 const [Y, { WebrtcProvider }, { yCollab }] = await Promise.all([ import("yjs"), import("y-webrtc"), import("y-codemirror.next"), ]) // 初始化文档和提供者 ydoc = new Y.Doc() ytext = ydoc.getText("codemirror") const roomName = `problem-${problemId}` provider = new WebrtcProvider(roomName, ydoc, { signaling: [import.meta.env.PUBLIC_SIGNALING_URL], maxConns: 1, filterBcConns: true, }) // 监听连接状态 provider.on("status", (event: StatusEvent) => { if (!event.connected) { updateStatus( { connected: false, roomUsers: 0, canSync: false, message: "连接已断开", error: "WebRTC 连接断开", }, onStatusChange, ) message.warning("协同编辑连接已断开") } }) // 监听用户加入/离开 provider.on("peers", (event: PeersEvent) => { const roomUsers = event.webrtcPeers.length + 1 if (roomUsers > SYNC_CONSTANTS.MAX_ROOM_USERS) { updateStatus( { connected: false, roomUsers, canSync: false, message: "房间人数已满,已自动断开连接", error: `房间最多只能有${SYNC_CONSTANTS.MAX_ROOM_USERS}个人`, }, onStatusChange, ) message.warning( `房间人数已满(最多${SYNC_CONSTANTS.MAX_ROOM_USERS}人),已自动断开连接`, ) stopSync() return } setTimeout(() => { checkRoomPermissions(roomUsers, onStatusChange) }, SYNC_CONSTANTS.AWARENESS_SYNC_DELAY) }) // 监听 awareness 变化 provider.awareness.on("change", (changes: any) => { if (!provider) return const awarenessStates = provider.awareness.getStates() if (changes.removed?.length > 0) { checkIfSuperAdminLeft(changes.removed, onStatusChange) } awarenessStates.forEach((state, clientId) => { if (state.user) { const normalizedId = normalizeClientId(clientId) roomUserInfo.set(normalizedId, { name: state.user.name, isSuperAdmin: state.user.isSuperAdmin, }) } }) checkRoomPermissions(awarenessStates.size, onStatusChange) }) // 配置编辑器扩展 if (editorView && ytext) { currentEditorView = editorView const userColor = userStore.isSuperAdmin ? SYNC_CONSTANTS.SUPER_ADMIN_COLOR : SYNC_CONSTANTS.REGULAR_USER_COLOR const userName = userStore.user?.username || "匿名用户" const savedContent = editorView.state.doc.toString() // 设置用户信息 provider.awareness.setLocalStateField("user", { name: userName, color: userColor, isSuperAdmin: userStore.isSuperAdmin, }) // 清空编辑器并应用协同扩展 editorView.dispatch({ changes: { from: 0, to: editorView.state.doc.length, insert: "" }, }) const collabExt = yCollab(ytext, provider.awareness) editorView.dispatch({ effects: collabCompartment.reconfigure(collabExt), }) // 设置内容同步 setupContentSync(editorView, ytext, provider, savedContent) // 设置初始状态 updateStatus( { connected: true, roomUsers: 1, canSync: false, message: "正在等待加入", }, onStatusChange, ) message.info( userStore.isSuperAdmin ? "正在等待学生加入..." : "正在等待超管加入...", ) lastSyncState = "waiting" } return () => stopSync() } function stopSync() { if (currentEditorView) { try { currentEditorView.dispatch({ effects: collabCompartment.reconfigure([]), }) } catch (error) { console.warn("移除协同编辑扩展失败:", error) } currentEditorView = null } provider?.disconnect() provider?.destroy() ydoc?.destroy() provider = null ydoc = null ytext = null lastSyncState = null roomUserInfo.clear() hasShownSuperAdminLeftMessage = false } function getInitialExtension() { return collabCompartment.of([]) } return { startSync, stopSync, getInitialExtension, } }