Send data to renderer process - node.js

I am using "electron": "^4.1.4" and I am getting my data via knex from an sqlite3 db.
I tried to send the data via an ipcMain call. Find below my main.js
const {
app,
BrowserWindow,
ipcMain
} = require('electron')
// require configuration file
require('dotenv').config()
// database
const knex = require('./config/database')
// services
const {
ScheduledContent
} = require('./service/ScheduledContent')
require('electron-reload')(__dirname);
let mainWindow
const contentService = new ScheduledContent(knex)
ipcMain.on('content-edit', async (e, args) => {
console.log("content-edit - Backend");
let res = await contentService.getAllContent()
event.sender.send('content-edit-res', res)
})
function createWindow() {
mainWindow = new BrowserWindow({
width: 1000,
height: 600,
webPreferences: {
nodeIntegration: true
}
})
// and load the index.html of the app.
mainWindow.loadFile('./public/index.html')
mainWindow.on('closed', function () {
mainWindow = null
})
}
app.on('ready', createWindow)
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
app.on('activate', function () {
if (mainWindow === null) createWindow()
})
My renderer.js looks like the following:
const {
ipcRenderer
} = require('electron')
console.log("loaded renderer.js 123")
document.addEventListener('DOMContentLoaded', pageLoaded);
function pageLoaded() {
console.log('The page is loaded');
ipcRenderer.send('content-edit', 'x')
}
ipcRenderer.on('content-edit-res', (event, arg) => {
console.log(arg)
})
I tried to get the data in the frontend when the page is loaded, then I wanted to append it to my <table id="table1">-tag within my index.html
However, I do not get any data in the frontend.
Any suggestions what I am doing wrong?
I appreciate your replies!

As for now, the problem with code...
// event in receive, not e...
ipcMain.on('content-edit', async (event, args) => {
console.log("content-edit - Backend");
let res = await contentService.getAllContent()
event.sender.send('content-edit-res', res)
})
But, as additional information, you also can send data to renderer process with webContents of window.
const BrowserWindow = electron.BrowserWindow;
let win = new BrowserWindow(<your configs>);
win.webContents.send('message', 'Hello from main!');
You can find useful information here

Related

Electron security (is my code is enough?)

I know about #reZach's answer about the electron security, and I know about his electron secured template.. I tried to use it, but it's just too much for today's me, I don't know anything about webpack, I know react not completely etc, the overall complexity of the template is too much for the current me. So I wanted to ask (maybe my code is good enough to be secured and safe?). I made a trading app, so I cannot give it to people not being sure that it is safe to use. I used just vanilla JS, express, localtunnel for receiving webhooks, and several modules. In short here's the code:
main.js
const { app, BrowserWindow, ipcMain } = require('electron');
const fs = require('fs');
const path = require('path');
const Store = require('electron-store');
const store = new Store();
const CryptoJS = require('crypto-js');
const axios = require('axios');
const WebSocket = require('ws');
const express = require('express');
const bodyParser = require('body-parser');
const localtunnel = require('localtunnel');
(async () => {
const tunnel = await localtunnel({ port: 3000, subdomain: 'mysubdomain' });
// the assigned public url for your tunnel
// i.e. https://abcdefgjhij.localtunnel.me
console.log(tunnel.url);
tunnel.on('close', () => {
// tunnels are closed
console.log('connection closed');
});
})();
const server = express();
const PORT = 3000;
let webhookMsg;
server.use(bodyParser.text());
server.listen(PORT, () => console.log(`Server running on port ${PORT}`));
server.get('/', function (req, res) {
res.send('Server is ready!');
});
server.post('/', (req, res) => {
webhookMsg = req.body;
res.sendStatus(200);
});
let win;
async function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 500,
height: 550,
titleBarStyle: 'hidden',
webPreferences: {
nodeIntegration: false, // is default value after Electron v5
contextIsolation: true, // protect against prototype pollution
enableRemoteModule: false, // turn off remote
preload: path.join(app.getAppPath(), 'preload.js'), // use a preload script
backgroundThrottling: false,
},
});
// Load app
win.loadFile(path.join(__dirname, './index.html'));
win.setAlwaysOnTop(true, 'screen');
// win.removeMenu();
// rest of code..
}
app.on('ready', createWindow);
// NEXT IS MY APP MAIN LOGICS ON RECEIVING MESSAGES AND SENDING API REQUEST TO AN EXCHANGE PLATFORM AND GIVING BACK THE ANSWERS TO THE 'front-end script js file'
//for example
ipcMain.on('loadPrefs', () => {
const allPrefs = store.get('prefs');
win.webContents.send('prefs', allPrefs);
});
ipcMain.on('giveBalance', async e => {
const [apiKey, secret] = fs.readFileSync('api.txt').toString('UTF8').replace(/\r/g, '').split('\n');
const timestamp = Date.now().toString();
const params = {
api_key: apiKey,
timestamp: timestamp,
};
let orderedParams = '';
Object.keys(params)
.sort()
.forEach(function (key) {
orderedParams += key + '=' + params[key] + '&';
});
orderedParams = orderedParams.substring(0, orderedParams.length - 1);
var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, secret);
hmac.update(orderedParams);
const sign = hmac.finalize().toString(CryptoJS.enc.Hex);
try {
const res = await axios.get(`https://api.bybit.com/v2/private/wallet/balance?&api_key=${apiKey}&sign=${sign}&timestamp=${timestamp}`);
// console.log(res.data);
const responseObj = res.data.result.USDT.equity;
win.webContents.send('balance', { responseObj });
} catch (error) {
// console.log(error);
}
});
preload.js
const { contextBridge, ipcRenderer } = require('electron');
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld('api', {
send: (channel, data) => {
ipcRenderer.removeAllListeners(channel);
let validChannels = [
// myChannelsList
];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
ipcRenderer.removeAllListeners(channel);
let validChannels = [
// myResponseChannelsList
];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
},
});
In short, I just copied this reZach's example from this answer and added my JS logics to it.
Could you please tell is it secured and safe to use, I just worry that somebody can get the user's api keys for example and take their money away:) Thank you

