/** * 环信 SDK Node.js Polyfill 工具模块 * * 提供 easemob-websdk 在 Node.js 下运行所需的浏览器 API 模拟。 * * 用法: * const { applyManualPolyfill, applyHappyDomPolyfill, printGlobalState } = require('./easemob-polyfill'); * applyManualPolyfill(); */ // ────────────────────────────────────────── // 手动 Polyfill:逐项注入缺失的浏览器 API // ────────────────────────────────────────── function applyManualPolyfill() { // window → globalThis(关键!让 typeof window === 'object',SDK 据此判断浏览器环境) if (typeof globalThis.window === 'undefined') { globalThis.window = globalThis; console.log(' + window = globalThis'); } // self → globalThis if (typeof globalThis.self === 'undefined') { globalThis.self = globalThis; console.log(' + self = globalThis'); } // WebSocket → ws if (typeof globalThis.WebSocket === 'undefined') { try { const wsModule = require('ws'); globalThis.WebSocket = wsModule.WebSocket || wsModule; console.log(' + WebSocket = ws (Node.js)'); } catch { console.warn(' ! ws 包未安装,跳过 WebSocket polyfill'); } } // localStorage → 内存 Map if (typeof globalThis.localStorage === 'undefined') { const store = new Map(); globalThis.localStorage = { getItem: (key) => store.get(key) ?? null, setItem: (key, value) => { store.set(key, String(value)); }, removeItem: (key) => { store.delete(key); }, clear: () => { store.clear(); }, get length() { return store.size; }, key: (_index) => null, }; console.log(' + localStorage = in-memory Map'); } // XMLHttpRequest 由调用方按需注入(xhr2 / xmlhttprequest),此处不再自动注入 // navigator 基本模拟 if (typeof globalThis.navigator === 'undefined') { globalThis.navigator = { userAgent: 'Node.js', platform: process.platform, appName: 'Node.js', appVersion: process.version, language: 'zh-CN', }; console.log(' + navigator = basic mock'); } } // ────────────────────────────────────────── // happy-dom Polyfill:注入完整浏览器环境 // ────────────────────────────────────────── async function applyHappyDomPolyfill() { let happyDOM; try { happyDOM = require('happy-dom'); } catch { console.error(' ✗ happy-dom 未安装'); console.error(' 请运行: npm install happy-dom --save-dev'); process.exit(1); } // happy-dom v20+ uses Browser + BrowserContext API if (happyDOM.Browser && happyDOM.BrowserContext) { const browser = new happyDOM.Browser(); const page = browser.newPage('https://a1.easemob.com'); const window = page.mainFrame.window; const document = window.document; globalThis.window = window; globalThis.document = document; globalThis.self = window; globalThis.navigator = window.navigator; globalThis.localStorage = window.localStorage; globalThis.XMLHttpRequest = window.XMLHttpRequest; globalThis.WebSocket = window.WebSocket; globalThis.fetch = window.fetch.bind(window); globalThis.Headers = window.Headers; globalThis.Request = window.Request; globalThis.Response = window.Response; console.log(' + happy-dom Browser + BrowserContext ✓'); console.log(` + page URL: ${window.location.href}`); return; } // 旧版 GlobalRegistrator API (happy-dom < v15) const Registrator = happyDOM.GlobalRegistrator; if (Registrator && Registrator.register) { await Registrator.register(); console.log(' + happy-dom GlobalRegistrator.register() ✓'); return; } console.error(' ✗ happy-dom 版本不兼容,无法注入全局 API'); console.error(' 当前版本:', require('happy-dom/package.json').version); process.exit(1); } // ────────────────────────────────────────── // 打印当前全局 API 状态 // ────────────────────────────────────────── function printGlobalState() { const apis = [ 'self', 'WebSocket', 'localStorage', 'XMLHttpRequest', 'navigator', 'fetch', 'window', 'document', ]; console.log('\n 全局 API 状态:'); for (const api of apis) { const exists = typeof globalThis[api] !== 'undefined'; const type = typeof globalThis[api]; console.log(` ${exists ? '✓' : '✗'} ${api}: ${exists ? type : 'undefined'}`); } } module.exports = { applyManualPolyfill, applyHappyDomPolyfill, printGlobalState, };