How to close a child electron window? - electron

childWin.close() doesn't work, because .close() can only work on the parent window.
I cannot use childWin.hide(), because I need to completely kill the child window and then create it again like it's created at initialization of the electron process. There doesn't seem to be any straightforward way of doing this.
'use strict'
import electron from 'electron'
import {
app,
protocol,
BrowserWindow
} from 'electron'
import {
createProtocol
} from 'vue-cli-plugin-electron-builder/lib'
import installExtension, {
VUEJS3_DEVTOOLS
} from 'electron-devtools-installer'
const isDevelopment = process.env.NODE_ENV !== 'production'
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([{
scheme: 'app',
privileges: {
secure: true,
standard: true
}
}])
let win
const winWidth = 1000
const winHeight = 700
async function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: winWidth,
height: winHeight,
webPreferences: {
// webSecurity false fixes CORS issues, remove in production!
webSecurity: false,
enableRemoteModule: true,
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
nodeIntegration: true,
contextIsolation: false
}
})
win.webContents.openDevTools({
mode: 'docked'
})
win.setMenuBarVisibility(false)
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
if (!process.env.IS_TEST) win.webContents.openDevTools()
} else {
createProtocol('app')
// Load the index.html when not in development
win.loadURL('app://./index.html')
}
}
let childWin
const childWidth = 60
const childHeight = 60
async function createChildWindow() {
// Create the browser window.
childWin = new BrowserWindow({
width: childWidth,
height: childHeight,
frame: false,
closeable: true,
webPreferences: {
// webSecurity false fixes CORS issues, remove in production!
webSecurity: false,
enableRemoteModule: true,
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
nodeIntegration: true,
contextIsolation: false
},
Parent: win
})
const modalPath = process.env.NODE_ENV === 'development' ?
`${process.env.WEBPACK_DEV_SERVER_URL}/#/electronMenu` :
`file://${__dirname}/index.html#electronMenu`
childWin.loadURL(modalPath)
}
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// 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()
})
// 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.on('ready', async() => {
if (isDevelopment && !process.env.IS_TEST) {
// Install Vue Devtools
try {
await installExtension(VUEJS3_DEVTOOLS)
} catch (e) {
console.error('Vue Devtools failed to install:', e.toString())
}
}
createWindow()
createChildWindow()
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', (data) => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}
electron.ipcMain.on('updateTileDataInElectronMenu', (event, tiles) => {
childWin.close()
})

Related

How to disable contextIsolation and enable nodeIntegration for cypress electron tests?

I am trying to access these properties (made available by this update).
I tried the following in cypress.config.ts but it does not seem to be working:
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
on("before:browser:launch", (browser, launchOptions) => {
launchOptions.preferences.webPreferences.contextIsolation = false;
launchOptions.preferences.webPreferences.nodeIntegration = true;
});
},
},
});
Thank you in advance.
Add a browser check as launch options are specific to Electron and will stop other browsers running. Then return the launch options.
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
on('before:browser:launch', (browser = {}, launchOptions) => {
if (browser.name === 'electron') {
launchOptions.preferences.webPreferences.nodeIntegration = true;
launchOptions.preferences.webPreferences.contextIsolation = false;
launchOptions.preferences.webPreferences.enableRemoteModule = true;
}
return launchOptions;
})
},
},
});
You must return the launchOptions object in your callaback for them to take effect.
https://docs.cypress.io/api/plugins/browser-launch-api#Modify-browser-launch-arguments-preferences-extensions-and-environment
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
on("before:browser:launch", (browser, launchOptions) => {
launchOptions.preferences.webPreferences.contextIsolation = false;
launchOptions.preferences.webPreferences.nodeIntegration = true;
return launchOptions;
});
},
},
});

Electron: Why does showing localhost only work if never loaded another URL before?