Wait for an answer from Electron synchronously

I'm trying to make a desktop bybit trading app... and I can't figure it out how to make the code wait for the response from the main script..... I need to wait for a response with the needed info for example like wallet balance. Instead the code runs asynchronously and I the var I need it to render is undefined.... I found the article about the vulnerability of nodeIntegration: true from the author of electron security or smth. So I did everything like he said... but now I can't "pause" the code for the data to receive and render.... here's the code
main.js
const { app, BrowserWindow, ipcMain, Menu } = require('electron');
const path = require('path');
const CryptoJS = require('crypto-js');
const axios = require('axios');
let win;
async function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 385,
height: 200,
titleBarStyle: 'hidden',
webPreferences: {
nodeIntegration: false, // is default value after Electron v5
contextIsolation: true, // protect against prototype pollution
enableRemoteModule: false, // turn off remote
preload: path.join(app.getAppPath(), 'preload.js'), // use a preload script
backgroundThrottling: false,
},
});
// Load app
win.loadFile(path.join(__dirname, './index.html'));
win.setAlwaysOnTop(true, 'screen');
// win.removeMenu();
// rest of code..
}
app.on('ready', createWindow);
ipcMain.on('giveBalance', async e => {
const apiKey = '';
const secret = '';
const timestamp = Date.now().toString();
const params = {
api_key: apiKey,
timestamp: timestamp,
};
let orderedParams = '';
Object.keys(params)
.sort()
.forEach(function (key) {
orderedParams += key + '=' + params[key] + '&';
});
orderedParams = orderedParams.substring(0, orderedParams.length - 1);
var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, secret);
hmac.update(orderedParams);
const sign = hmac.finalize().toString(CryptoJS.enc.Hex);
const res = await axios.get(`https://api.bybit.com/v2/private/wallet/balance?&api_key=${apiKey}&sign=${sign}&timestamp=${timestamp}`);
let responseObj = res.data.result.USDT.equity.toFixed(2);
console.log(res.data.result.USDT.equity.toFixed(2));
win.webContents.send('balance', { responseObj });
});
preload.js
const { contextBridge, ipcRenderer } = require('electron');
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld('api', {
send: (channel, data) => {
// whitelist channels
let validChannels = ['giveBalance', 'givePosition'];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
let validChannels = ['balance', 'position'];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
},
});
script.js
const getBalance = () => {
window.api.send('giveBalance');
const res = window.api.receive('balance', data => {
console.log('hi');
return data.responseObj;
});
const myBalance = '$' + res;
document.querySelector('.myBalance').textContent = `My Balance: ${myBalance}`;
console.log('not in time');
};
getBalance();
I need to get the balance in the scipt.js, to stop the code untill the res var has received the data... but all of the code gets executed right away and only then it receives a message... thank you..
if I change the part in preload.js to this
changing .on to .sendSync
receive: (channel, func) => {
let validChannels = ['balance', 'position'];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.sendSync(channel, (event, ...args) => func(...args));
}
},
then I get
Uncaught Error: An object could not be cloned.
I just needed to put the rest logic inside of the receive message scope...

