electron auto-update on click - electron

I'm trying to make my electron app auto-updatable, and after searching on google I found nice guide that works. The only problem is that I want to create a button for when update is downloaded so that the user can decide when he wants to update and restart the app. So far I was able to put this code together
renderer.js
const electron = require('electron')
const ipcRenderer = electron.ipcRenderer
let lastMsgId = 0
window.quitAndInstall = function () {
electron.remote.autoUpdater.quitAndInstall()
}
ipcRenderer.on('console', (event, consoleMsg) => {
console.log(consoleMsg)
})
ipcRenderer.on('message', (event, data) => {
showMessage(data.msg, data.hide, data.replaceAll)
})
function showMessage(message, hide = true, replaceAll = false) {
const messagesContainer = document.querySelector('.messages-container')
const msgId = lastMsgId++ + 1
const msgTemplate = `<div id="${msgId}" class="alert alert-info alert-info-message animated fadeIn">${message}</div>`
if (replaceAll) {
messagesContainer.innerHTML = msgTemplate
} else {
messagesContainer.insertAdjacentHTML('afterbegin', msgTemplate)
}
if (hide) {
setTimeout(() => {
const msgEl = document.getElementById(msgId)
msgEl.classList.remove('fadeIn')
msgEl.classList.add('fadeOut')
}, 4000)
}
}
and this is my index.js where messages are storred
const electron = require('electron');
const {autoUpdater} = require('electron-updater');
const log = require('electron-log');
const appVersion = require('./package.json').version
// configure logging
autoUpdater.logger = log;
autoUpdater.logger.transports.file.level = 'info';
log.info('App starting...');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({
width: 1020,
height: 800,
});
mainWindow.loadURL('file://' +__dirname + '/public/index.html');
// Open the DevTools.
//mainWindow.webContents.openDevTools();
mainWindow.on('closed', function() {
mainWindow = null;
});
}
app.on('ready', createWindow);
// Quit when all windows are closed.
app.on('window-all-closed', function() {
app.quit();
});
app.on('activate', function() {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow();
}
});
//-------------------------------------------------------------------
// Auto updates
//-------------------------------------------------------------------
const sendStatusToWindow = (text) => {
log.info(text);
if (mainWindow) {
mainWindow.webContents.send('console', `App version: ${appVersion}`)
mainWindow.webContents.send('message', { msg: `App version: ${appVersion}` })
}
};
autoUpdater.on('error', (ev, err) => {
mainWindow.webContents.send('message', { msg: `Error: ${err}` })
})
autoUpdater.once('checking-for-update', (ev, err) => {
mainWindow.webContents.send('message', { msg: 'Checking for updates' })
})
autoUpdater.once('update-available', (ev, err) => {
mainWindow.webContents.send('message', { msg: 'Update available. Downloading ⌛️', hide: false })
})
autoUpdater.once('update-not-available', (ev, err) => {
mainWindow.webContents.send('message', { msg: 'Update not available' })
})
autoUpdater.once('update-downloaded', (ev, err) => {
const msg = 'Update downloaded - <button onclick="quitAndInstall()">Restart</button>'
mainWindow.webContents.send('message', { msg, hide: false, replaceAll: true })
})
autoUpdater.checkForUpdates()
As you can see I added a button to call for function but it doesnt work. When I press the button nothing happens. If I remove button and just say auto.updater.quitAndInstall() it works. It auto close window and install new version. What am I missing?

I think it's that electron.remote.autoUpdater.quitAndInstall() doesn't work when run in the renderer.
In the docs it doesn't say anything against running it in the renderer process but I think sending a message back to the main process to run the quitAndInstall function would work.
Inside of the quitAndInstall function put this instead:
ipcRenderer.send('asynchronous-message', 'quitAndInstall');
Then in the main process put:
electron.ipcMain.on('asynchronous-message', function (evt, message) {
if (message == 'quitAndInstall') {
autoUpdater.quitAndInstall();
}
});

Related

What does Uncaught Exception Error: HTTP Trailers are not supported mean (Electron)

I have a website where I have various notes for my classes. These notes are in pdfs and the structure of my website is like this: /year_1/class_1/notes.pdf.
I'm trying to use electron to make a desktop application for viewing my notes and I'm using PDFwindow because electron can't open pdfs otherwise.
However, when I try to go to class_1.html, I get this error: https://i.imgur.com/Nj5GGjD.png
Here is my code and I'm using electron version 18.0.3 and electron-pdf-window version 1.0.12.
const { app, BrowserWindow, Tray, Menu } = require("electron");
const PDFWindow = require("electron-pdf-window");
app.on("ready", () => {
const mainWindow = new PDFWindow({
title: "title",
});
mainWindow.maximize();
mainWindow.show();
mainWindow.on("minimize", function (event) {
event.preventDefault();
mainWindow.hide();
});
mainWindow.on("close", function (event) {
if (!app.isQuiting) {
event.preventDefault();
mainWindow.hide();
}
return false;
});
mainWindow.loadURL("link/to/my/site");
let tray = new Tray("/some/pic");
const contextMenu = Menu.buildFromTemplate([
{
label: "Show App",
click: () => {
mainWindow.show();
},
},
{
label: "Quit",
click: () => {
app.isQuiting = true;
app.quit();
},
},
]);
tray.setContextMenu(contextMenu);
});
Thank you