In my Electron app I want to load an URL after the app has started:
mainWindow.loadURL(`file:///${process.resourcesPath}/Content/index.html`);
And later I want to show the file which is served on localhost:
mainWindow.loadURL(`http://127.0.0.1:2015/`);
I found out that showing localhost only works if I never load another URL before. And it does not matter if I load a local index.html or an online URL before showing localhost.
Does someone know how I can resolve this?
Here is my code. The problem is in the StartCaddy function. This function gets called when pressing on a menu item in the file menu.
// This only works if never used mainWindow.loadURL before???
mainWindow.loadURL("http://127.0.0.1:2015/");
If I comment out following line than it works without problem:
mainWindow.loadURL(windowURL);
// Modules to control application life and create native browser window
const { app, BrowserWindow } = require('electron')
const path = require('path')
let mainWindow;
var windowURL = "https://www.google.com/";
function createWindow() {
// Create the browser window.
//const mainWindow = new BrowserWindow({
mainWindow = new BrowserWindow({
width: 800,
height: 600,
title: "", // Remove title in title bar
//frame: false,
//titleBarStyle: 'hidden',
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
// MENU BEGIN /////////////////////////////////////////////
const { app, Menu } = require('electron')
const isMac = process.platform === 'darwin'
const template = [
// { role: 'appMenu' }
...(isMac ? [{
label: app.name,
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
}] : []),
// { role: 'fileMenu' }
{
label: 'File',
submenu: [
{
label: 'Start Caddy Server', click() {
StartCaddy();
}
},
{
label: 'Git Action', click() {
GitAction();
}
},
{
label: 'ZIP Action', click() {
ZipAction();
}
},
isMac ? { role: 'close' } : { role: 'quit' }
]
},
// { role: 'editMenu' }
{
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
...(isMac ? [
{ role: 'pasteAndMatchStyle' },
{ role: 'delete' },
{ role: 'selectAll' },
{ type: 'separator' },
{
label: 'Speech',
submenu: [
{ role: 'startSpeaking' },
{ role: 'stopSpeaking' }
]
}
] : [
{ role: 'delete' },
{ type: 'separator' },
{ role: 'selectAll' }
])
]
},
// { role: 'viewMenu' }
{
label: 'View',
submenu: [
{ role: 'reload' },
{ role: 'forceReload' },
{ role: 'toggleDevTools' },
{ type: 'separator' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
},
// { role: 'windowMenu' }
{
label: 'Window',
submenu: [
{ role: 'minimize' },
{ role: 'zoom' },
...(isMac ? [
{ type: 'separator' },
{ role: 'front' },
{ type: 'separator' },
{ role: 'window' }
] : [
{ role: 'close' }
])
]
},
{
role: 'help',
submenu: [
{
label: 'Learn More',
click: async () => {
const { shell } = require('electron')
await shell.openExternal('https://electronjs.org')
}
}
]
}
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
// MENU END ////////////////////////////////////////////////
// and load the index.html of the app.
mainWindow.loadURL(windowURL);
// 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(() => {
//windowURL = `file:///${process.resourcesPath}/Content/index.html`;
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()
})
// 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.
// FUNCTIONS ///////////////////////////////////////////////
// Path to the resources directory (works only in builded app)
// ${process.resourcesPath}
// Path to the project's root folder
// ${__dirname}
// Start Caddy function
async function StartCaddy() {
console.log("Start of StartCaddy function");
const caddyAction = require("child_process").exec(`${process.resourcesPath}/Bin/caddy-start`, shellCallback);
caddyAction.stdout.pipe(process.stdout)
caddyAction.on("exit", () => console.log("Caddy action finished")) // This gets never executed
await sleep(3000); // Wait 3 seconds
console.log("Waited for 3 seconds now show localhost in new window");
//windowURL = `http://127.0.0.1:2015/`;
//mainWindow.close();
//createWindow();
// This only works if never used mainWindow.loadURL before???
mainWindow.loadURL("http://127.0.0.1:2015/");
//mainWindow.loadURL(`http://localhost:2015`);
//mainWindow.reload();
mainWindow.webContents.reloadIgnoringCache();
}
// Sleep function
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
// Git action function
function GitAction() {
const gitAction = require("child_process").exec(`${process.resourcesPath}/Bin/git-action`, shellCallback);
gitAction.stdout.pipe(process.stdout)
gitAction.on("exit", () => console.log("Git action finished"))
}
// ZIP action function
function ZipAction() {
const zipAction = require("child_process").exec(`${process.resourcesPath}/Bin/zip-action`, shellCallback);
zipAction.stdout.pipe(process.stdout)
zipAction.on("exit", () => console.log("ZIP action finished"))
}
// Callback
function shellCallback(error, stdout, stderr) {
//console.log(error, stdout)
console.log("error: " + error)
//console.log("stdout: " + stdout)
//console.log("stderr: " + stderr)
}
// Execute bash script
//const exec = require("child_process").exec;
//exec("/Users/aronsommer/Documents/Xcode-Projects/CocoaWebView/Resources/caddy-start", shellCallback);
// Execute bash script and than do something if finished
// CAUTION!!! ${process.resourcesPath} does not work when yarn start, only works with app in dist folder
//const child = require("child_process").exec(`${process.resourcesPath}/Bin/test`, shellCallback);
//child.stdout.pipe(process.stdout)
//child.on("exit", () => console.log("guguuus fertig"))
// Load file from Resources folder
// CAUTION!!! ${process.resourcesPath} does not work when yarn start, only works with app in dist folder
//mainWindow.loadURL(`file:///${process.resourcesPath}/Content/index.html`);
//console.log(`file:///${process.resourcesPath}/Content/index.html`);
It looks like a scoping problem.
At the very end of your createWindow() function add the following line.
function createWindow() {
...
return mainWindow; // <-- Add this line
}
In your app.whenReady() function edit the following line.
app.whenReady().then(() => {
// windowURL = `file:///${process.resourcesPath}/Content/index.html`;
mainWindow = createWindow(); // <-- Edit this line
If functions in your above code are seperated into their own files then you can do something similar as shown below.
main.js
const { app, BrowserWindow } = require('electron')
const appMainWindow = require('mainWindow');
let mainWindow;
app.whenReady().then(() => {
mainWindow = appMainWindow.createWindow();
// Re-activate Application when in macOS dock.
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) { appMainWindow.createWindow(); }
})
})
mainWindow.js
let mainWindow;
function createWindow() {
...
return mainWindow;
}
function getWindow() {
return mainWindow;
}
module.exports = {createWindow, getWindow};
startCaddy.js
const appMainWindow = require('mainWindow');
function StartCaddy() {
...
appMainWindow.getWindow().loadURL("http://127.0.0.1:2015/");
...
}

Can not update electron application

Does anybody hit this error while updating autoupdate for electron application.
It says:
Error: Can not find Squirrel
enter image description here
below is my code:
const server = 'http://10.172.120.67:8081'
// const url = `${server}/update/${process.platform}/${app.getVersion()}`
const url = `${server}/update/`
console.log(url)
autoUpdater.setFeedURL({ url })
autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => {
console.log("getin")
const dialogOpts = {
type: 'info',
buttons: ['Restart', 'Later'],
title: 'Application Update',
message: process.platform === 'win32' ? releaseNotes : releaseName,
detail: 'A new version has been downloaded. Restart the application to apply the updates.'
}
dialog.showMessageBox(dialogOpts).then((returnValue) => {
if (returnValue.response === 0) autoUpdater.quitAndInstall()
})
})
autoUpdater.on('error', message => {
console.error('There was a problem updating the application')
console.error(message)
})
autoUpdater.on('update-avliable', function (info){
console.log('update-avliable')
})
autoUpdater.on('checking-for-update', function () {
console.log('checking-for-update')
});
console.log("hello electron")
autoUpdater.checkForUpdates();
Try calling the check for updates when the app is ready:
app.on('ready', function() {
autoUpdater.checkForUpdates();
}

Using puppeteer, on TimeoutError screenshot the current state

I'm trying to screenshot a website using puppeteer, and on slow sites I receive a TimeoutError.
In this case, I'd like to get the screenshot of the current page state - is this possible? if so, how?
Code sample:
const puppeteer = require('puppeteer');
let url = "http://...";
let timeout = 30000;
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page
.goto(url, {waitUntil: 'load', timeout: timeout}).then(async () => {
await page
.screenshot({path: 'example.png'})
.catch(error => console.error(error));
})
.catch(error => {
if (error.name === "TimeoutError") {
// -----> calling await page.screenshot({path: 'example.png'}) gets stuck
} else {
console.error(error);
}
});
await browser.close();
})();
Don't use browser.close when using puppeteer in development, as this may cause the browser closed and puppeteer crashed.
const puppeteer = require('puppeteer')
let url = "https://www.tokopedia.com"
let filename = 'timeout.png'
let timeoutNum = 30000
;(async () => {
const browser = await puppeteer.launch({
headless: false
});
const [page] = await browser.pages ()
page.setViewport ({ width: 1366, height: 768 })
try {
await page.goto(url, {waitUntil: 'networkidle0', timeout: timeoutNum}).then(async () => {
await page.screenshot({ path: 'example.png', fullPage: true })
})
} catch (error) {
if (error.name === "TimeoutError") {
console.log (error.name)
console.log (`Screenshot saved as ${filename}`)
await page.screenshot({ path: filename, fullPage: true })
} else {
console.log (error)
}
}
})()

