- 新增 config.json 存储环信配置和测试账号 - 修改 test-easemob-msg.js 从配置文件读取配置 - 移除 sql.js 依赖,简化项目依赖 - 更新测试脚本注释说明 Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
273 lines
9.5 KiB
JavaScript
273 lines
9.5 KiB
JavaScript
/**
|
||
* 环信 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);
|
||
});
|