Electron Dialog undefined

After seen tons of stackoverflow answers about why electron Dialog and ipcMain returns undefined.
I understood that both of them must be called in the main process.
My problem is that I don't know how to declare the main process since I can't call the ipcMain. I don't know if I'm not understanding this well or I'm missing some file (I also try creating a renderer.js file to separate both processes... didn't work).
produces an error:
TypeError: Cannot read property 'showOpenDialog' of undefined
my preload.js is
const {ipcMain} = require('electron').remote
console.log(ipcMain)
my main.js is:
const { app, BrowserWindow } = require('electron')
const path = require('path')
require('electron-reload')(__dirname);
function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
Electron version 12.0.5

Electron: Trying to set `nativeTheme.themeSource`, but `nativeTheme` is undefined

I'm unable to set the themeSource of my electron app. Platform is Windows 8.1.
const electron = require('electron');
const app = electron.app;
if (app) app.on('ready', function() {
nativeTheme = electron.nativeTheme;
nativeTheme.themeSource = 'dark';
});
This produces an error in a modal pop-up alert saying nativeTheme is undefined.
I'm definitely doing something wrong, but for the life of me I can't see it.
Everything else I'm doing in Electron works like a charm.
Here's my entire app.js:
// server-side jquery
const jsdom = require('jsdom');
const jquery = require('jquery');
const { JSDOM } = jsdom;
const dom = new JSDOM('<!DOCTYPE html>');
const $ = jquery(dom.window);
global.jq = $;
// for priming the webapp
const request = require('request');
// electron config stuff
var backgroundColor = '#1A1A1A';
var width = 800, height = 600;
// get electron
const electron = require('electron');
// prime electron app
const app = electron.app;
// flags: don't enter GUI launch until both sails & electron report ready
var electronIsReady = false;
var sailsIsReady = false;
var gruntIsReady = false;
// block repeat launches (edge contingency)
var windowIsLaunching = false;
var splashIsUp = false;
var splashResponse = 'pong';
// electron window(s)
var mainWindow = null;
var splashWindow = null;
// delay after all preflight checks pass
var windowCreationDelay = 0;
// sails app address
const appAddress = 'http://127.0.0.1';
const appPort = 1337;
const BrowserWindow = electron.BrowserWindow;
if (app) app.on('ready', tryLaunchingForElectron);
else electronIsReady = true;
function tryLaunchingForSails() {
sailsIsReady = true;
try {
// "prime" the webapp by requesting content early
request(`${appAddress}:${appPort}`, (error,response,body) => {/*nada*/});
if (app && electronIsReady && gruntIsReady) createWindow();
}
catch (e) { console.error(e); }
}
function tryLaunchingForElectron() {
// try to prevent multiple instances of the app running
app.requestSingleInstanceLock();
electronIsReady = true;
if (!splashIsUp) {
splashIsUp = true;
// show splash screen
splashWindow = new BrowserWindow({
width: width, height: height,
transparent: true, frame: false, alwaysOnTop: true,
focusable: false, fullscreenable: false,
webPreferences: { nodeIntegration: true }
});
splashWindow.loadURL(`file://${__dirname}/splash.html`);
}
// enter UI phase if sails is also ready
if (app && sailsIsReady && gruntIsReady) createWindow();
}
function createWindow() {
if (windowIsLaunching === true) return -1;
windowIsLaunching = true;
// optional: give sails time to get it fully together
setTimeout(function() {
try {
// tell the splash page to close
splashResponse = 'close';
// create main window
mainWindow = new BrowserWindow({show: false, width: width, height: height,
backgroundColor: backgroundColor
});
// hide menu bar where available
mainWindow.setMenuBarVisibility(false);
// maximize the window
mainWindow.maximize();
// bring to the front
mainWindow.focus();
// go to the sails app
mainWindow.loadURL(`${appAddress}:${appPort}/`);
// show javascript & DOM consoles
mainWindow.webContents.openDevTools();
// show browser only when it's ready to render itself
mainWindow.once('ready-to-show', function() {
// get the splash out of the way
splashWindow.setAlwaysOnTop(false);
// show the main window
mainWindow.setAlwaysOnTop(true);
mainWindow.show();
mainWindow.setAlwaysOnTop(false);
app.focus();
});
// setup close function
mainWindow.on('closed', function() {
mainWindow = null;
});
}
catch (e) { console.error(e); }
}, windowCreationDelay);
}
// tell the splash window when it's time to hide & close
if (app) app.on('ready', function() {
var ipcMain = electron.ipcMain;
ipcMain.on('splashPing', (event, arg) => {
try {
event.sender.send('splashPing', splashResponse);
} catch (e) { console.log(e); }
if (splashResponse === 'close') {
//splashWindow = null;
ipcMain.removeAllListeners('splashPing');
}
// console.log(`${arg}||${splashResponse}`);
});
});
// quit when all windows are closed
if (app) app.on('window-all-closed', function() {
if (process.platform !== 'darwin') {
sails.lower(function (err) {
if (err) {
console.log(err);
app.exit(1);
} else
app.quit();
setTimeout(()=>{app.quit();},5000);
});
}
});
// probably for mobile
if (app) app.on('activate', function() {
if (mainWindow === null) {
createWindow();
}
})
if (app) app.on('ready', function() {
nativeTheme = electron.nativeTheme;
nativeTheme.themeSource = 'dark';
});
// sails wants this
process.chdir(__dirname);
// import sails & rc
var sails;
var rc;
try {
sails = require('sails');
sails.on('hook:grunt:done', () => {
gruntIsReady = true;
tryLaunchingForSails();
});
rc = require('sails/accessible/rc');
} catch (err) {
console.error(err);
}
// Start server
try {
sails.lift(rc('sails'), tryLaunchingForSails );
}
catch (e) { console.log(e); }
nativeTheme = electron.nativeTheme;
This is the problem. You need to do:
const nativeTheme = electron.nativeTheme;
Although in this case there's no need for the extra variable - just do electron.nativeTheme.themeSource = 'dark';.
I strongly suggest you use Typescript - it would tell you this:
Edit: I'm sure I mentioned this in the comments but it seems to have been removed somehow: You also need to make sure you are using Electron 7.0.0 or greater - nativeTheme was not available before that.

