javascript, electron, ipc doesn't give error or output - javascript

I'm new to js and electron, i keep getting no outputs or errors when running this code. Any help would be appreciated.
//Main.js
const ipcMain = require('electron').ipcMain;
ipcMain.on('x', function(event, arg) {
console.log(arg);
});
// index.html
const { ipcRenderer } = require('electron').ipcRenderer;
ipc.send('x', "Hello");

You should open the DevTools console in your main window; there are most certainly error messages displayed there telling you what's going wrong in your renderer process.
There are at least two issues in your code:
1/ The line
const { ipcRenderer } = require('electron').ipcRenderer;
should be either:
const { ipcRenderer } = require('electron');
or:
const ipcRenderer = require('electron').ipcRenderer;
2/ Once correctly defined, use ipcRenderer, not ipc:
ipcRenderer.send('x', "Hello");

Related

Sending message from Main to Renderer

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.)

ipcMain receiving nothing from ipcRenderer

I'm trying to do a very simple communication between ipcRenderer and IPCMain but it's not working ! can someone tell me why ?
GALLERY.JS
const { ipcRenderer } = require("electron");
document.addEventListener('DOMContentLoaded', (e) => {
ipcRenderer.send('test');
});
I really don't understand why nothing is printed in my console
GALERYCONTROLLER.JS
const { ipcMain} = require('electron');
const userId;
const Axios = require('axios')
ipcMain.on('test', (e) =>{
console.log('droneDataGallery received')
})
});
gallery.ejs
<link rel="stylesheet" href="../assets/css/GalleryPage.css"></link>
<div class='galleryPage'>
</div>
<script src="./../assets/js/gallery.js"></script>
Thanks a lot for your help !
Assuming you use the latest version of Electron, you need to expose the ipc messaging system to your HTML (gallery.js) code through the contextBrige object at the preload.js level. This is very well explained here and it worked great for me. Of course, it implies some extra plumbing.
document.addEventListener('DOMContentLoaded', (e) => {
ipcRenderer.send( 'test' ,data you want to send to GALERYCONTROLLER.JS);
});
ipcMain.on('test', (e, data) =>{
// data is array
console.log(data)
})
});

ipcRenderer data not sent to ipcMain in Electron

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>
)
}

TypeError: Cannot read property 'webContents' of undefined at ipcMainImpl. in electron

In my electron projects I'm receiving the following TypeError while trying to send and receive data from different windows.
My Imports
const electron = require("electron");
const url = require("url");
const path = require("path");
const { Menu } = require("electron/main");
const { webContents } = require("electron");
const { app, BrowserWindow, ipcMain } = require("electron");
Place of Error
Send data
<script>
const electron = require("electron");
const { ipcRenderer } = electron;
const form = document.querySelector("form");
form.addEventListener("submit", submitForm);
function submitForm(e) {
e.preventDefault();
const item = document.querySelector("#item").value;
ipcRenderer.send("item:add", item);
}
</script>
Receive data
// Catch item:add
ipcMain.on("item:add", function (e, item) {
console.log(item);
mainWindow.webContents.send("item:add", item);
addWindow.close();
});
I figured out where my error was lying, so I'm answering my own question.
It seems it I was trying to send data that hadn't fully been received, thus I had to wait for my page to load and then send the data as follows.
ipcMain.on("item:add", function (e, item) {
mainWindow.webContents.on("did-finish-load", () => {
mainWindow.webContents.send("item:add", item);
});
});
Try something like this:
import { remote } from 'electron' // ES6
const { remote } = require('electron') // ES5
// stuff
const webContents = remote.getCurrentWindow().webContents
// Do something with webContents
webContents.send("item:add", item);

How to use electron's app.getPath() to store data?

I want to store images on the users computer, so I figure it should be stored in users data folder, as described here.
app.getPath(name)
name. Returns String - A path to a special directory or file associated with name. On failure an Error is thrown. You can request the following paths by the name:
home User's home directory
appData Per-user application data directory, which by default points to:
%APPDATA% on Windows
$XDG_CONFIG_HOME or ~/.config on Linux
~/Library/Application Support on macOS
userData The directory for storing your app's configuration files, which by default it is the appData directory appended with your app's name.
...
This is what I think you're supposed to do:
const app = require('electron');
alert(app.getPath('userData'));
But I get "getPath is not a function". I am not sure where to put it. It does not work from my html file or the renderer file, and I'm not sure how to use it from the main file because that's not linked to the web page.
Since the remote method is being considered deprecated, as shown here, I'd suggest you do this:
const {app} = require('electron');
console.log(app.getPath('userData'));
remote is considered dangerous.
app.getPath will be always available in main process.
Here is how to do it in renderer process without using remote (electron 7+)
Basically you have to use invoke in renderer.
in main
ipcMain.handle('read-user-data', async (event, fileName) => {
const path = electron.app.getPath('userData');
const buf = await fs.promises.readFile(`${path}/${fileName}`));
return buf;
})
in renderer
ipcRenderer.invoke('read-user-data', 'fileName.txt').then(
result => doSomething()
);
Here is what I use when I need to switch between dev and release
const electron = require('electron');
export const userDataPath = (electron.app || electron.remote.app).getPath(
'userData'
);
Another way to prevent the error "getPath is not a function" is to make the code work both in the renderer process and the main process:
const electron = require('electron');
const configDir = (electron.app || electron.remote.app).getPath('userData');
console.log(configDir);
I had trouble with app.getPath('userData') to save/load config files, etc and ended up using OS specific env vars in the meantime:
const getAppBasePath = () => {
//dev
if (process.env.RUN_ENV === 'development') return './'
if (!process.platform || !['win32', 'darwin'].includes(process.platform)) {
console.error(`Unsupported OS: ${process.platform}`)
return './'
}
//prod
if (process.platform === 'darwin') {
console.log('Mac OS detected')
return `/Users/${process.env.USER}/Library/Application\ Support/${YOUR_APP_NAME}/`
} else if (process.platform === 'win32') {
console.log('Windows OS detected')
return `${process.env.LOCALAPPDATA}\\${YOUR_APP_NAME}\\`
}
}
if you wanna do it in renderer process
try this,it is work for me
// main.js
const electron = require('electron')
const electronRemote = process.type === 'browser' ? electron :
require('#electron/remote')
const { app, ipcMain, Menu, globalShortcut } = require('electron')
const BrowserWindow = electronRemote.BrowserWindow
const isDev= require('electron-is-dev')
const { initialize, enable } = require('#electron/remote/main')
initialize()
let mainWindow
app.on('ready', ()=>{
mainWindow = new BrowserWindow({
width: 1024,
height: 600,
minWidth:600,
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false
}
})
enable(mainWindow.webContents)
})
render process
// render process
const { app } = window.require('#electron/remote')
const savedPath = app.getPath('userData')

Categories

Resources