Files
YwxAppThink/addon/wxchat/view/indexi/index.html
T

482 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🔐 端到端加密聊天</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Microsoft YaHei", sans-serif
}
body {
background: #f0f2f5;
padding: 20px;
max-width: 900px;
margin: 0 auto
}
.header {
text-align: center;
margin-bottom: 20px;
padding: 15px;
background: #1a73e8;
color: white;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1)
}
.chat-container {
display: flex;
gap: 20px
}
.panel {
background: white;
border-radius: 12px;
box-shadow: 0 3px 15px rgba(0, 0, 0, 0.1);
padding: 20px;
flex: 1
}
.title {
font-size: 18px;
margin-bottom: 15px;
color: #1a73e8;
display: flex;
align-items: center;
gap: 8px
}
.title i {
font-size: 20px
}
#chatBox {
height: 400px;
overflow-y: auto;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
background: #fafafa;
line-height: 1.6
}
.msg {
margin-bottom: 12px;
padding: 10px 15px;
border-radius: 18px;
max-width: 80%
}
.sent {
background: #e3f2fd;
margin-left: auto;
border-bottom-right-radius: 5px
}
.received {
background: #f1f8e9;
border-bottom-left-radius: 5px
}
.sys {
background: #fff8e1;
color: #5d4037;
font-size: 13px;
text-align: center;
margin: 5px 0
}
.input-area {
display: flex;
gap: 10px
}
textarea {
flex: 1;
padding: 12px;
border: 1px solid #ddd;
border-radius: 20px;
resize: none;
font-size: 15px;
height: 45px
}
button {
background: #1a73e8;
color: white;
border: none;
border-radius: 20px;
padding: 0 25px;
cursor: pointer;
font-weight: bold;
transition: all 0.3s
}
button:hover {
background: #0d5bbb;
transform: scale(1.03)
}
button:disabled {
background: #b0b0b0;
cursor: not-allowed;
transform: none
}
.status {
padding: 8px;
border-radius: 6px;
margin-top: 10px;
font-size: 14px;
text-align: center
}
.status.connected {
background: #e8f5e9;
color: #2e7d32
}
.status.connecting {
background: #e3f2fd;
color: #1565c0
}
.status.error {
background: #ffebee;
color: #c62828
}
.debug {
background: #2d2d2d;
color: #64dd17;
padding: 10px;
border-radius: 6px;
margin-top: 15px;
font-family: monospace;
font-size: 13px;
max-height: 150px;
overflow: auto;
display: none
}
.badge {
background: #e3f2fd;
color: #1565c0;
padding: 2px 8px;
border-radius: 10px;
font-size: 12px;
margin-left: 5px
}
@media (max-width:768px) {
.chat-container {
flex-direction: column
}
}
</style>
</head>
<body>
<div class="header">
<h1>🔐 端到端加密聊天 <span class="badge">微信级安全</span></h1>
<div style="margin-top:8px; font-size:14px; opacity:0.9">AES-256-CBC加密 | SHA1签名验证 | 防篡改防重放</div>
</div>
<div class="chat-container">
<div class="panel">
<div class="title">💬 聊天窗口</div>
<div id="chatBox"></div>
<div class="input-area">
<textarea id="messageInput" placeholder="输入消息... (按Ctrl+Enter发送)" disabled></textarea>
<button id="sendBtn" disabled>发送</button>
</div>
<div id="statusBar" class="status connecting">⏳ 正在连接服务器...</div>
<button id="toggleDebug" style="margin-top:10px; padding:5px 15px; background:#f5f5f5; color:#5f6368">显示调试日志</button>
<div id="debugLog" class="debug"></div>
</div>
<div class="panel">
<div class="title">⚙️ 连接配置</div>
<div style="margin-bottom:15px">
<div style="margin-bottom:8px"><strong>用户ID:</strong></div>
<input type="text" id="userId" value="user_<?= bin2hex(random_bytes(4)) ?>" style="width:100%; padding:10px; border:1px solid #ddd; border-radius:6px">
</div>
<div style="margin-bottom:15px">
<div style="margin-bottom:8px"><strong>服务器地址:</strong></div>
<input type="text" id="serverUrl" value="wss://dev.xixingwl.cn:2346" style="width:100%; padding:10px; border:1px solid #ddd; border-radius:6px">
</div>
<button id="connectBtn" style="width:100%; background:#34a853">🔌 连接服务器</button>
<div style="margin-top:20px; padding:12px; background:#e8f0fe; border-radius:8px; font-size:13px; line-height:1.5">
<strong>🔒 安全说明:</strong><br>
• 所有消息端到端加密,服务器无法查看内容<br>
• 每条消息独立签名,防篡改防伪造<br>
• 时间戳验证,防止消息重放攻击
</div>
</div>
</div>
<script src="/assets/ywxapp/modules/aescgm.js"></script>
<script>
// =============== 聊天客户端 ===============
class ChatClient {
constructor() {
this.ws = null;
this.crypto = null;
this.userId = '';
this.isConnected = false;
this.initUI();
}
initUI() {
// DOM元素
this.chatBox = document.getElementById('chatBox');
this.messageInput = document.getElementById('messageInput');
this.sendBtn = document.getElementById('sendBtn');
this.statusBar = document.getElementById('statusBar');
this.connectBtn = document.getElementById('connectBtn');
this.toggleDebug = document.getElementById('toggleDebug');
this.debugLog = document.getElementById('debugLog');
// 事件绑定
this.connectBtn.onclick = () => this.connect();
this.sendBtn.onclick = () => this.sendMessage();
this.messageInput.onkeypress = (e) => {
if (e.ctrlKey && e.key === 'Enter') this.sendMessage();
};
this.toggleDebug.onclick = () => {
this.debugLog.style.display = this.debugLog.style.display === 'none' ? 'block' : 'none';
};
// 初始化状态
this.updateStatus('⏳ 未连接,请配置后点击连接', 'connecting');
}
log(msg, type = 'info') {
const prefix = type === 'error' ? '❌' : type === 'success' ? '✅' : '️';
this.debugLog.innerHTML += `<span style="color:${type === 'error' ? '#ff5252' : type === 'success' ? '#4caf50' : '#aaa'}">${prefix} ${msg}</span><br>`;
this.debugLog.scrollTop = this.debugLog.scrollHeight;
console.log(`[ChatClient] ${msg}`);
}
updateStatus(text, className) {
this.statusBar.textContent = text;
this.statusBar.className = `status ${className}`;
}
addMessage(text, type = 'received', user = '系统') {
const div = document.createElement('div');
div.className = type === 'sys' ? 'sys' : `msg ${type}`;
if (type === 'sys') {
div.innerHTML = text;
} else {
div.innerHTML = `<strong>${user}:</strong> ${this.escapeHtml(text)}`;
}
this.chatBox.appendChild(div);
this.chatBox.scrollTop = this.chatBox.scrollHeight;
}
escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
async connect() {
try {
this.userId = document.getElementById('userId').value.trim() || 'anonymous';
const serverUrl = document.getElementById('serverUrl').value.trim();
if (!serverUrl.startsWith('ws://') && !serverUrl.startsWith('wss://')) {
throw new Error('服务器地址必须以 ws:// 或 wss:// 开头');
}
this.updateStatus('🔄 正在连接...', 'connecting');
this.connectBtn.disabled = true;
// 创建WebSocket
this.ws = new WebSocket(serverUrl);
this.ws.onopen = () => {
this.log('WebSocket连接已建立');
// 发送认证请求
this.ws.send(JSON.stringify({
action: 'auth',
token: 'your_secure_token_123456', // 与服务端约定
user_id: this.userId
}));
};
this.ws.onmessage = (event) => this.handleMessage(event.data);
this.ws.onerror = (error) => {
this.log(`连接错误: ${error.message}`, 'error');
this.log(error);
this.updateStatus('❌ 连接失败: ' + error.message, 'error');
this.connectBtn.disabled = false;
};
this.ws.onclose = () => {
this.isConnected = false;
this.log('连接已关闭', 'error');
if (this.isConnected) {
this.updateStatus('⚠️ 连接意外断开', 'error');
this.addMessage('连接已断开', 'sys');
}
this.connectBtn.disabled = false;
this.messageInput.disabled = true;
this.sendBtn.disabled = true;
};
} catch (e) {
this.log(`连接异常: ${e.message}`, 'error');
this.updateStatus('❌ ' + e.message, 'error');
this.connectBtn.disabled = false;
}
}
handleMessage(data) {
try {
const msg = JSON.parse(data);
// 认证响应
if (msg.action === 'auth_success') {
// 初始化加密模块 (使用服务端返回的密钥)
this.crypto = new ChatCrypto(
msg.encodingAesKey,
'your_secure_token_123456',
'CHAT_APP_2024'
);
this.isConnected = true;
this.updateStatus(`✅ 已连接 | 会话ID: ${msg.session_id.substring(0, 8)}...`, 'connected');
this.addMessage(`欢迎 ${this.userId}!端到端加密已启用`, 'sys');
this.messageInput.disabled = false;
this.sendBtn.disabled = false;
this.connectBtn.textContent = '🔌 已连接';
this.connectBtn.style.background = '#9e9e9e';
this.connectBtn.disabled = true;
this.log('认证成功,加密模块已初始化', 'success');
return;
}
// 错误处理
if (msg.error) {
this.log(`服务端错误: ${msg.error} (代码: ${msg.code})`, 'error');
if (msg.code === 401) {
this.updateStatus('❌ 认证失败,请检查token', 'error');
this.ws.close();
}
this.addMessage(`[系统] ${msg.error}`, 'sys');
return;
}
// =============== 接收聊天消息 ===============
// 验证签名
if (!this.crypto.verifySignature(
msg.signature,
msg.timestamp,
msg.nonce,
msg.encrypt
)) {
this.addMessage(`[警告] 消息签名验证失败,来源: ${msg.from_user}`, 'sys');
this.log(`收到无效签名消息 (来源: ${msg.from_user})`, 'error');
return;
}
// 防重放检查
if (Math.abs(Date.now() / 1000 - msg.timestamp) > 300) {
this.log(`收到过期消息 (时间差: ${Math.abs(Date.now() / 1000 - msg.timestamp)}秒)`, 'error');
return;
}
// 解密消息
try {
const plainText = this.crypto.decrypt(msg.encrypt);
const chatData = JSON.parse(plainText);
this.addMessage(chatData.content, 'received', chatData.from_user || msg.from_user);
this.log(`收到消息: "${chatData.content.substring(0, 20)}..." (来源: ${msg.from_user})`, 'success');
} catch (e) {
this.addMessage(`[系统] 消息解密失败: ${e.message}`, 'sys');
this.log(`解密异常: ${e.message}`, 'error');
}
} catch (e) {
this.log(`消息处理异常: ${e.message}`, 'error');
console.error(e);
}
}
sendMessage() {
const content = this.messageInput.value.trim();
if (!content || !this.isConnected || !this.crypto) return;
try {
// 构建消息体
const chatMsg = {
content: content,
type: 'text',
timestamp: Math.floor(Date.now() / 1000)
};
console.log('原始消息体:', chatMsg);
console.log('加密前消息体字符串:', JSON.stringify(chatMsg));
console.log('加密前消息体字符串:', window.encryptJson(chatMsg, 'your_secure_token_123456'));
// 加密
const encryptMsg = this.crypto.encrypt(JSON.stringify(chatMsg));
// 生成签名参数
const timestamp = Math.floor(Date.now() / 1000);
const nonce = this.crypto.getRandomStr(10);
const signature = this.crypto.generateSignature(timestamp, nonce, encryptMsg);
// 发送
this.ws.send(JSON.stringify({
encrypt: encryptMsg,
signature: signature,
timestamp: timestamp,
nonce: nonce
}));
// 显示到聊天框
this.addMessage(content, 'sent', this.userId);
this.messageInput.value = '';
this.log(`发送消息: "${content.substring(0, 20)}..."`, 'success');
} catch (e) {
this.log(`发送失败: ${e.message}`, 'error');
alert('发送失败: ' + e.message);
}
}
}
const aes = new AESGCM();
// 加密示例
aes.encrypt('Hello World!', 'mypassword')
.then(encrypted => {
console.log('加密结果:', encrypted);
// 解密示例
return aes.decrypt(encrypted, 'mypassword');
})
.then(decrypted => {
console.log('解密结果:', decrypted);
})
.catch(error => {
console.error('操作失败:', error);
});
// 初始化
document.addEventListener('DOMContentLoaded', () => {
console.log('页面加载完成,正在初始化聊天客户端...');
window.chatClient = new ChatClient();
document.getElementById('messageInput').focus();
});
</script>
</body>
</html>