Using ipcRenderer with contextBridge - electron

I want use contextBridge in my preload.js in electron. Every work as usual when when i send data from the ipcrender-to-ipcmain, but problem arises when i send response from ipcmain-to-ipcrenderer. I have gone through some of there solutions here but i still get the same results.
This is my mainjs code
ipcMain.on('users:load', async function () {
const users = await User.getAll();
win.webContents.send('users:get', users);
});
(async function () {
try {
const rows = await User.getAll();
console.log(rows.length);
win.webContents.send('users:total', rows.length);
} catch (error) {
return console.log(error);
}
})();
This is my preload.js
contextBridge.exposeInMainWorld('UserApi', {
loginuser: (user) => ipcRenderer.invoke('user:login', user),
totalusers: () =>
ipcRenderer.on('user:total', (e, res) => {
return res;
}),
loadUsers: () => {
ipcRenderer.send('users:load', () => {
ipcRenderer.on('users:get', (e, users) => {
return users;
});
});
},
});
In my preload.js ipcRenderer.invoke works perfectly but ipcRenderer.on does not. Any help will be appreciated.

Related

Admob reward ads don't work on ios on Expo App

On App.js I have initialized AdMobRewarded as following:
if (Platform.OS === 'ios') {
AdMobRewarded.setAdUnitID('ca-app-pub-xxx/xxx');
} else {
AdMobRewarded.setAdUnitID('ca-app-pub-xxx/xxx');
}
And here is the class:
export default class App extends React.Component {
state = {
fontsLoaded: false,
};
render() {
const { fontsLoaded } = this.state;
if (!fontsLoaded) {
return (
<AppLoading
startAsync={fetchFonts}
onFinish={() => this.setState({ fontsLoaded: true })}
/>
);
}
return (
<Provider store={store}>
<AppContainer
ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef);
}}
/>
<CommonComponents />
</Provider>
);
}
}
Inside the CommonComponents I have put the listener for AdMobRewarded:
useEffect(() => {
AdMobRewarded.addEventListener('rewardedVideoDidRewardUser', () => {
setState({
hintModalVisible: true,
adIsLoading: false,
mainMenuVisible: false,
});
});
return () => {
AdMobRewarded.removeAllListeners();
};
}, []);
setState is actually not React setState, it's a redux action I have implemented:
const setStateAction = (obj, sceneName) => {
const type = sceneName ? `${sceneName}_SET_STATE` : 'SET_STATE';
return { ...obj, type };
};
Without the rewardedVideoDidRewardUser listener, calling setState does open the Modal and everything is fine.
hintModalVisible is used for Modal isVisible prop, which opens and closes the Modal.
On Android everything works as expected, but there is a strange behavior on iOS. The ad shows for a second and automatically closes, and the Hint Modal doesn't open.
Here is the function that requests and shows the ad. It is present in all screens of the app:
showHint = async () => {
const { setState } = this.props;
try {
setState({
mainMenuVisible: false,
});
await AdMobRewarded.requestAdAsync();
await AdMobRewarded.showAdAsync();
} catch (e) {
setState({
hintModalVisible: true,
mainMenuVisible: false,
});
}
};
It's an open source project, so you can the code here
The problem was with React Native Modal.
setState({
hintModalVisible: true,
adIsLoading: false,
mainMenuVisible: false,
});
This block of code should have closed the main menu modal and open the hint modal. But it seems that on IOS you cannot do this simultaneously. So this is how I handled it.
useEffect(() => {
AdMobRewarded.addEventListener('rewardedVideoDidRewardUser', () => {
setTimeout(() => {
setState({
hintModalVisible: true,
});
}, 1000);
});
return () => {
AdMobRewarded.removeAllListeners();
};
}, []);
And took the closing of main menu modal in the ad requesting function:
const requestHint = useCallback(async () => {
try {
setState({
mainMenuVisible: false,
});
await AdMobRewarded.requestAdAsync();
await AdMobRewarded.showAdAsync();
} catch (e) {
setState({
mainMenuVisible: false,
});
setTimeout(() => {
setState({
hintModalVisible: true,
});
}, 500);
}
}, [setState, hintModalVisible]);
So this is not concerned to the Admob Rewarded. It is more a React Native Modal bug. https://github.com/react-native-community/react-native-modal/issues/192