OAuth2 in electron application in current window

I'm trying to implement OAuth2 authentication in Angular 2 ( Electron ) application.
I achieve that on the way with a popup that is called after user click on 'Sign In' button.
In popup user types their credentials and allows the access and on confirm code is returned and I'm able to catch redirect request which I can't do without popup.
Here is implementation that works:
return Observable.create((observer: Observer<any>) => {
let authWindow = new electron.remote.BrowserWindow({ show: false, webPreferences: {
nodeIntegration: false
} });
authWindow.maximize();
const authUrl = AUTHORIZATION_WITH_PROOF_KEY_URL
+ `?client_id=${CLIENT_ID}&response_type=code&scope=api_search&`
+ `redirect_uri=${REDIRECT_URL}&code_challenge=${challenge}&code_challenge_method=S256`;
if (this.clearStorage) {
authWindow.webContents.session.clearStorageData({}, () => {
this.clearStorage = false;
authWindow.loadURL(authUrl);
authWindow.show();
});
} else {
authWindow.loadURL(authUrl);
authWindow.show();
}
authWindow.webContents.on('did-get-redirect-request', (event, oldUrl, newUrl) => {
const code = this.getCode(newUrl, authWindow);
if (!code) {
this.clearStorage = true;
return;
}
this.requestToken({
grant_type: 'authorization_code',
code: code,
code_verifier: verifier,
redirect_uri: REDIRECT_URL
})
.subscribe((response: { access_token: string, refresh_token: string }) => {
observer.next(response);
});
});
// Reset the authWindow on close
authWindow.on('close', () => {
authWindow = null;
});
});
and as you can see in above code I'm creating new BrowserWindow with:
new electron.remote.BrowserWindow({ show: false, webPreferences: {
nodeIntegration: false
} });
and with that approach I'm able to catch up redirect request with a block of code that starts with:
authWindow.webContents.on('did-get-redirect-request', (event, oldUrl, newUrl) => {
....
}
but I'm not able to solve this without popup ( modal ).
Here is my attempt:
return Observable.create((observer: Observer<any>) => {
let authWindow = electron.remote.getCurrentWindow();
const authUrl = AUTHORIZATION_WITH_PROOF_KEY_URL
+ `?client_id=${CLIENT_ID}&response_type=code&scope=api_search&`
+ `redirect_uri=${REDIRECT_URL}&code_challenge=${challenge}&code_challenge_method=S256`;
if (this.clearStorage) {
authWindow.webContents.session.clearStorageData({}, () => {
this.clearStorage = false;
authWindow.loadURL(authUrl);
});
} else {
authWindow.loadURL(authUrl);
}
authWindow.webContents.on('did-get-redirect-request', (event, oldUrl, newUrl) => {
debugger;
// this is not called, I'm not able to catch up redirect request
});
// Reset the authWindow on close
authWindow.on('close', () => {
authWindow = null;
});
});
With my approach I get login screen from remote URL in a current window, but the problem is that I'm not able to catch redirect request with ('did-get-redirect-request') event.
I also tried with 'will-navigate' and many others.
Although I don't have a direct answer I thought I'd point you to Google's AppAuth-JS libraries, which cover OAuth based usage for Electron Apps.
My company have used AppAuth libraries for the mobile case and they worked very well for us, so that we wrote less security code ourselves and avoided vulnerabilities.
There is also an Electron Code Sample.

Resources