/** * 环信 SDK 双用户消息互发测试脚本 * * 同时登录两个环信账号,测试双向消息收发。 * 配置从项目根目录的 config.json 文件读取。 * * 用法: * node scripts/test-easemob-msg.js # 默认互发测试 * node scripts/test-easemob-msg.js <自定义消息> # 指定初始消息内容 */ const path = require('path'); const fs = require('fs'); // ────────────────────────────────────────── // 从配置文件读取配置 // ────────────────────────────────────────── function loadConfig() { const configPath = path.join(__dirname, '..', 'config.json'); if (!fs.existsSync(configPath)) { throw new Error(`配置文件不存在: ${configPath}`); } const content = fs.readFileSync(configPath, 'utf-8'); return JSON.parse(content); } const config = loadConfig(); const ACCOUNTS = config.accounts; const APP_KEY = config.easemob.appKey; // ────────────────────────────────────────── // Polyfill // ────────────────────────────────────────── const { applyManualPolyfill } = require('./easemob-polyfill'); // ────────────────────────────────────────── // 为账号创建连接实例并登录 // ────────────────────────────────────────── function createConnection(account, websdk, appConfig) { const conn = new websdk.connection({ appKey: appConfig.appKey, debug: appConfig.debug || false, }); const peer = account.label === 'bot' ? ACCOUNTS.tester : ACCOUNTS.bot; // 消息统计(per-account) const stats = { sent: 0, received: 0 }; conn.listen({ onOpened: () => { console.log(`\n [${account.label}] ✅ 登录成功!`); }, onClosed: () => { console.log(` [${account.label}] 连接已关闭`); }, onError: (error) => { const errMsg = error?.message || error?.data?.error_description || JSON.stringify(error); console.error(` [${account.label}] ❌ Error: ${errMsg}`); }, onTextMessage: (message) => { stats.received++; const from = message.from || 'unknown'; const data = message.data || ''; console.log(`\n [${account.label}] 📩 收到消息 from=${from}: "${data}"`); // 自动 echo 回复 const replyText = `[${account.label} Echo] 收到: "${data.substring(0, 30)}"`; sendTextMessage(conn, websdk, from, replyText, account.label, stats); }, onPresence: (message) => { console.log(` [${account.label}] Presence: ${message?.type} from ${message?.from || ''}`); }, }); return { conn, stats, account, peer }; } // ────────────────────────────────────────── // 发送文本消息 // ────────────────────────────────────────── function sendTextMessage(conn, websdk, to, text, label, stats) { try { const message = new websdk.message({ to, type: 'chat', msg: text, chatType: 'singleChat', }); const result = conn.send(message); // conn.send 可能返回 Promise if (result && typeof result.catch === 'function') { result.catch((err) => { console.error(` [${label}] ❌ 发送失败(async): ${err?.message || err}`); }); } stats.sent++; console.log(` [${label}] 📤 已发送 -> ${to}: "${text}"`); } catch (err) { console.error(` [${label}] ❌ 发送失败: ${err.message}`); } } // ────────────────────────────────────────── // 延时工具 // ────────────────────────────────────────── const delay = (ms) => new Promise((r) => setTimeout(r, ms)); // ────────────────────────────────────────── // 主流程 // ────────────────────────────────────────── async function main() { const customMsg = process.argv.slice(2).join(' ') || '你好,这是双向测试消息!'; console.log('=== 环信双向消息收发测试 ==='); console.log(` Bot 账号: ${ACCOUNTS.bot.user}`); console.log(` 测试账号: ${ACCOUNTS.tester.user}`); console.log(` 初始消息: "${customMsg}"`); console.log('================================\n'); // Step 1: 显示配置 console.log('[Step 1] 加载配置...'); console.log(` AppKey: ${config.easemob.appKey}`); console.log(` REST Server: ${config.easemob.restServer}`); console.log(` Socket Server: ${config.easemob.socketServer}`); // Step 2: Polyfill console.log('\n[Step 2] 应用 Polyfill...'); applyManualPolyfill(); // WebSocket polyfill try { const wsModule = require('ws'); const OriginalWebSocket = wsModule.WebSocket || wsModule; globalThis.WebSocket = function DebugWebSocket(url, protocols, options) { console.log(` [WS] new WebSocket(${JSON.stringify(url)})`); if (protocols !== undefined) { return new OriginalWebSocket(url, protocols, options); } return new OriginalWebSocket(url); }; Object.assign(globalThis.WebSocket, OriginalWebSocket); globalThis.WebSocket.prototype = OriginalWebSocket.prototype; console.log(' + WebSocket = ws (debug-wrapped)'); } catch (err) { console.warn(' ! ws 包未安装:', err.message); } // XHR polyfill try { const { XMLHttpRequest } = require('xhr2'); globalThis.XMLHttpRequest = XMLHttpRequest; const baseUrl = config.easemob.restServer; const origOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function patchedOpen(method, url, ...rest) { let finalUrl = url; if (typeof url === 'string' && url.startsWith('/')) { finalUrl = baseUrl + url; } return origOpen.call(this, method, finalUrl, ...rest); }; console.log(` + XMLHttpRequest = xhr2 (base: ${baseUrl})`); } catch { console.warn(' ! xhr2 包未安装'); } // Step 3: 加载 SDK console.log('\n[Step 3] 加载 easemob-websdk...'); let websdk; try { const rawSdk = require('easemob-websdk'); websdk = rawSdk.default || rawSdk; console.log(' SDK 加载成功 ✓'); } catch (err) { console.error('✗ SDK 加载失败:', err.message); process.exit(1); } // Step 4: 创建两个连接 console.log('\n[Step 4] 创建双用户连接...'); const botCtx = createConnection(ACCOUNTS.bot, websdk, config.easemob); const testerCtx = createConnection(ACCOUNTS.tester, websdk, config.easemob); console.log(' 两个连接实例创建成功 ✓'); // Step 5: 双方登录 console.log('\n[Step 5] 双方登录...'); const loginPromises = [ (async () => { try { await botCtx.conn.open({ user: ACCOUNTS.bot.user, pwd: ACCOUNTS.bot.pwd }); } catch (err) { console.error(` [bot] 登录失败: ${err.message}`); } })(), (async () => { try { await testerCtx.conn.open({ user: ACCOUNTS.tester.user, pwd: ACCOUNTS.tester.pwd }); } catch (err) { console.error(` [tester] 登录失败: ${err.message}`); } })(), ]; await Promise.all(loginPromises); // 等待连接建立 console.log('\n 等待双方连接建立...'); await delay(5000); // Step 6: 双向发消息 console.log('\n[Step 6] 开始双向消息测试...\n'); // tester -> bot console.log('--- 测试 1: testuser1 -> originclaw_bot ---'); sendTextMessage( testerCtx.conn, websdk, ACCOUNTS.bot.user, customMsg, testerCtx.account.label, testerCtx.stats ); await delay(3000); // bot -> tester console.log('\n--- 测试 2: originclaw_bot -> testuser1 ---'); sendTextMessage( botCtx.conn, websdk, ACCOUNTS.tester.user, `[Bot] ${customMsg}`, botCtx.account.label, botCtx.stats ); await delay(3000); // Step 7: 统计结果 console.log('\n\n=== 测试结果 ==='); console.log(` [${botCtx.account.label}] 发送: ${botCtx.stats.sent}, 接收: ${botCtx.stats.received}`); console.log(` [${testerCtx.account.label}] 发送: ${testerCtx.stats.sent}, 接收: ${testerCtx.stats.received}`); console.log('\n保持连接中... (Ctrl+C 退出)\n'); // 优雅退出 const cleanup = () => { console.log('\n=== 退出统计 ==='); console.log(` [${botCtx.account.label}] 发送: ${botCtx.stats.sent}, 接收: ${botCtx.stats.received}`); console.log(` [${testerCtx.account.label}] 发送: ${testerCtx.stats.sent}, 接收: ${testerCtx.stats.received}`); try { botCtx.conn.close(); } catch {} try { testerCtx.conn.close(); } catch {} setTimeout(() => process.exit(0), 500); }; process.on('SIGINT', cleanup); process.on('SIGTERM', cleanup); } // 防止未处理的 Promise 拒绝导致进程崩溃 process.on('unhandledRejection', (reason) => { console.error('⚠ unhandledRejection:', reason?.message || reason); }); // ─── 启动 ─── main().catch((err) => { console.error('Fatal error:', err); process.exit(1); });