最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Sending message from Main to Renderer - Stack Overflow

programmeradmin4浏览0评论

Could someone help me out here. I'm pletely confused on how to solve this. I've now spent about a week trying to find a solution for this but have e up short and there appears to be a lack of a solid solution online. I've made a github repository trying to demonstrate the issue.

In short I've implemented a status bar in my application which i want to populate with various string messages. These messages would be sent from functions that are contained inside a js file that imports electron, which mean's it doesn't have direct access to the Renderer. So how would I send these messages to the Renderer. I'm assuming this needs to be done using the ContextBridge, but i have no clue how to successfully do this, so if your response is just linking me to the context bridge docs, don't bother, lol I've exhausted myself looking at that. The other alternative i was considering is using a custom event but but i'm not sure that would solve the problem either.

Here is a sample of what im trying to do along with repo on github. If you do a pull-request to fix the repo, ill gladly merge and keep the repo public for others to benefit from and share with the munity.

As a minor problem, im not sure why i can no longer call getPath from 'app' from within a js file that's not loaded into the render thread.

I trigger a method from Renderer

index.vue

const doWork = () => {
    window.messenger.doWork();
}

electron-preload.js

import { contextBridge } from "electron";

const messenger = require("../src/helpers/messenger");
contextBridge.exposeInMainWorld("messenger", messenger);

messenger.js

const { app } = require("electron");
const path = require("path");

// using electron module to demonstrate this file can't be imported into renderer
export function showMessage(msg) {
  const dir = path.join(app.getPath("documents"), "presets");
  console.log(dir);
  // TODO: send message to renderer...
}

export function doWork() {
  console.log("Doing working...");

  // step 1: long process
  showMessage("Processing step 1...");

  // step 2: long process
  showMessage("Processing step 2...");

  // step 3: long process
  showMessage("Processing step 3...");
}

I'd like to display the messages sent from the main to renderer to be displayed in the status bar of

main.vue

<q-footer>
     <q-bar>
        <span class="text-caption">Show message here...</span>
    </q-bar>
</q-footer>

** UPDATE 01 **

For some reason my message is not being received in the Renderer. Here are my code changes

electron-preload.js

import { contextBridge, ipcRenderer } from "electron";

contextBridge.exposeInMainWorld("electronAPI", {
  setStatus: (callback, func) =>
    ipcRenderer.on("set-status", (event, ...args) => func(...args)),
});

index.vue

<template>
  <q-page class="flex flex-center">
    <q-btn label="Show Message" @click="doWork" />
  </q-page>
</template>

<script>
import { defineComponent } from "vue";

export default defineComponent({

  setup() {
    // send message for testing...
    const doWork = () => {
      window.electronAPI.setStatus("sfsfsdfsd");
    };

    // recieve status messages...
    window.electronAPI.setStatus("set-status", (data) => {
      console.log("STATUS:", data);
      // TODO $store.dispatch("....");
    });

    return {
      doWork,
    };
  },
});
</script>

Could someone help me out here. I'm pletely confused on how to solve this. I've now spent about a week trying to find a solution for this but have e up short and there appears to be a lack of a solid solution online. I've made a github repository trying to demonstrate the issue.

In short I've implemented a status bar in my application which i want to populate with various string messages. These messages would be sent from functions that are contained inside a js file that imports electron, which mean's it doesn't have direct access to the Renderer. So how would I send these messages to the Renderer. I'm assuming this needs to be done using the ContextBridge, but i have no clue how to successfully do this, so if your response is just linking me to the context bridge docs, don't bother, lol I've exhausted myself looking at that. The other alternative i was considering is using a custom event but but i'm not sure that would solve the problem either.

Here is a sample of what im trying to do along with repo on github. If you do a pull-request to fix the repo, ill gladly merge and keep the repo public for others to benefit from and share with the munity. https://github./JokerMartini/statusbar

As a minor problem, im not sure why i can no longer call getPath from 'app' from within a js file that's not loaded into the render thread.

I trigger a method from Renderer

index.vue

const doWork = () => {
    window.messenger.doWork();
}

electron-preload.js

import { contextBridge } from "electron";

const messenger = require("../src/helpers/messenger");
contextBridge.exposeInMainWorld("messenger", messenger);

messenger.js

const { app } = require("electron");
const path = require("path");

// using electron module to demonstrate this file can't be imported into renderer
export function showMessage(msg) {
  const dir = path.join(app.getPath("documents"), "presets");
  console.log(dir);
  // TODO: send message to renderer...
}

