Files
easemob-sdk-test/scripts/test-easemob-polyfill.js
zyh fd549c6e3d feat: 初始化环信 SDK 测试项目
- 添加项目配置文档 (AGENTS.md)
- 创建 package.json,包含环信 SDK 及测试依赖
- 添加环信 polyfill 工具模块
- 添加双用户消息互发测试脚本
- 添加 polyfill 测试脚本

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-03-29 23:14:09 +00:00

220 lines
8.1 KiB
JavaScript
Raw Permalink 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.

/**
* 环信 SDK Node.js Polyfill 测试脚本
*
* 用法:
* node scripts/test-easemob-polyfill.js [方案] [appKey] [user] [pwd] [restServer] [socketServer]
*
* 方案:
* manual - 手动 polyfill (self + ws + localStorage)
* happydom - 使用 happy-dom 注入完整浏览器环境
* xhr2 - 手动 polyfill + xhr2 包 (推荐)
*
* 私有云配置:
* restServer - REST API 服务器地址 (如 http://a1.easemob.com)
* socketServer - WebSocket 服务器地址 (如 ws://im-api-v2.easemob.com/ws)
*
* 示例:
* node scripts/test-easemob-polyfill.js xhr2 "your#appkey" "username" "password"
* node scripts/test-easemob-polyfill.js xhr2 "your#appkey" "username" "password" "http://a1.easemob.com" "ws://im-api-v2.easemob.com/ws"
*/
const { applyManualPolyfill, applyHappyDomPolyfill, printGlobalState } = require('./easemob-polyfill');
const [,, method = 'xhr2', appKey, user, pwd, restServer, socketServer] = process.argv;
if (!appKey || !user || !pwd) {
console.log('用法: node scripts/test-easemob-polyfill.js <manual|xhr2|happydom> <appKey> <user> <pwd> [restServer] [socketServer]');
console.log('');
console.log('方案说明:');
console.log(' manual - 手动 polyfill (无 XMLHttpRequest)');
console.log(' xhr2 - 手动 polyfill + xhr2 包 (推荐)');
console.log(' happydom - 使用 happy-dom 注入完整浏览器环境');
console.log('');
console.log('私有云参数:');
console.log(' restServer - REST API 地址 (如 http://a1.easemob.com)');
console.log(' socketServer - WebSocket 地址 (如 ws://im-api-v2.easemob.com/ws)');
console.log('');
console.log('示例:');
console.log(' # 公有云:');
console.log(' node scripts/test-easemob-polyfill.js xhr2 "org#app" "user1" "pass1"');
console.log(' # 私有云:');
console.log(' node scripts/test-easemob-polyfill.js xhr2 "org#app" "user1" "pass1" "http://a1.easemob.com" "ws://im-api-v2.easemob.com/ws"');
process.exit(1);
}
const resolvedRest = restServer || 'https://a1.easemob.com';
const resolvedSocket = socketServer || 'wss://im-api-v2.easemob.com/ws';
console.log('=== 环信 SDK Node.js Polyfill 测试 ===');
console.log(`方案: ${method}`);
console.log(`AppKey: ${appKey}`);
console.log(`User: ${user}`);
console.log(`REST Server: ${resolvedRest}`);
console.log(`Socket Server: ${resolvedSocket}`);
console.log('');
async function main() {
// ─── Step 1: 应用 polyfill ───
console.log('[Step 1] 应用 polyfill...');
if (method === 'happydom') {
await applyHappyDomPolyfill();
} else {
applyManualPolyfill();
// 按 method 选择 XHR polyfill
if (method === 'xhr2') {
try {
const { XMLHttpRequest } = require('xhr2');
globalThis.XMLHttpRequest = XMLHttpRequest;
console.log(' + XMLHttpRequest = xhr2');
} catch {
console.warn(' ! xhr2 包未安装,跳过');
}
} else if (method === 'xhr1') {
try {
const { XMLHttpRequest } = require('xmlhttprequest');
globalThis.XMLHttpRequest = XMLHttpRequest;
console.log(' + XMLHttpRequest = xmlhttprequest (旧版)');
} catch {
console.warn(' ! xmlhttprequest 包未安装,跳过');
}
}
// manual 方案不注入 XMLHttpRequest
// Patch XHR.open: SDK 用相对路径Node.js XHR 需要绝对 URL
if (globalThis.XMLHttpRequest) {
const BASE_URL = resolvedRest;
const origOpen = globalThis.XMLHttpRequest.prototype.open;
globalThis.XMLHttpRequest.prototype.open = function patchedOpen(method, url, ...rest) {
let finalUrl = url;
if (typeof url === 'string' && url.startsWith('/')) {
finalUrl = BASE_URL + url;
console.log(` >>> XHR patch: ${method} ${url} -> ${finalUrl}`);
}
return origOpen.call(this, method, finalUrl, ...rest);
};
console.log(' + XHR.open patched (相对路径 -> ' + BASE_URL + ')');
}
}
console.log('[Step 1] Polyfill 完成 ✓');
printGlobalState();
// ─── Step 2: 加载 SDK ───
console.log('\n[Step 2] 加载 easemob-websdk...');
let websdk;
try {
const rawSdk = require('easemob-websdk');
websdk = rawSdk.default || rawSdk;
console.log('[Step 2] SDK 加载成功 ✓');
console.log(` - SDK keys: ${Object.keys(websdk).slice(0, 10).join(', ')}`);
console.log(` - Has connection: ${typeof websdk.connection}`);
console.log(` - Has message: ${typeof websdk.message}`);
} catch (err) {
console.error('[Step 2] SDK 加载失败 ✗');
console.error(` Error: ${err.message}`);
console.error(` Stack: ${err.stack}`);
process.exit(1);
}
// ─── Step 3: 创建连接实例 ───
console.log('\n[Step 3] 创建连接实例...');
let conn;
try {
conn = new websdk.connection({
appKey,
isHttpDNS: false,
restUrl: resolvedRest,
msyncUrl: resolvedSocket,
});
console.log('[Step 3] 连接实例创建成功 ✓');
console.log(` - conn type: ${typeof conn}`);
console.log(` - Has open: ${typeof conn.open}`);
console.log(` - Has listen: ${typeof conn.listen}`);
console.log(` - Has close: ${typeof conn.close}`);
} catch (err) {
console.error('[Step 3] 创建连接实例失败 ✗');
console.error(` Error: ${err.message}`);
console.error(` Stack: ${err.stack}`);
process.exit(1);
}
// ─── Step 4: 注册事件监听 ───
console.log('\n[Step 4] 注册事件监听...');
try {
conn.listen({
onOpened: () => {
console.log('\n★ [Event] 连接已打开 (onOpened) ★');
printGlobalState();
console.log('');
console.log('=== 测试成功!环信 SDK 在 Node.js 下正常工作 ===');
console.log('5 秒后自动断开...');
setTimeout(() => {
try { conn.close(); } catch (e) { /* ignore */ }
setTimeout(() => process.exit(0), 1000);
}, 5000);
},
onClosed: () => {
console.log('[Event] 连接已关闭 (onClosed)');
},
onError: (error) => {
const errMsg = error?.message || error?.data?.error_description || JSON.stringify(error);
console.error(`\n✗ [Event] 错误 (onError): ${errMsg}`);
// 不退出,有些错误之后还能重连
},
onTextMessage: (message) => {
console.log(`\n[Event] 收到文本消息: ${JSON.stringify(message).substring(0, 200)}`);
},
onPictureMessage: (message) => {
console.log(`\n[Event] 收到图片消息: from=${message.from}`);
},
onAudioMessage: (message) => {
console.log(`\n[Event] 收到语音消息: from=${message.from}`);
},
onVideoMessage: (message) => {
console.log(`\n[Event] 收到视频消息: from=${message.from}`);
},
onFileMessage: (message) => {
console.log(`\n[Event] 收到文件消息: from=${message.from}`);
},
onPresence: (message) => {
console.log(`[Event] Presence: ${message?.type}`);
},
});
console.log('[Step 4] 事件监听注册成功 ✓');
} catch (err) {
console.error('[Step 4] 注册事件监听失败 ✗');
console.error(` Error: ${err.message}`);
process.exit(1);
}
// ─── Step 5: 登录 ───
console.log('\n[Step 5] 开始登录...');
console.log(` user: ${user}`);
try {
await conn.open({ user, pwd });
console.log('[Step 5] open() 已调用,等待 onOpened 回调...');
} catch (err) {
console.error('[Step 5] 登录失败 ✗');
console.error(` Error: ${err.message}`);
console.error(` Stack: ${err.stack}`);
process.exit(1);
}
// 超时保护30 秒无 onOpened 则退出
setTimeout(() => {
console.error('\n✗ 超时30 秒内未收到 onOpened 回调');
console.error(' 可能原因:');
console.error(' 1. WebSocket 连接失败(检查网络/防火墙)');
console.error(' 2. Polyfill 不完整(缺少必要的浏览器 API');
console.error(' 3. 账号密码错误');
process.exit(1);
}, 30000);
}
// ─── 启动 ───
main().catch((err) => {
console.error('Fatal error:', err);
process.exit(1);
});