Puppeteer in Electron: Error: Passed function is not well-serializable

Trying to come up with a GUI for Puppeteer project.
I thought about using Electron, but run into error:
Error: Passed function is not well-serializable!
when running Puppeteer functions like:
await page.waitForSelector('.modal', { visible: true });
I found a proper way to serialize when dealing with page.evaluate() but how to proceed in case of page.waitForSelector()?
Is there a work around for Puppeter's API functions to be properly serialized when required?
EDIT
I decided to rewrite
await page.waitForSelector('.modal', { visible: true });
using page.evaluate, here is the code:
// first recreate waitForSelector
const awaitSelector = async (selector) => {
return await new Promise(resolve => {
const selectorInterval = setInterval(() => {
if ($(selector).is(':visible')) {
console.log(`${selector} visible`);
resolve();
clearInterval(selectorInterval);
};
}, 1000);
});
}
and later call that function using page.evaluate():
// remember to pass over selector's name, in this case it is ".modal"
await page.evaluate('(' + awaitSelector.toString() + ')(".modal");');
Firstly context:
Generally you can not run puppeteer from browser environment, it works solely in nodejs. Electron provides 2 processes renderer and main. Whenever you want to use node you have to do it in main one.
About communication between both procesess you can read in docs, there are many ways of handling it. From what I know the best practice is to declare it in preload and use ipc bridge. Other solutions violate contextIsolation rule.
I was w wandering aound from one problem to another: like not serializable function, require not defined and many others.
Finally I rewrote everything from scratch and it works here's my solution:
main.js
const { app, BrowserWindow } = require('electron')
const path = require('path')
const { ipcMain } = require('electron');
const puppeteer = require("puppeteer");
function createWindow() {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
contextIsolation: true,
},
})
ipcMain.handle('ping', async () => {
await checkPup()
})
async function checkPup() {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
page
.waitForSelector('h1', { visible: true })
.then(() => {
console.log('got it')
});
const [button] = await page.$x("//button[contains(., 'Create account')]");
if (button) {
console.log('button: ', button)
await button.click();
await page.screenshot({ path: 'tinder.png' });
const [button] = await page.$x("//button[contains(., 'Create account')]");
if (button) {
console.log('button: ', button)
await button.click();
await page.screenshot({ path: 'tinder.png' });
}
}
await browser.close();
}
// and load the index.html of the app.
mainWindow.loadFile('index.html')
// Open the DevTools.
// mainWindow.webContents.openDevTools()
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
// Attach listener in the main process with the given ID
ipcMain.on('request-mainprocess-action', (event, arg) => {
// Displays the object sent from the renderer process:
//{
// message: "Hi",
// someData: "Let's go"
//}
console.log(
arg
);
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
preload.js
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
const { contextBridge, ipcRenderer } = require('electron')
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}
for (const type of ['chrome', 'node', 'electron']) {
replaceText(`${type}-version`, process.versions[type])
}
})
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
ping: () => ipcRenderer.invoke('ping'),
// we can also expose variables, not just functions
})
renderer.js
const information = document.getElementById('info')
const btn = document.getElementById('btn')
information.innerText = `This app is using Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), and Electron (v${versions.electron()})`
btn.addEventListener('click', () => {
console.log('habad!!!!!')
func()
})
const func = async () => {
const response = await window.versions.ping()
information.innerText = response;
console.log(response) // prints out 'pong'
}
Sorry for a little bit of a mess I hope it will help someone maybe finding solutions to some other problems

PWA Add to Home Screen not display in android chrome when ask for user permissions