app.browserWindow returns TypeError during testing of electron app with Spectron

I am trying to run simple tests on my electron application using Spectron and mocha. However, whenever I try to use the browserWindow API I obtain an error of the form:
TypeError: Cannot read property '.....' of undefined
I did a bit of research on the internet. Some proposed solutions I found were to ensure nodeIntegration is set to true, and that DevTools is not open on the application window. I have ensured that both these criteria are satisfied, but I am still receiving the same error. What am I missing here? I am attaching my code for reference.
main.js
const {app, BrowserWindow} = require('electron')
const path = require('path')
let mainWindow
function createWindow () {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration:true
}
})
mainWindow.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
test.js
const Application = require('spectron').Application
const assert = require('assert')
const electronPath = require('electron')
const path = require('path')
const { app } = require('electron')
const { expect } = require('chai')
describe('Application launch', function () {
this.timeout(10000)
before(function () {
this.app = new Application({
path: electronPath,
args: [path.join(__dirname, '.')]
})
return this.app.start()
})
after(function () {
if (this.app && this.app.isRunning()) {
return this.app.stop()
}
})
it('should be a focused window', function(){
return this.app.client.waitUntilWindowLoaded().browserWindow.isFocused().then(res => {
expect(res).to.be.true
})
})
})
The issue arises at the line this.app.client.waitUntilWindowLoaded().browserWindow.isFocused()

Why did electron not create file?

I'm building an app on electron and now I'm trying to create a simple file.
Here's the JS:
const app = require("electron").remote;
var dialog = app.dialog;
var fs = require("fs");
document.getElementById('save_project').onclick=() => {
dialog.showSaveDialog((filename) => {
if(filename === undefined){
console.log("You didnt save the file");
return;
};
var content = "hello there";
fs.writeFile(filename, content, (err) => {
if(err) console.log(err);
alert("The file has been successfully saved.")
})
});
};
This window will open as supposed:
Then, I wrote for instance: "hello.txt" on the name input and clicked save.
There's no log written on the console neither a file written in the directory
Edit:
with this js, happens the same ():
const fs = require("fs");
const {dialog} = require("electron").remote;
document.getElementById("save_project").addEventListener("click", () => {
dialog.showSaveDialog((filename) => {
if(filename === undefined){
console.log("nop");
return;
}
fs.writeFile(filename, content, (err) => {
if(err){
console.log(err);
return;
}
alert("file created");
});
});
}, false);
Edited:
Here's the createWindow()
function createWindow () {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 1920,
height: 1080,
webPreferences: {
preload: path.join(__dirname, "preload.js"),
nodeIntegration: true
},
});
const childWindow = new BrowserWindow ({ width: 1600, height: 800, parent: mainWindow, modal: true, show : false});
// and load the index.html of the app.
mainWindow.loadFile("index.html");
childWindow.loadFile("welcome.html");
childWindow.once("ready-to-show", () => {
childWindow.show();
});
// Open the DevTools.
mainWindow.webContents.openDevTools();
}
Basically, from what i understood, "dialog.showSaveDialog((filename).." this was kinda blocking... i fixed by using:
const {dialog} = require("electron").remote;
let filename = dialog.showSaveDialogSync()
if(filename === undefined){
console.log("filename undefined");
return;
}else{
console.log(filename)
saveAsToFile(filename, ...);
}

Puppeteer in Electron: Error: Passed function is not well-serializable

