Electron - Invalid package on unzip - javascript

For around 3 weeks I've been working on an Electron app and finally decided to get around to adding update checking. For my research, the standard way to do this in Electron (using Squirrel) requires the user to physically install the application onto their computer. I would rather not do this, and keep everything as portable as possible. I then decided to try making my own update script by having the program download the update.zip, and extract it to overwrite the existing files. This works well, up until the very end. At the very end of the extraction, I receive a Invalid package error, and the actual app.asar file is missing, rendering the application useless.
I am using this to download and extract the updates:
function downloadFile(url, target, fileName, cb) { // Downloads
var req = request({
method: 'GET',
uri: url
});
var out = fs.createWriteStream(target+'/'+fileName);
req.pipe(out);
req.on('end', function() {
unzip(target+'/'+fileName, target, function() {
if (cb) {
cb();
}
});
});
}
function unzip(file, target, cb) { // Unzips
var out = fs.createReadStream(file);
out.pipe(unzipper.Extract({ path: target })).on('finish', function () {
dialog.showMessageBox({
type: 'question',
message: 'Finished extracting to `'+target+'`'
});
if (cb) {
cb();
}
});
}
And call it with:
downloadFile('http://example.com/update.zip', path.join(__dirname, './'), 'update.zip', function() { // http://example.com/update.zip is not the real source
app.relaunch();
app.quit();
});
And I use the unzipper NPM package (https://www.npmjs.com/package/unzipper).
The code works perfectly for all other zips, but it fails when trying to extract a zip containing an Electron app.
Anything I'm doing wrong, or maybe a different package that properly supports extracting zips with .asar files?
Edit 1
I just found https://www.npmjs.com/package/electron-basic-updater, which does not throw the same JavaScript error however it still does not extract the .asar files correctly, and will throw it's own error. Since the .asar is still missing, the app is still useless after the "update"

Thanks to your link to electron-basic-updater, I have found this issue mentioned there: https://github.com/TamkeenLMS/electron-basic-updater/issues/4.
They refer to the issue in the electron app: https://github.com/electron/electron/issues/9304.
Finally, in the end of the second topic there's a solution:
This is due to the electron fs module treating asar files as directories rather than files. To make the unzip process work you need to do one of two things:
Set process.noAsar = true
Use original-fs instead of fs
I have seen the people working with original-fs. But it looked like a big trouble to me.
So I tried setting process.noAsar = true (and then process.noAsar = false after unzipping) - and that worked like a charm.

Related

How debug a nodejs API

I've been worked on a vue project.
This vue project use the nodejs API I've created, in simple way, they are two entire differents project which are not located in the same directory and they are launched separately.
The problem is whenever I debug a route with node --inspect --debug-break event_type.controller.js for example named:
"/eventtype/create"
exports.create = (req, res) => {
const userId = jwt.getUserId(req.headers.authorization);
if (userId == null) {
res.status(401).send(Response.response401());
return;
}
// Validate request
if (!req.body.label || !req.body.calendarId) {
res.status(400).send(Response.response400());
return;
}
const calendarId = req.body.calendarId; // Calendar id
// Save to database
EventType.create({
label: req.body.label,
}).then((eventType) => {
Calendar.findByPk(calendarId).then((calendar) => {
eventType.addCalendar(calendar); // Add a Calendar
res.status(201).send(eventType);
}).catch((err) => {
res.status(500).send(Response.response500(err.message));
});
}).catch((err) => {
res.status(500).send(Response.response500(err.message));
});
};
Even if I create a breakpoint on const userId = jwt.getUserId(req.headers.authorization);
and from my vue app I trigger the api createEventType event, my break point is not passed.
Also when I press f8 after the breakpoint on my first line with the debugger, my file close automatically.
I do not use VS Code but Vim for coding but I've heard that maybe Vs Code could allow a simplified way to debug nodesjs application.
NOTE: I use the V8 node debugger.
For newer NodeJS versions (> 7.0.0) you need to use
node --inspect-brk event_type.controller.js
instead of
node --inspect --debug-break event_type.controller.js
to break on the first line of the application code. See https://nodejs.org/api/debugger.html#debugger_advanced_usage for more information.
The solution (even if it's not really a solution) has been to add console.log to the line I wanted to debug.

copyFileSync not copying file and not throwing error

I'm running a function which I've written in JavaScript inside a nodejs/Electron client.
This function is meant to copy a file from the users flash drive to their c:/Windows/System32 (The file is being copied there so that it can be ran from Command Prompt manually next time the computer is touched without having to switch directories)
The problem is, the files are not being copied, and copyFileSync is not throwing an error.
Here is the code I'm specifically having a problem with:
try {
console.log('copying t.bat');
fs.copyFileSync(remote.app.getAppPath() + '\\app\\files\\scripts\\files\\t.bat', 'C:\\Windows\\System32\\t.bat');
} catch(err) {
console.log('could not copy t.bat', err);
$('#mfail_title').text('Could not copy t.bat file');
$('#mfail_data').text(err);
UIkit.modal("#master_fail").show();
return false;
}
As you can see, I have copyFileSync inside a TRY CATCH block. I know this code is running because in the console I get copying t.bat, but nothing else.
How can I get my files to copy, or at least throw an error when it cannot?
This client is running inside OOBE mode on various Windows 10 machines, therefore always has administrator access.
I've tried updating to the async version of copyFile, but I'm having the same issue. Here is my code
var source = remote.app.getAppPath() + '\\app\\files\\scripts\\files\\t.bat';
var destination = 'C:\\Windows\\System32\\t.bat';
fs.copyFile(source, destination, (err) => {
if (err) {
console.log(err);
} else {
source = remote.app.getAppPath() + '\\app\\files\\scripts\\files\\p.bat';
destination = 'C:\\Windows\\System32\\p.bat';
fs.copyFile(source, destination, (err) => {
if (err) {
console.log(err);
} else {
source = remote.app.getAppPath() + '\\app\\files\\scripts\\files\\p.bat';
destination = 'C:\\Windows\\System32\\p.bat';
child = spawn("powershell.exe",['-ExecutionPolicy', 'ByPass', '-File', remote.app.getAppPath() + '\\app\\files\\scripts\\' + type + '.ps1']);
}
});
}
});
This should copy a file, then when it's complete it should copy another file, once that is complete, it should run a powershell script.
Each copyFile checks for an error before moving on, but it never throws an error, and the file is never copied.
I had a similar issue earlier, In which an Antivirus(Comodo) was not allowing electron app to access the hard drive.
Copy and other file operations were successful in that case as well, because electron in such case access the corresponding sandbox
Please check this is not the case with you.
You can actually access 'fs' in console from electron and check other things in the file system.
Looks to me as if you're using fs on then renderer process (client side) which will not work (assuming that your fs is the node.js fs module and (*)). Your first script seems to use jQuery (hints for renderer) and the second one uses remote in the first line.
fs can only (*) be used on the main process and you'll need to create an IRC channel and do something like:
ircRenderer.sendSync('copy-file-sync', {from: '/from/path', to: '/to/path'})
and, of course, implement the handler for that quickly invented 'copy-file' channel on the main process.
(*) Edit: I haven't played around a lot with nodeIntegration = true, so fs may or may not work on the renderer process with that flag set on the BrowserWindow. But the irc messaging should definitely work and if not, the problem is outside electron, probably related to file permissions.

WebSocket connection closes after calling child process

Here's the setup: I create simple WebSocket Server using the ws library. I then attach a listener for when the client sends me the URL of a PDF to transform. I download it locally then I call another command to transform it:
const download = require("download");
wss.on("connection", ws => {
ws.onmessage = async msg => {
await download(msg.data, destination, {
filename: fileName
});
transformPDF(ws, msg.data);
};
// ...
});
After that, the transformPDF function calls the spawn command to execute a command line binary. I parse the percentage done from the stdout and then try to emit it to the client. But even before this, the connection has been closed and I'm not sure why:
const { spawn } = require("child_process");
const transformPDF = (ws, url) => {
// ...
const child = spawn("k2pdfopt", settings);
child.stdout.on("data", data => {
// ...
ws.send(percentageDone); // <--- connection is broken before this is called
});
};
I have tried to make the transformPDF function a promise and then awaiting it. I have also tried adding an optional detached option to the spawn process. I'm not really sure why it's closing since I've also successfully replaced the command k2pdfopt with something like a lengthy find, and that worked just fine (although it did batch all of the data in the stdout before calling ws.send).
Any help or insight on why it's closing is much appreciated.
Turns out that when I was creating a child process, it was resetting the Visual Studio Code live-server extension that I had running the index.html. That explains why I was also getting a status code of 1001, which I found out most likely means the client refreshed.
I fixed the issue by simply installing the node package live-server and running my index.html from a different terminal.

Node render process gone while downloading files

I'm programming a small Electron program, that (currently) is able to download multiple big files from Google Drive. Some files are too big to be scanned so it tricks the server into believing the "Download anyway" button was clicked.
The problem is, that after 3, 7 or 12 files, the Chrome Dev Tools window shows "Render process gone".
3 files:
- download unblocking turned on while trying to download the same blocked file 3 times in a row. The 4th file crashes.
7 files:
- mixture of 5 non-blocked files and 2 blocked ones with download unblocking turned on. The 8th file (non-blocked) request crashes
12 files:
- downloading 9 non-blocked files and 3 blocked ones while download unblocking turned off
Therefore, I came to conclude, that "unblocking" a file takes up 3 of the 12 possible "slots". Then, everything (or at least the numbers) would make sense.
Unblocking works by sending another request with the same cookie char and a ?confirm=xxxx at the same url.
Note that the whole thing doesn't crash after 12 files if I use another file provider with 100mb test files.
It would be extremely helpful if you could point me in the right direction.
Here's a really simplified version of the code that's used. Note that a lot of variable declarations and other stuff is missing, but these parts seem to be the problematic ones:
// downloadmanager.js
function DownloadManager(pack) {
var _this = this;
this.downloadpackages = function (package, data, cb) {
sync.fiber(function () {
...
Object.keys(ht_distinct_urls).forEach(function (url) {
localfile = sync.await(_this.download(remotefile, sync.defer()));
console.log("Downloaded:" + localfile);
});
});
}
this.dl = function (remotefile, cb) {
request(request_options, (err, response, body) => {
// cb() in this location makes it crash at the 13th file
cb(null, "");
});
// cb() in this location doesnt make it crash (but also not download anything)
//cb(null, "");
}
this.download = function (remotefile, cb) {
// Try to download
_this.dl(remotefile, function (err, data) {
if (data.downloaded) { // It worked
cb(err, data);
} else if (data.unblocked) { // It was able to get a code to unblock it
_this.dl(data, cb); // Try again with the new cookie and the code
} else {
// Fck it, just return the data anyway for debugging
cb(err, data);
}
});
};
}
// renderer.js
sync.fiber(function () {
var pack = getPackage();
var dm = new DownloadManager(pack);
var download_result = sync.await(dm.downloadpackages(pack, ht_distinct_urls, sync.defer()));
console.log(download_result);
});
Okay, it seems like I found a solution while posting this question. I'll just post it anyway, maybe it helps someone else...
It seems like google blocks instant downloading. Adding a timeout of 5 seconds between downloads solved my problem. Haven't tested how low the timeout can be, but maybe the problem was solved by the settimeout callback anyway...

Protractor e2e test case for downloading pdf file

Can anyone tell me how to write test case for a link to download pdf file using jasmine framework ?
Thanks in advance.
I can currently set download path location
Chrome
capabilities: {
'browserName': 'chrome',
'platform': 'ANY',
'version': 'ANY',
'chromeOptions': {
// Get rid of --ignore-certificate yellow warning
args: ['--no-sandbox', '--test-type=browser'],
// Set download path and avoid prompting for download even though
// this is already the default on Chrome but for completeness
prefs: {
'download': {
'prompt_for_download': false,
'default_directory': '/e2e/downloads/',
}
}
}
}
For remote testing you would need a more complex infrastructure like setting up a Samba share or network shared directory destination.
Firefox
var FirefoxProfile = require('firefox-profile');
var q = require('q');
[...]
getMultiCapabilities: getFirefoxProfile,
framework: 'jasmine2',
[...]
function getFirefoxProfile() {
"use strict";
var deferred = q.defer();
var firefoxProfile = new FirefoxProfile();
firefoxProfile.setPreference("browser.download.folderList", 2);
firefoxProfile.setPreference("browser.download.manager.showWhenStarting", false);
firefoxProfile.setPreference("browser.download.dir", '/tmp');
firefoxProfile.setPreference("browser.helperApps.neverAsk.saveToDisk", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
firefoxProfile.encoded(function(encodedProfile) {
var multiCapabilities = [{
browserName: 'firefox',
firefox_profile : encodedProfile
}];
deferred.resolve(multiCapabilities);
});
return deferred.promise;
}
Finally and maybe obvious, to trigger the download you click on the download link as you know, e.g.
$('a.some-download-link').click();
I needed to check the contents of the downloaded file (a CSV export in my case) against an expected result, and found the following to work:
var filename = '/tmp/export.csv';
var fs = require('fs');
if (fs.existsSync(filename)) {
// Make sure the browser doesn't have to rename the download.
fs.unlinkSync(filename);
}
$('a.download').click();
browser.driver.wait(function() {
// Wait until the file has been downloaded.
// We need to wait thus as otherwise protractor has a nasty habit of
// trying to do any following tests while the file is still being
// downloaded and hasn't been moved to its final location.
return fs.existsSync(filename);
}, 30000).then(function() {
// Do whatever checks you need here. This is a simple comparison;
// for a larger file you might want to do calculate the file's MD5
// hash and see if it matches what you expect.
expect(fs.readFileSync(filename, { encoding: 'utf8' })).toEqual(
"A,B,C\r\n"
);
});
I found Leo's configuration suggestion helpful for allowing the download to be saved somewhere accessible.
The 30000ms timeout is the default, so could be omitted, but I'm leaving it in as a reminder in case someone would like to change it.
it could be the test for checking href attribute like so:
var link = element(by.css("a.pdf"));
expect(link.getAttribute('href')).toEqual('someExactUrl');
The solutions above would not work for remote browser testing, e.g. via BrowserStack. An alternative solution, just for Chrome, could look like this:
if ((await browser.getCapabilities()).get('browserName') === 'chrome') {
await browser.driver.get('chrome://downloads/');
const items =
await browser.executeScript('return downloads.Manager.get().items_') as any[];
expect(items.length).toBe(1);
expect(items[0].file_name).toBe('some.pdf');
}
One thing I've done in the past is to use an HTTP HEAD command. Basically, it's the same as a 'GET', but it only retrieves the headers.
Unfortunately, the web server needs to support 'HEAD' explicitly. If it does, you can actually try the URL and then check for 'application/pdf' in the Content-Type, without having to actually download the file.
If the server isn't set up to support HEAD, you can probably just check the link text like was suggested above.

Categories