PWA app add to home screen install banner not display on a chrome browser for android when it asks for user permissions like geolocation and notification
I also tried to implement this functionality with beforeinstallprompt event but it's not fired
ServiceWorker.js
self.importScripts('/js/push-notification/push-notifications-controller.js');
(function () {
'use strict';
const pushNotificationTitle = 'FM Service';
// Update 'version' if you need to refresh the cache
var version = '~1.0.0.1129^';
var offlineUrl = "/offline.html";
// Store core files in a cache (including a page to display when offline)
function updateStaticCache() {
return caches.open(version)
.then(function (cache) {
return cache.addAll([
offlineUrl,
]);
});
}
function addToCache(request, response) {
if (!response.ok && response.type !== 'opaque')
return;
var copy = response.clone();
caches.open(version)
.then(function (cache) {
cache.put(request, copy);
});
}
function serveOfflineImage(request) {
if (request.headers.get('Accept').indexOf('image') !== -1) {
return new Response('<svg role="img" aria-labelledby="offline-title" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg"><title id="offline-title">Offline</title><g fill="none" fill-rule="evenodd"><path fill="#D8D8D8" d="M0 0h400v300H0z"/><text fill="#9B9B9B" font-family="Helvetica Neue,Arial,Helvetica,sans-serif" font-size="72" font-weight="bold"><tspan x="93" y="172">offline</tspan></text></g></svg>', { headers: { 'Content-Type': 'image/svg+xml' } });
}
}
self.addEventListener('install', function (event) {
event.waitUntil(updateStaticCache());
self.skipWaiting();
});
self.addEventListener('activate', function (event) {
event.waitUntil(
caches.keys()
.then(function (keys) {
// Remove caches whose name is no longer valid
return Promise.all(keys
.filter(function (key) {
return key.indexOf(version) !== 0;
})
.map(function (key) {
return caches.delete(key);
})
);
})
);
});
self.addEventListener('message', function (event) {
if (event.data.action === 'skipWaiting') {
self.skipWaiting();
}
});
self.addEventListener('fetch', function (event) {
var request = event.request;
// Always fetch non-GET requests from the network
if (request.method !== 'GET' || request.url.match(/\/browserLink/ig)) {
event.respondWith(
fetch(request)
.catch(function () {
return caches.match(offlineUrl);
})
);
return;
}
// For HTML requests, try the network first, fall back to the cache, finally the offline page
if (request.headers.get('Accept').indexOf('text/html') !== -1) {
event.respondWith(
fetch(request)
.then(function (response) {
// Stash a copy of this page in the cache
addToCache(request, response);
return response;
})
.catch(function () {
return caches.match(request)
.then(function (response) {
return response || caches.match(offlineUrl);
});
})
);
return;
}
// cache first for fingerprinted resources
if (request.url.match(/(\?|&)v=/ig)) {
event.respondWith(
caches.match(request)
.then(function (response) {
return response || fetch(request)
.then(function (response) {
addToCache(request, response);
return response || serveOfflineImage(request);
})
.catch(function () {
return serveOfflineImage(request);
});
})
);
return;
}
// network first for non-fingerprinted resources
event.respondWith(
fetch(request)
.then(function (response) {
// Stash a copy of this page in the cache
addToCache(request, response);
return response;
})
.catch(function () {
return caches.match(request)
.then(function (response) {
return response || serveOfflineImage(request);
})
.catch(function () {
return serveOfflineImage(request);
});
})
);
});
//push notification events
self.addEventListener('push', function (event) {
event.waitUntil(self.registration.showNotification(pushNotificationTitle, {
body: event.data.text(),
icon: '/img/fm-badge-192x192.png'
}));
});
self.addEventListener('notificationclick', function (event) {
console.log('[Service Worker] Notification click Received.');
event.notification.close();
});
self.addEventListener('pushsubscriptionchange', function (event) {
const handlePushSubscriptionChangePromise = Promise.resolve();
if (event.oldSubscription) {
handlePushSubscriptionChangePromise = handlePushSubscriptionChangePromise.then(function () {
return PushNotificationsController.discardPushSubscription(event.oldSubscription);
});
}
if (event.newSubscription) {
handlePushSubscriptionChangePromise = handlePushSubscriptionChangePromise.then(function () {
return PushNotificationsController.storePushSubscription(event.newSubscription);
});
}
if (!event.newSubscription) {
handlePushSubscriptionChangePromise = handlePushSubscriptionChangePromise.then(function () {
return PushNotificationsController.retrievePublicKey().then(function (applicationServerPublicKey) {
return pushServiceWorkerRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerPublicKey
}).then(function (pushSubscription) {
return PushNotificationsController.storePushSubscription(pushSubscription);
});
});
});
}
event.waitUntil(handlePushSubscriptionChangePromise);
});
self.addEventListener('notificationclick', function (event) {
event.notification.close();
});
// end push notification events
})();
Index.html
<script nws-csp-add-nonce="true">
let newWorker;
// The click event on the pop up notification
document.getElementById('reload').addEventListener('click', function () {
localStorage.setItem("IsNewVersionAvailable", false);
window.location.reload();
});
'serviceWorker' in navigator && navigator.serviceWorker.register('/serviceworker.js').then(reg => {
pushServiceWorkerRegistration = reg;
initializeUIState();
writeToConsole('Push Service Worker has been registered successfully');
subscribeForPushNotifications();
writeToConsole('Subscribe Notification Successfully');
reg.installing; // the installing worker, or undefined
reg.waiting; // the waiting worker, or undefined
reg.active; // the active worker, or undefined
reg.addEventListener('updatefound', () => {
// A wild service worker has appeared in reg.installing!
newWorker = reg.installing;
newWorker.state;
// "installing" - the install event has fired, but not yet complete
// "installed" - install complete
// "activating" - the activate event has fired, but not yet complete
// "activated" - fully active
// "redundant" - discarded. Either failed install, or it's been
// replaced by a newer version
newWorker.addEventListener('statechange', () => {
switch (newWorker.state) {
case 'installed':
// There is a new service worker available, show the notification
if (navigator.serviceWorker.controller) {
localStorage.setItem("IsNewVersionAvailable", true);
$('#msg-newversion').removeClass('d-none');
}
break;
}
});
});
});
var refreshing;
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (refreshing) return;
window.location.reload();
refreshing = true;
});
</script>
Can you guys please let me know your feedback on this
Thank
Sahadev Dodiya

