aslan-h5/src/utils/appBridge.js

536 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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,
})
}