Could someone help me out here. I'm completely confused on how to solve this. I've now spent about a week trying to find a solution for this but have come 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 community. https://github.com/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>
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 communicate 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 components.
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 compare your scripts to the examples at https://www.electronjs.org/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.)
Related
I am trying to learn electron, but I'm struggling to understand this basic functionality of IPC.
Already tried from documentation or tutorials but everytime they overcomplicate with complex examples or using other libaries like svelte, typescript etc. Also I don't want to show in console, I want to display directly in HTML page. I know these are basics, but any help is appreciated.
So far I understood that I need:
main.js
const { app, BrowserWindow, ipcMain } = require("electron");
const path = require("path");
app.whenReady().then(main);
let window;
async function main() {
window = new BrowserWindow({
width: 100,
height: 100,
webPreferences: {
preload: path.join(__dirname + "./preload.js")
},
})
window.on("ready-to-show", window.show);
window.loadFile(path.join(__dirname, "./index.html"));
}
ipcMain.on("GetMyVar", (event, args) => {
*???*
})
preload.js
const { ipcRenderer, contextBridge } = require("electron");
const API = {
window: {
GetMyVar: () => ipcRenderer.send("GetMyVar", MyVar) *???*
},
}
contextBridge.exposeInMainWorld("app", API);
renderer.js
const MyVar = "JUSTSHOWUP!";
index.html
<html>
<body>
<h1 id='MyVar'>???</h1>
</body>
</html>
Thank you!
Electron Inter-Process Communication is a difficult subject to
grasp without easy to understand examples.
Your preload.js script can be written in a number of different ways. In the below code example, I have included two
ways to do this.
preload-1.js uses a simplified, easy to understand direct implementation method. That said, for ease of
maintainability and debugging, as your Electron application grows, you will want to split your preload.js script up
into smaller individual preload scripts. Bearing in mind that you can only load one preload script per window
instance, you may have a need to repeat code between preload scripts.
preload-2.js uses a more flexible white listed channel naming system where you only need to load the one preload
script across all created windows. As a result, your preload script only performs a single function, to communicate
between your main process and render process(es). Implementation of the sent and received channel names are kept within
your specific code domains.
main.js (main process)
No matter which type of preload script you use, implementation will be the same within your main.js file.
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;
const nodePath = require('path');
// Prevent garbage collection.
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-1.js')
// preload: nodePath.join(__dirname, 'preload-2.js')
}
});
window.loadFile('index.html')
.then(() => { window.webContents.send('messageFromMain', 'Message from main..!'); })
.then(() => { window.show(); });
return window;
}
electronApp.on('ready', () => {
window = createWindow();
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// ---
electronIpcMain.on('messageToMain', (event, message) => {
console.log('Message to main: ' + message);
})
preload-1.js (main process)
// Import the necessary Electron components
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// Exposed protected methods in the render process
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods
'ipcRender', {
// From render to main
messageToMain: (message) => {
ipcRenderer.send('messageToMain', message)
},
// From main to render
messageFromMain: (message) => {
ipcRenderer.on('messageFromMain', message)
}
});
preload-2.js (main process)
At the end of this preload.js script, I have included a section on how to use it within your main process and render
process(es).
// Import the necessary Electron components
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels
const ipc = {
'render': {
// From render to main
'send': [
'messageToMain'
],
// From main to render
'receive': [
'messageFromMain'
],
// 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);
}
}
}
);
/**
* Render --> Main
* ---------------
* Render: window.ipcRender.send('channel', data); // Data is optional.
* Main: electronIpcMain.on('channel', (event, data) => { methodName(data); })
*
* Main --> Render
* ---------------
* Main: windowName.webContents.send('channel', data); // Data is optional.
* Render: window.ipcRender.receive('channel', (data) => { methodName(data); });
*
* Render --> Main (Value) --> Render
* ----------------------------------
* Render: window.ipcRender.invoke('channel', data).then((result) => { methodName(result); });
* Main: electronIpcMain.handle('channel', (event, data) => { return someMethod(data); });
*
* Render --> Main (Promise) --> Render
* ------------------------------------
* Render: window.ipcRender.invoke('channel', data).then((result) => { methodName(result); });
* Main: electronIpcMain.handle('channel', async (event, data) => {
* return await promiseName(data)
* .then(() => { return result; })
* });
*/
index.html (render process)
To test which form of preload.js script you prefer, just comment out one whilst uncommenting the other.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Electron IPC Test</title>
</head>
<body>
<div>
<label for="messageFromMain">Message from Main Process: </label>
<input type="text" id="messageFromMain" disabled>
</div>
<hr>
<div>
<label for="messageToMain">Message to Main Process: </label>
<input type="text" id="messageToMain">
<input type="button" id="send" value="Send">
</div>
</body>
<script>
let messageFromMain = document.getElementById('messageFromMain');
let messageToMain = document.getElementById('messageToMain');
// Message from main (preload-1.js)
window.ipcRender.messageFromMain((event, message) => {
messageFromMain.value = message;
})
// Message from main (preload-2.js)
// window.ipcRender.receive('messageFromMain', (message) => {
// messageFromMain.value = message;
// })
// Message to main
document.getElementById('send').addEventListener('click', () => {
// (preload-1.js)
window.ipcRender.messageToMain(messageToMain.value);
// (preload-2.js)
// window.ipcRender.send('messageToMain', messageToMain.value);
})
</script>
</html>
I have an Electron app that is displayed using BrowserWindow. I want to open an external URL in the same window so that the user can log in (to an external website) and after the user logs in it should display the Electron application again instead of the external website that the user used to log in.
I've been able to open the external url in the same window by using:
<a href="https://loginsite-example.com" target="_blank" rel="noreferrer">
site where you have to log in
</a>
However, I don't know how to show the Electron application again after the user successfully logs in to the external website. Also, I would like to keep the session from the external website so that I could consume its API inside the electron application.
Moving between window sources, whether it be local (file) or remote (URL) can be accomplished by just calling window.loadFile(...) or window.loadURL(...), but only after the instance of the window has been created.
main.js (main thread)
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;
const nodePath = require("path");
// Prevent garbage collection
let window;
function createWindow() {
return new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: nodePath.join(__dirname, 'preload.js')
}
});
}
function showMainWindow() {
window.loadFile('index.html')
.then(() => { window.show(); })
}
function showLoginWindow() {
// window.loadURL('https://www.your-site.com/login')
window.loadFile('login.html') // For testing purposes only
.then(() => { window.show(); })
}
electronApp.on('ready', () => {
window = createWindow();
showMainWindow();
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// ----- IPC -----
electronIpcMain.on('message:loginShow', (event) => {
showLoginWindow();
})
electronIpcMain.on('message:loginSuccessful', (event, session) => {
showMainWindow();
})
index.html (render thread)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Main Window</title>
</head>
<body>
<div>Main Window</div><hr>
<button type="button" id="show-login">Login</button>
</body>
<script>
document.getElementById('show-login').addEventListener('click', () => {
window.ipcRender.send('message:loginShow');
});
</script>
</html>
login.html (render thread)
Used for testing purposes only as we do not have access to a real login page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login Window</title>
</head>
<body>
<div>Login Window</div><hr>
<label for="username">Username: </label>
<input type="text" id="username"><br>
<label for="password">Password: </label>
<input type="password" id="password"><br>
<button type="button" id="login">Login</button>
</body>
<script>
// For testing purposes only.
document.getElementById('login').addEventListener('click', () => {
window.ipcRender.send('message:loginSuccessful');
});
</script>
</html>
And finally, a preload.js script to communicate safely between the main thread and render thread(s).
preload.js (main thread)
// Import the necessary Electron components.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [
'message:loginShow',
'message:loginSuccessful'
],
// From main to render.
'receive': [],
// 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);
}
}
}
);
A problem that will need to be overcome is once you submit your login credentials to the server, how are you going to get the session data? Your html Javascript will need to detect if and when the necessary session data is available. Once the session data is available, it is an easy process of transferring the session via IPC from the render thread to the main thread. To figure this out, one will require some additional information and understanding of your login system (a separate StackOverflow question).
As a pre-cursor, I suspect one would need to detect in the main thread when the login page has been submitted to the server via something like window.webContents.on('did-navigate', ...). Once detected, check the next loaded page quickly to see if a session exists. If so, get it, send it to the main thread and then redirect back to the index.html page.
I think there should be an easier way if you can login via an API. Then the whole process can be self-contained in your Electron application. IE: Show local (file) login.html, submit data to server and await a "success" or "fail" response. If successful, pass the session data in the response. If unsuccessful, display an appropriate error message.
I'm tyring to send messages back and forth between the main electron process and the vue instance. What i have so far is
Preload.js:
import { contextBridge, ipcRenderer } from 'electron'
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}
for (const dependency of ['chrome', 'node', 'electron']) {
replaceText(`${dependency}-version`, process.versions[dependency])
}
})
window.ipcRenderer = require('electron').ipcRenderer;
contextBridge.exposeInMainWorld('ipcRenderer', {
//Render (Vue) to main (Electron)
send: (channel, data) => {
let validChannels = ['clientMessage'] // <-- Array of all ipcRenderer Channels used in the client
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data)
}
},
//Main (Electron) to Render (Vue)
on: (channel, func) => {
let validChannels = ['electronMessage'] // <-- Array of all ipcMain Channels used in the electron
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args))
}
}
})
So i have 2 whitelisted channels in ipcRender, one is called 'clientMessage' to send messaged from the vue instance to the electron main process, the other is 'electronMessage' to send messages from the electron main process to the vue instance.
In my background.js i have the following:
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
ipcMain.on('clientMessage', (event, args) => {
console.log('received a message from vue: '+args)
event.sender.send('message','return Message from electron'+args);
});
And in my app.Vue i have:
mounted () {
window.ipcRenderer.on('electronMessage', (event, data) => {
console.log('message from electron: '+data)
})
},
methods: {
sendMessage(){
window.ipcRenderer.send('clientMessage','testing')
}
}
The app runs fine, and when i call the sendMessage function I correctly get a console log on the electron terminal saying message received. So clearly vue -> electron messaging has worked, but why wont it work for the reverse?
Your contextBridge.exposeInMainWorld "keys" are ipcRenderer.send and ipcRenderer.receive.
You must use these keys to access your defined preload.js IPC methods. IE: send and on.
Specifically, to use the ipcRender.on(...) method, you call it with window.ipcRenderer.receive(...).
app.Vue (render thread)
// To send a message from render thread to main thread.
window.ipcRenderer.send('clientMessage','testing'); // Working
// To receive a message from main thread to render thread.
window.ipcRenderer.receive('electronMessage', (event, data) => {
console.log('message from electron: ' + data);
});
I figured out why, the way to send messages from electron main process to the renderer instance is not the same. Since there is only one electron main process running at all times, we can simple do window.ipcRenderer.send('', 'your message') in the vue renderer instance, but since there can be more than one renderer instances running, the main electron process needs to know which render process to send it to.
So in your main.js/background.js whatever .js you are running the electron main process out of. do this:
let win;
async function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
enableRemoteModule: true,
preload: path.join(__dirname, "preload.js")
}
})
.
.
.
and then to send a message to the render instance:
win.webContents.send('<channelName>','your message')
that way it sends it to the correct render process. So my code in the end looks like this:
ipcMain.on('clientMessage', (event, args) => {
console.log('received a message from vue: '+args)
win.webContents.send('electronMessage','Reply from main process: '+args)
});
Alright so I have these methods in my index.js main process that I want to access from the render process. I have tried two ways to go about this process.
ipcMain and ipcRender
The first idea was to use ipcMain and ipcRender using an "on" and "sendSync" The error I get back is "an object could not be cloned"
Index.js - Main Process
ipcMain.on( "getData", ( event, callBack ) => {
db = new sqlite3.Database(
dbPath,
sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE,
function(error) {
if(error){
log.error(`exception: ${error}`);
//throw error
if (callBack) callBack(error);
}
data.getModel(db, log, function(rawDataStr) {
if (callBack) callBack(rawDataStr);
})
}
)
return db
} );
App.Js - Render Process
window.require('electron').ipcRenderer.sendSync( "getData",function(rawData){
if (rawData.name && rawData.name == 'Error') {
alert('PRT DB is not present');
} else {
sharedObj.rawData = rawData;
app.advanceReadiness();
}
})
#electron/remote
The other solution I tried was to use #electron/remote. I understand the remote module was depreciated, but I was willing to try it. This works when I run the the app locally, but as soon as I build the app with electron-forge it can no longer find my global variable.
Index.js - Main Process
require('#electron/remote/main').initialize()
global.sharedObj = {
getData:function(callBack){
db = new sqlite3.Database(
dbPath,
sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE,
function(error) {
if(error){
log.error(`exception: ${error}`);
//throw error
if (callBack) callBack(error);
}
data.getModel(db, log, function(rawDataStr) {
if (callBack) callBack(rawDataStr);
})
}
)
}
}
App.js - Render Process
var sharedObj = window.require('#electron/remote').getGlobal('sharedObj');
sharedObj.getData(function (rawData) {
if (rawData.name && rawData.name == 'Error') {
alert('PRT DB is not present');
} else {
sharedObj.rawData = rawData;
app.advanceReadiness();
}
});
I suspect that your DB connection isn't cloneable, because this DB object doesn't fit one of the valid values that can be serialized by IPC (inter-process communication). (See this section to see what we can pass between renderer > main process without issue).
You probably need to do something like this. I'm not familiar with using sqlite3 in JS, but this should hopefully get you started on the right track. The general gist is that you should store a reference to your DB in your main.js file and then set up a listener that listens for requests from your front-end page. Once a message is sent to the main.js file (ie. backend), you will query your DB, and then return the results to the front-end by sending an IPC message back (win.webContents.send("fromMain", data); in the example elow).
main.js
const {
app,
BrowserWindow,
ipcMain
} = require("electron");
const path = require("path");
const fs = require("fs");
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;
let db = new sqlite3.Database(
dbPath,
sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE,
function (error) {
if (error) {
log.error(`exception: ${error}`);
//throw error
if (callBack) callBack(error);
}
data.getModel(db, log, function (rawDataStr) {
if (callBack) callBack(rawDataStr);
})
}
);
async function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false, // is default value after Electron v5
contextIsolation: true, // protect against prototype pollution
enableRemoteModule: false, // turn off remote
preload: path.join(__dirname, "preload.js") // use a preload script
}
});
// Load app
win.loadFile(path.join(__dirname, "dist/index.html"));
// rest of code..
}
app.on("ready", createWindow);
ipcMain.on("toMain", (event, args) => {
db.retrieveData().then(data => {
// Send result back to renderer process
win.webContents.send("fromMain", data);
});
});
preload.js
const {
contextBridge,
ipcRenderer
} = require("electron");
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
"api", {
send: (channel, data) => {
// whitelist channels
let validChannels = ["toMain"];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
let validChannels = ["fromMain"];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
}
}
);
index.html
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8"/>
<title>Title</title>
</head>
<body>
<script>
window.api.receive("fromMain", (data) => {
console.log(`Received ${data} from main process`);
});
window.api.send("toMain", "some data");
</script>
</body>
</html>
I recently started developing desktop application using electron.
I want to send form details to main.js from the index.html on button click event. I have added a listener to the button in index.js. Searched online and found that I have to use ipcMain in main.js and ipcRenderer in index.js but the data is not being sent to ipcMain .
How do I get the form data in main.js ?
In index.html
<div class="btn-div fld">
<button id="loginButton" class="btn btn-primary st-btn" type="submit" name="button">Login</button>
</div>
<script src="index.js" charset="utf-8"></script>
In index.js
document.querySelector("#loginButton").addEventListener('click', function(){
userDetails = document.getElementById('login');
username = userDetails.username.value;
password = userDetails.password.value;
console.log("Hello");
const {ipcRenderer} = require('electron');
ipcRenderer.send('asynchronous-message', username);
})
In main.js
const { app, BrowserWindow , ipcMain } = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {
console.log( arg );
});
While creating a browser window in electron using new BrowserWindow(options) where options is an object. Define the object as:
options = {
webPreferences: {
preload: preload.js, //You need to create a file named preload.js (or any name) in your code
nodeIntegration: true,
contextIsolation: false,
}
}
Now in a new file called preload.js:
window.ipcRenderer = require('electron').ipcRenderer;
In your snippet you added const { app } ... which should be done this way to inject the javascript using a preload property in the object.
Now in the main app.js file (whatever you named maybe index.js) where you created the browser window:
const ipc = require('electron').ipcMain; //Add to your pre-existing code
ipc.on("close-app", (event, message) => { //"close-app" can be anything but, you need to use the same key in the send message side (later in this answer)
browserWindow.close(); //If you named the browserwindow as browserWindow
});
Now in your HTML (i.e., send message side)
...
<script>
window.ipcRenderer("close-app", ""); //Second parameter is used if you want to send some extra message. The extra message can be viewed in the server side from the message parameter in the app.js code (just above this paragraph)
</script>
This is a bit difficult if you are doing it for the first time.
I've added more articles which will help you clear your confusions:
A related answer at StackOverflow
Relation with socket.io communication in NodeJS
While I have seen that the other answer to this question may have worked for others, it did not work for me... I was using webpack and, for the life of me, could not get it to work even when adding ExternalsPlugin commonjs and electron. The following worked instead:
main.js
ipcMain.on("download", (event, info) => {
info.properties.onProgress = status => win.webContents.send("downloadProgress", status);
});
preload.js
contextBridge.exposeInMainWorld('electron', {
api: {
//receiving message from main.js
responseProgress: (channel, func) => {
let validChannels = ["downloadProgress"];
if (validChannels.includes(channel)) {
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
},
process: (channel) => {
//example for process that is called by button in reactjs etc
}
});
ReactComponent.js
function ReactComponent() {
useEffect(() => {
//retrieving information from preload.js originally sent from main.js
window.electron.api.responseProgress("downloadProgress", (progress) => {
console.log(progress);
console.log(progress.percent);
});
}, []);
return (
//example of calling api on button click
<button onClick={() => {
window.electron.api.process("toMain");
}}>Download</button>
)
}