No communication between electron app and user AddData on production - javascript

I created an electron app that has user configuration/settings (changing of theme, tracking and persisting window's current size and position). I saved the json files that contained all of these in user AppData (app.getPath('userData')) working well on development but after being packaged with electron-builder, the app no longer communicates the AppData. Tried to change the theme but the new colors could not be written in AppData, size and position not changing. Frustrated 😒😭...
Please what next should I do if I must use the AppData
const { app, BrowserWindow, ipcMain } = require("electron");
let isDev = require('electron-is-dev');
const path = require('path');
const fs = require('fs');
const { setColors } = require('./ipc');
const userDir = app.getPath('userData');
let isConfigDir = fs.existsSync(path.join(userDir, 'config'));
let isSizeDir = fs.existsSync(path.join(userDir, 'config/size.json'))
let isPosDir = fs.existsSync(path.join(userDir, 'config/pos.json'));
let isColorDir = fs.existsSync(path.join(userDir, 'config/colors.json'));
//create config dir if not already exists
if(!isConfigDir){
fs.mkdirSync(path.join(userDir, '/config'))
}
//check and create config files if not already exist
let pos;
if(!isPosDir){
let pos_ = {"x":636,"y":0};
fs.writeFileSync(path.join(userDir, 'config/pos.json'), JSON.stringify(pos_));
pos = pos_
}else{
pos = JSON.parse(fs.readFileSync(path.join(userDir, 'config/pos.json'), "utf8"))
}
let size
if(!isSizeDir){
let size_ = {"width":701,"height":768}
fs.writeFileSync(path.join(userDir, 'config/size.json'), JSON.stringify(size_));
size = size_;
}else{
size = JSON.parse(fs.readFileSync(path.join(userDir, 'config/size.json'), "utf8"))
}
ipcMain.handle("getColors", (event, args)=>{
let colors;
if(!isColorDir){
let colors_ = {"bg":"gold","text":"#000"}
fs.writeFileSync(path.join(userDir, 'config/colors.json'), JSON.stringify(colors_));
colors = colors_
return colors;
}else{
colors = JSON.parse(fs.readFileSync(path.join(userDir, 'config/colors.json'), "utf8"));
return colors;
}
})
let win;
function createWin(){
win = new BrowserWindow({
width:size.width,
height: size.height,
x: pos.x,
y: pos.y,
title: 'BMS',
webPreferences:{
preload: path.join(__dirname, "preload.js")
}
});
isDev ? win.loadURL('http://localhost:3000') : win.loadFile('views/build/index.html')
win.on("closed", ()=>win = null);
// set window size in file size.json when the system resized
win.on('resized', ()=>{
let size = {
width: win.getBounds().width,
height: win.getBounds().height,
}
fs.writeFileSync(path.join(userDir, 'config/size.json'), JSON.stringify(size))
})
// set window position in file size.json when the window moves
win.on('move', ()=>{
let pos = {
x: win.getBounds().x,
y: win.getBounds().y
}
fs.writeFileSync(path.join(userDir, 'config/pos.json'), JSON.stringify(pos))
})
}
app.on("ready", ()=>{
//create window
createWin();
setColors(userDir)
})
app.on("window-all-closed", ()=>{
if(process.platform !== 'darwin'){
app.quit()
}
})
app.on('active', ()=>{
if(win===null){
createWin()
}
})

Looking at your code, I suspect that your /config folder does not exists when running your application. So, all your variables isSizeDir, isPosDir, etc. are false and stays false even after creating the folder on line 17.
I would move the /config folder creation after line 11.

You should take a look at the electron-json-storage package. The package will handle where to put the persisent data depending on the user OS system, and the JSON parsing. You just need to call the package with whatever JS Object you want to store.
I am using this package in my differents electron projects, and it really makes it easy. I recommand using it instead of struggling with the 'fs' Node API.

Got it working now.
So basically.
app.getPath('userData') gives you path up into electron app dir so I have been writing into my electron app directory.
But using app.getPath('appData') get path unto electron app directory level and does not include it. hence won't be packages by electron-builder and it always be in the pc app dir.
const appDir = app.getPath('appData');
instead of
const appDir = app.getPath('userData');
I got this from Jay. chatted with him earlier. His youtube channel - ithinktechnologies
Thanks #Guillaume and #Dony for your time. Means a lot to me

Related

How can you prevent multiple instances of imported modules using node.js?

I have three files and I am attempting to share a variable I have called sharedArray, stored in the array.js file, on my main js file and another file fileA using export. Although, it appears that main.js and fileA.js create their own instances of array.js. Is there any way to prevent this and have both main.js and fileA.js point to the same variable sharedArray?
main.js
const electron = require('electron');
const path = require('path');
const arrayJS = require(path.join(__dirname,"array.js"));
const {ipcMain} = electron;
function countArray(){
console.log(`There are ${arrayJS.sharedArray.length} values in shared array`);
}
ipcMain.on('countArrayFromMain', function(e){
countArray();
});
array.js
var sharedArray = [];
function getSharedArray(){
return sharedArray;
}
function setSharedArray(newArray){
sharedArray = newArray;
}
module.exports = {
getSharedArray,
setSharedArray
}
fileA.js
const electron = require('electron') ;
const {ipcRenderer} = electron;
const arrayJS = require(path.join(__dirname,"array.js"));
var newArray = [1,2,3];
arrayJS.setSharedArray(newArray);
console.log(`There are ${arrayJS.sharedArray.length} values in shared array`); // outputs 3
ipcRenderer.send('countArrayFromMain'); // outputs 0
Per code, main.js represents Main process of Electron and filaA.js is for Renderer process. Since those 2 are different process, there is no way to share same object reference across processes: you should use IPC to ask one process's value if you want to achieve singleton across process.

Issue handling file download from cypress

While I was working on Cypress trying to download a .xlsx report and further manipulate the data in it for further verification, problem I faced was when Cypress was running test with the electron browser-it prompted a window based popup.
Moreover, when i selected chrome browser for running tests, the default directory of download directory could not be modified. Hence, manipulation of data wasn't possible if it's not present in the project directory as it would cause faliures in the CI execution...
Any workaround for this would be appreciated.
I solved it with the index.js file in the plugins folder by doing the following stuff:
const cypressTypeScriptPreprocessor = require('./cy-ts-preprocessor');
const path = require('path');
const fs = require('fs');
const RESULT_FOLDER = 'results';
const downloadDirectory = path.join(__dirname, '..', RESULT_FOLDER);
module.exports = on => {
on('file:preprocessor', cypressTypeScriptPreprocessor);
on('before:browser:launch', (browser = {}, options) => {
if (fs.existsSync(downloadDirectory)) {
fs.rmdirSync(downloadDirectory, { recursive: true });
}
if (browser.family === 'chromium' && browser.name !== 'electron') {
options.preferences.default['download'] = { default_directory: downloadDirectory };
return options;
}
if (browser.family === 'firefox') {
options.preferences['browser.download.dir'] = downloadDirectory;
options.preferences['browser.download.folderList'] = 2;
return options;
}
});
};
The documentation for that you will find here: https://docs.cypress.io/api/plugins/browser-launch-api.html#Change-download-directory
Be aware this works for Chromium browsers but currently not for the Electron browser in CI mode. Cypress knows about the issue and is currently implementing a solution for that: https://github.com/cypress-io/cypress/issues/949#issuecomment-755975882
You can change the download path in your test like below.
const downloadFolder = path.resolve(__dirname, '../users/user/source/repos/containingRoot/cypress/downloads');

Electron - fs.readdir callback sometimes doesn't execute on reload

About 30% of the time my electron app does not execute the fs.readdir callback after I've reloaded the window that contains that script. When I open the application with electron ., the issue never occurs, it only ever occurs after I've reloaded the window.
I've tried adding a setTimeout of 5 seconds before executing fs.readdir, however this didn't change anything. Additionally, after the first time fs.readdir is run, all proceeding fs.readdir callbacks are never executed unless done immediately afterwards or if the window has never been reloaded before.
Anyone know a why this occurs and a solution?
mainWindow.js:
const fs = require('fs')
// read all files in "images" directory
function readDirectory(){
fs.readdir('images', (e, files) => {
// On error, show and return error
if(e) return console.error(e);
console.log(files)
});
}
readDirectory()
main.js:
const electron = require('electron')
const path = require('path')
const {app, BrowserWindow, Menu} = electron
process.env.NODE_ENV = 'development'
let mainWindow
let mainMenu
function createMainWindow(){
// Create mainWindow
mainWindow = new BrowserWindow({
icon:'icon.png',
webPreferences:{
nodeIntegration:true,
fullscreen: true
}
})
// Load html file
mainWindow.loadFile('mainWindow.html')
// Main menu
mainMenu = Menu.buildFromTemplate(mainMenuTemplate)
// Set main menu
Menu.setApplicationMenu(mainMenu)
// Garbage handling
mainWindow.on('close',()=>{
mainWindow = null
mainMenu = null // idk if necessary
})
if (process.env.NODE_ENV !== 'production'){
mainWindow.webContents.openDevTools()
}
}
app.on('ready',e=>{
createMainWindow()
})
const mainMenuTemplate = [
{
role:'reload',
accelerator:'cmdorctrl+r',
}
]
Also, sometimes the content is read but then it seems like it disappears. Vid: https://imgur.com/a/qZjwSBi
If you're using the fs module set:
app.allowRendererProcessReuse = false;
https://github.com/electron/electron/issues/22119

Express.js: Serving path provided by user

So everyone is familiar with serving static files from a hard-coded path such as /public. How do I do that but serve files from a path provided as input from the user?
Use case: This is a local server and so after running the server, the user selects which folder to serve so that it can be available across the local network for access. I'm looking to create a video streaming API which will play videos served on the path selected by the user.
Currently, I list the items in the path like so:
const Promise = require('bluebird');
const path = require('path')
const fs = Promise.promisifyAll(require('fs'));
async listDir(dir) {
const list = [];
const dirItems = await fs.readdirAsync(dir); // get all items in the directory
for (const item of dirItems) {
const absPath = path.join(dir, item);
const stat = await fs.lstatAsync(absPath);
const isFile = stat.isFile(); // check if item is a file
list.push({ name: item, isFile }); // return item name and isFile boolean
}
return list;
}
Do I then just do pattern-matching in another route to return whichever file is asked for inside the dir?
Am I going about this the right way? Any suggestion/answer is appreciated.

Order of execution of a program in main.js electron (where exec of child_process of nodejs is also used)

noobie here..
In the course of making an electron app,
I have used electron-process package to make electron run in the background(even when all windows are closed). I noticed that, when the electron app was relaunched, the background process of the previous launch of the app remained, and this made background processes pile up with increase in the number of relaunches of the app (If the app was launched 10 times, I would have 10 background processes of the app).. Then I decided to terminate the background process of the previous launch, when I made the next launch.. I have made an attempt to do the same, which is shown below. I noticed that, the app was launched and almost instantly, the app was terminated(which I figured it out to be, the process termination part of the code being exectued later than the part which created the window and started the app. Hence terminating the current process only, instead of the process of the previous launch of the app).. Please help me in making this code run sequentially.. All comments, suggestions, explainations, advices are warmly welcomed. Thanks in advance..
'use strict';
const electron = require('electron');
const main = require('electron-process').main;
const app = electron.app; // Module to control application life.
const BrowserWindow = electron.BrowserWindow; // Module to create native browser window.
const exec = require('child_process').exec;
const exec1 = require('child_process').exec;
const fs = require('fs');
let mainWindow = null;
let mainWindow1 = null;
app.on('ready', function() {
var pidrun;
var killtask;
var read;
if(process.platform == 'win32'){
exec('tasklist /v /fo csv | findstr /i "electron.exe"', (error, stdout, stderr) => {
if (error){
console.error(`exec error: ${error}`);
return;
}
pidrun = stdout[16]+stdout[17]+stdout[18]+stdout[19];
killtask = "Taskkill /FI \"PID eq "+pidrun+"\" /F";
exec(killtask, (error, stdout, stderr) => {
if (error) {
console.error('exec error: ${error}');
return;
}
if(stdout == "INFO: No tasks running with the specified criteria."){
return;
}
});
});
}
const backgroundhtml = 'file://' + __dirname + '/background.html';
const backgroundProcessHandler = main.createBackgroundProcess(backgroundhtml);
mainWindow = new BrowserWindow({width: 1280, height: 600});
backgroundProcessHandler.addWindow(mainWindow);
mainWindow.loadURL('file://'+__dirname+'/foreground2.html');
mainWindow.loadURL('file://' + __dirname + '/foreground.html');
mainWindow.webContents.openDevTools()
});
Sounds to me like you want only a single instance of your app running at any time, Electron provides app.makeSingleInstance specifically for that purpose.

Categories