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

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.

Related

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...

Send data to renderer process

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

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.

Electron close button not working

I am trying to create an application using Electron (formerly Atom Shell). This application wraps an AngularJS application and interacts with endpoints created in nodejs to edit and save the HTML content. I am able to create the application with no issues.
When I try to access "/saveContent" from electron causes close button (Windows close on top right corner) to become unresponsive, however minimize and maximize works fine without issue. If I access any other endpoint through electron this issue doesn't come up. I have tried with both sync file write and otherwise too. So I assume "/saveContent" in main.js is cause of the issue.
If I end node.exe in "Windows task Manager" this closes the whole application.
I have the main process code below
'use strict';
var fs = require("fs");
const util = require('util')
var cheerio = require("cheerio");
var express = require('express');
var exapp = express();
var bodyParser = require('body-parser');
var urlencodedParser = bodyParser.urlencoded({extended: false});
exapp.use(bodyParser.json());
const electron = require('electron');
const app = electron.app; // Module to control application life.
const BrowserWindow = electron.BrowserWindow; // Module to create native browser window.
const path = require('path')
const url = require('url')
var mainWindow = null;
app.on('ready', function() {
mainWindow = new BrowserWindow({width: 1280, height: 900, title: "2018JL", "nodeIntegration":false});
//mainWindow.loadURL(__dirname + '/app/index.html');
mainWindow.loadURL('http://localhost:5001/');
mainWindow.on('closed', function() {
mainWindow = null;
});
});
app.on('window-all-closed', function() {
if (process.platform != 'darwin') {
app.quit();
}
});
exapp.get('/editPage', function(req,res){
if(req){
//console.log("req.query.editURL "+ req.query.editURL);
var url = req.query.editURL;
var editURL = path.join(__dirname + '/app/views'+ url+".html");
fs.exists(editURL, function(fileok){
if(fileok){
fs.readFile(editURL, 'utf8', function (err, data) {
if (err) {
console.log("error.... "+ err);
return console.error(err);
}
//console.log("data "+ editURL);
res.send(JSON.stringify({path:editURL, content:data}));
});
}else{
console.log("file not found");
}
});
}
});
exapp.post('/saveContent', function (req, res) {
//console.log(util.inspect(req, false, null))
if (req) {
//console.log(req.query.url + " ------ " + req.query.content);
var $ = cheerio.load(req.query.content);
var htmlContent = $('body').children();
console.log('htmlContent '+htmlContent);
fs.writeFile(req.query.url, htmlContent, function(err) {
if (err) {
res.send("Error");
}
console.log("End of write file");
res.send("success");
});
}
console.log("End of function .....");
});
exapp.get('/test', function (req, res) {
res.send("Test success ");
});
exapp.use(express.static(__dirname + '/app'));
exapp.listen(process.env.PORT || 5001);
Client code below
$scope.editPage = function () {
$http({method: "GET",
url: "/editPage",
params: {editURL: $location.path()}
}).then(function success(response) {
//var result = JSON.parse(response.data);
//console.log("HTTP Success "+response.data.path);
$scope.showEditor = true;
$scope.editURL = response.data.path;
tinymce.get('contentEditor').setContent(response.data.content);
}, function error(response) {
console.log("HTTP Error " + response.statusText);
});
};
Commenting file write code in '/saveContent' doesn't cause electron close button to become unresponsive.
I replace the code for mainWindow close to as below and works fine
mainWindow.on('close', function(e) {
e.preventDefault();
mainWindow.destroy();
});
https://github.com/electron/electron/blob/master/docs/api/browser-window.md#windestroy

How can I group users into sessions in a node.js draw application?

