diff --git a/client/public/connect-directory.js b/client/public/connect-directory.js new file mode 100644 index 0000000..5036797 --- /dev/null +++ b/client/public/connect-directory.js @@ -0,0 +1,159 @@ +const EMPTY_CONNECTION_IDS_HTML = '

\u6682\u65e0\u53ef\u7528\u7684\u8fde\u63a5ID

'; +const EMPTY_USERS_HTML = '

\u6682\u65e0\u5728\u7ebf\u7528\u6237

'; +const HALL_LABEL = '\u5927\u5385\uff08\u672a\u52a0\u5165\u623f\u95f4\uff09'; +const HOST_LABEL = '\u623f\u4e3b'; +const PARTICIPANT_LABEL = '\u6210\u5458'; +const UNKNOWN_USER_LABEL = '\u533f\u540d\u7528\u6237'; +const UNSET_USER_ID_LABEL = '\u672a\u8bbe\u7f6eID'; +const SELF_LABEL = '\u81ea\u5df1'; +const SELECT_LABEL = '\u9009\u62e9'; +const USER_COUNT_SUFFIX = '\u4eba'; +const ONLINE_USERS_SUMMARY_SUFFIX = ' \u4e2aWebSocket\u7528\u6237\u5728\u7ebf'; + +function escapeHtml(value) { + return String(value || '') + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +export async function fetchOnlineUsers() { + const response = await fetch('/signaling/users'); + if (!response.ok) { + throw new Error('Failed to fetch online users'); + } + + const data = await response.json(); + return Array.isArray(data.users) ? data.users : []; +} + +export async function fetchConnectionDirectory() { + const [connectionResponse, usersResponse] = await Promise.all([ + fetch('/signaling/connection-ids'), + fetch('/signaling/users') + ]); + + if (!connectionResponse.ok) { + throw new Error('Failed to fetch connection IDs'); + } + if (!usersResponse.ok) { + throw new Error('Failed to fetch online users'); + } + + const connectionData = await connectionResponse.json(); + const usersData = await usersResponse.json(); + + return { + connectionIds: connectionData.connectionIds || [], + users: Array.isArray(usersData.users) ? usersData.users : [] + }; +} + +export function renderConnectionIds({ connectionIds, idsContainer, connectionIdsList, onSelectConnectionId }) { + if (!idsContainer) { + return; + } + + idsContainer.innerHTML = ''; + + if (connectionIds.length === 0) { + idsContainer.innerHTML = EMPTY_CONNECTION_IDS_HTML; + } else { + connectionIds.forEach((connectionId) => { + const item = document.createElement('div'); + item.className = 'flex items-center justify-between p-2 bg-white/5 rounded-lg hover:bg-white/10 cursor-pointer transition-colors'; + + const label = document.createElement('span'); + label.className = 'text-sm'; + label.textContent = connectionId; + + const button = document.createElement('button'); + button.className = 'text-xs bg-indigo-600 hover:bg-indigo-700 px-2 py-1 rounded'; + button.type = 'button'; + button.textContent = SELECT_LABEL; + button.addEventListener('click', () => onSelectConnectionId(connectionId)); + + item.appendChild(label); + item.appendChild(button); + idsContainer.appendChild(item); + }); + } + + if (connectionIdsList) { + connectionIdsList.classList.remove('hidden'); + } +} + +export function renderOnlineUsers({ users, currentUserId, onlineUsersList, usersContainer, onlineUsersSummary }) { + if (!onlineUsersList || !usersContainer || !onlineUsersSummary) { + return; + } + + onlineUsersSummary.textContent = `${users.length}${ONLINE_USERS_SUMMARY_SUFFIX}`; + usersContainer.innerHTML = ''; + + if (users.length === 0) { + usersContainer.innerHTML = EMPTY_USERS_HTML; + onlineUsersList.classList.remove('hidden'); + return; + } + + const groupedUsers = users.reduce((groups, user) => { + const groupName = user.connectionId ? `\u623f\u95f4 ${user.connectionId}` : HALL_LABEL; + if (!groups[groupName]) { + groups[groupName] = []; + } + groups[groupName].push(user); + return groups; + }, {}); + + Object.entries(groupedUsers).forEach(([groupName, roomUsers]) => { + const section = document.createElement('div'); + section.className = 'rounded-lg border border-white/10 bg-white/5 p-3'; + + const roomTitle = document.createElement('div'); + roomTitle.className = 'flex items-center justify-between mb-2'; + roomTitle.innerHTML = ` + ${escapeHtml(groupName)} + ${roomUsers.length} ${USER_COUNT_SUFFIX} + `; + section.appendChild(roomTitle); + + const roomList = document.createElement('div'); + roomList.className = 'space-y-2'; + + roomUsers.forEach((user) => { + const userName = user.name || user.userId || UNKNOWN_USER_LABEL; + const avatar = user.avatar || '/images/p2.png'; + const roleLabel = user.role === 'host' + ? HOST_LABEL + : (user.role === 'participant' ? PARTICIPANT_LABEL : HALL_LABEL); + const isSelf = Boolean(user.userId) && user.userId === currentUserId; + const identity = user.userId || user.socketId || user.participantId || UNSET_USER_ID_LABEL; + + const userItem = document.createElement('div'); + userItem.className = 'flex items-center justify-between rounded-lg bg-black/20 px-3 py-2'; + userItem.innerHTML = ` +
+ ${escapeHtml(userName)} +
+
${escapeHtml(userName)}
+
${escapeHtml(identity)}
+
+
+
+ ${roleLabel} + ${isSelf ? `${SELF_LABEL}` : ''} +
+ `; + roomList.appendChild(userItem); + }); + + section.appendChild(roomList); + usersContainer.appendChild(section); + }); + + onlineUsersList.classList.remove('hidden'); +} diff --git a/client/public/connectview.js b/client/public/connectview.js index d5d62e8..fa36d8d 100644 --- a/client/public/connectview.js +++ b/client/public/connectview.js @@ -5,6 +5,12 @@ import { showNotification } from './utils.js'; import store from './store.js'; +import { + fetchConnectionDirectory, + fetchOnlineUsers, + renderConnectionIds, + renderOnlineUsers +} from './connect-directory.js'; const MAX_AVATAR_SIZE = 2 * 1024 * 1024; // 2MB @@ -66,13 +72,8 @@ export async function initWebSocket() { */ async function refreshOnlineUsers(silent = true) { try { - const response = await fetch('/signaling/users'); - if (!response.ok) { - throw new Error('Failed to fetch online users'); - } - const data = await response.json(); - cachedOnlineUsers = Array.isArray(data.users) ? data.users : []; - displayOnlineUsers(cachedOnlineUsers); + cachedOnlineUsers = await fetchOnlineUsers(); + updateOnlineUsersList(cachedOnlineUsers); if (!silent) { showNotification(`当前共有 ${cachedOnlineUsers.length} 个WebSocket用户在线`); } @@ -90,24 +91,10 @@ async function refreshOnlineUsers(silent = true) { async function getAllConnectionIds() { showNotification('正在获取连接ID和在线用户...'); try { - const [connectionResponse, usersResponse] = await Promise.all([ - fetch('/signaling/connection-ids'), - fetch('/signaling/users') - ]); - - if (!connectionResponse.ok) { - throw new Error('Failed to fetch connection IDs'); - } - - if (!usersResponse.ok) { - throw new Error('Failed to fetch online users'); - } - - const connectionData = await connectionResponse.json(); - const usersData = await usersResponse.json(); - cachedOnlineUsers = Array.isArray(usersData.users) ? usersData.users : []; - displayConnectionIds(connectionData.connectionIds || []); - displayOnlineUsers(cachedOnlineUsers); + const { connectionIds, users } = await fetchConnectionDirectory(); + cachedOnlineUsers = users; + updateConnectionIdList(connectionIds); + updateOnlineUsersList(cachedOnlineUsers); } catch (error) { console.error('Error fetching connection IDs:', error); showNotification('获取连接信息失败', 'error'); @@ -118,49 +105,22 @@ async function getAllConnectionIds() { * 显示连接ID列表 * @param {string[]} connectionIds - 连接ID数组 */ -function displayConnectionIds(connectionIds) { +function updateConnectionIdList(connectionIds) { const idsContainer = document.getElementById('idsContainer'); const connectionIdsList = document.getElementById('connectionIdsList'); + renderConnectionIds({ + connectionIds, + idsContainer, + connectionIdsList, + onSelectConnectionId: selectConnectionId + }); + if (idsContainer) { - idsContainer.innerHTML = ''; - - if (connectionIds.length === 0) { - idsContainer.innerHTML = '

暂无可用的连接ID

'; - } else { - connectionIds.forEach(id => { - const idElement = document.createElement('div'); - idElement.className = 'flex items-center justify-between p-2 bg-white/5 rounded-lg hover:bg-white/10 cursor-pointer transition-colors'; - idElement.innerHTML = ` - ${id} - - `; - idsContainer.appendChild(idElement); - }); - } - - if (connectionIdsList) { - connectionIdsList.classList.remove('hidden'); - } - showNotification(`找到 ${connectionIds.length} 个连接ID`); } } -/** - * 转义HTML特殊字符 - * @param {string} value - 原始字符串 - * @returns {string} 安全字符串 - */ -function escapeHtml(value) { - return String(value || '') - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); -} - function getCurrentUserId() { try { const settings = JSON.parse(localStorage.getItem('userSettings') || '{}'); @@ -175,77 +135,18 @@ function getCurrentUserId() { * 显示全部在线WebSocket用户 * @param {Array} users - 在线用户列表 */ -function displayOnlineUsers(users) { +function updateOnlineUsersList(users) { const onlineUsersList = document.getElementById('onlineUsersList'); const usersContainer = document.getElementById('usersContainer'); const onlineUsersSummary = document.getElementById('onlineUsersSummary'); - if (!onlineUsersList || !usersContainer || !onlineUsersSummary) { - return; - } - - onlineUsersSummary.textContent = `${users.length} 个WebSocket用户在线`; - - usersContainer.innerHTML = ''; - - if (users.length === 0) { - usersContainer.innerHTML = '

暂无在线用户

'; - onlineUsersList.classList.remove('hidden'); - return; - } - - const groupedUsers = users.reduce((groups, user) => { - const groupName = user.connectionId ? `房间 ${user.connectionId}` : '大厅(未加入房间)'; - if (!groups[groupName]) { - groups[groupName] = []; - } - groups[groupName].push(user); - return groups; - }, {}); - - Object.entries(groupedUsers).forEach(([groupName, roomUsers]) => { - const section = document.createElement('div'); - section.className = 'rounded-lg border border-white/10 bg-white/5 p-3'; - - const roomTitle = document.createElement('div'); - roomTitle.className = 'flex items-center justify-between mb-2'; - roomTitle.innerHTML = ` - ${escapeHtml(groupName)} - ${roomUsers.length} 人 - `; - section.appendChild(roomTitle); - - const roomList = document.createElement('div'); - roomList.className = 'space-y-2'; - - roomUsers.forEach((user) => { - const userName = user.name || user.userId || '匿名用户'; - const avatar = user.avatar || '/images/p2.png'; - const roleLabel = user.role === 'host' ? '房主' : (user.role === 'participant' ? '成员' : '大厅'); - const isSelf = Boolean(user.userId) && user.userId === getCurrentUserId(); - const userItem = document.createElement('div'); - userItem.className = 'flex items-center justify-between rounded-lg bg-black/20 px-3 py-2'; - userItem.innerHTML = ` -
- ${escapeHtml(userName)} -
-
${escapeHtml(userName)}
-
${escapeHtml(user.userId || user.socketId || user.participantId || '未设置ID')}
-
-
-
- ${roleLabel} - ${isSelf ? '自己' : ''} -
- `; - roomList.appendChild(userItem); - }); - - section.appendChild(roomList); - usersContainer.appendChild(section); + renderOnlineUsers({ + users, + currentUserId: getCurrentUserId(), + onlineUsersList, + usersContainer, + onlineUsersSummary }); - - onlineUsersList.classList.remove('hidden'); } /** diff --git a/client/public/invite-controller.js b/client/public/invite-controller.js new file mode 100644 index 0000000..3e8f4f5 --- /dev/null +++ b/client/public/invite-controller.js @@ -0,0 +1,187 @@ +const DEFAULT_CALLER_NAME = '\u9080\u8bf7\u65b9'; +const DEFAULT_CALLER_AVATAR = '/images/p2.png'; +const DEFAULT_APPLY_REASON = '\u672a\u586b\u5199'; + +function readConnectionIdFromSearch(search) { + return new URLSearchParams(search).get('connectionId') || ''; +} + +function hideInviteDialog() { + const dialog = document.getElementById('callRequestDialog'); + if (dialog) { + dialog.classList.add('hidden'); + } +} + +function normalizeInviteCaller(caller = {}) { + return { + connectionId: caller.connectionId || '', + inviterSocketId: caller.inviterSocketId || '', + inviterUserId: caller.inviterUserId || '', + name: caller.name || caller.inviterName || DEFAULT_CALLER_NAME, + avatar: caller.avatar || caller.inviterAvatar || DEFAULT_CALLER_AVATAR, + applyReason: caller.applyReason || caller.reason || DEFAULT_APPLY_REASON + }; +} + +function renderInviteDialog(caller) { + const dialog = document.getElementById('callRequestDialog'); + if (!dialog) { + return false; + } + + const callRequestName = document.getElementById('callRequestName'); + const callRequestAvatar = document.getElementById('callRequestAvatar'); + const callRequestText = document.getElementById('callRequestText'); + const callRequestReason = document.getElementById('callRequestReason'); + + if (callRequestName) { + callRequestName.textContent = caller.name; + } + if (callRequestAvatar) { + callRequestAvatar.src = caller.avatar; + } + if (callRequestText) { + callRequestText.textContent = caller.connectionId + ? `\u6b63\u5728\u9080\u8bf7\u60a8\u52a0\u5165\u901a\u8bdd (${caller.connectionId})` + : '\u6b63\u5728\u8bf7\u6c42\u4e0e\u60a8\u8fdb\u884c\u89c6\u9891\u901a\u8bdd'; + } + if (callRequestReason) { + callRequestReason.textContent = caller.applyReason; + } + + dialog.classList.remove('hidden'); + return true; +} + +export function createInviteController({ + store, + notify, + onAcceptConnection, + getCurrentView, + getConnectionId, + setConnectionId +}) { + let pendingInvite = null; + let signalHandlersBound = false; + + function showCallRequestDialog(caller = {}) { + const normalizedCaller = normalizeInviteCaller(caller); + pendingInvite = normalizedCaller; + + if (normalizedCaller.connectionId) { + setConnectionId(normalizedCaller.connectionId); + } + + return renderInviteDialog(normalizedCaller); + } + + function getInvitePayloadFromUrl(search = window.location.search) { + const params = new URLSearchParams(search); + if (params.get('invite') !== '1') { + return null; + } + + return normalizeInviteCaller({ + name: params.get('callerName'), + avatar: params.get('callerAvatar'), + connectionId: params.get('connectionId') + }); + } + + function bindSignalHandlers() { + if (signalHandlersBound) { + return; + } + + store.onSocketEvent('invite-call', (payload) => { + const caller = normalizeInviteCaller(payload); + showCallRequestDialog(caller); + notify(`${caller.name} \u9080\u8bf7\u4f60\u52a0\u5165\u901a\u8bdd`); + }); + + signalHandlersBound = true; + } + + async function acceptInvite() { + hideInviteDialog(); + + const targetConnectionId = + (pendingInvite && pendingInvite.connectionId) || + getConnectionId() || + localStorage.getItem('connectionId') || + readConnectionIdFromSearch(window.location.search); + + if (!targetConnectionId) { + notify('\u7f3a\u5c11\u8fde\u63a5ID\uff0c\u65e0\u6cd5\u63a5\u53d7\u9080\u8bf7', 'error'); + return; + } + + setConnectionId(targetConnectionId); + + if (pendingInvite) { + try { + store.sendInviteAccepted({ + connectionId: targetConnectionId, + targetSocketId: pendingInvite.inviterSocketId, + targetUserId: pendingInvite.inviterUserId + }); + } catch (error) { + console.error('Error accepting invite:', error); + notify('\u63a5\u53d7\u9080\u8bf7\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5', 'error'); + return; + } + } + + pendingInvite = null; + notify('\u5df2\u63a5\u53d7\u901a\u8bdd\u8bf7\u6c42'); + + if (getCurrentView() !== 'call') { + await onAcceptConnection(targetConnectionId); + } + } + + function rejectInvite() { + hideInviteDialog(); + + if (pendingInvite) { + try { + store.sendInviteRejected({ + connectionId: pendingInvite.connectionId, + targetSocketId: pendingInvite.inviterSocketId, + targetUserId: pendingInvite.inviterUserId + }); + } catch (error) { + console.error('Error rejecting invite:', error); + } + } + + pendingInvite = null; + notify('\u5df2\u62d2\u7edd\u901a\u8bdd\u8bf7\u6c42'); + } + + function bindDialogEvents() { + window.showCallRequest = showCallRequestDialog; + window.rejectCall = rejectInvite; + window.acceptCall = acceptInvite; + + const rejectCallButton = document.getElementById('rejectCall'); + const acceptCallButton = document.getElementById('acceptCall'); + + if (rejectCallButton && !rejectCallButton.dataset.bound) { + rejectCallButton.addEventListener('click', rejectInvite); + rejectCallButton.dataset.bound = 'true'; + } + if (acceptCallButton && !acceptCallButton.dataset.bound) { + acceptCallButton.addEventListener('click', acceptInvite); + acceptCallButton.dataset.bound = 'true'; + } + } + + return { + bindDialogEvents, + bindSignalHandlers, + getInvitePayloadFromUrl, + showCallRequestDialog + }; +} diff --git a/client/public/main.js b/client/public/main.js index d5c8e76..fe146e8 100644 --- a/client/public/main.js +++ b/client/public/main.js @@ -12,176 +12,16 @@ import { initWebSocket, loadUserSettings } from './connectview.js'; +import { createInviteController } from './invite-controller.js'; // 全局变量 let connectionId = ""; // 当前视图状态:'connect' 或 'call'(可用于未来扩展) let currentView = 'connect'; -let pendingIncomingInvite = null; -let inviteHandlersBound = false; +function updateConnectionId(nextConnectionId) { + connectionId = nextConnectionId || ''; -function getInvitePayloadFromUrl() { - const params = new URLSearchParams(window.location.search); - if (params.get('invite') !== '1') { - return null; - } - - return { - name: params.get('callerName') || '邀请方', - avatar: params.get('callerAvatar') || '/images/p2.png', - connectionId: params.get('connectionId') || '' - }; -} - -function showCallRequestDialog(caller = {}) { - const dialog = document.getElementById('callRequestDialog'); - if (!dialog) { - return; - } - - const callerName = caller.name || '邀请方'; - const callerAvatar = caller.avatar || '/images/p2.png'; - const targetConnectionId = caller.connectionId || ''; - const applyReason = caller.applyReason || caller.reason || '未填写'; - pendingIncomingInvite = caller; - - if (document.getElementById('callRequestName')) { - document.getElementById('callRequestName').textContent = callerName; - } - if (document.getElementById('callRequestAvatar')) { - document.getElementById('callRequestAvatar').src = callerAvatar; - } - if (document.getElementById('callRequestText')) { - document.getElementById('callRequestText').textContent = targetConnectionId - ? `正在邀请您加入通话 (${targetConnectionId})` - : '正在请求与您进行视频通话'; - } - if (document.getElementById('callRequestReason')) { - document.getElementById('callRequestReason').textContent = applyReason; - } - - if (targetConnectionId) { - connectionId = targetConnectionId; - localStorage.setItem('connectionId', targetConnectionId); - } - - dialog.classList.remove('hidden'); -} - -function getCurrentUserProfile() { - try { - const settings = JSON.parse(localStorage.getItem('userSettings') || '{}'); - return { - userId: settings.userId || settings.id || '', - name: settings.name || '我', - avatar: settings.avatar || '/images/p1.png' - }; - } catch (error) { - console.error('Error parsing user settings:', error); - return { - userId: '', - name: '我', - avatar: '/images/p1.png' - }; - } -} - -function bindInviteSignalHandlers() { - if (inviteHandlersBound) { - return; - } - - store.onSocketEvent('invite-call', (payload) => { - pendingIncomingInvite = { - connectionId: payload.connectionId, - inviterSocketId: payload.inviterSocketId, - inviterUserId: payload.inviterUserId, - name: payload.inviterName || '邀请方', - avatar: payload.inviterAvatar || '/images/p2.png', - applyReason: payload.applyReason || payload.reason || '' - }; - showCallRequestDialog(pendingIncomingInvite); - showNotification(`${pendingIncomingInvite.name} 邀请你加入通话`); - }); - - inviteHandlersBound = true; -} - -function bindInviteDialogEvents() { - window.showCallRequest = function (caller) { - showCallRequestDialog(caller); - }; - - window.rejectCall = function () { - const dialog = document.getElementById('callRequestDialog'); - if (dialog) { - dialog.classList.add('hidden'); - } - if (pendingIncomingInvite) { - try { - store.sendInviteRejected({ - connectionId: pendingIncomingInvite.connectionId || '', - targetSocketId: pendingIncomingInvite.inviterSocketId || '', - targetUserId: pendingIncomingInvite.inviterUserId || '' - }); - } catch (error) { - console.error('Error rejecting invite:', error); - } - } - pendingIncomingInvite = null; - showNotification('已拒绝通话请求'); - }; - - window.acceptCall = async function () { - const dialog = document.getElementById('callRequestDialog'); - if (dialog) { - dialog.classList.add('hidden'); - } - - const targetConnectionId = - (pendingIncomingInvite && pendingIncomingInvite.connectionId) || - connectionId || - localStorage.getItem('connectionId') || - new URLSearchParams(window.location.search).get('connectionId'); - - if (!targetConnectionId) { - showNotification('缺少连接ID,无法接受邀请', 'error'); - return; - } - - connectionId = targetConnectionId; - localStorage.setItem('connectionId', targetConnectionId); - - if (pendingIncomingInvite) { - try { - store.sendInviteAccepted({ - connectionId: targetConnectionId, - targetSocketId: pendingIncomingInvite.inviterSocketId || '', - targetUserId: pendingIncomingInvite.inviterUserId || '' - }); - } catch (error) { - console.error('Error accepting invite:', error); - showNotification('接受邀请失败,请稍后重试', 'error'); - return; - } - } - - showNotification('已接受通话请求'); - pendingIncomingInvite = null; - - if (currentView !== 'call') { - await switchToCallView(targetConnectionId); - } - }; - - const rejectCall = document.getElementById('rejectCall'); - const acceptCall = document.getElementById('acceptCall'); - if (rejectCall && !rejectCall.dataset.bound) { - rejectCall.addEventListener('click', window.rejectCall); - rejectCall.dataset.bound = 'true'; - } - if (acceptCall && !acceptCall.dataset.bound) { - acceptCall.addEventListener('click', window.acceptCall); - acceptCall.dataset.bound = 'true'; + if (connectionId) { + localStorage.setItem('connectionId', connectionId); } } @@ -222,13 +62,22 @@ async function switchToCallView(connectionId) { } } +const inviteController = createInviteController({ + store, + notify: showNotification, + onAcceptConnection: switchToCallView, + getCurrentView: () => currentView, + getConnectionId: () => connectionId, + setConnectionId: updateConnectionId +}); + /** * 处理加入通话 * @param {string} connectionId - 连接ID */ async function handleJoinCall(connectionId) { showNotification(`正在加入通话 (${connectionId})`); - localStorage.setItem('connectionId', connectionId); + updateConnectionId(connectionId); await switchToCallView(connectionId); } @@ -240,7 +89,7 @@ async function handleCreateCall() { //const connectionId = 'conn_' + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); const connectionId = randomMeetingId(); - localStorage.setItem('connectionId', connectionId); + updateConnectionId(connectionId); await switchToCallView(connectionId); } @@ -363,7 +212,7 @@ function bindCallViewDomEvents() { } }); - bindInviteDialogEvents(); + inviteController.bindDialogEvents(); } // 页面加载完成后初始化(SPA入口) @@ -381,23 +230,23 @@ window.addEventListener('DOMContentLoaded', async () => { // 初始化WebSocket连接(在connect视图就建立WebSocket) await initWebSocket(); - bindInviteSignalHandlers(); + inviteController.bindSignalHandlers(); // 绑定connect视图事件(加入通话、创建通话等) bindConnectViewEvents(handleJoinCall, handleCreateCall); - bindInviteDialogEvents(); + inviteController.bindDialogEvents(); // 检查是否有保存的连接ID,填入输入框 const savedConnectionId = localStorage.getItem('connectionId'); if (savedConnectionId) { - connectionId = savedConnectionId; + updateConnectionId(savedConnectionId); const connectionIdInput = document.getElementById('connectionIdInput'); if (connectionIdInput) connectionIdInput.value = savedConnectionId; } - const invitePayload = getInvitePayloadFromUrl(); + const invitePayload = inviteController.getInvitePayloadFromUrl(); if (invitePayload) { - window.showCallRequest(invitePayload); + inviteController.showCallRequestDialog(invitePayload); } console.log('SPA initialized, showing connect view');