From 133e950dec54563f30ae87f6c31fa880c1522ca1 Mon Sep 17 00:00:00 2001 From: hzj <1304805162@qq.com> Date: Mon, 19 Jan 2026 12:07:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(app=E8=BF=9E=E6=8E=A5=E6=96=87=E4=BB=B6):?= =?UTF-8?q?=20=E5=8F=96=E6=B6=88=E6=96=87=E4=BB=B6=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/appBridge.js | 720 +++++++++++++++++++---------------------- 1 file changed, 336 insertions(+), 384 deletions(-) diff --git a/src/utils/appBridge.js b/src/utils/appBridge.js index 42242fd..20ea982 100644 --- a/src/utils/appBridge.js +++ b/src/utils/appBridge.js @@ -5,8 +5,6 @@ import { COMMON_HEADERS } from '@/utils/http.js' -// ==================== 工具函数 ==================== - /** * 检测是否为iOS系统 * @returns {boolean} @@ -31,28 +29,156 @@ 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 - 日志参数 + * 前往私聊 */ -function createSafeLogger() { - return function safeConsoleLog(level, ...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) => { 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() @@ -77,7 +203,6 @@ function createSafeLogger() { }) console[level](...processedArgs) } else { - // 非 iOS 设备正常使用 console[level](...args) } } catch (e) { @@ -86,361 +211,188 @@ function createSafeLogger() { } } } -} -// 创建日志函数实例 -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 + const debugLog = (...args) => safeConsoleLog('debug', ...args) + const errorLog = (...args) => safeConsoleLog('error', ...args) try { + // 安卓设备环境 if (android()) { - handleAndroidPlatform(renderFun) - } else if (ios()) { - handleIOSPlatform(renderFun) - } else { - handleWebPlatform(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 { + 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) + } } } 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字符串格式的访问参数 @@ -454,7 +406,7 @@ export function parseAccessOrigin(access) { data: accessParam, } } catch (error) { - errorLog('Failed to parse access origin:', error) + console.error('Failed to parse access origin:', error) return { success: false, error: 'accessOrigin Error.', @@ -463,26 +415,6 @@ 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 请求头对象 @@ -493,10 +425,30 @@ 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 = parseKeyValString(reqAppIntel) - const reqSysOriginObj = parseKeyValString(reqSysOrigin) + const reqAppIntelObj = getKeyValObj(reqAppIntel) + const reqSysOriginObj = getKeyValObj(reqSysOrigin) return { reqLang: head['Req-Lang'], @@ -521,12 +473,12 @@ export async function setHttpHeaders(headerInfo) { httpModule.setHeadersFromApp(headerInfo) } } catch (error) { - errorLog('Failed to update HTTP headers:', error) + console.error('Failed to update HTTP headers:', error) } - debugLog('🔑 Authorization token set:', headerInfo.authorization) - debugLog('🌍 Language set:', headerInfo.reqLang) - debugLog('📱 App info:', { + console.debug('🔑 Authorization token set:', headerInfo.authorization) + console.debug('🌍 Language set:', headerInfo.reqLang) + console.debug('📱 App info:', { version: headerInfo.appVersion, build: headerInfo.buildVersion, channel: headerInfo.appChannel,