实现简单加密/解密功能,增强房间数据验证,优化剪贴板复制方法,支持从 URL 自动填充房间和密码
This commit is contained in:
1 parent
4e232d6838
commit
92df4dee77
1 file changed
+203
-44
+203
-44
@@ -20,6 +20,142 @@ import {
|
||||
closeSettingsPanel
|
||||
} from './util.settings.js';
|
||||
|
||||
// Utility functions for security and error handling
|
||||
// 安全和错误处理工具函数
|
||||
|
||||
// Simple encryption/decryption using base64 and character shifting
|
||||
// 使用base64和字符偏移的简单加密/解密
|
||||
function simpleEncrypt(text) {
|
||||
if (!text) return '';
|
||||
// Convert to base64 and shift characters
|
||||
const base64 = btoa(unescape(encodeURIComponent(text)));
|
||||
return base64.split('').map(char => {
|
||||
const code = char.charCodeAt(0);
|
||||
return String.fromCharCode(code + 3);
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function simpleDecrypt(encrypted) {
|
||||
if (!encrypted) return '';
|
||||
try {
|
||||
// Reverse character shifting and decode base64
|
||||
const shifted = encrypted.split('').map(char => {
|
||||
const code = char.charCodeAt(0);
|
||||
return String.fromCharCode(code - 3);
|
||||
}).join('');
|
||||
return decodeURIComponent(escape(atob(shifted)));
|
||||
} catch (error) {
|
||||
console.warn('Failed to decrypt data:', error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// Validate room data
|
||||
// 验证房间数据
|
||||
function validateRoomData(roomData) {
|
||||
if (!roomData) {
|
||||
return { valid: false, error: 'No room data available' };
|
||||
}
|
||||
if (!roomData.roomName || roomData.roomName.trim() === '') {
|
||||
return { valid: false, error: 'Room name is required' };
|
||||
}
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
// Copy text to clipboard with fallback
|
||||
// 复制文本到剪贴板(含降级处理)
|
||||
function copyToClipboard(text, successMessage = 'Copied to clipboard!', errorPrefix = 'Copy failed, text:') {
|
||||
if (!text) {
|
||||
window.addSystemMsg && window.addSystemMsg('Nothing to copy');
|
||||
return;
|
||||
}
|
||||
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
window.addSystemMsg && window.addSystemMsg(successMessage);
|
||||
}).catch((error) => {
|
||||
console.error('Clipboard write failed:', error);
|
||||
showFallbackCopy(text, errorPrefix);
|
||||
});
|
||||
} else {
|
||||
showFallbackCopy(text, errorPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
// Show fallback copy method
|
||||
// 显示降级复制方法
|
||||
function showFallbackCopy(text, prefix) {
|
||||
if (typeof prompt === 'function') {
|
||||
prompt(prefix, text);
|
||||
} else {
|
||||
// For environments where prompt is not available
|
||||
window.addSystemMsg && window.addSystemMsg('Copy not supported in this environment');
|
||||
}
|
||||
}
|
||||
|
||||
// Execute menu action with error handling
|
||||
// 执行菜单操作并处理错误
|
||||
function executeMenuAction(action, closeMenuCallback) {
|
||||
try {
|
||||
switch (action) {
|
||||
case 'share':
|
||||
handleShareAction();
|
||||
break;
|
||||
case 'exit':
|
||||
handleExitAction();
|
||||
break;
|
||||
default:
|
||||
console.warn('Unknown menu action:', action);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Menu action failed:', error);
|
||||
window.addSystemMsg && window.addSystemMsg('Action failed. Please try again.');
|
||||
} finally {
|
||||
closeMenuCallback && closeMenuCallback();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle share action
|
||||
// 处理分享操作
|
||||
function handleShareAction() {
|
||||
const validation = validateRoomData(roomsData[activeRoomIndex]);
|
||||
if (!validation.valid) {
|
||||
window.addSystemMsg && window.addSystemMsg(`Cannot share: ${validation.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const rd = roomsData[activeRoomIndex];
|
||||
const roomName = rd.roomName.trim();
|
||||
const password = rd.password || '';
|
||||
|
||||
// Encrypt room name and password
|
||||
const encryptedRoom = simpleEncrypt(roomName);
|
||||
const encryptedPwd = password ? simpleEncrypt(password) : '';
|
||||
|
||||
// Create share URL with encrypted data
|
||||
let url = `${location.origin}${location.pathname}?r=${encodeURIComponent(encryptedRoom)}`;
|
||||
if (encryptedPwd) {
|
||||
url += `&p=${encodeURIComponent(encryptedPwd)}`;
|
||||
}
|
||||
|
||||
copyToClipboard(url, 'Share link copied!', 'Copy failed, url:');
|
||||
}
|
||||
|
||||
// Handle exit action
|
||||
// 处理退出操作
|
||||
function handleExitAction() {
|
||||
try {
|
||||
const result = exitRoom();
|
||||
if (!result) {
|
||||
location.reload();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Exit room failed:', error);
|
||||
// Force reload as fallback
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
// Render the main header
|
||||
// 渲染主标题栏
|
||||
export function renderMainHeader() {
|
||||
@@ -181,53 +317,36 @@ export function setupMoreBtnMenu() {
|
||||
animating = false;
|
||||
}, 300);
|
||||
}
|
||||
|
||||
btn.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
if (menu.classList.contains('open')) {
|
||||
closeMenu()
|
||||
closeMenu();
|
||||
} else {
|
||||
openMenu()
|
||||
openMenu();
|
||||
}
|
||||
};
|
||||
|
||||
menu.onclick = function(e) {
|
||||
if (e.target.classList.contains('more-menu-item')) {
|
||||
if (e.target.dataset.action === 'share') {
|
||||
if (activeRoomIndex >= 0 && roomsData[activeRoomIndex]) {
|
||||
const rd = roomsData[activeRoomIndex];
|
||||
const room = encodeURIComponent(rd.roomName || '');
|
||||
const pwd = encodeURIComponent(rd.password || '');
|
||||
const url = `${location.origin}${location.pathname}?node=${room}&pwd=${pwd}`;
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
window.addSystemMsg('Share link copied!')
|
||||
}, () => {
|
||||
prompt('Copy failed, url:', url)
|
||||
})
|
||||
} else {
|
||||
prompt('Copy failed, url:', url)
|
||||
}
|
||||
}
|
||||
closeMenu()
|
||||
} else if (e.target.dataset.action === 'exit') {
|
||||
const result = exitRoom();
|
||||
if (!result) {
|
||||
location.reload()
|
||||
}
|
||||
closeMenu()
|
||||
}
|
||||
const action = e.target.dataset.action;
|
||||
executeMenuAction(action, closeMenu);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', function hideMenu(ev) {
|
||||
if (!menu.contains(ev.target) && ev.target !== btn) {
|
||||
closeMenu()
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
|
||||
menu.addEventListener('animationend', function(e) {
|
||||
animating = false
|
||||
animating = false;
|
||||
});
|
||||
|
||||
menu.addEventListener('transitionend', function(e) {
|
||||
animating = false
|
||||
})
|
||||
animating = false;
|
||||
});
|
||||
}
|
||||
|
||||
// Prevent space and special character input
|
||||
@@ -334,10 +453,9 @@ export function openLoginModal() {
|
||||
modal.querySelector('.login-modal-close').onclick = () => modal.remove();
|
||||
preventSpaceInput(modal.querySelector('#userName-modal'));
|
||||
preventSpaceInput(modal.querySelector('#roomName-modal'));
|
||||
preventSpaceInput(modal.querySelector('#password-modal'));
|
||||
const form = modal.querySelector('#login-form-modal');
|
||||
preventSpaceInput(modal.querySelector('#password-modal')); const form = modal.querySelector('#login-form-modal');
|
||||
form.addEventListener('submit', loginFormHandler(modal));
|
||||
autofillRoomPwd('userName-modal')
|
||||
autofillRoomPwd('-modal')
|
||||
}
|
||||
|
||||
// Setup member list tabs
|
||||
@@ -356,26 +474,67 @@ export function setupTabs() {
|
||||
// 从 URL 自动填充房间和密码
|
||||
export function autofillRoomPwd(formPrefix = '') {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const room = params.get('node');
|
||||
const pwd = params.get('pwd');
|
||||
if (room) {
|
||||
|
||||
// Check for new encrypted format first
|
||||
const encryptedRoom = params.get('r');
|
||||
const encryptedPwd = params.get('p');
|
||||
|
||||
// Check for old plaintext format (for backward compatibility)
|
||||
const plaintextRoom = params.get('node');
|
||||
const plaintextPwd = params.get('pwd');
|
||||
|
||||
let roomValue = '';
|
||||
let pwdValue = '';
|
||||
let isPlaintext = false;
|
||||
|
||||
if (encryptedRoom) {
|
||||
// New encrypted format
|
||||
roomValue = simpleDecrypt(decodeURIComponent(encryptedRoom));
|
||||
if (encryptedPwd) {
|
||||
pwdValue = simpleDecrypt(decodeURIComponent(encryptedPwd));
|
||||
}
|
||||
} else if (plaintextRoom) {
|
||||
// Old plaintext format - show security warning
|
||||
roomValue = decodeURIComponent(plaintextRoom);
|
||||
if (plaintextPwd) {
|
||||
pwdValue = decodeURIComponent(plaintextPwd);
|
||||
}
|
||||
isPlaintext = true;
|
||||
|
||||
// Show security warning for plaintext URLs
|
||||
if (window.addSystemMsg) {
|
||||
window.addSystemMsg('⚠️ This link uses an old format. Room data is not encrypted.', true);
|
||||
}
|
||||
}
|
||||
// Fill in the form fields
|
||||
if (roomValue) {
|
||||
const roomInput = document.getElementById(formPrefix + 'roomName');
|
||||
if (roomInput) {
|
||||
roomInput.value = decodeURIComponent(room);
|
||||
roomInput.value = roomValue;
|
||||
roomInput.readOnly = true;
|
||||
roomInput.style.background = '#f5f5f5'
|
||||
roomInput.style.background = isPlaintext ? '#fff9e6' : '#f5f5f5'; // Yellow tint for plaintext
|
||||
}
|
||||
}
|
||||
if (pwd) {
|
||||
// Always lock password field when coming from a share link
|
||||
const pwdInput = document.getElementById(formPrefix + 'password');
|
||||
if (pwdInput) {
|
||||
pwdInput.value = decodeURIComponent(pwd);
|
||||
pwdInput.value = pwdValue; // Will be empty string if no password
|
||||
pwdInput.readOnly = true;
|
||||
pwdInput.style.background = '#f5f5f5'
|
||||
pwdInput.style.background = isPlaintext ? '#fff9e6' : '#f5f5f5'; // Yellow tint for plaintext
|
||||
|
||||
// Add visual indicator for no password and keep label floating
|
||||
if (!pwdValue) {
|
||||
pwdInput.placeholder = 'No password required';
|
||||
// Add a space to make the input appear "filled" so the label stays floating
|
||||
pwdInput.value = ' ';
|
||||
// Make the text invisible but keep the label floating behavior
|
||||
pwdInput.style.color = 'transparent';
|
||||
}
|
||||
}
|
||||
}
|
||||
if (room || pwd) {
|
||||
window.history.replaceState({}, '', location.pathname)
|
||||
|
||||
// Clear URL parameters for security
|
||||
if (roomValue || pwdValue) {
|
||||
window.history.replaceState({}, '', location.pathname);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user