稀土掘金 稀土掘金

Electron 内嵌 Iframe 的数据通信

Electron 内嵌 IFrame 的数据通信

  • 背景:项目A打包成网页,然后套壳在 Electron (下面成为项目B)和其他平台,平台差异化的代码逻辑在各自平台实现。

  • 环境:Vue2 + electron-builder

  • 遇到问题:使用 Electron 自带的 webview ,同样的代码,webview 无法加载到 preload.js ,网上找了很久都没法实现,只能切换成 iframe ,也不知道正常是否是用此逻辑来处理通信。

主要步骤

  1. Electron 配置 preload.js ,在 preload.js 配置可供 iframe 调用的方法。
  2. iframe 里按需调用,ipcMain 接受事件,并转发给 ipcRenderer
  3. ipcRenderer 收到消息,通过 postMessage 发送给 iframe
  4. iframe 收到回调消息,处理数据

1648881307039.jpg

具体实现

  1. 配置 preload.js ,监听事件
// B/src/background.ts
const win = new BrowserWindow({
  //...其他配置
  webPreferences: {
      webviewTag: true,
      nodeIntegration: true,
      contextIsolation: false,
      enableRemoteModule: true,
      nodeIntegrationInSubFrames: true,
      // 对应的文件在 public/repload.js 
      preload: path.join(__static, "preload.js"),
  }
});
// 加载 Electron 项目里的 app.vue,后续会用到这个 win
const containerHtml = process.env.WEBPACK_DEV_SERVER_URL as string;
if (containerHtml) {
  await win.loadURL(containerHtml);
} else {
  createProtocol("app");
  await win.loadURL("app://./index.html");
}
// 这里监听来自 iframe 传回的消息,并转发给 win.webContents,即 Electron 项目里的 app.vue
ipcMain.on("event_from_iframe", (evt, data) => {
  win.webContents.send("event_from_iframe", data);
});
// B/public/preload.js
const { ipcRenderer } = require("electron");
window.isElectron = true;
window.$bridge = {
  ipcRenderer,
};
  1. iframe 里通过调用 NativeElectron.send 按需传递信息,ipcMain 接受事件,并转发给 ipcRenderer
<!-- A/src/index.vue -->
<!-- 项目A 里的代码,假设最后打包的地址是 http://a.com , 后续会使用到 -->
<script lang="ts">
  import { onMounted, onUnmounted } from "@vue/composition-api";
  import {NativeElectron} from "@/utils/native/native.electron";
  
  onMounted(() => {
    NativeElectron.addMessageListener({
      xxx: (res) => {
        console.log("xxx response: ", res);;
      }
    })
  })
  onUnmounted(() => {
    NativeElectron.removeMessageListener();
  })
  handleXXX() {
    NativeElectron.send("xxx", {data: "data"});
  }
</script>
// A/src/utils/native/native.electron.ts
interface NativeElectronCallback {
  [key: string]: Function;
}

// 和 Electron 外壳约定好的数据方式,其他不做响应
interface NativeElectronData {
  from: "electron" | any;
  name: string; // 回调方法名
  payload: any;
}

export class NativeElectron {
  static callbacks: NativeElectronCallback = {}; // 注册回调函数

  static isElectron(): boolean {
    const is = window && window.isElectron;
    console.log("isElectron : ", is);
    return is;
  }

  static addMessageListener (callbacks: NativeElectronCallback) {
    if (!NativeElectron.isElectron()) {
      return;
    }
    window.addEventListener("message", NativeElectron.messageHandler)
    NativeElectron.callbacks = callbacks;
  }

  static removeMessageListener() {
    if (!NativeElectron.isElectron()) {
      return;
    }
    window.removeEventListener("message", NativeElectron.messageHandler)
  }

  // 接收 Electron 里通过的 postMessage
  static messageHandler = (e) => {
    const data = e.data as NativeElectronData;
    if (typeof data !== "object") {
      return;
    }
    const { from = "", name = "", payload } = data;
    if (from !== "electron") {
      return;
    }
    const callback = NativeElectron.callbacks[name];
    callback && callback(payload);
  }
  
  // iframe 里传消息给 ipcMain
  static send(type: string, payload: any) {
    if (!NativeElectron.isElectron()) {
      return;
    }
    // @ts-ignore
    window.$bridge.ipcRenderer.send("event_from_iframe", {
      type,
      payload
    });
  }
}
  1. ipcRenderer 收到消息,通过 postMessage 发送给 iframe