TypeError: keytar.addPassword is not a function on electron

I am trying to use keytar at my electron project, but I got this error:
TypeError: keytar.addPassword is not a function
I saw the docs but it seems that addPassword does not exist.
My main.js is:
const electron = require('electron');
const keytar = require('keytar');
const { app, BrowserWindow } = electron;
const path = require('path');
const url = require('url');
let mainWindow;
let appIcon;
function createWindow() {
keytar.addPassword('KeytarTest', 'AccountName', 'secret');
const secret = keytar.getPassword('KeytarTest', 'AccountName');
console.log(secret);
const { width, height } = electron.screen.getPrimaryDisplay().workAreaSize;
mainWindow = new BrowserWindow({ width, height });
mainWindow.loadURL(startUrl);
const contents = mainWindow.webContents;
mainWindow.on('closed', () => {
mainWindow = null;
});
mainWindow.on('closed', () => {
mainWindow = null;
});
}
app.on('ready', createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (mainWindow === null) {
createWindow();
}
});
Can anyone help me?
I saw the docs but it seems that addPassword does not exist.
Yes, that's right. The function addPassword does not exist and that's why you're getting this TypeError.
In general, this has nothing to do with Electron, because the keytar package just does not provide the function you were trying to call.
If a function is not mentioned in the docs, it's most likely to not exist.

Resources