/** * 环信 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 [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); });