Electron - Main process vs Renderer Process - electron

I find it difficult to understand how to differentiate between the main process and the renderer in the code.
here is my file structure:
I want to write a method in the server side and call it in the front-end side.
where should I write it? in the main or renderer process?
and if I wrote the method inside js folder from above image, will it be considered in the main or renderer process?

I'm assuming that your main.js file is where you created your BrowserWindow(s). This is your main process and is where you would write your server side method.
In your main process you can create a method using ipcMain either asynchronously or synchronously like so:
// In main process.
const {ipcMain} = require('electron');
ipcMain.on('asynchronous-message', (event, arg) => {
console.log(arg); // prints "ping"
event.sender.send('asynchronous-reply', 'pong');
});
ipcMain.on('synchronous-message', (event, arg) => {
console.log(arg); // prints "ping"
event.returnValue = 'pong';
});
You can then call this method in a render process (js running in the chromium instance) like so:
// In renderer process (web page).
const {ipcRenderer} = require('electron');
console.log(ipcRenderer.sendSync('synchronous-message', 'ping')); // prints "pong"
ipcRenderer.on('asynchronous-reply', (event, arg) => {
console.log(arg); // prints "pong"
});
ipcRenderer.send('asynchronous-message', 'ping');
Writing a method in the js folder you show above would be part of a render process.
Hope this helps!

Related

Electron IPC behaving inconsistently

I have ipc connected and sending messages early, but when I call ipc later in onclick it is failing.
I am using ipc because I want to communicate with the file system for some settings. It works fine here in Main:
ipcMain.on("settings", (event, arg) => {
console.log("Writing muting to settings: " + arg.settings)
switch(arg.settings){
case "mute_status":
event.reply("settings_reply", settings.muted)
break;
case "mute:true":
event.reply("settings_reply", "muted set to true")
default:
event.reply('settings_reply', 'Sure thing, got it')
}
})
Renderer:
ipc.sendSync('settings',{"settings": "mute_status"})
ipc.once('settings_reply', (event, arg) => {
console.log('Muted is: ' + arg)
if(arg){
place_icon("mute", "volume_div")
document.getElementById("audio").muted = true
} else {
place_icon("volume-up", "volume_div")
}
})
When I later make another call it works sometimes(almost always if I restart Windows). It either works or doesn't with each run of the program. The second call connects to the same in main.js with this in the renderer:
ipc.sendSync("settings", {"settings": "mute:" + muted})
ipc.once('settings_reply', (event, arg) => {
console.log("We're talking")
} )
I've tried quite a few things and I'm happy to provide more info if needed. What is causing this? How do I fix it?
You are using ipcRenderer.once(channel, listener). Its purpose is to register a one-time event handler which will be removed after it is triggered. Change it to ipcRenderer.on(channel, listener)
ipcRenderer.once(channel, listener)
• channel String
• listener Function
event IpcRendererEvent
...args any[]
Adds a one time listener function for the event. This listener is
invoked only the next time a message is sent to channel, after which
it is removed.

Electron: accessing the browser window of the single instance when called a second time

I have been able to successfully create an Electron app that links to the web app (using window.loadUrl).
I have been able to pass some command line params to the web app using window.webContents.send .... On the web app, the javascript receives the parameter and updates the screen.
I am using (2) by opening a file (right-click on it from the directory) through file association using process.argv[1]. This too works.
What I would like is that if I right-click on a second file, it must be passed on to the same electron instance. I am having some issues for this.
I have used the recommended approach for preventing multiple instances as below:
...
let myWindow = null
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
// I do not want to quit
app.quit()
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
...
}
In the above logic, when the program is unable to get-the-lock, the boiler-plate code quits. That works fine, in the sense, that a second window does not open. But in my case, I would like to use the process.argv[1] of the second request and pass it to the web program of the existing instance.
I have not been successful in getting a handle to the browserWindow of the other instance. I would not want to work on multiple windows where each window would call another load of the web app. The current webapp has the ability to update multiple tabs in the same window based on different parameters. Basically, that is handled in the web app itself.
What could be the solution? Thanks
I got it working. It starred at my face and I did not see it. Added a few logs and it helped. Something like this would be a solution.
...
let myWindow = null;
...
function createWindow() {
....
return win;
}
function processParams(...) {
...
}
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
//.. this is called the second time
// process any commandLine params
processParams(...)
...
});
app.on('whenReady')
.then(_ => {
myWindow = createWindow();
// this is called the first time
// process any argv params
processParms(...);
});
}

Creating a folder outside the exe in Electron

How would I create a folder outside the Electron exe.
I'm planning to build the app as a portable windows exe so I'm not sure how to get the path of the exe.
EDIT #1:
I have tried to use app.getPath("exe"); on the main process, but I'm getting a reference error whenever I run the app ReferenceError: exe is not defined
It was indeed app.getPath("exe"), but it has to be implemented using the Electron event emitter pattern.
To have access to the data I triggered the path on the main process.
ipcMain.on("CALL_PRINT_EXE_FILE_PATH", (event) => {
console.log("printing the file path of the exe");
const exePath = app.getPath("exe");
console.log(`exePath: ${exePath}`);
mainWindow.send("PRINT_EXE_FILE_PATH", exePath);
});
Then inside the renderer (I use React), I emit the event and also trigger an event listener.
const { ipcRenderer } = window.require("electron");
...
componentDidMount() {
ipcRenderer.send("CALL_PRINT_EXE_FILE_PATH");
}
componentWillMount() {
ipcRenderer.on("PRINT_EXE_FILE_PATH", this.handlePrintExePath);
}
componentWillUnmount() {
ipcRenderer.removeListener("PRINT_EXE_FILE_PATH", this.handlePrintExePath);
}
...
handlePrintExePath(event, exePath) {
console.log("printing the app exe in the render");
console.log(`exeFilePath: ${exePath}`);
}

