Electron JS write file permission problems - node.js

We are developing an Electron JS app which should get a configuration file from a server at one part. This worked until an hour ago, but now it "magically" throws a permission error. It throws a permission error when we try to write to anything. Here is what we explicitly tested:
app.getPath('userData')
"C:/test"
app.getAppPath()
We tried lauching it from an administrator elevated powershell, but still no success. This is our code snippet:
function canWrite(path, callback) {
fs.access(path, fs.W_OK, function (err) {
callback(null, !err);
});
}
function downloadFile(url, target, target_name) {
canWrite(target, function (err, isWritable) {
if (isWritable){
electronDl.download(
BrowserWindow.getFocusedWindow(),
url,
{
directory: target,
filename: target_name
}
)
console.log("Downloaded from: " + url + " to: " + target);
return true;
} else {
console.log("No permission to write to target");
return false;
}
});
}
downloadFile(REMOTEURL, app.getPath('userData'), 'sessionfile.json');
We rewrote this code, tried to change filenames, tried it without the filename (..) and are a bit out of ideas now. We furthermore implemented a file check (whether the file exists or not) and if so a deletion before executing this. We commented it out for now for debugging because it worked before.
Update:
After somebody pointed out that the outer check is pretty useless, I updated the code to this (still doesn't work):
function downloadFile(url, target) {
electronDl.download(
BrowserWindow.getFocusedWindow(),
url,
{
directory: target,
}
)
}
downloadFile(REMOTEURL, "C:/test");

Since it appears that electron-dl doesn't give clear error messages, you may want to check/create the directory beforehand as you initially did.
The basic procedure could look like this:
Check if the target directory exists.
If it exists, check if it is writable.
If it is writable, proceed to downloading.
If it is not writable, print an informative error message and stop.
If it doesn't exist, try to create it.
If this works, proceed to downloading.
If this fails, print an informative error message and stop.
The following code implements this idea (using the synchronous versions of the fs methods for simplicity). Be sure to use the asynchronous versions if required.
const electronDl = require('electron-dl')
const fs = require('fs')
function ensureDirExistsAndWritable(dir) {
if (fs.existsSync(dir)) {
try {
fs.accessSync(dir, fs.constants.W_OK)
} catch (e) {
console.error('Cannot access directory')
return false
}
}
else {
try {
fs.mkdirSync(dir)
}
catch (e) {
if (e.code == 'EACCES') {
console.log('Cannot create directory')
}
else {
console.log(e.code)
}
return false
}
}
return true
}
function downloadFile(url, target) {
if (ensureDirExistsAndWritable(target) == false) {
return
}
electronDl.download(
BrowserWindow.getFocusedWindow(),
url,
{
directory: target,
}
)
.then(
dl => console.log('Successfully downloaded to ' + dl.getSavePath())
)
.catch(
console.log('There was an error downloading the file')
)
}

Related

Returning a value from a mix of async/sync function from a provider (different script) to express server

Apologies for asking this question - I know there are tons of information about async functions out there but I seem to have tried everything and cannot find a solution..
First of all let me outline the architecture of my program. There are two scripts: a main server script (node.js, express), which processes GET requests and provider script, which deals with the blockchain in the background to return some values. The server script is responsible for invoking a method that returns a value from the provider. The provider does all the work.
The snippet of the provider script:
getInfo(index, account, key) {
//Waiting on an asynchronous method, which does some work in the blockchain in the background; everything functions as it should be
try {
await this.getBlockchain
(
index
, account
, key
).then(result => {
// Here instead I invoke a SYNCHRONOUS method, which simply formats the response in a correct way
const reply = this.sendReply(result)
console.log(reply) //Logs the correct reply in the format in which the server is expecting it
return reply;
});
}
catch (error) {
return { error: 003, result: false };
}
}
The snippet of the server script:
server.get("/getAccount", async (req, res) => {
let index = req.query.index;
let account = req.query.account;
let key = req.query.key;
// Here I also check for the validity of the query values, irrelevant to this issue
// The provider class is imported as provider, hence, the provider.method (this has been tested many times before)
try {
await provider.getInfo(index, account, key).then(reply => {
const { error: infoError, result: infoValue } = reply
if (infoError == false) {
res.send(`${infoValue}`);
} else {
res.send(`${infoError}`);
};
});
}
catch (error) {
res.send("008");
}
}
);
I honestly have no idea how to approach this; I tried self-contained async function on the server side as well as different syntax but the reply is always undefined even though the reply from a synchronous call in the provider is correct.
Could someone help me to understand what I'm doing wrong? This is my first time working with async with numerous scripts and functions and I'm finding it very confusing.
Thank you so much!
With your current structure, you need to return the result of the await so that the top level of your function is returning something from the async function.
async getInfo(index, account, key) {
try {
let retVal = await this.getBlockchain(index, account, key).then(result => {
return this.sendReply(result);
});
return retVal;
} catch (error) {
return { error: 003, result: false };
}
}
But, really, it's a better coding style to not mix await and .then() and to just go with one style like this:
async getInfo(index, account, key) {
try {
let result = await this.getBlockchain(index, account, key);
return this.sendReply(result);
} catch (error) {
return { error: 003, result: false };
}
}
Note, this function never rejects because it's catching its own rejections and turning it into a resolved value. So, the caller cannot use .catch() to see errors. The caller must always check for the error property in the resolved object. This is not usually how you program with promises. It can be made to work, but often does not meet the expectations of the caller (as errors are usually communicated back via rejected promises).
This has to be a dup. but... Don't mix await and .then.
You simply try/catch around await.
try {
const reply = await provider.getInfo(index, account, key);
const { error: infoError, result: infoValue } = reply
if (infoError == false) {
res.send(`${infoValue}`);
} else {
res.send(`${infoError}`);
};
} catch (error) {
res.send(500);
}

Discord.js-commando: Stopping all commands if not in a specific channel

For testing I am trying to stop all commands, unless in a certain channel. I know how to do this for each command specifically, but I am trying to catch it inside of the main bot file, and return a message. I have tried two ways so far:
bot.on('command', async m => { (Also tried 'commandmessage')
console.log('COMMAND');
if (m.channel != 'bot-testing') {
return m.channel.send('You can\'t use commands here!');
}
});
Which doesn't work at all. Then I tried this:
bot.on('message', async m => {
m.isDM = (m.guild ? false : true);
if (m.content[0] != bot.commandPrefix) {
return;
} else {
if (m.channel != 'bot-testing') {
m.channel.send('You can\'t use commands here!');
}
}
});
Which kind of works, but doesn't stop the command.
It looks like you were super close - you just need to look at m.channel.name in your second if-statement (using method #2):
bot.on('message', async m => {
// ...
if (m.content[0] != bot.commandPrefix) {
return;
} else {
// [NEW: add .name prop here]
if (m.channel.name != 'bot-testing') {
m.channel.send('You can\'t use commands here!');
}
}
});

Using try catch statements with the node.js URL module for URL validation

I'm parsing the URL input to a function using the URL module in node.
If a relative path is given a TypeError is thrown. I'm using a try...catch statement to deal with this as shown below:
const { URL } = require('url');
try {
const absolute = new URL('https://static.pexels.com/photos/126407/pexels-photo-126407.jpeg');
console.log(absolute);
} catch (e) {
if (e instanceof TypeError) {
console.log('absolute is a relative path');
} else {
throw e;
}
}
try {
const relative = new URL('/images/picture.jpg');
console.log(relative);
} catch (e) {
if (e instanceof TypeError) {
console.log('relative is a relative path');
} else {
throw e;
}
}
Is this a legitimate use of try catch statements or am I abusing it somewhat? What's the correct way to do this if I am approaching it incorrectly?
Basically if you have a piece of code that might throw an error you should use a try catch block ... and to check whether it will throw any error you have to check the documentation.
Here as far as I know creating a URL throws an error so you have done well.
Also when you are catching an error you better log the error in a log-stream output ... it is better to log the information in a text file, also log the time,date and which part of the code is throwing the error as well.
UPDATE:
you can also write a module like URLCreator.js that handles the URL validation for you some think like this:
const {URL} = require(URL);
function createURL(givenURL, callback) {
try {
let myURL = new URL(givenURL);
callback(myURL);
} catch (e) {
if (e instanceof TypeError) {
//error handling procedure...
} else {
throw e;//cause we dont know what it is or we want to only handle TypeError
}
}
}
module.exports = createURL;

Electron 4 Windows -> electron-builder -> auto-update: custom solution

I'm building an app for windows using Electron. To package and distribute it I'm using electron-builder. Electron-builder relies on many packages, and for auto-updates it uses Squirrel-windows.
I've been battling with auto-update on Windows for 3 days and at the end I've come up with a working solution that seems to give no problems.
I wont' go into the details of what I've tried, and failed. Instead, I'll post here the solution with which I've come up.
I'm sharing it with you guys, to see if you may point out to me any flaws that will make my system fail, or, if it truly is a solid solution, to help those who are struggling as I was. For this latter reason, I'm posting some more code than it would be necessary, hoping it will help others.
The logic is as follows:
if the sub-folder fullupdate inside the path of the current executable does not exists (see later, it will be clarified), we connect with an online server and check if there is an update by sending the current app version;
if there is no update, do nothing.
if there is an update, we instruct the server to return a json string that contains the url from which we can download of the .exe installer produced by electron-builder. NB: not the .nupkg (server code not provided :-)).
we download the file and save it inside a sub folder fullupdate in the local folder in which our app is currently saved. This should be "safe" as electron-builder saves the app in the current user folder AppData, so we should not have permissions issues.
at the end of the download, we create a new file update inside the folder fullupdate to be sure the download has finished successfully. We could also rename the file, but I prefer this way.
next time the app opens:
if the folder fullupdate exists we check if the file update exists. If it does not exists, the download was not finished, so we delete the folder fullupdate and call the remote server again to start all over again.
else, if the file update exists, we launch the .exe file we have downloaded, and return true. This will prevent the app from opening the main window. The cool thing is that the updater will delete the whole old version of the app saved in AppData (while leaving local user data) and replace it with the new version. In this way we will get rid also of the folder fullupdate.
Now the code:
// we want to run this only on windows
var handleStartupEvent = function() {
if (process.platform !== 'win32') {
return false;
}
/////////////////
// MANUAL UPDATER
/////////////////
var appFolder = 'app-' + appVersion;
var pathApp = path.dirname(process.execPath);
var pathUpdate = pathApp + '\\fullupdate';
var checkupdateurl = 'https://api.mysite.com/getjson/' + appVersion.split('.').join('-');
function checkIfDownloaded(){
if (!fs.existsSync(pathUpdate)) checkUpdate();
else return checkIfInstallLocal();
}
function checkIfInstallLocal(){
if(fileExists('fullupdate\\update')) return installLocal();
else {
deleteFolderRecursive(pathUpdate);
checkUpdate();
}
}
function installLocal(){
cp.exec('fullupdate\\Update.exe', function( error, stdout, stderr){
if ( error != null ) {
console.log(stderr);
}
});
return true;
}
// from http://www.geedew.com/remove-a-directory-that-is-not-empty-in-nodejs/
var deleteFolderRecursive = function(path) {
if( fs.existsSync(path) ) {
fs.readdirSync(path).forEach(function(file,index){
var curPath = path + "/" + file;
if(fs.lstatSync(curPath).isDirectory()) deleteFolderRecursive(curPath);
else fs.unlinkSync(curPath);
});
fs.rmdirSync(path);
}
};
// from http://stackoverflow.com/questions/4482686/check-synchronously-if-file-directory-exists-in-node-js
function fileExists(path) {
try {
return fs.statSync(path).isFile();
}
catch (e) {
if (e.code == 'ENOENT') { // no such file or directory. File really does not exist
return false;
}
throw e; // something else went wrong, we don't have rights, ...
}
}
function checkUpdate(){
https.get('https://api.mysite.com/getjson/' + app.getVersion().split('.').join('-'), (res) => {
res.setEncoding('utf8');
res.on('data', function(chunk) {
if(chunk) thereIsUpdate(chunk);
});
}).on('error', (e) => {
console.log(e);
});
}
function thereIsUpdate(chunk){
var data = JSON.parse(chunk);
if(data && data.url) getNewUpdate(data.urlsetup);
}
function getNewUpdate(url){
fs.mkdirSync(pathUpdate);
var file = fs.createWriteStream(pathUpdate + '/Update.exe');
var responseSent = false; // flag to make sure that response is sent only once.
var request = https.get(url, function(response) {
response.pipe(file);
file.on('finish', () =>{
file.close(() => {
if(responseSent) return;
responseSent = true;
});
fs.closeSync(fs.openSync(pathUpdate + '/update', 'w'));
});
});
}
if(checkIfDownloaded()) return true;
/////////////////////////
// SQUIRREL EVENTS HANDLER
//////////////////////////
// see http://stackoverflow.com/questions/30105150/handle-squirrels-event-on-an-electron-app
};
// here we call the function. It is before the opening of the window, so that we prevent the opening if we are updating, or if there is a Squirrel event going on (see SO question, link above)
if (handleStartupEvent()) {
return;
}

How to tell if a script is run as content script or background script?

In a Chrome extension, a script may be included as a content script or background script.
Most stuff it does is the same, but there are some would vary according to different context.
The question is, how could a script tell which context it is being run at?
Thank you.
I think this is a fairly robust version that worked in my initial tests and does not require a slower try catch, and it identifies at least the three primary contexts of a chrome extension, and should let you know if you are on the base page as well.
av = {};
av.Env = {
isChromeExt: function(){
return !!(window['chrome'] && window['chrome']['extension'])
},
getContext: function(){
var loc = window.location.href;
if(!!(window['chrome'] && window['chrome']['extension'])){
if(/^chrome/.test(loc)){
if(window == chrome.extension.getBackgroundPage()){
return 'background';
}else{
return 'extension';
}
}else if( /^https?/.test(loc) ){
return 'content';
}
}else{
return window.location.protocol.replace(':','');
}
}
};
Well I managed to work out this:
var scriptContext = function() {
try {
if (chrome.bookmarks) {
return "background";
}
else {
return "content";
}
}
catch (e) {
return "content";
}
}
It's because an exception would be thrown if the content script tries to access the chrome.* parts except chrome.extension.
Reference: http://code.google.com/chrome/extensions/content_scripts.html
The best solution I've found to this problem comes from over here.
const isBackground = () => location.protocol === 'chrome-extension:'
The background service worker at Manifest v3 does not contain a window.
I use this as part of my extension error handling which reloads the content scripts, when i receive an Extension context invalidated error:
...
if (!self.window) {
console.warn('Background error: \n', error);
} else {
location.reload();
}
...

Resources