How to send search text to findInPage in Electron

I try using contents.findInPage.
I have code in index.js:
const { webContents } = require('electron')
webContents.on('found-in-page', (event, result) => {
if (result.finalUpdate) webContents.stopFindInPage('clearSelection')
})
const requestId = webContents.findInPage('api')
console.log(requestId)
And code in component:
searchText(value){
this.query = value;
if (this.query.length > 0) {
ipcRenderer.send('api', this.query);
}
}
I wrote this code on the example of this answer.
But function find not work. I do not understand how I can send the text to be searched and the word to be searched.
How I can use function findInPage ?
sorry my answer to the other question wasn't clear enough (it was 2 years ago! I don't remember it that well but I'll give it a shot)
This is the documentation for webcontents and IPCMain
Here's what I have in my main.development.js (globals for the mainWindow and ipc communication):
mainWindow.on('focus', () => {
globalShortcut.register('CmdorCtrl+F', () => {
mainWindow.webContents.send('find_request', '');
});
});
mainWindow.webContents.on('found-in-page', (event, result) => {
if (result.finalUpdate) {
mainWindow.webContents.stopFindInPage('keepSelection');
}
});
ipcMain.on('search-text', (event, arg) => {
mainWindow.webContents.findInPage(arg);
});
mainWindow.on('blur', () => {
globalShortcut.unregister('CmdorCtrl+F');
});
Then I made an ipc listener for CmdorCtrl+F:
ipcRenderer.on('find_request', () => {
const modalbox = document.getElementById('modalbox');
if (modalbox.style.display === 'block') {
modalbox.style.display = 'none';
} else {
modalbox.style.display = 'block';
}
});
Then I made a modal searchbox:
const searchBox = (
<div
id="modalbox"
style={{ display: 'none', position: 'fixed', zIndex: 1 }}
><input type="text" onChange={Calls.searchPage} />
</div>);
The onchange sends the input text to the ipc listener:
static searchPage(event) {
ipcRenderer.send('search-text', event.target.value);
}
I hope this is enough for you to get it fixed :)

