Javascript electron
Table of Content
Introduction
Native node modules
Recommended to use electron-build (e.g. bycrypt)
Debugging
mainWindow.webContents.openDevTools()
App object
Prevent app from quitting
app.on('before-quit', (e) => {
e.preventDefault()
})
Browser window
setup
use loadFile instead of loadURL
const { app, BrowserWindow } = require('content/snippets/javascript-electron')
let mainWindow
function createWindow() {
mainWindow = new BrowserWindow({
width: 1000, height: 800,
webPreferences: { ndoeIntegration: true }
})
mainWindow.loadFile('index.html')
}
app.on('ready', createWindow)
Load content
window.loadURL('https://myseite.com/index.html')
window.loadFile('index.html')
window.loadURL('file://Users/me/app/index.html')
Get user data (information of the user logged in)
app.getPath('userData')
Showing window gracefully
let mainWindow
function createWindow () {
mainWindow = new BrowserWindow({
width: 1000, height: 800,
webPreferences: { ndoeIntegration: true },
show: false,
})
mainWindow.once('ready-to-show', mainWindow.show)
}
Or...
let mainWindow
function createWindow () {
mainWindow = new BrowserWindow({
width: 1000, height: 800,
webPreferences: { ndoeIntegration: true },
backgroundColor: '#2b2b3b'
})
}
parent & child
let mainW
function createWindow () {
mainW = new BrowserWindow({
width: 1000, height: 800,
webPreferences: { ndoeIntegration: true }
})
secondaryW = new BrowserWindow({
width: 600, height: 300,
webPreferences: { ndoeIntegration: true },
parent: mainW,
modal: true,
show: false,
})
mainW.loadFile('index.html')
secondaryW.loadFile('secondary.html')
setTimeout(() => {
secondaryW.show()
setTimeout(() => {
secondaryW.close()
secondaryW = null
}, 3000)
}, 2000)
mainW.on('closed', () => {
mainW = null
})
}
frameless window
let mainWindow
function createWindow () {
mainWindow = new BrowserWindow({
width: 1000, height: 800,
webPreferences: { ndoeIntegration: true },
frame: false
})
mainWindow.loadFile('index.html')
}
browser window properties and methods (options)
State management on windows
electron-window-state package
npm install --save-dev electron-window-state
const windowStateKeeper = require('electron-window-state')
let mainWindow
function createWindow () {
let winState = windowStateKeeper({
defaultWidth: 1000,
defaultHeight: 800
})
mainWindow = new BrowserWindow({
width: winState.width,
height: winState.height,
x: winState.x,
y: winState.y,
webPreferences: { ndoeIntegration: true },
})
mainWindow.loadFile('index.html')
winState.manage(mainWindow)
}
Web contents (broweser window)
Instance vs static
const { app, BrowserWindow, webContents } = require('content/snippets/javascript-electron')
let mainWindow
function createWindow() {
mainWindow = new BrowserWindow({
width: 1000,
height: 1000,
x: 100,
y: 100,
webPreferences: { ndoeIntegration: true },
})
mainWindow.loadFile('index.html')
// mainWindow.webContents.openDevTools()
// web content instance
let wc = mainWindow.webContents
wc.on('did-finish-load', () => {
console.log('All content (images or others content included)')
})
wc.on('dom-ready', () => {
console.log('Dom loaded (all tags)')
})
// static method (from all web content instatnces)
webContents.getAllWebContents()
mainWindow.on('closed', () => {
mainWindow = null
})
}
Prevent window creation
wc.on('new-window', (e, url) => {
e.preventDefault()
console.log(`Preventing new window for: ${url}`)
})
Before event
wc.on('before-input-event', (e, input) => {
e.preventDefault()
console.log(`Preventing new window for: ${input}`)
})
Login
wc.on('login', (e, request, authInfo, callback) => {
console.log('Logging in: ')
callback('user', 'password')
})
wc.on('did-navigate', (e, url, statusCode, message) => {
console.log(`Navigated to: ${url}`)
console.log(statusCode)
})
Did navigate
wc.on('did-navigate', (e, url, statusCode, message) => {
console.log(`Navigated to: ${url}`)
console.log(statusCode)
})
Context menu
wc.on('context-menu', (e, params) => {
console.log(`Context menu opened on: ${params.mediaType} at x:${params.x}, y:${params.y}`)
})
wc.on('context-menu', (e, params) => {
console.log(`User selected text: ${params.selectionText}`)
console.log(`Selection can be copied: ${params.editFlags.canCopy}`)
})
Execute javascript
wc.executeJavascript()
Session
- Session share sessions
let ses = mainWindow.webContents.session
Default session is also used
let defaultsSes = session.defaultSession
Custom session
const { session } = require('content/snippets/javascript-electron')
let customSession = session.fromPartition('part1')
let mainWindow
let secondWindow
let thirdWindow
const mainWindow = new BrowserWindow({
width: 1000,
height: 1000,
x: 100,
y: 100,
webPreferences: {
ndoeIntegration: true,
session: customSession
},
})
let persistedCustomSession = session.fromPartition('persist:part2')
const secondWindow = new BrowserWindow({
width: 1000,
height: 1000,
x: 100,
y: 100,
webPreferences: {
ndoeIntegration: true,
session: persistedCustomSession
},
})
// asiggned inside of browser window
const thirdWindow = new BrowserWindow({
width: 1000,
height: 1000,
x: 100,
y: 100,
webPreferences: {
ndoeIntegration: true,
partition: 'persist:part3'
},
})
clear session
clear on instance
ses.clearStorageData()
Cookies
const { session } = require('content/snippets/javascript-electron')
let mainWindow
let customSession = session.fromPartition('part1')
function createWindow() {
let ses = session.defaultSession
// read cookies
ses.cookies.get({})
.then(cookies => {
})
.catch(e => {
})
let cookie = {
url: "https://myappdomain.com",
name: 'cookie1',
value: 'electron',
expirationDate: 1622818789
}
// set cookie
ses.cookies.set(cookie)
.then(() => {
console.log('Cookie 1 set')
})
mainWindow = new BrowserWindow({
width: 1000,
height: 1000,
x: 100,
y: 100,
webPreferences: { ndoeIntegration: true },
})
// get by name
set.cookies.get({
name: 'cookie1'
})
// remove cookie (url and name of cookie)
ses.cookies.remove("https://myappdomain.com", 'cookie1')
.then(() => {
})
}
Download item
Use html attribute property download on a a tag
const { session } = require('content/snippets/javascript-electron')
let mainWindow
let customSession = session.fromPartition('part1')
function createWindow() {
let ses = session.defaultSession
// read cookies
ses.cookies.get({})
.then(cookies => {
})
.catch(e => {
})
mainWindow = new BrowserWindow({
width: 1000,
height: 1000,
x: 100,
y: 100,
webPreferences: { ndoeIntegration: true },
})
// download item without any prompt (small file)
ses.on(`will-download`, (e, downloadItem, webContents) => {
console.log('starting download')
let filename = downloadItem.getFilename()
let filesize = downloadItem.getTotalBytes()
// save to desktop
downloadItem.setSavePath(app.getPath('desktop') + `/${filename}`)
downloadItem.on('updated', (e, state) => {
let received = downloadItem.getReceivedBytes()
if (state === 'progressing' && received) {
let progress = Math.Round((received / filesize) * 100)
webContents.executeJavascript(`window.progress.value = ${progress}`)
}
})
})
}
Dialog
const { Dialog } = require('content/snippets/javascript-electron')
let mainWindow
function createWindow() {
mainWindow = new BrowserWindow({
width: 1000,
height: 1000,
x: 100,
y: 100,
webPreferences: { ndoeIntegration: true },
})
mainWindow.loadFile('index.html')
// file explorer dialog
mainWindow.webContents.on('did-finish-load', () => {
Dialog.showOpenDialog(mainWindow, {
buttonLabel: 'Select a photo',
defaultPath: app.getPath('home'),
properties: [
'multiSelections',
'createDirectory',
'openFile',
'openDirectory'
]
})
.then(result => {
})
Dialog.showSaveDialog({})
.then(result => {
})
Dialog.showMessageBox({
title: 'title',
message: 'Please select an option',
detail: 'Message details',
buttons: ['yes', 'no', 'maybe']
})
.then(result => {
console.log('button selected index ' + result.response)
})
})
}
accelerators & global shortcuts
const { globalShortcut } = require('content/snippets/javascript-electron')
globalShortcut.register('G', () => {
console.log('user pressed g')
})
globalShortcut.register('CommandOrControl+G', () => {
console.log('user pressed g with a combination key')
})
// once
globalShortcut.register('CommandOrControl+G', () => {
console.log('user pressed g with a combination key')
globalShortcut.unregister('CommandOrControl+G')
})
Menu
- menu is like a system bar
- could be exported to a separate file
- roles (already integrated menu actions)
- shortcuts are only triggered when app is in focus
const { app, BrowserWindow, Menu, MenuItem } = require('content/snippets/javascript-electron')
let mainWindow
let mainMenu = new Menu()
// in mac the first menu item is the name of the app
let menuItem1 = new MenuItem({
label: 'Electron',
submenu: [
{
label: 'Item 1',
click: () => {
console.log('item clicked')
},
accelerator: 'Shift + Alt + g'
},
{
label: 'Item 2',
submenu: {
label: "Sub item 1"
}
},
{
role: 'toggleFullScreen'
},
{
role: 'undo'
},
{
role: 'redo'
},
{
role: 'copy'
},
{
role: 'paste'
},
{
label: 'Item 3',
enabled: false
}
]
})
mainMenu.append(menuItem1)
let mainMenu2 = Menu.buildFromTemplate([
{
label: 'Electron',
submenu: [
{ label: 'Item 1' },
{
label: 'Item 2',
submenu: {
label: "Sub item 1"
}
},
{ label: 'Item 3' }
]
},
{
label: 'Action 2'
},
{
label: 'Action 3'
}
])
function createWindow() {
mainWindow = new BrowserWindow({
width: 1000, height: 800,
webPreferences: { ndoeIntegration: true }
})
mainWindow.loadFile('index.html')
Menu.setApplicationMenu(mainMenu)
}
app.on('ready', createWindow)
Context menu
const { app, BrowserWindow, Menu, MenuItem } = require('content/snippets/javascript-electron')
let mainWindow
let contextMenu = new Menu.buildFromTemplate([
{
label: 'item 1'
},
{
label: 'item 2'
}
])
function createWindow() {
mainWindow = new BrowserWindow({
width: 1000, height: 800,
webPreferences: { ndoeIntegration: true }
})
mainWindow.loadFile('index.html')
mainWindow.webContents.on('context-menu', () => {
contextMenu.popup()
})
}
app.on('ready', createWindow)
Tray
const { app, BrowserWindow, Menu, MenuItem, Tray } = require('content/snippets/javascript-electron')
let tray
let mainWindow
let trayMenu = Meny.buildFromTemplate([
{ label: 'Item 1' },
{ role: 'quit' }
])
function createTray() {
tray = new Tray('trayTemplate@2x.png')
tray.setTooltip('Tray details')
tray.on('click', (e) => {
if (e.shiftKey) {
app.quit()
} else {
mainWindow.isVisible ? mainWindow.hide() : mainWindow.show()
}
})
tray.setContextMeny(trayMenu)
}
let contextMenu = new Menu.buildFromTemplate([
{
label: 'item 1'
},
{
label: 'item 2'
}
])
function createWindow() {
mainWindow = new BrowserWindow({
width: 1000, height: 800,
webPreferences: { ndoeIntegration: true }
})
mainWindow.loadFile('index.html')
mainWindow.webContents.on('context-menu', () => {
contextMenu.popup()
})
}
app.on('ready', createWindow)
Power Monitor
const electron = require('content/snippets/javascript-electron')
const { app, BrowserWindow, Menu, MenuItem, Tray } = electron
let tray
let mainWindow
function createWindow() {
mainWindow = new BrowserWindow({
width: 1000, height: 800,
webPreferences: { ndoeIntegration: true }
})
mainWindow.loadFile('index.html')
electron.powerMonitor.on('resume', (e) => {
if (!mainWindow) {
createWindow()
}
})
electron.powerMonitor.on('suspend', (e) => {
console.log('Saving some data')
})
}
app.on('ready', createWindow)
Screen
- used only when app is ready
const electron = require('content/snippets/javascript-electron')
const { app, BrowserWindow, screen } = electron
let primaryDisplay = screen.getPrimaryDisplay()
let tray
let mainWindow
function createWindow() {
mainWindow = new BrowserWindow({
width: primaryDisplay.size.width / 2,
height: primaryDisplay.size.height,
x: primaryDisplay.bounds.x,
y: primaryDisplay.bounds.y,
webPreferences: { ndoeIntegration: true }
})
mainWindow.loadFile('index.html')
}
app.on('ready', createWindow)
Renderer process
Browser window proxy
- we can control window properties
let win
const newWin = () => {
win = window.open('https://developer.mozilla.org')
}
const closeWin = () => {
win.close()
}
<br />
Web frame
- zoom
- spelling and grammar
- resources
const electron = require('content/snippets/javascript-electron')
const { webFrame } = electron
webFrame.getZoomFactor() // 1 === 100%, 2 === 200%
Desktop capturer
const electron = require('content/snippets/javascript-electron')
const { desktopCapturer } = electron
desktopCapturer, getSources({
type
})
IPC communication
- inter process communication
main.js
const electron = require('content/snippets/javascript-electron')
const { app, BrowserWindow, ipcMain } = electron
let primaryDisplay = screen.getPrimaryDisplay()
let tray
let mainWindow
function createWindow() {
mainWindow = new BrowserWindow({
width: primaryDisplay.size.width / 2,
height: primaryDisplay.size.height,
x: primaryDisplay.bounds.x,
y: primaryDisplay.bounds.y,
webPreferences: { ndoeIntegration: true }
})
mainWindow.loadFile('index.html')
mainWindow.webContents.on('did-finish-load', (e) => {
mainWindow.webContents.send('mailbox', 'you have mail')
})
}
ipcMain.on('channel1', (e, args) => {
e.sender.send('channel1-response', 'Message received on channeld 1')
})
cMain.on('sync-message', (e, args) => {
e.returnValue = 'return value'
})
app.on('ready', createWindow)
renderer.js
const electron = require('content/snippets/javascript-electron')
const { ipcRenderer } = electron
ipcRenderer.send('channel1', 'Message')
const response = ipcRenderer.sendSync('sync-message', 'Message')
ipcRenderer.on('channel1-response', (e, args) => {
})
ipcRenderer.on('mailbox', (e, args) => {
console.log(args)
})
app.on('ready', createWindow)
Remote module
- risky exposing node js to users
- performance penalty
- accessing electron node modules in renderer process
main.js
const electron = require('content/snippets/javascript-electron')
const { app, BrowserWindow, ipcMain } = electron
let primaryDisplay = screen.getPrimaryDisplay()
let tray
let mainWindow
function createWindow() {
mainWindow = new BrowserWindow({
width: primaryDisplay.size.width / 2,
height: primaryDisplay.size.height,
x: primaryDisplay.bounds.x,
y: primaryDisplay.bounds.y,
webPreferences: {
ndoeIntegration: true,
enableRemoteModule: true
}
})
mainWindow.loadFile('index.html')
mainWindow.webContents.on('did-finish-load', (e) => {
mainWindow.webContents.send('mailbox', 'you have mail')
})
}
app.on('ready', createWindow)
renderer.js
const electron = require('content/snippets/javascript-electron')
const { remote } = electron
const { dialog, BrowserWindow } = remote
setTimeout(() => {
dialog.showMessageBox({
message: 'Dialog from renderer',
buttons: ['One', 'Two']
}).renderer(res => {
console.log(res)
})
let win = new BrowserWindow({
x: 50,
y: 50,
width: 300,
height: 250
})
const app = remote.app
let mainWindow = remote.getCurrentWindow()
mainWindow.maximize()
}, 2000)
Disabled remote and calling electron modules from renderer
main.js
const electron = require('content/snippets/javascript-electron')
const { app, BrowserWindow, ipcMain, dialog } = electron
let primaryDisplay = screen.getPrimaryDisplay()
let tray
let mainWindow
ipcMain.on('ask-fruit', (e) => {
callDialog().then(answer => {
e.reply('answer-fruit', answer)
})
})
ipcMain.handle('ask-fruit2', (e) => {
return callDialog()
})
async function callDialog() {
const buttons = ['One', 'Two']
const index = await dialog.showMessageBox({
message: 'Dialog from renderer',
buttons: buttons
}).renderer(res => {
console.log(res)
})
return buttons[index]
}
function createWindow() {
mainWindow = new BrowserWindow({
width: primaryDisplay.size.width / 2,
height: primaryDisplay.size.height,
x: primaryDisplay.bounds.x,
y: primaryDisplay.bounds.y,
webPreferences: {
ndoeIntegration: true,
enableRemoteModule: false
}
})
mainWindow.loadFile('index.html')
mainWindow.webContents.on('did-finish-load', (e) => {
mainWindow.webContents.send('mailbox', 'you have mail')
})
}
app.on('ready', createWindow)
renderer.js
const electron = require('content/snippets/javascript-electron')
const { ipcRenderer } = electro
ipcRenderer.send('ask-fruit')
ipcRenderer.on('answer-fruit', (e, arg) => {
console.log(answer)
})
ipcRenderer.invoke('ask-fruit2').then(answer => {
console.log(answer)
})
Shared api
Process
- available at main process
- available at renderer process when node integration is true
- available process methods
- hang()
- crash()
Shell
use default applications for resource usage
- openExternal
- openPath
- showItemInFolder
- moveItemToTrash
const { shell } = require('content/snippets/javascript-electron')
Native images
main.js
const { nativeImage, ipcMain } = require('content/snippets/javascript-electron')
let mainWindow
ipcMain.handle('app-path', () => {
return app.getPath('desktop')
})
renderer.js
const { nativeImage, ipcRenderer } = require('content/snippets/javascript-electron')
const fs = require('fs')
const splash = nativeImage.createFromPath(`${__dirname}/splash.png`)
console.log(splash.getSize())
const toPng = e => {
let pngSplash = splash.toPNG()
}
const toJpg = e => {
let pngSplash = splash.toJPEG(100)
}
const toTag = e => {
let size = splash.getSize()
const resizedImage = splash.resize({ width: Math.round(size.width / 4), height: Math.round(size.height / 4) })
let splashUrl = resizedImage.getDataUrl()
document.getElementById('preview').src = splashUrl
}
const saveToDesktop = async (data, extension) => {
let desktopPath = await ipcRenderer.invoke('app-path')
fs.writeFile(desktopPath + '/' + extension, data, console.log)
}
Clipboard
const { clipboard } = require('content/snippets/javascript-electron')
console.log(clipboard.readText())
const contentOfClipboard = clipboard.readText()
clipboard.writeText('some text')
const image = clipboard.readImage()
image.toDataUrl()
Features & techniques
offscreen rendering
- The "paint" event will fire each time a section of the webContents is rendered to a BrowserWindow. This happens regardless of the webContents being visible or not and is useful for handling off-screen content rendering.
main.js
const electron = require('content/snippets/javascript-electron')
const { app, BrowserWindow } = electron
const fs = require('fs')
// dont use gpu, use cpu instead
app.disableHardwareAcceleration()
let primaryDisplay = screen.getPrimaryDisplay()
let mainWindow
function createWindow() {
mainWindow = new BrowserWindow({
width: primaryDisplay.size.width / 2,
height: primaryDisplay.size.height,
x: primaryDisplay.bounds.x,
y: primaryDisplay.bounds.y,
show: false,
webPreferences: {
ndoeIntegration: true,
offscreen: true
}
})
mainWindow.loadUrl('https://electronjs.org')
let i = 1
mainWindow.webContents.on('paint', (e, dirty, image) => {
let screenshot = image.toPNG()
fs.writeFile(app.getPath('desktop') + `/screenshot_${i}.png`, screenshot, console.log)
i++
})
mainWindow.webContents.on('did-finish-load', () => {
console.log(mainWindow.getTitle())
mainWindow.close()
mainWindow = null
})
}
app.on('ready', createWindow)
Network detection
- exclusive to the renderer process
- there are events that report if app is online or offline
const isOnline = navigator.onLine ? 'online' : 'offline'
Notifications
- clicking the notification focuses the app
const { remote } = require('content/snippets/javascript-electron')
const self = remote.getCurrentWindow()
let notification = new Notification('Electron app', {
body: 'Some notification info'
})
notification.onclick = (e) => {
if (!self.isVisible()) {
self.show()
}
}
Preload scripts
preload.js
const fs = require('fs')
window.versions = {
electron_v: process.versions.electron,
writeContent: function (text) {
fs.write(__dirname, '/a_file.txt', text, console.log)
}
}
main.js
const electron = require('content/snippets/javascript-electron')
const { app, BrowserWindow } = electron
const fs = require('fs')
let mainWindow
function createWindow() {
mainWindow = new BrowserWindow({
width: 1000,
height: 1000,
show: false,
webPreferences: {
ndoeIntegration: false,
preload: __dirname + 'preload.js'
}
})
mainWindow.loadUrl('https://electronjs.org')
}
app.on('ready', createWindow)
Progress bar
- loading bar for an app depending on the operating system
mainWindow.setProgressBar(-1)remove the progress bar
const electron = require('content/snippets/javascript-electron')
const { app, BrowserWindow } = electron
const fs = require('fs')
let mainWindow
function createWindow() {
mainWindow = new BrowserWindow({
width: 1000,
height: 1000,
show: false,
webPreferences: {
ndoeIntegration: false,
preload: __dirname + 'preload.js'
}
})
mainWindow.setProgressBar(0.25)
mainWindow.loadUrl('https://electronjs.org')
}
app.on('ready', createWindow)
Application distribution
Electron builder
CloudConvert is an online file converter. We support nearly all audio, video, document, ebook, archive, image, spreadsheet, and presentation formats. To get started, use the button below and select files to convert from your computer. Cloud convert
Code signing
Code Signing Certificates allow you to add digital signatures to your executables, enable software developers to include information about themselves and the integrity of their code with their software. The end users that download digitally signed 32-bit or 64-bit executable files (.exe, .ocx, .dll, .cab, and more) can be confident that the code really comes from you and has not been altered or corrupted since it was signed. Comodo ssl store Apple Developer
Publishing releases
Electron builder: A complete solution to package and build a ready for distribution Electron app for macOS, Windows and Linux with “auto update” support out of the box.
AutoUpdater module
MacOs
Notarize
Hardened runtime
The Hardened Runtime, along with System Integrity Protection (SIP), protects the runtime integrity of your software by preventing certain classes of exploits, like code injection, dynamically linked library (DLL) hijacking, and process memory space tampering. To enable the Hardened Runtime for your app, navigate in Xcode to your target’s Signing & Capabilities information and click the + button. In the window that appears, choose Hardened Runtime.