From 37c154a58750e7e99e142d89dacbf0d6a975029d Mon Sep 17 00:00:00 2001 From: hzj <1304805162@qq.com> Date: Mon, 19 Jan 2026 12:16:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(app=E8=BF=9E=E6=8E=A5=E6=96=87=E4=BB=B6):?= =?UTF-8?q?=20=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/appBridge.js | 720 ++++++++++++++++++++++------------------- 1 file changed, 384 insertions(+), 336 deletions(-) diff --git a/src/utils/appBridge.js b/src/utils/appBridge.js index 20ea982..42242fd 100644 --- a/src/utils/appBridge.js +++ b/src/utils/appBridge.js @@ -5,6 +5,8 @@ import { COMMON_HEADERS } from '@/utils/http.js' +// ==================== 工具函数 ==================== + /** * 检测是否为iOS系统 * @returns {boolean} @@ -29,156 +31,28 @@ export function isInApp() { return !!(window.app || window.webkit || window.FlutterPageControl) } -/** - * 关闭当前页面(返回到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) { - console.error('关闭页面失败:', error) - // 降级处理 - window.history.back() - } -} +// ==================== 安全日志系统 ==================== /** - * 前往私聊 + * 安全的日志函数,避免 iOS WebView 中的 "JavaScript execution returned a result of an unsupported type" 错误 + * @param {string} level - 日志级别 ('log', 'debug', 'warn', 'error') + * @param {...any} args - 日志参数 */ -export function gotoPrivateChat(userId) { - if (!userId) return - try { - console.log(`userId:${userId}`) - if (window.app && window.app.gotoPrivateChat) { - console.log('使用gotoPrivateChat') - // 使用注入的方法 - window.app.gotoPrivateChat(userId) - } else if (window.FlutterPageControl) { - // 直接使用Flutter通道 - window.FlutterPageControl.postMessage(`private_chat:${userId}`) - } - } catch (error) { - console.error('前往私聊失败:', error) - } -} - -/** - * 查看个人页面 - */ -export function viewUserInfo(userId) { - if (!userId) return - try { - console.log(`userId:${userId}`) - if (window.app && window.app.viewUserInfo) { - console.log('使用viewUserInfo') - // 使用注入的方法 - window.app.viewUserInfo(userId) - } else if (window.FlutterPageControl) { - // 直接使用Flutter通道 - window.FlutterPageControl.postMessage(`view_user_info:${userId}`) - } - } catch (error) { - console.error('查看个人页面失败:', error) - } -} - -/** - * 前往房间 - */ -export function gotoRoom(roomId) { - if (!roomId) return - try { - console.log(`roomId:${roomId}`) - if (window.app && window.app.gotoRoom) { - console.log('使用gotoRoom') - // 使用注入的方法 - window.app.gotoRoom(roomId) - } else if (window.FlutterPageControl) { - // 直接使用Flutter通道 - window.FlutterPageControl.postMessage(`go_to_room:${roomId}`) - } - } catch (error) { - console.error('前往房间失败:', error) - } -} - -/** - * 前往app页面 - */ -export function gotoAppPage(pageName) { - if (!pageName) return - try { - console.log(`pageName:${pageName}`) - // 直接使用Flutter通道 - window.FlutterPageControl.postMessage(pageName) - } catch (error) { - console.error('前往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) { - console.error('退出APP失败:', error) - } -} - -/** - * 处理返回按钮逻辑 - * @param {boolean} isHomePage 是否为首页 - */ -export function handleBackButton(isHomePage = false) { - if (isHomePage) { - // 首页返回:关闭页面 - closePage() - } else { - // 非首页:正常返回 - window.history.back() - } -} - -/** - * 连接APP并获取访问参数 - * @param {Function} renderFun 回调函数,接收APP传递的access参数 - */ -export function connectApplication(renderFun) { - window.renderData = renderFun - // 兼容历史版本 - window.getIosAccessOriginParam = renderFun - - // 安全日志函数 - const safeConsoleLog = (level, ...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() @@ -203,6 +77,7 @@ export function connectApplication(renderFun) { }) console[level](...processedArgs) } else { + // 非 iOS 设备正常使用 console[level](...args) } } catch (e) { @@ -211,188 +86,361 @@ export function connectApplication(renderFun) { } } } +} - const debugLog = (...args) => safeConsoleLog('debug', ...args) - const errorLog = (...args) => safeConsoleLog('error', ...args) +// 创建日志函数实例 +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()) { - 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 { - 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() - } - } - - // 苹果设备环境 - else if (ios()) { - 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') { - debugLog('检测到window.app,尝试调用getAccessOrigin') - try { - const result = window.app.getAccessOrigin() - debugLog('window.app.getAccessOrigin 返回结果:', result) - renderFun(result) - } catch (error) { - errorLog('调用window.app.getAccessOrigin出错:', error) - waitForIOSInterface() // 尝试WebKit方式 - } - } else { - debugLog('等待iOS WebKit接口注入...') - waitForIOSInterface() - } - } - - // 浏览器环境 - else { - 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) - } + 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字符串格式的访问参数 @@ -406,7 +454,7 @@ export function parseAccessOrigin(access) { data: accessParam, } } catch (error) { - console.error('Failed to parse access origin:', error) + errorLog('Failed to parse access origin:', error) return { success: false, error: 'accessOrigin Error.', @@ -415,6 +463,26 @@ export function parseAccessOrigin(access) { } } +/** + * 解析键值对字符串 (如: "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 请求头对象 @@ -425,30 +493,10 @@ export function parseHeader(head) { return {} } - /** - * 解析键值对字符串 (如: "key1=value1;key2=value2") - * @param {string} values - * @returns {Object} - */ - function getKeyValObj(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 - } - const reqAppIntel = head['Req-App-Intel'] || '' const reqSysOrigin = head['Req-Sys-Origin'] || '' - const reqAppIntelObj = getKeyValObj(reqAppIntel) - const reqSysOriginObj = getKeyValObj(reqSysOrigin) + const reqAppIntelObj = parseKeyValString(reqAppIntel) + const reqSysOriginObj = parseKeyValString(reqSysOrigin) return { reqLang: head['Req-Lang'], @@ -473,12 +521,12 @@ export async function setHttpHeaders(headerInfo) { httpModule.setHeadersFromApp(headerInfo) } } catch (error) { - console.error('Failed to update HTTP headers:', error) + errorLog('Failed to update HTTP headers:', error) } - console.debug('🔑 Authorization token set:', headerInfo.authorization) - console.debug('🌍 Language set:', headerInfo.reqLang) - console.debug('📱 App info:', { + debugLog('🔑 Authorization token set:', headerInfo.authorization) + debugLog('🌍 Language set:', headerInfo.reqLang) + debugLog('📱 App info:', { version: headerInfo.appVersion, build: headerInfo.buildVersion, channel: headerInfo.appChannel,