electron auto-update on click

I'm trying to make my electron app auto-updatable, and after searching on google I found nice guide that works. The only problem is that I want to create a button for when update is downloaded so that the user can decide when he wants to update and restart the app. So far I was able to put this code together
renderer.js
const electron = require('electron')
const ipcRenderer = electron.ipcRenderer
let lastMsgId = 0
window.quitAndInstall = function () {
electron.remote.autoUpdater.quitAndInstall()
}
ipcRenderer.on('console', (event, consoleMsg) => {
console.log(consoleMsg)
})
ipcRenderer.on('message', (event, data) => {
showMessage(data.msg, data.hide, data.replaceAll)
})
function showMessage(message, hide = true, replaceAll = false) {
const messagesContainer = document.querySelector('.messages-container')
const msgId = lastMsgId++ + 1
const msgTemplate = `<div id="${msgId}" class="alert alert-info alert-info-message animated fadeIn">${message}</div>`
if (replaceAll) {
messagesContainer.innerHTML = msgTemplate
} else {
messagesContainer.insertAdjacentHTML('afterbegin', msgTemplate)
}
if (hide) {
setTimeout(() => {
const msgEl = document.getElementById(msgId)
msgEl.classList.remove('fadeIn')
msgEl.classList.add('fadeOut')
}, 4000)
}
}
and this is my index.js where messages are storred
const electron = require('electron');
const {autoUpdater} = require('electron-updater');
const log = require('electron-log');
const appVersion = require('./package.json').version
// configure logging
autoUpdater.logger = log;
autoUpdater.logger.transports.file.level = 'info';
log.info('App starting...');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({
width: 1020,
height: 800,
});
mainWindow.loadURL('file://' +__dirname + '/public/index.html');
// Open the DevTools.
//mainWindow.webContents.openDevTools();
mainWindow.on('closed', function() {
mainWindow = null;
});
}
app.on('ready', createWindow);
// Quit when all windows are closed.
app.on('window-all-closed', function() {
app.quit();
});
app.on('activate', function() {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow();
}
});
//-------------------------------------------------------------------
// Auto updates
//-------------------------------------------------------------------
const sendStatusToWindow = (text) => {
log.info(text);
if (mainWindow) {
mainWindow.webContents.send('console', `App version: ${appVersion}`)
mainWindow.webContents.send('message', { msg: `App version: ${appVersion}` })
}
};
autoUpdater.on('error', (ev, err) => {
mainWindow.webContents.send('message', { msg: `Error: ${err}` })
})
autoUpdater.once('checking-for-update', (ev, err) => {
mainWindow.webContents.send('message', { msg: 'Checking for updates' })
})
autoUpdater.once('update-available', (ev, err) => {
mainWindow.webContents.send('message', { msg: 'Update available. Downloading ⌛️', hide: false })
})
autoUpdater.once('update-not-available', (ev, err) => {
mainWindow.webContents.send('message', { msg: 'Update not available' })
})
autoUpdater.once('update-downloaded', (ev, err) => {
const msg = 'Update downloaded - <button onclick="quitAndInstall()">Restart</button>'
mainWindow.webContents.send('message', { msg, hide: false, replaceAll: true })
})
autoUpdater.checkForUpdates()
As you can see I added a button to call for function but it doesnt work. When I press the button nothing happens. If I remove button and just say auto.updater.quitAndInstall() it works. It auto close window and install new version. What am I missing?
I think it's that electron.remote.autoUpdater.quitAndInstall() doesn't work when run in the renderer.
In the docs it doesn't say anything against running it in the renderer process but I think sending a message back to the main process to run the quitAndInstall function would work.
Inside of the quitAndInstall function put this instead:
ipcRenderer.send('asynchronous-message', 'quitAndInstall');
Then in the main process put:
electron.ipcMain.on('asynchronous-message', function (evt, message) {
if (message == 'quitAndInstall') {
autoUpdater.quitAndInstall();
}
});

Resources