Electron IPC behaving inconsistently - electron

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.

Related

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(...);
});
}

skipWaiting not working in Service Worker

As part of writing ServiceWorker code in typescript, I am attaching install handler and then calling skipWaiting inside it:
self.addEventListener('install', this.onInstall);
protected onInstall() {
console.log('onInstall called');
workbox.skipWaiting();
}
With this, new ServiceWorker is still in waiting state and skipWaiting doesn't seem to be working. onInstall handler gets called perfectly fine.
Is typescript implementation artifact causing problem here? Like I should write something like this?
self.addEventListener('install', event => {
self.skipWaiting();
});
Or self.skipWaiting() doesn't work the same way as workbox.skipWaiting()?
Interestingly, moving workbox.skipWaiting() to ctor where install handler is being attached fixes the issue.

Difference between .then() and .whenCompleted() methods when working with Futures?

I'm exploring Futures in Dart, and I'm confused about these two methods that Future offers, .then() and .whenCompleted(). What's the main difference between them?
Lets say I want to read a .txt using .readAsString(), I would do it like this:
void main(){
File file = new File('text.txt');
Future content = file.readAsString();
content.then((data) {
print(content);
});
}
So .then() is like a callback that fires a function once the Future is completed.
But I see there is also .whenComplete() that can also fire a function once Future completes. Something like this :
void main(){
File file = new File('text.txt');
Future content = file.readAsString();
content.whenComplete(() {
print("Completed");
});
}
The difference I see here is that .then() has access to data that was returned!
What is .whenCompleted() used for? When should we choose one over the other?
.whenComplete will fire a function either when the Future completes with an error or not, instead .then will fire a function after the Future completes without an error.
Quote from the .whenComplete API DOC
This is the asynchronous equivalent of a "finally" block.
then runs if the future completes successfully.
catchError runs if the future fails.
whenComplete runs regardless of the future completed with a value or with an error.
Here's the basic flow:
someFuture().then((value) {
print('Future finished successfully i.e. without error');
}).catchError((error) {
print('Future finished with error');
}).whenComplete(() {
print('Either of then or catchError has run at this point');
});
.whenComplete = The function inside .whenComplete is called when this future completes, whether it does so with a value or with an error.
.then = Returns a new Future which is completed with the result of the call to onValue (if this future completes with a value) or to onError (if this future completes with an error)
Read detail on API DOC
whenComplete then

Electron - Main process vs Renderer Process

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!

Console application - StringDecoder stdin

The following or similar was shown for terminal input, however terminating input with ctl-d is not good. Is there another way to exit from this "loop"?
import "dart:io";
void main() {
stdout.write("Enter Data : ");
new StringDecoder().bind(stdin).listen((String sInput){});
//// Do something with sInput ............
}
You can terminate a dart program by running the exit method when using dart:io
void exit(int status)
Exit the Dart VM process immediately with the given status code.
This does not wait for any asynchronous operations to terminate.
Using exit is therefore very likely to lose data.
From the docs
That code would go inside a check in the event handler in listen
A few options come to mind. First, you could use takeWhile() to set a 'done' condition:
new StringDecoder().bind(stdin)
.takeWhile((s) => s.trim() != 'exit')
.listen((sInput) {
That will use the same onDone handler (if one is set) when the user inputs the EOF character or types exit followed by the enter key. You can have more flexibility by cancelling the subscription with cancel():
void main() {
stdout.write("Enter Data : ");
var sub;
sub = new StringDecoder().bind(stdin).listen((String sInput) {
if (sInput.trim() == 'exit' || sInput.trim() == 'bye')
sub.cancel();
// Do something with sInput ............
});
Cancelling the subscription doesn't close the Stream, so any onDone handler isn't called.
Of course, if you've got nothing left to do, you can always terminate with exit(0) [1].

Resources