Send Data from render process to renderer process in Electron - javascript

I am developing desktop application using Electron,
Scenario is I have 2 BrowserWindow, From FirstBrwoserWindow, I am going to SecondBrowserWindow after button click. i have instantiated SecondBrowserWindow on FirstBrwoserWindow's button click to avoid Object has been destroyed Exception.
As per Electron, if we want to send data between processes we have to use IPC. So actual problem starts here, I am creating SecondBrowserWindow object in FirstBrwoserWindow's renderer file, and for IPC i need to get SecondBrowserWindow object in main process.
How do i get SecondBrowserWindow Object in main.js and use IPC.on there????

The way I've solved this is to pass the data with ipcRenderer from the first window to the main process and then pass it with ipcMain to the second window using BrowserWindow.webContents.send().
It looks kinda like this.
Window 1
...
// Emit an ipc message with your data
ipcRenderer('your-message', { foo: 'bar' });
...
Main process
...
let window1 = new BrowserWindow(...);
let window2 = new BrowserWindow(...);
...
// when ipc message received pass it on to second window object with webContents
ipcMain.on('your-message', (event, payload) => {
window2.webContents.send('your-relayed-message', payload);
});
...
Window 2
...
// when ipc messaged received in second window do what you want with the data
ipcRenderer.on('your-relayed-message', (event, payload) => {
console.log(payload);
});
...

You can do it in more ways but what I recommend you to:
1) when you receive the input to open the 2nd BrowserWindow in the renderer, send a message to main.js
2) from main.js open the 2nd BrowserWindow so you can control it and send message to it in a cleaner way.
In this way, you can close the previous BrowserWindow without errors in communication and you have a more scalable and readable logic for N BrowserWindows.

Related

Service Worker determine number of clients

We've got a SaaS app that's PWA capable and we're using Workbox for the grunt work. Up to now we've been following the tried and trusted recipe of displaying an update is available banner to the users, prompting them to update their web app.
Viewing usage data (via Sentry.io) we've noticed that most users simply seem to ignore the update banner and continue with the version they're on.
So we're looking at trying something different. This is to perform the update automatically when they change the route in the web app (when we know there's an update available).
Testing this shows that it does work. However there's a side-effect, and that is if they've got web app open in multiple tabs, then all the tabs get updated. This could be problematic for users' if they've got an un-saved form open in one of the tabs in the background - they'll potentially loose their work.
This happens during this piece of code:
// app shell page, created lifecycle hook
document.addEventListener('swUpdated', this.SetPwaRegistration, { once: true })
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (this.refreshing) {
return
}
this.refreshing = true
window.location.reload()
})
// app shell page, method in methods collection
SetPwaRegistration (event) {
// call mutation to pass the registration object to Vuex
this.PWA_REGISTRATION_SET({ pwaRegistration: event.detail })
}
// main.js
router.afterEach((to, from) => {
// retrieve the registration object from Vuex
const pwaRegistration = app.$store.getters.pwaRegistration
if (pwaRegistration) {
pwaRegistration.waiting.postMessage('skipWaiting')
}
})
the above code is from our Vue.js app code-base, the this.refreshing is set to false by default in the data property collection.
What I'd like to know if whether it is possible to determine if the Service Worker has only one client under it's control (i.e. the web app is only open in 1 browser tab), and if this is the case, the auto-update can happen without potential issues. If there's more than one client, then we'll display the update banner as usual.
As a brief update to this, I've come across code examples similar to this:
self.clients.matchAll().then(clients => {
const clientCount = clients.length
// store count of client in Vuex
})
Which looks like an option (i.e. count how many clients there are, store this in Vuex store), I'm just not sure where it should be used.
If you want to orchestrate the reload from entirely within the service worker, you can effectively do that by using the WindowClient interface to programmatically navigate to whatever the current URL is, which is roughly equivalent to a reload.
The two things to keep in in mind that navigate() will only work on WindowClients (not Workers) and that you can only call it if the service worker controls the WindowClient.
Putting that together, here's something to try:
// This can be run, e.g., in a `message` handler in your SW:
self.clients.matchAll({
// These options are actually the defaults, but just
// to be explicit:
includeUncontrolled: false,
type: 'window',
}).then((clients) => {
if (clients.length === 1) {
clients[0].navigate(clients[0].url);
}
})

How to get electron autoUpdater instance inside Angular 9 app?

So I have an application that can run in electron, and it can auto update. The update server is up and can be used.
The problem is, my updater process runs in electron main process, not inside Angular app. I want the users to be able to check, and manually update the application themselves, which I think we need to implement main process instance inside my Angular app.
Here is how I call the updater inside main.js.
if (!require('electron-is-dev')) {
require('./updater');
} else {
console.log('App is running in dev environment')
}
And here is a slice of my updater.js
const { app, dialog, autoUpdater } = require('electron');
const server = 'http://my-release-server.com:5000';
const url = `${server}/update/${process.platform}/${app.getVersion()}`;
console.log(`[autoUpdater] init ${url}`);
autoUpdater.setFeedURL({ url });
setInterval(() => {
autoUpdater.checkForUpdates();
}, 30000)
autoUpdater.on('checking-for-update', () => {
console.log('[autoUpdater] Checking for updates');
});
// And other events handler.
My autoUpdater works like charm when I am using default setup, which runs the updater inside the main electron process, but I can not show or inform the user about the updating process.
So, is there any solution or right way to implement this thing? I learnt in some article to use electron remote which handled inter process communication, can I use that? Can I use angular service to handle these things?
Thank you.

