优化目录结构
This commit is contained in:
159
client/public/call/signaling/connect-directory.js
Normal file
159
client/public/call/signaling/connect-directory.js
Normal file
@@ -0,0 +1,159 @@
|
||||
const EMPTY_CONNECTION_IDS_HTML = '<p class="text-gray-500 text-sm">\u6682\u65e0\u53ef\u7528\u7684\u8fde\u63a5ID</p>';
|
||||
const EMPTY_USERS_HTML = '<p class="text-gray-500 text-sm">\u6682\u65e0\u5728\u7ebf\u7528\u6237</p>';
|
||||
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, '"')
|
||||
.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 = `
|
||||
<span class="text-sm font-medium text-white">${escapeHtml(groupName)}</span>
|
||||
<span class="text-xs text-gray-400">${roomUsers.length} ${USER_COUNT_SUFFIX}</span>
|
||||
`;
|
||||
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 = `
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<img src="${escapeHtml(avatar)}" alt="${escapeHtml(userName)}" class="w-8 h-8 rounded-full object-cover">
|
||||
<div class="min-w-0">
|
||||
<div class="text-sm text-white truncate">${escapeHtml(userName)}</div>
|
||||
<div class="text-xs text-gray-400 truncate">${escapeHtml(identity)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs px-2 py-1 rounded-full ${user.role === 'host' ? 'bg-indigo-500/20 text-indigo-300' : (user.role === 'participant' ? 'bg-white/10 text-gray-300' : 'bg-emerald-500/20 text-emerald-300')}">${roleLabel}</span>
|
||||
${isSelf ? `<span class="text-xs text-gray-500">${SELF_LABEL}</span>` : ''}
|
||||
</div>
|
||||
`;
|
||||
roomList.appendChild(userItem);
|
||||
});
|
||||
|
||||
section.appendChild(roomList);
|
||||
usersContainer.appendChild(section);
|
||||
});
|
||||
|
||||
onlineUsersList.classList.remove('hidden');
|
||||
}
|
||||
102
client/public/call/signaling/signaling-session.js
Normal file
102
client/public/call/signaling/signaling-session.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import { Signaling, WebSocketSignaling } from '/module/core/signaling.js';
|
||||
import { createLogger } from '../../shared/logger.js';
|
||||
|
||||
const logger = createLogger('signaling');
|
||||
|
||||
const INVITE_EVENT_NAMES = Object.freeze([
|
||||
'invite-call',
|
||||
'invite-accepted',
|
||||
'invite-rejected',
|
||||
'invite-failed'
|
||||
]);
|
||||
|
||||
const DEFAULT_SOCKET_USER_NAME = '?';
|
||||
const DEFAULT_SOCKET_USER_AVATAR = '/images/p1.png';
|
||||
|
||||
export function createSignalingInstance(useWebSocket) {
|
||||
return useWebSocket ? new WebSocketSignaling() : new Signaling();
|
||||
}
|
||||
|
||||
export async function ensureSignalingStarted(existingSignaling, useWebSocket) {
|
||||
if (existingSignaling) {
|
||||
return {
|
||||
signaling: existingSignaling,
|
||||
reused: true
|
||||
};
|
||||
}
|
||||
|
||||
const signaling = createSignalingInstance(useWebSocket);
|
||||
await signaling.start();
|
||||
|
||||
return {
|
||||
signaling,
|
||||
reused: false
|
||||
};
|
||||
}
|
||||
|
||||
export function bindInviteSocketEvents(signaling, eventHandlers, boundSignaling = null) {
|
||||
if (!signaling || signaling === boundSignaling || typeof signaling.addEventListener !== 'function') {
|
||||
return boundSignaling;
|
||||
}
|
||||
|
||||
INVITE_EVENT_NAMES.forEach((eventName) => {
|
||||
signaling.addEventListener(eventName, (event) => {
|
||||
const handler = eventHandlers[eventName];
|
||||
if (typeof handler === 'function') {
|
||||
handler(event.detail);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return signaling;
|
||||
}
|
||||
|
||||
export function getActiveSignalingInstance(preconnectedSignaling, renderstreaming) {
|
||||
if (preconnectedSignaling) {
|
||||
return preconnectedSignaling;
|
||||
}
|
||||
|
||||
if (renderstreaming && renderstreaming._signaling) {
|
||||
return renderstreaming._signaling;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function sendInviteSignal(signaling, methodName, payload) {
|
||||
if (!signaling || typeof signaling[methodName] !== 'function') {
|
||||
throw new Error('Invite signaling is not ready');
|
||||
}
|
||||
|
||||
signaling[methodName](payload);
|
||||
}
|
||||
|
||||
function readStoredSocketUserInfo() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem('userSettings') || '{}');
|
||||
} catch (error) {
|
||||
logger.error('Error parsing user settings:', error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export function buildSocketUserInfoPayload(userInfo, localUser) {
|
||||
const settings = userInfo || readStoredSocketUserInfo();
|
||||
|
||||
return {
|
||||
id: settings.id || settings.userId || localUser.id || '',
|
||||
name: settings.name || localUser.name || DEFAULT_SOCKET_USER_NAME,
|
||||
avatar: settings.avatar || localUser.avatar || DEFAULT_SOCKET_USER_AVATAR
|
||||
};
|
||||
}
|
||||
|
||||
export function sendSocketUserInfo(signaling, payload) {
|
||||
if (!signaling || typeof signaling.sendMessage !== 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
signaling.sendMessage('', {
|
||||
type: 'user-info',
|
||||
data: payload
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user