Electron's app.relaunch() loose stdio

Using
chokidar.watch(mainFile).on('change', () => {
app.relaunch();
app.exit(0);
});
I can have my Electron app auto-reload on source change. However since the main process is killed, I loose the terminal output which is necessary for debugging. Is there a way to use app.relaunch() retaining the stdio of the parent process?
Disclaimer: below I explain the setup I found as a decent workaround in 2016. Not sure if there is a better way in 2018.
Reloading of my Electron app is handled in two parts:
Rendered process
I use electron-reload listening for changes only within the renderer folder:
require("electron-reload")(path.join(__dirname, "/renderer"));
Main process
Auto-reload handled "manually" via a Gulp script, as follows:
let child = null;
let start = () => {
// Was already running? Kill it and restart.
if (child) child.kill();
let env = process.env;
env.NODE_ENV = "dev";
child = proc.spawn(electron, ["./main/main.js"], { env: env });
function print(data) {
let str = data.toString().trim();
if (str) gutil.log(str);
}
child.stdout.on("data", print);
child.stderr.on("data", print);
};
// Run (dev mode with watch and autoreload)
gulp.task("dev", ["watch"], () => {
start();
});
// Watch and move to dist on the fly.
// Hard reset if main process source changes.
gulp.task("watch", () => {
// If ./renderer/**/*.js changes, electron-reload will handle reload of renderer process only.
// If ./main/**/*.js changes, we kill the Electron process and start a new one.
gulp.watch("./main/**/*.js", start);
});
Running the app with gulp run dev will start a watcher and kill/restart the entire application on file change.

Open external file with Electron

I have a running Electron app and is working great so far. For context, I need to run/open a external file which is a Go-lang binary that will do some background tasks.
Basically it will act as a backend and exposing an API that the Electron app will consume.
So far this is what i get into:
I tried to open the file with the "node way" using child_process but i have fail opening the a sample txt file probably due to path issues.
The Electron API expose a open-file event but it lacks of documentation/example and i don't know if it could be useful.
That's it.
How i open an external file in Electron ?
There are a couple api's you may want to study up on and see which helps you.
fs
The fs module allows you to open files for reading and writing directly.
var fs = require('fs');
fs.readFile(p, 'utf8', function (err, data) {
if (err) return console.log(err);
// data is the contents of the text file we just read
});
path
The path module allows you to build and parse paths in a platform agnostic way.
var path = require('path');
var p = path.join(__dirname, '..', 'game.config');
shell
The shell api is an electron only api that you can use to shell execute a file at a given path, which will use the OS default application to open the file.
const {shell} = require('electron');
// Open a local file in the default app
shell.openItem('c:\\example.txt');
// Open a URL in the default way
shell.openExternal('https://github.com');
child_process
Assuming that your golang binary is an executable then you would use child_process.spawn to call it and communicate with it. This is a node api.
var path = require('path');
var spawn = require('child_process').spawn;
var child = spawn(path.join(__dirname, '..', 'mygoap.exe'), ['game.config', '--debug']);
// attach events, etc.
addon
If your golang binary isn't an executable then you will need to make a native addon wrapper.
Maybe you are looking for this ?
dialog.showOpenDialog refer to: https://www.electronjs.org/docs/api/dialog
If using electron#13.1.0, you can do like this:
const { dialog } = require('electron')
console.log(dialog.showOpenDialog({ properties: ['openFile', 'multiSelections'] }))
dialog.showOpenDialog(function(file_paths){
console.info(file_paths) // => this gives the absolute path of selected files.
})
when the above code is triggered, you can see an "open file dialog" like this (diffrent view style for win/mac/linux)
Electron allows the use of nodejs packages.
In other words, import node packages as if you were in node, e.g.:
var fs = require('fs');
To run the golang binary, you can make use of the child_process module. The documentation is thorough.
Edit: You have to solve the path differences. The open-file event is a client-side event, triggered by the window. Not what you want here.
I was also totally struggling with this issue, and almost seven years later the documentation is quite not clear what's the case with Linux.
So, on Linux it falls under Windows treatment in this regard, which means you have to look into process.argv global in the main processor, the first value in the array is the path that fired the app. The second argument, if one exist, is holding the path that requested the app to be opened. For example, here is the output for my test case:
Array(2)
0: "/opt/Blueprint/b-test"
1: "/home/husayngonzalez/2022-01-20.md"
length: 2
So, when you're creating a new window, you check for the length of process.argv and then if it was more than 1, i.e. = 2 it means you have a path that requested to be opened with your app.
Assuming you got your application packaged with the ability to process those files, and also you set the operating system to request your application to open those.
I know this doesn't exactly meet your specification, but it does cleanly separate your golang binary and Electron application.
The way I have done it is to expose the golang binary as a web service. Like this
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
//TODO: put your call here instead of the Fprintf
fmt.Fprintf(w, "HI there from Go Web Svc. %s", r.URL.Path[1:])
}
func main() {
http.HandleFunc("/api/someMethod", handler)
http.ListenAndServe(":8080", nil)
}
Then from Electron just make ajax calls to the web service with a javascript function. Like this (you could use jQuery, but I find this pure js works fine)
function get(url, responseType) {
return new Promise(function(resolve, reject) {
var request = new XMLHttpRequest();
request.open('GET', url);
request.responseType = responseType;
request.onload = function() {
if (request.status == 200) {
resolve(request.response);
} else {
reject(Error(request.statusText));
}
};
request.onerror = function() {
reject(Error("Network Error"));
};
request.send();
});
With that method you could do something like
get('localhost/api/somemethod', 'text')
.then(function(x){
console.log(x);
}

Resources