Pass data between components in a new tab

I have an object in one component and I want to pass it to another component. The problem is that this other component will open in a new tab.
I tried to use the data service strategy, but when the tab opens, the data service object comes undefined.
I thought about using the querys params and passing in the url. But the object is very complex
My data service:
#Injectable({providedIn: 'root'})
export class DataService {
private anime: Anime;
constructor() { }
setAnime(anime: Anime) {
this.anime = anime;
}
getAnime() {
return this.anime;
}
}
Setting object in data service:
goToDetailsByService(anime: Anime) {
this.dataService.setAnime(anime);
//this.router.navigateByUrl('/details');
window.open('/details');
}
Getting the anime object via service data:
ngOnInit(): void {
console.log(this.dataService.getAnime());
this.anime = this.dataService.getAnime()
}
When accessing the details component via navigate router works
I think there are two ways to do it. The first one is localStorage , the second one is PostMessage
localStorage
we can use localstorage because storage can be read across windows, and there is a storage event fire when you write something to storage.
Here is the code example.
// parent window
localStorage.setItem("EVENT.PUB", JSON.stringify(anime));
// child widnow
window.addEventListener('storage', function(event) {
console.log(event);
const anime = JSON.parse(event.newValue);
}, false);
postMessage
The window.postMessage() method safely enables communication between Window objects; e.g., between a page and a pop-up that it spawned, or between a page and an iframe embedded within it.
Here is the code example.
// parent window
const detailPage = window.open('/details');
detailPage.postMessage(anime, '*');
// important notice: anime should be object that can be serialize
// otherwise error will happen when execute this function.
// child window
window.addEventListener('message', (event) => {
// get out the message
console.log(event.data);
// and you can even send message back to parent window too.
event.source.postMessage('Got it!', event.origin);
}, false);
I think the easiest way to do it would be to use the browsers localStorage since that will keep the applicaton state between tabs. When you open a new tab the two web pages are seperate and the state doesn't carry over.
So using localStorage you can do..
SET
goToDetailsByService(anime: Anime) {
localStorage.setItem('anime', JSON.stringify(anime));
window.open('/details');
}
GET
ngOnInit(): void {
this.anime = JSON.parse(localStorage.getItem('anime'));
// here you can choose to keep it in the localStorage or remove it as shown below
localStorage.removeItem('anime');
}

Methods no longer exist when sending to/from ipcRenderer

I have the following in my render process:
class Component {
findClosestComponent(c) { /* Do stuff */ }
}
class Item extends Component {
download() {
this.myProperty = 123;
ipcRenderer.send('download-game', this);
ipcRenderer.on('download-complete', (evt, dl) => {
console.log(dl.myProperty);
dl.findClosestComponent(GameCard);
});
}
}
I then have this in my main process:
ipcMain.on('download-game', (evt, dl) => {
/* Download the file the send back the object */
evt.sender.send('download-complete', dl);
});
When I execute the download() method, it sends the current object to the main process, and then the main process sends it back. When I try to access the method findClosestComponent() it is no longer there. However, when I try to access the property myProperty it does exist and prints out.
How can I send the object to the main process then get it back and access the methods?
Electron's ipcMain and ipcRenderer modules serialize the message to JSON before sending it, so if you want to get a better idea of what actually gets sent between processes put the message through JSON.stringify(). Functions/methods and prototype chains won't make it across the process boundry, if you want that to work you'll have to implement your own serialization/deserialization scheme.

Getting "simple" remote objects in electron

When I call a remote function in electron, the results are always littered with getters and setters. I think I understand why this is, but I'd like to be able to get simple objects.
My current solution is this:
import {remote} from 'electron'
const bridge = remote.require('bridge') // This is a little script I create for talking to a python process. Over stdin/stdout i.e. pipes.
bridge.on('fileTreeUpdate', (data) => {
myDataStore.update(JSON.parse(JSON.stringify(data.tree))
})
Is there a more elegant way to do this?
As mentioned in the comments, I would suggest to require 'bridge' directly in the Renderer; this way you don't need to pass its data between the Main and Renderer processes at all.
Having said that, if another requirement made it necessary to load the 'bridge' module in the Main process only, while still accessing its data from the Renderer, I'd suggest to use Electron's 'ipc' modules.
For instance, in your main process you could do something like this (assuming the variable win is a reference to the window of the Renderer process you wish to communicate with):
import bridge from 'bridge'
bridge.on('fileTreeUpdate', data => {
// assuming win {BrowserWindow} has already been initialized
win.webContents.send('bridge:fileTreeUpdate', data)
})
And in your Renderer processes (associated with win):
import ipcRenderer from 'electron'
ipcRenderer.on('bridge:fileTreeUpdate', (event, data) => {
myDataStore.update(data.tree)
})

Categories

Resources