export function doWork() {
  console.log("Doing working...");

  // step 1: long process
  showMessage("Processing step 1...");

  // step 2: long process
  showMessage("Processing step 2...");

  // step 3: long process
  showMessage("Processing step 3...");
}

I'd like to display the messages sent from the main to renderer to be displayed in the status bar of

main.vue

<q-footer>
     <q-bar>
        <span class="text-caption">Show message here...</span>
    </q-bar>
</q-footer>

** UPDATE 01 **

For some reason my message is not being received in the Renderer. Here are my code changes

electron-preload.js

import { contextBridge, ipcRenderer } from "electron";

contextBridge.exposeInMainWorld("electronAPI", {
  setStatus: (callback, func) =>
    ipcRenderer.on("set-status", (event, ...args) => func(...args)),
});

index.vue

<template>
  <q-page class="flex flex-center">
    <q-btn label="Show Message" @click="doWork" />
  </q-page>
</template>

<script>
import { defineComponent } from "vue";

export default defineComponent({

  setup() {
    // send message for testing...
    const doWork = () => {
      window.electronAPI.setStatus("sfsfsdfsd");
    };

    // recieve status messages...
    window.electronAPI.setStatus("set-status", (data) => {
      console.log("STATUS:", data);
      // TODO $store.dispatch("....");
    });

    return {
      doWork,
    };
  },
});
</script>
Share Improve this question edited Mar 22, 2022 at 12:38 JokerMartini asked Mar 21, 2022 at 18:56 JokerMartiniJokerMartini 6,16712 gold badges102 silver badges223 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 6

A technique that works for me is not to use the preload.js script to define concrete implementations. Instead, I use the preload.js script to only define channel (names) that I can municate with between the main and render threads. IE: Seperating your concerns. Implement your concrete functions within your main thread scripts and render thread scripts.


preload.js

// Import the necessary Electron ponents.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;

// White-listed channels.
const ipc = {
    'render': {
        // From render to main.
        'send': [],
        // From main to render.
        'receive': [
            'message:update' // Here is your channel name
        ],
        // From render to main and back again.
        'sendReceive': []
    }
};

// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
    // Allowed 'ipcRenderer' methods.
    'ipcRender', {
        // From render to main.
        send: (channel, args) => {
            let validChannels = ipc.render.send;
            if (validChannels.includes(channel)) {
                ipcRenderer.send(channel, args);
            }
        },
        // From main to render.
        receive: (channel, listener) => {
            let validChannels = ipc.render.receive;
            if (validChannels.includes(channel)) {
                // Deliberately strip event as it includes `sender`.
                ipcRenderer.on(channel, (event, ...args) => listener(...args));
            }
        },
        // From render to main and back again.
        invoke: (channel, args) => {
            let validChannels = ipc.render.sendReceive;
            if (validChannels.includes(channel)) {
                return ipcRenderer.invoke(channel, args);
            }
        }
    }
);

Note: Though I do not use Vue.js, you should get the gist of the below two files.

main.js (main thread)

const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;

const nodePath = require("path");

let window;

function createWindow() {
    const window = new electronBrowserWindow({
        x: 0,
        y: 0,
        width: 800,
        height: 600,
        show: false,
        webPreferences: {
            nodeIntegration: false,
            contextIsolation: true, 
            preload: nodePath.join(__dirname, 'preload.js')
        }
    });

    window.loadFile('index.html')
        .then(() => { window.show(); });

    return window;
}

electronApp.on('ready', () => {
    window = createWindow();
    
    // Send a message to the window.
    window.webContents.send('message:update', 'Doing work...');
});

electronApp.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        electronApp.quit();
    }
});

electronApp.on('activate', () => {
    if (electronBrowserWindow.getAllWindows().length === 0) {
        createWindow();
    }
});

index.html (render thread)

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    
    <body>
        <span id="text-caption">Show message here...</span>
    </body>
    
    <script>      
        // Listen for message updates from the main thread.
        window.ipcRender.receive('message:update', (message) => {
            document.getElementById('text-caption').innerText = message;
        });
    </script>
</html>

You want to be using ipcMain (in the main process) and ipcRenderer (in the renderer process). If you pare your scripts to the examples at https://www.electronjs/docs/latest/tutorial/ipc that is what is missing.

(There is a section specifically on doing main to renderer.)

This did use to be clearer and simpler, but carried more potential for abuse. So best to ignore any online tutorials older than a year or so. (Though contextBridge is quite new, so if they mention it then they should be recent.)

发布评论

评论列表(0)

  1. 暂无评论