QtWebChannel Promise化封装

·  阅读 579
QtWebChannel Promise化封装

QT简介

Qt是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些宏,Qt很容易扩展,并且允许真正地组件编程。

web通讯

利用Qt的Qt WebEngine和WebChannel模块与web进行通讯。前端需要引入官方提供的qwebchannel.js库进行与Qt客户端进行交互(异步式交互)官方链接

Qt<--->web交互原理

实例化QWebChannel后生成一个交互通道channel,然后获取通道对象let qtContext=channel.objects.webBridge;对qtContext做双向通信的事件处理,接受Client的消息:

qtContext.contentChanged.connect((response) => {
  const { event, data } = response
  responseLog({ event, data })
  EE.emit(event, data)
})
复制代码

发送消息到Client(参数格式为:事件名称,数据,回调事件名称):

qtContext.dataChanged(
  JSON.stringify({ event, data, callbackEvent })
)
复制代码

封装Promise

  • 判断是否为Qt环境
export const isQtClient = (function () {
  return navigator.userAgent.includes('QtWebEngine')
})()
复制代码
  • 数据异步回调

为了实现类ajax式的交互逻辑引入eventemitter3作为桥梁:

import EventEmitter from 'eventemitter3'
export const EE = new EventEmitter()
复制代码

接着定义接口fetchQt,其接收事件名event、数据data、回调事件名callbackEvent作为参数:

fetchQt: ({ event, data, callbackEvent }) => {
    requestLog(event, data)
    return qtContext.dataChanged(
      JSON.stringify({ event, data, callbackEvent })
    )
  }
复制代码

fetchQtCallback为自动注册回调函数的方法,其行为类似ajax:

fetchQtCallback: ({ event, data,callbackEvent = `${event}_callback` },config = {}) => {
    const options = { ...conf, ...config }
    return new Promise((resolve, reject) => {
      let timer = null
      const apiHandler = (data) => {
        clearTimeout(timer)
        timer = null
        resolve(data)
      }
      EE.once(callbackEvent, apiHandler)
      if (options.timeout) {
        timer = setTimeout(() => {
          EE.off(callbackEvent, apiHandler)
          reject({ callbackEvent, msg: 'Timeout', time: options.timeout })
        }, options.timeout)
      }
      qWebChannel.__apis__.fetchQt({ event, data, callbackEvent })
    })
  }
复制代码
  • 使用方式
// 只下发
export const qwebApi = async ({ event, data, callbackEvent }) => {
  const channel = await qwebchannel()
  return channel.fetchQt({ event, data, callbackEvent }, {})
}

// 下发、监听、返回值
export const fetchQwebApi = async ({ event, data, callbackEvent }, config = {}) => {
  const channel = await qwebchannel()
  return channel.fetchQtCallback({ event, data, callbackEvent }, config)
}
复制代码

注意点

以上定义的数据对象以参数含义需要跟Qt端保持一致比如callbackEvent在Qt端收到此参数时在处理完事件后会发射一个事件名为callbackEvent的事件并返回数据

全部代码

utils.js

export const isQtClient = (function () {
  return navigator.userAgent.includes('QtWebEngine')
})()

export function assert(condition, msg) {
  if (!condition) console.error(msg || `[ASSERT]: ${condition} is a falsy value.`)
}

export const __DEV__ = process.env.NODE_ENV === 'development'

复制代码

qwebchannel.js

import EventEmitter from 'eventemitter3'
import { QWebChannel } from './qwebchannel'
import { assert, isQtClient, __DEV__ } from './utils'

let qWebChannel = null

const conf = {
  timeout: 10000,
}

// 页面标识符,可进行或操作-》表示qwebchannel事件往目标页面发送
export const PageType = {
  Current: 0x0000,
  Home: 0x0001,
  CloudGames: 0x0002,
  AndroidGames: 0x0004,
}

// 请求log
const requestLog = (event, data, ga) => {
  try {
    if (ga && Object.keys(ga).length) {
      console.info(
        `%cRequest:qwebApi for <${event}>(Ga-Data):`,
        'color:green;font-size:18px;',
        '\n',
        ga
      )
    }
    if (data) {
      console.info(
        `%cRequest:qwebApi for <${event}>(Req-Data):`,
        'color:pink;font-size:18px;',
        '\n',
        data
      )
    } else {
      console.info(`%cRequest:qwebApi for <${event}>`, 'color:pink;font-size:18px;')
    }
  } catch (error) {}
}

// 相应log
const responseLog = (response, isMock) => {
  try {
    const { event, data } = response
    console.info(
      `%cResponse:qwebApi for <${event}>${isMock ? ' [[MOCK-DATA]]' : ''}:`,
      'color:green;font-size:18px;',
      '\n',
      data
    )
  } catch (error) {
    console.error(
      '%cqtContext.contentChanged(数据解析出错)',
      'color:red;font-size:18px;',
      '\n',
      error
    )
  }
}

