/** * APP桥接工具函数 * 用于H5页面与原生APP之间的通信 */ import { COMMON_HEADERS } from '@/utils/http.js' // ==================== 工具函数 ==================== /** * 检测是否为iOS系统 * @returns {boolean} */ export function ios() { return /iPad|iPhone|iPod/.test(navigator.userAgent) } /** * 检测是否为Android系统 * @returns {boolean} */ export function android() { return /Android/.test(navigator.userAgent) } /** * 检测是否在APP环境中 * @returns {boolean} */ export function isInApp() { return !!(window.app || window.webkit || window.FlutterPageControl) } // ==================== 安全日志系统 ==================== /** * 安全的日志函数,避免 iOS WebView 中的 "JavaScript execution returned a result of an unsupported type" 错误 * @param {string} level - 日志级别 ('log', 'debug', 'warn', 'error') * @param {...any} args - 日志参数 */ function createSafeLogger() { return function safeConsoleLog(level, ...args) { if (process.env.NODE_ENV === 'production') return try { if (ios()) { // 对 iOS 设备进行特殊处理 const processedArgs = args.map((arg) => { if (typeof arg === 'object' && arg !== null) { try { // 序列化对象,避免循环引用 return JSON.stringify( arg, (key, value) => { // 处理特殊对象类型 if (value instanceof HTMLElement) return '[HTMLElement]' if (typeof value === 'function') return '[Function]' if (value instanceof Date) return value.toISOString() if (value instanceof RegExp) return value.toString() if (value instanceof Error) return { message: value.message, stack: value.stack } if (this.__visited__) { if (this.__visited__.has(value)) return '[Circular Reference]' } else { this.__visited__ = new Set() } this.__visited__.add(value) return value }, 2, ) } catch (e) { return '[Object with serialization error]' } } return arg }) console[level](...processedArgs) } else { // 非 iOS 设备正常使用 console[level](...args) } } catch (e) { if (process.env.NODE_ENV === 'development') { console.error('Console logging failed:', e) } } } } // 创建日志函数实例 const safeConsoleLog = createSafeLogger() const debugLog = (...args) => safeConsoleLog('debug', ...args) const errorLog = (...args) => safeConsoleLog('error', ...args) const warnLog = (...args) => safeConsoleLog('warn', ...args) const infoLog = (...args) => safeConsoleLog('log', ...args) // ==================== 平台特定处理函数 ==================== /** * Android 平台处理逻辑 * @param {Function} renderFun - 回调函数 */ function handleAndroidPlatform(renderFun) { debugLog('Flutter Android WebView 检测') debugLog('window.app 对象:', window.app) // 检查Flutter注入的接口 if (window.app) { debugLog('window.app 的所有方法:') debugLog(Object.keys(window.app)) // 检查具体的方法 debugLog('getAccessOrigin 方法存在?', typeof window.app.getAccessOrigin) debugLog('getAccessOrigin 是函数?', typeof window.app.getAccessOrigin === 'function') } // 等待Flutter注入完成 const waitForFlutterInterface = () => { let attempts = 0 const maxAttempts = 50 // 最多等待5秒 const checkInterface = () => { attempts++ debugLog(`尝试第 ${attempts} 次检查Flutter接口...`) if ( window.app && window.app.getAccessOrigin && typeof window.app.getAccessOrigin === 'function' ) { debugLog('Flutter接口检测成功,调用 getAccessOrigin') try { const result = window.app.getAccessOrigin() // 直接同步调用获取数据 debugLog('getAccessOrigin 返回结果:', result) renderFun(result) // 传递给回调函数 } catch (error) { errorLog('调用 getAccessOrigin 出错:', error) } } else if (attempts < maxAttempts) { // 没找到,继续查询 setTimeout(checkInterface, 100) } else { // 查询次数用完 errorLog('Flutter接口注入超时,使用默认数据') // 使用默认的开发数据 const mockAccess = JSON.stringify({ Authorization: 'Bearer FLUTTER_MOCK_TOKEN', 'Req-Lang': 'en', 'Req-App-Intel': 'build=1.0;version=2.1;channel=flutter;Req-Imei=flutter-device', 'Req-Sys-Origin': 'origin=LIKEI;originChild=LIKEI', }) renderFun(mockAccess) } } checkInterface() } // window.app已存在,直接检查 if (window.app) { waitForFlutterInterface() } else { // window.app不存在,等待注入 debugLog('等待Flutter注入window.app...') // 监听window.app的创建 let checkCount = 0 const checkApp = () => { checkCount++ if (window.app) { debugLog('检测到window.app,开始连接') waitForFlutterInterface() } else if (checkCount < 50) { setTimeout(checkApp, 100) } else { errorLog('等待window.app超时') // 手动通知Flutter注入接口 if (window.FlutterApp && window.FlutterApp.postMessage) { window.FlutterApp.postMessage('requestAccessOrigin') } } } checkApp() } } /** * iOS 平台处理逻辑 * @param {Function} renderFun - 回调函数 */ function handleIOSPlatform(renderFun) { debugLog('iOS access detected') // iOS WebKit 消息处理器检测和等待 const waitForIOSInterface = () => { let attempts = 0 const maxAttempts = 50 // 最多等待5秒 const checkIOSInterface = () => { attempts++ debugLog(`尝试第 ${attempts} 次检查iOS接口...`) if ( window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.getAccessOrigin ) { debugLog('iOS接口检测成功,调用 getAccessOrigin') try { // iOS的postMessage是异步的,需要通过回调处理 window.webkit.messageHandlers.getAccessOrigin.postMessage({}) // 设置全局回调函数来接收iOS返回的数据 window.receiveAccessOrigin = function (accessData) { debugLog('收到iOS返回的access数据:', accessData) renderFun(accessData) // 接收数据后传递给回调函数 } } catch (error) { errorLog('调用 getAccessOrigin 出错:', error) // 使用模拟数据作为降级处理 const mockAccess = JSON.stringify({ Authorization: 'Bearer IOS_MOCK_TOKEN', 'Req-Lang': 'en', 'Req-App-Intel': 'build=1.0;version=2.1;channel=ios;Req-Imei=ios-device', 'Req-Sys-Origin': 'origin=LIKEI;originChild=IOS', }) renderFun(mockAccess) } } else if (attempts < maxAttempts) { // 没找到,继续查询 setTimeout(checkIOSInterface, 100) } else { // 查询次数用完 errorLog('iOS接口注入超时,使用默认数据') const mockAccess = JSON.stringify({ Authorization: 'Bearer IOS_MOCK_TOKEN', 'Req-Lang': 'en', 'Req-App-Intel': 'build=1.0;version=2.1;channel=ios;Req-Imei=ios-device', 'Req-Sys-Origin': 'origin=LIKEI;originChild=IOS', }) renderFun(mockAccess) } } checkIOSInterface() } // window.app 存在(某些情况下iOS也可能注入window.app) if (window.app && typeof window.app.getAccessOrigin === 'function') { try { const result = window.app.getAccessOrigin() debugLog('window.app.getAccessOrigin 返回结果:', result) renderFun(result) } catch (error) { errorLog('调用window.app.getAccessOrigin出错:', error) waitForIOSInterface() // 尝试WebKit方式 } } else { // window.app不存在,等待注入 debugLog('等待iOS WebKit接口注入...') waitForIOSInterface() } } /** * Web 浏览器环境处理逻辑 * @param {Function} renderFun - 回调函数 */ function handleWebPlatform(renderFun) { debugLog('非移动设备环境') // 非APP环境,可能是浏览器调试 console.warn('Not in APP environment, using mock data for development') // 开发环境可以提供模拟数据 if (process.env.NODE_ENV === 'development') { const mockAccess = JSON.stringify({ Authorization: COMMON_HEADERS.authorization, 'Req-Lang': COMMON_HEADERS['req-lang'], 'Req-App-Intel': COMMON_HEADERS['req-app-intel'], 'Req-Sys-Origin': COMMON_HEADERS['req-sys-origin'], }) setTimeout(() => renderFun(mockAccess), 100) } } // ==================== 主要功能函数 ==================== /** * 连接APP并获取访问参数 * @param {Function} renderFun 回调函数,接收APP传递的access参数 */ export function connectApplication(renderFun) { window.renderData = renderFun // 兼容历史版本 window.getIosAccessOriginParam = renderFun try { if (android()) { handleAndroidPlatform(renderFun) } else if (ios()) { handleIOSPlatform(renderFun) } else { handleWebPlatform(renderFun) } } catch (error) { errorLog('Failed to connect to APP:', error) } } // ==================== APP 通信函数 ==================== /** * 关闭当前页面(返回到Flutter上一页) */ export function closePage() { try { if (window.app && window.app.closePage) { // 使用注入的方法 window.app.closePage() } else if (window.FlutterPageControl) { // 直接使用Flutter通道 window.FlutterPageControl.postMessage('close_page') } else { // 降级处理:浏览器环境 // if (window.history.length > 1) { // window.history.back() // } else { // window.close() // } } } catch (error) { errorLog('关闭页面失败:', error) // 降级处理 window.history.back() } } /** * 前往私聊 */ export function gotoPrivateChat(userId) { if (!userId) return try { infoLog(`userId:${userId}`) if (window.app && window.app.gotoPrivateChat) { infoLog('使用gotoPrivateChat') // 使用注入的方法 window.app.gotoPrivateChat(userId) } else if (window.FlutterPageControl) { // 直接使用Flutter通道 window.FlutterPageControl.postMessage(`private_chat:${userId}`) } } catch (error) { errorLog('前往私聊失败:', error) } } /** * 查看个人页面 */ export function viewUserInfo(userId) { if (!userId) return try { infoLog(`userId:${userId}`) if (window.app && window.app.viewUserInfo) { infoLog('使用viewUserInfo') // 使用注入的方法 window.app.viewUserInfo(userId) } else if (window.FlutterPageControl) { // 直接使用Flutter通道 window.FlutterPageControl.postMessage(`view_user_info:${userId}`) } } catch (error) { errorLog('查看个人页面失败:', error) } } /** * 前往房间 */ export function gotoRoom(roomId) { if (!roomId) return try { infoLog(`roomId:${roomId}`) if (window.app && window.app.gotoRoom) { infoLog('使用gotoRoom') // 使用注入的方法 window.app.gotoRoom(roomId) } else if (window.FlutterPageControl) { // 直接使用Flutter通道 window.FlutterPageControl.postMessage(`go_to_room:${roomId}`) } } catch (error) { errorLog('前往房间失败:', error) } } /** * 前往app页面 */ export function gotoAppPage(pageName) { if (!pageName) return try { infoLog(`pageName:${pageName}`) // 直接使用Flutter通道 window.FlutterPageControl.postMessage(pageName) } catch (error) { errorLog('前往APP页面失败:', error) } } /** * 退出APP */ export function exitApp() { try { if (window.app && window.app.exitApp) { window.app.exitApp() } else if (window.FlutterPageControl) { window.FlutterPageControl.postMessage('exit_app') } } catch (error) { errorLog('退出APP失败:', error) } } /** * 处理返回按钮逻辑 * @param {boolean} isHomePage 是否为首页 */ export function handleBackButton(isHomePage = false) { if (isHomePage) { // 首页返回:关闭页面 closePage() } else { // 非首页:正常返回 window.history.back() } } // ==================== 数据解析函数 ==================== /** * 解析APP传递的访问参数 * @param {string} access JSON字符串格式的访问参数 * @returns {Object} 解析后的参数对象 */ export function parseAccessOrigin(access) { try { const accessParam = JSON.parse(access) return { success: true, data: accessParam, } } catch (error) { errorLog('Failed to parse access origin:', error) return { success: false, error: 'accessOrigin Error.', data: null, } } } /** * 解析键值对字符串 (如: "key1=value1;key2=value2") * @param {string} values * @returns {Object} */ function parseKeyValString(values) { const keyValBody = {} if (!values) return keyValBody const valueArray = values.split(';') for (let index = 0; index < valueArray.length; index++) { const keyVal = valueArray[index] if (keyVal && keyVal.includes('=')) { const keyValArray = keyVal.split('=') keyValBody[keyValArray[0]] = keyValArray[1] } } return keyValBody } /** * 解析请求头信息 * @param {Object} head 请求头对象 * @returns {Object} 解析后的头信息 */ export function parseHeader(head) { if (!head) { return {} } const reqAppIntel = head['Req-App-Intel'] || '' const reqSysOrigin = head['Req-Sys-Origin'] || '' const reqAppIntelObj = parseKeyValString(reqAppIntel) const reqSysOriginObj = parseKeyValString(reqSysOrigin) return { reqLang: head['Req-Lang'], authorization: head['Authorization'], buildVersion: reqAppIntelObj['build'], appVersion: reqAppIntelObj['version'], appChannel: reqAppIntelObj['channel'], reqImei: reqAppIntelObj['Req-Imei'], sysOrigin: reqSysOriginObj['origin'], sysOriginChild: reqSysOriginObj['child'], } } /** * 设置HTTP请求的默认头部信息 * @param {Object} headerInfo 解析后的头部信息 */ export async function setHttpHeaders(headerInfo) { try { const httpModule = await import('../utils/http.js') if (httpModule.setHeadersFromApp) { httpModule.setHeadersFromApp(headerInfo) } } catch (error) { errorLog('Failed to update HTTP headers:', error) } debugLog('🔑 Authorization token set:', headerInfo.authorization) debugLog('🌍 Language set:', headerInfo.reqLang) debugLog('📱 App info:', { version: headerInfo.appVersion, build: headerInfo.buildVersion, channel: headerInfo.appChannel, system: headerInfo.sysOrigin, }) }