Trying to come up with a GUI for Puppeteer project.
I thought about using Electron, but run into error:
Error: Passed function is not well-serializable!
when running Puppeteer functions like:
await page.waitForSelector('.modal', { visible: true });
I found a proper way to serialize when dealing with page.evaluate() but how to proceed in case of page.waitForSelector()?
Is there a work around for Puppeter's API functions to be properly serialized when required?
EDIT
I decided to rewrite
await page.waitForSelector('.modal', { visible: true });
using page.evaluate, here is the code:
// first recreate waitForSelector
const awaitSelector = async (selector) => {
return await new Promise(resolve => {
const selectorInterval = setInterval(() => {
if ($(selector).is(':visible')) {
console.log(`${selector} visible`);
resolve();
clearInterval(selectorInterval);
};
}, 1000);
});
}
and later call that function using page.evaluate():
// remember to pass over selector's name, in this case it is ".modal"
await page.evaluate('(' + awaitSelector.toString() + ')(".modal");');
Firstly context:
Generally you can not run puppeteer from browser environment, it works solely in nodejs. Electron provides 2 processes renderer and main. Whenever you want to use node you have to do it in main one.
About communication between both procesess you can read in docs, there are many ways of handling it. From what I know the best practice is to declare it in preload and use ipc bridge. Other solutions violate contextIsolation rule.
I was w wandering aound from one problem to another: like not serializable function, require not defined and many others.
Finally I rewrote everything from scratch and it works here's my solution:
main.js
const { app, BrowserWindow } = require('electron')
const path = require('path')
const { ipcMain } = require('electron');
const puppeteer = require("puppeteer");
function createWindow() {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
contextIsolation: true,
},
})
ipcMain.handle('ping', async () => {
await checkPup()
})
async function checkPup() {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
page
.waitForSelector('h1', { visible: true })
.then(() => {
console.log('got it')
});
const [button] = await page.$x("//button[contains(., 'Create account')]");
if (button) {
console.log('button: ', button)
await button.click();
await page.screenshot({ path: 'tinder.png' });
const [button] = await page.$x("//button[contains(., 'Create account')]");
if (button) {
console.log('button: ', button)
await button.click();
await page.screenshot({ path: 'tinder.png' });
}
}
await browser.close();
}
// and load the index.html of the app.
mainWindow.loadFile('index.html')
// Open the DevTools.
// mainWindow.webContents.openDevTools()
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
// Attach listener in the main process with the given ID
ipcMain.on('request-mainprocess-action', (event, arg) => {
// Displays the object sent from the renderer process:
//{
// message: "Hi",
// someData: "Let's go"
//}
console.log(
arg
);
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
preload.js
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
const { contextBridge, ipcRenderer } = require('electron')
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}
for (const type of ['chrome', 'node', 'electron']) {
replaceText(`${type}-version`, process.versions[type])
}
})
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
ping: () => ipcRenderer.invoke('ping'),
// we can also expose variables, not just functions
})
renderer.js
const information = document.getElementById('info')
const btn = document.getElementById('btn')
information.innerText = `This app is using Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), and Electron (v${versions.electron()})`
btn.addEventListener('click', () => {
console.log('habad!!!!!')
func()
})
const func = async () => {
const response = await window.versions.ping()
information.innerText = response;
console.log(response) // prints out 'pong'
}
Sorry for a little bit of a mess I hope it will help someone maybe finding solutions to some other problems

Electron | onbeforeunload prevents reloading

I'm trying to develop an app with a separate settings window (in electron). When the settings window is open and the main window is closed, I want to prevent the closing of the main window and focus the settings window.
Right now I do this as following:
window.onbeforeunload = e => {
console.log(e);
if(settingsWindow) {
settingsWindow.focus();
e.returnValue = false;
}
else e.returnValue = true;
};
This although prevents reloading the window, which I don't want.
So I'm asking for a different preventing method or a way to detect if it is a reload or a close.
Greeting Elias =)
EDIT/Solution:
utilize ipcMain and ipcRenderer to handle everything with creating windows in the main process. There you can catch and prevent the close event.
MAIN.JS
const {ipcMain} = require("electron");
...
ipcMain.addListener("ASYNC_MSG", (event, arg) => {
switch(arg) {
case "OPEN_SETTINGS": createSettingsWindow(); break;
}
});
...
mainWindow.addListener("close", e => {
if(settingsWindow) {
e.preventDefault();
e.returnValue = false;
settingsWindow.focus();
}
else e.returnValue = true;
});
RENDERER.JS
const {ipcRenderer} = require("electron");
...
ipcRenderer.send("ASYNC_MSG", "OPEN_SETTINGS");
You can use 'close' event, which is called before onbeforeunload and doesn't collide with reloading
const { app, BrowserWindow } = require('electron')
app.once('ready', () => {
let main = new BrowserWindow({title: 'main'})
let settings = new BrowserWindow({title: 'settings'})
main.on('close', (event) => {
if (settings) {
event.preventDefault()
event.returnValue = false
settings.focus()
} else {
event.returnValue = true
}
})
settings.on('closed', () => {
settings = null
})
})
Soo after some more trial and error I found out that this works:
window.onbeforeunload = e => {
console.log(e);
if(settingsWindow) {
settingsWindow.focus();
e.returnValue = false;
}
- else e.returnValue = true;
+ else window.destroy();
};
Glad I finally found a solution :)

Resources