export const EE = new EventEmitter()
// console.log(EE)
export default function QWC() {
  return new Promise((resolve, reject) => {
    if (!__DEV__) {
      assert(
        window && window.qt && window.qt.webChannelTransport,
        "'qt' or 'qt.webChannelTransport' should be initialized(injected) by QtWebEngine"
      )
    }
    // 用于在浏览器端开发时,模拟 `Qt` 的注入行为
    if (!isQtClient) {
      window.qt = {
        webChannelTransport: {
          send() {
            console.log(`QWebChannel simulator activated !`)
            setTimeout(() => {
              qWebChannel.__apis__ = {
                fetchQt: ({ event, data }) => {
                  requestLog(event, data)
                  return Promise.resolve()
                },
                fetchQtCallback: (
                  { event, data, callbackEvent = `${event}_callback` },
                  { initial }
                ) => {
                  // initial表示初始数据(mock)
                  return qWebChannel.__apis__.fetchQt({ event, data }).then(() => {
                    responseLog({ event: callbackEvent, data: initial }, true)
                    EE.emit(callbackEvent, initial)
                    return initial
                  })
                },
                qtContext: null,
                on: EE.on.bind(EE),
                off: EE.off.bind(EE),
                once: EE.off.bind(EE),
                emit: EE.emit.bind(EE),
              }
              resolve(qWebChannel.__apis__)
            }, 0)
          },
        },
      }
    }
    if (!qWebChannel) {
      qWebChannel = new QWebChannel(window.qt.webChannelTransport, (channel) => {
        const qtContext = channel.objects.webBridge
        qtContext.contentChanged.connect((response) => {
          const { event, data } = response
          responseLog({ event, data })
          EE.emit(event, data)
        })
        qWebChannel.__apis__ = {
          fetchQt: ({ event, data, page = PageType.Current, callbackEvent, ga = {} }) => {
            const { path, module, ...extra } = ga
            const gaTemp = {
              path: path || window.location.pathname,
              module,
              extra: JSON.stringify(extra),
            }
            requestLog(event, data, gaTemp)
            return qtContext.dataChanged(
              JSON.stringify({ event, data, page, callbackEvent, ga: gaTemp })
            )
          },
          fetchQtCallback: (
            { event, data, page = PageType.Current, callbackEvent = `${event}_callback`, ga },
            config = {}
          ) => {
            const options = { ...conf, ...config }
            return new Promise((resolve, reject) => {
              let timer = null
              const apiHandler = (data) => {
                clearTimeout(timer)
                timer = null
                resolve(data)
              }
              EE.once(callbackEvent, apiHandler)
              if (options.timeout) {
                timer = setTimeout(() => {
                  EE.off(callbackEvent, apiHandler)
                  reject({ callbackEvent, msg: 'Timeout', time: options.timeout })
                }, options.timeout)
              }
              qWebChannel.__apis__.fetchQt({ event, data, page, callbackEvent, ga })
            })
          },
          qtContext,
          on: EE.on.bind(EE),
          off: EE.off.bind(EE),
          once: EE.off.bind(EE),
          emit: EE.emit.bind(EE),
        }
        resolve(qWebChannel.__apis__)
      })
    } else {
      resolve(qWebChannel.__apis__)
    }
  })
}

复制代码

qwebApi.js

import qwebchannel, { EE } from '@/tool/qwebchannel'
export const on = EE.on.bind(EE)
export const off = EE.off.bind(EE)
export const once = EE.once.bind(EE)
export const emit = EE.emit.bind(EE)
// 只下发
export const qwebApi = async ({ event, data, page, callbackEvent, ga }) => {
  const channel = await qwebchannel()
  return channel.fetchQt({ event, data, page, callbackEvent, ga }, {})
}

// 下发、监听、返回值
export const fetchQwebApi = async ({ event, data, page, callbackEvent, ga }, config = {}) => {
  const channel = await qwebchannel()
  return channel.fetchQtCallback({ event, data, page, callbackEvent, ga }, config)
}

复制代码
  • 在Vue及React中应用时需等待channel建立完成,例如React:
import React from 'react'
import ReactDOM from 'react-dom'
import qwebchannel from '@/tool/qwebchannel'
qwebchannel().then(async (qwebApi) => {
    ReactDOM.render(
    <App/>,
    document.getElementById('root')
  )
})
复制代码
  • 具体组件中使用
import { qwebApi, fetchQwebApi } from '@/apis/qwebApi'
qwebApi({
  event: 'openClientLoading',
  data: null,
})z

const vmList = await fetchQwebApi(
  {
    event: 'getVmList',
    data: { packageName: game.package_name},
  },
  { initial: { installed: [], uninstalled: [], defaultEngines: '' } }
)
复制代码

总结

以上就是Qtwebchannel的使用总结,让客户端与web的交互统一化

DoubleScreen

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改