<!-- B/src/app.vue -->
<template>
  <div>这是 Electron 项目,下面使用 iframe 标签包裹的 A 项目</div>
  <iframe src="http://a.com"></iframe>
</template>
<script>
  mounted() {
    const webView = document.querySelector("iframe");
    // 监听从 ipcMain 传来的消息,并转发给 iframe
    ipcRenderer.on("event_from_iframe", (e, data) => {
      webView.contentWindow.postMessage(
        { from: "electron", name: "xxx", payload: data },
        "http://a.com" // postMessage 的第二个参数是 iframe 里加载的源地址
      );
    });
  }
</script>

具体流程

文件加载流程:

  1. B/src/background.ts 里创建 win,加载 B/public/preload.jsB/src/app.vue
  2. B/src/app.vue 里通过 iframe 加载打包之后的 A/src/index.vue
  3. A/src/index.vuewindow 对象上,能拿到 B/public/preload.js 上挂载的函数。

数据流转过程(具体看文章开头的流程图)

  1. A/src/index.vue 调用 handleHelloWorld 把消息 {data: "data"} 传给 B/src/background.ts 里的 ipcMain.on("event-from-iframe")
  2. ipcMain.on("event-from-iframe") 把数据转发给 B/src/app.vue 里的 ipcRenderer.on("event_from_iframe")
  3. ipcRenderer.on("event_from_iframe") 通过 webView.contentWindow.postMessage 传给 iframe
  4. iframe 通过 window.addEventListener("message") 的回调拿到数据。

代做工资流水公司邯郸房贷流水开具绵阳打印银行流水重庆代做背调流水江门打印房贷收入证明天津背调银行流水代办沈阳收入证明模板贵阳流水账单价格珠海贷款流水报价桂林银行流水修改代开莆田背调流水代办鞍山打印入职银行流水镇江代做工资银行流水江门开转账银行流水湛江查工作收入证明黄冈查银行对公流水泰州代办房贷工资流水大庆工作收入证明公司株洲查企业银行流水南通办背调银行流水佛山做房贷收入证明东莞签证银行流水 公司中山车贷流水代做惠州办车贷流水荆州车贷流水制作襄阳打银行流水PS宁德转账银行流水费用太原离职证明开具常德办理银行流水账单佛山流水单查询东莞代做背调流水香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声卫健委通报少年有偿捐血浆16次猝死汪小菲曝离婚始末何赛飞追着代拍打雅江山火三名扑火人员牺牲系谣言男子被猫抓伤后确诊“猫抓病”周杰伦一审败诉网易中国拥有亿元资产的家庭达13.3万户315晚会后胖东来又人满为患了高校汽车撞人致3死16伤 司机系学生张家界的山上“长”满了韩国人?张立群任西安交通大学校长手机成瘾是影响睡眠质量重要因素网友洛杉矶偶遇贾玲“重生之我在北大当嫡校长”单亲妈妈陷入热恋 14岁儿子报警倪萍分享减重40斤方法杨倩无缘巴黎奥运考生莫言也上北大硕士复试名单了许家印被限制高消费奥巴马现身唐宁街 黑色着装引猜测专访95后高颜值猪保姆男孩8年未见母亲被告知被遗忘七年后宇文玥被薅头发捞上岸郑州一火锅店爆改成麻辣烫店西双版纳热带植物园回应蜉蝣大爆发沉迷短剧的人就像掉进了杀猪盘当地回应沈阳致3死车祸车主疑毒驾开除党籍5年后 原水城县长再被查凯特王妃现身!外出购物视频曝光初中生遭15人围殴自卫刺伤3人判无罪事业单位女子向同事水杯投不明物质男子被流浪猫绊倒 投喂者赔24万外国人感慨凌晨的中国很安全路边卖淀粉肠阿姨主动出示声明书胖东来员工每周单休无小长假王树国卸任西安交大校长 师生送别小米汽车超级工厂正式揭幕黑马情侣提车了妈妈回应孩子在校撞护栏坠楼校方回应护栏损坏小学生课间坠楼房客欠租失踪 房东直发愁专家建议不必谈骨泥色变老人退休金被冒领16年 金额超20万西藏招商引资投资者子女可当地高考特朗普无法缴纳4.54亿美元罚金浙江一高校内汽车冲撞行人 多人受伤

代做工资流水公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化