I made a draw app in node.js and socket.io recently. It works fine but all things drawn by a user will be seen by all users. I want to add the concept of sessions so the things a user draws will only be seen by people in the same session. How can I do it?
Here's the server code I use:
var express = require('express');
var app = express();
var server = http.createServer(app);
var io = require('socket.io').listen(server);
server.listen(8000);
app.use(express.static(__dirname));
app.use(express.logger());
app.engine('html', require('ejs').__express);
app.get('/',function(req,res){
res.sendfile(__dirname+'/index.html');
});
io.sockets.on('connection',function(socket){
socket.on('mousedown',function(data){
socket.broadcast.emit('mousedown',data);
});
socket.on('mousemove',function(data){
socket.broadcast.emit('mousedown',data);
});
socket.on('mouseup',function(data){
socket.broadcast.emit('mousedown',data);
});
socket.on('tool',function(data){
socket.broadcast.emit('mousedown',data);
});
});
Also, this is the code I used for the clinet side:
var socket = {};
if(typeof io !== 'undefined' && io){
socket = io.connect('http://127.0.0.1:8000');
}
else
{
socket = {
emit:function(){
console.log(arguments);
},
on:function(){
console.log(arguments);
}
};
}
$(function(){
var canvas, context, canvaso, contexto;
// The active tool instance.
var tool;
var tool_default = 'line';
function init () {
// Find the canvas element.
canvaso = document.getElementById('imageView');
canvaso.width = window.innerWidth * 0.9;
canvaso.height = window.innerHeight/2;
if (!canvaso) {
console.log('Error: I cannot find the canvas element!');
return;
}
if (!canvaso.getContext) {
console.log('Error: no canvas.getContext!');
return;
}
// Get the 2D canvas context.
contexto = canvaso.getContext('2d');
if (!contexto) {
console.log('Error: failed to getContext!');
return;
}
// Add the temporary canvas.
var container = canvaso.parentNode;
canvas = document.createElement('canvas');
if (!canvas) {
console.log('Error: I cannot create a new canvas element!');
return;
}
canvas.id = 'imageTemp';
canvas.width = canvaso.width;
canvas.height = canvaso.height;
container.appendChild(canvas);
context = canvas.getContext('2d');
// Get the tool select input.
var tool_select = document.getElementById('dtool');
if (!tool_select) {
console.log('Error: failed to get the dtool element!');
return;
}
tool_select.addEventListener('change', ev_tool_change, false);
// Activate the default tool.
if (tools[tool_default]) {
tool = new tools[tool_default]();
tool_select.value = tool_default;
}
// Attach the mousedown, mousemove and mouseup event listeners.
$(canvas).on('mousedown',ev_canvas);
$(canvas).on('mousemove',ev_canvas);
$(canvas).on('mouseup',ev_canvas);
}
// The general-purpose event handler. This function just determines the mouse
// position relative to the canvas element.
function ev_canvas (ev) {
ev._x = ev.offsetX;
ev._y = ev.offsetY;
// Call the event handler of the tool.
var func = tool[ev.type];
socket.emit(ev.type,{x:ev._x,y:ev._y});
if (func) {
func(ev);
}
}
socket.on('mousedown',function(data){
ev_canvas({type:'mousedown',_x:data.x,_y:data.y});
});
socket.on('mousemove',function(data){
ev_canvas({type:'mousemove',_x:data.x,_y:data.y});
});
socket.on('mouseup',function(data){
ev_canvas({type:'mouseup',_x:data.x,_y:data.y});
});
// The event handler for any changes made to the tool selector.
function ev_tool_change (ev) {
if (tools[this.value]) {
tool = new tools[this.value]();
socket.emit('tool',this.value);
}
}
socket.emit('tool',tool_default);
socket.on('tool',function(stool){
tool = new tools[stool]();
});
// This function draws the #imageTemp canvas on top of #imageView, after which
// #imageTemp is cleared. This function is called each time when the user
// completes a drawing operation.
function img_update () {
contexto.drawImage(canvas, 0, 0);
context.clearRect(0, 0, canvas.width, canvas.height);
}
// This object holds the implementation of each drawing tool.
var tools = {};
// The drawing pencil.
tools.pencil = function () {
var tool = this;
this.started = false;
// This is called when you start holding down the mouse button.
// This starts the pencil drawing.
this.mousedown = function (ev) {
context.beginPath();
context.moveTo(ev._x, ev._y);
tool.started = true;
};
// This function is called every time you move the mouse. Obviously, it only
// draws if the tool.started state is set to true (when you are holding down
// the mouse button).
this.mousemove = function (ev) {
if (tool.started) {
context.lineTo(ev._x, ev._y);
context.stroke();
}
};
// This is called when you release the mouse button.
this.mouseup = function (ev) {
if (tool.started) {
tool.mousemove(ev);
tool.started = false;
img_update();
}
};
};
// The rectangle tool.
tools.rect = function () {
var tool = this;
this.started = false;
this.mousedown = function (ev) {
tool.started = true;
tool.x0 = ev._x;
tool.y0 = ev._y;
};
this.mousemove = function (ev) {
if (!tool.started) {
return;
}
var x = Math.min(ev._x, tool.x0),
y = Math.min(ev._y, tool.y0),
w = Math.abs(ev._x - tool.x0),
h = Math.abs(ev._y - tool.y0);
context.clearRect(0, 0, canvas.width, canvas.height);
if (!w || !h) {
return;
}
context.strokeRect(x, y, w, h);
};
this.mouseup = function (ev) {
if (tool.started) {
tool.mousemove(ev);
tool.started = false;
img_update();
}
};
};
// The line tool.
tools.line = function () {
var tool = this;
this.started = false;
this.mousedown = function (ev) {
tool.started = true;
tool.x0 = ev._x;
tool.y0 = ev._y;
};
this.mousemove = function (ev) {
if (!tool.started) {
return;
}
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.moveTo(tool.x0, tool.y0);
context.lineTo(ev._x, ev._y);
context.stroke();
context.closePath();
};
this.mouseup = function (ev) {
if (tool.started) {
tool.mousemove(ev);
tool.started = false;
img_update();
}
};
};
init();
});
Any help will be appreciated.
It sounds to me like you want to be using Rooms
You can add users to rooms as they connect just within io.sockets.on('connection')
For example:
io.sockets.on('connection', function (socket) {
socket.join('room');
socket.broadcast.to('room').send("I'm in this room now");
});
You can then use this to broadcast new drawings within just the room of where it originated.

Resources