'use client'; import React, { useEffect, useRef } from 'react'; interface WhiteboardIframeProps { boardId: string; username?: string; token?: string; /** Только ментор может очищать холст. */ isMentor?: boolean; onToggleView?: () => void; showingBoard?: boolean; className?: string; style?: React.CSSProperties; } /** * Интерактивная доска Excalidraw + Yjs. * Iframe создаётся императивно один раз — React не пересоздаёт его при переключении. */ export function WhiteboardIframe({ boardId, username = 'Пользователь', token: tokenProp, isMentor = false, showingBoard = true, className = '', style = {}, }: WhiteboardIframeProps) { const containerRef = useRef(null); const iframeRef = useRef(null); const createdRef = useRef(false); useEffect(() => { if (typeof window === 'undefined' || !containerRef.current || !boardId) return; if (createdRef.current) return; const token = tokenProp ?? localStorage.getItem('access_token') ?? ''; const apiBase = process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:8123/api'; const apiUrl = apiBase.replace(/\/api\/?$/, '') || 'http://127.0.0.1:8123'; // Вариант 1: отдельный URL доски (как LiveKit) — если app.uchill.online отдаёт один Next без nginx по путям const excalidrawBaseUrl = (process.env.NEXT_PUBLIC_EXCALIDRAW_URL || '').trim().replace(/\/?$/, ''); const url: URL = excalidrawBaseUrl ? new URL(excalidrawBaseUrl.startsWith('http') ? excalidrawBaseUrl + '/' : `${window.location.origin}/${excalidrawBaseUrl}/`) : (() => { const path = process.env.NEXT_PUBLIC_EXCALIDRAW_PATH || ''; if (path) { return new URL((path.startsWith('/') ? path : '/' + path) + '/', window.location.origin); } return new URL('/', `${window.location.protocol}//${window.location.hostname}:${process.env.NEXT_PUBLIC_EXCALIDRAW_PORT || '3001'}`); })(); url.searchParams.set('boardId', boardId); url.searchParams.set('apiUrl', apiUrl); if (token) url.searchParams.set('token', token); if (isMentor) url.searchParams.set('isMentor', '1'); const iframeSrc = excalidrawBaseUrl && excalidrawBaseUrl.startsWith('http') ? url.toString() : `${url.origin}${url.pathname.replace(/\/?$/, '/')}?${url.searchParams.toString()}`; const iframe = document.createElement('iframe'); iframe.src = iframeSrc; iframe.style.cssText = 'width:100%;height:100%;border:none;display:block'; iframe.title = 'Интерактивная доска (Excalidraw)'; iframe.setAttribute('allow', 'camera; microphone; fullscreen'); iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-forms allow-popups allow-modals'); const decodeName = (raw: string): string => { if (!raw || typeof raw !== 'string') return raw; try { if (/%[0-9A-Fa-f]{2}/.test(raw)) return decodeURIComponent(raw); return raw; } catch { return raw; } }; const sendUsername = () => { if (iframe.contentWindow) { try { iframe.contentWindow.postMessage( { type: 'excalidraw-username', username: decodeName(username) }, url.origin ); } catch (_) {} } }; iframe.onload = () => { sendUsername(); setTimeout(sendUsername, 500); }; containerRef.current.appendChild(iframe); iframeRef.current = iframe; createdRef.current = true; setTimeout(sendUsername, 300); return () => { createdRef.current = false; iframeRef.current = null; iframe.remove(); }; }, [boardId, tokenProp, isMentor]); useEffect(() => { if (!iframeRef.current?.contentWindow || !createdRef.current) return; const excalidrawBaseUrl = (process.env.NEXT_PUBLIC_EXCALIDRAW_URL || '').trim().replace(/\/?$/, ''); const targetOrigin = excalidrawBaseUrl.startsWith('http') ? new URL(excalidrawBaseUrl).origin : (() => { const path = process.env.NEXT_PUBLIC_EXCALIDRAW_PATH || ''; if (path) return window.location.origin; return `${window.location.protocol}//${window.location.hostname}:${process.env.NEXT_PUBLIC_EXCALIDRAW_PORT || '3001'}`; })(); const decodeName = (raw: string): string => { if (!raw || typeof raw !== 'string') return raw; try { if (/%[0-9A-Fa-f]{2}/.test(raw)) return decodeURIComponent(raw); return raw; } catch { return raw; } }; try { iframeRef.current.contentWindow.postMessage( { type: 'excalidraw-username', username: decodeName(username) }, targetOrigin ); } catch (_) {} }, [username]); return (
); }