Note: This site is currently "Under construction". I'm migrating to a new version of my site building software. Lots of things are in a state of disrepair as a result (for example, footnote links aren't working). It's all part of the process of building in public. Most things should still be readable though.

Electron v12 Example App Template

Code

TODO: Figure out why this isn't working in MDX

### There is a better version

This works, but there is a better version I'll post as soon as I can. 




### NOTE:

I spent a good bit of time figuring all this out. Then researching to make sure I had stuff in line I found a post that confirmed my approach and showed an example that I could have just used.... 

It does a different (and better) way of issolating the messages from main to renderer. 

You can read up on it here:

https://github.com/electron/electron/issues/9920#issuecomment-575839738





### TL;DR: 

Neither the <<link|official quick start tutorial|https://www.electronjs.org/docs/tutorial/quick-start) nor any other examples I could find provide a complete example of secure, bi-directional communication between the main.js Node file and the windows of an Electron (v12>> app. So, I made this full sample. More details below the code.

### Module Instillation

    mkdir electron_v12_app_template
    cd electron_v12_app_template
    npm init -y
    npm install --save-dev electron


### Updated package.json

    {
        "name": "electron_v12_app_template",
        "version": "1.0.0",
        "description": "Basic example of an Electron v12 app",
        "main": "main.js",
        "scripts": {
            "start": "electron ."
        },
        "devDependencies": {
            "electron": "^12.0.2"
        }
    }

### main.js

    const { app, BrowserWindow, ipcMain } = require('electron')
    const path = require('path')

    const overseer = {
        'count': 0 
    }

    function createWindow () {
        const win = new BrowserWindow({
            width: 800,
            height: 600,
            webPreferences: {
            preload: path.join(__dirname, 'preload.js')
            }
        })
        
        win.loadFile('index.html')

        win.webContents.openDevTools()

    }

    app.whenReady().then(() => {
        createWindow()
    })

    ipcMain.on('main:increment-count', (event, payload) => {
        console.log('main:increment-count')
        overseer.count += 1
    })

    ipcMain.on('main:request-count', (event, payload) => {
        console.log('main:request-count')
        event.reply('preload:set-count', overseer.count)
    })


### preload.js

    const { contextBridge, ipcRenderer } = require('electron')

    contextBridge.exposeInMainWorld(
        'bridgeAPI',
        {
            updateCountDisplay: () => { 
                console.log('preload:bridgeAPI:updateCountDisplay')
                ipcRenderer.send('main:request-count', {})
            },
            incrementCount: () => {
                console.log('preload:bridgeAPI:incrementCount')
                ipcRenderer.send('main:increment-count', {})
                ipcRenderer.send('main:request-count', {})
            },
        }
    )

    ipcRenderer.on('preload:set-count', (event, newCount) => {
        console.log("preload:set-count")
        document.getElementById('count').innerHTML = newCount
    })



### renderer.js

    document.getElementById('incrementCount').addEventListener('click', () => {
        console.log('renderer:incrementCount:click')
        bridgeAPI.incrementCount()
    })

    document.addEventListener('DOMContentLoaded', () => {
        console.log('renderer:DOMContentLoaded')
        bridgeAPI.updateCountDisplay()
    })


### index.html

    <!DOCTYPE html>
    <html>
        <head>
            <title>Electron v12 Example App Template</title>
        </head>
        <body>

            <div>Counter: <span id="count"></span><div>
            <button id="incrementCount">Increment the counter</button>

            <script src="./renderer.js"></script>

        </body>
    </html>


### Details

- Most Electron examples tell you to override the defaults for `nodeIntegration` and `contextIsolation` when setting up BrowserWindows to allow Node API calls (e.g. for reading files) from directly inside the app windows. That practice <<link|opens a security hole](https://www.electronjs.org/docs/tutorial/security#isolation-for-untrusted-content). The solution is to use inter-process communication via [ipcMain](https://www.electronjs.org/docs/api/ipc-main#ipcmain) and [ipcRenderer|https://www.electronjs.org/docs/api/ipc-renderer#ipcrenderer>> as demonstrated here. 
- This example uses async methods. Details on synchronous methods are available in the <<link|ipcMain](https://www.electronjs.org/docs/api/ipc-main#ipcmain) and [ipcRenderer|https://www.electronjs.org/docs/api/ipc-renderer#ipcrenderer>> docs. 
- There's nothing special about the colon separated channel names (e.g. `preload:set-count`), that's just a convention I'm using to track what files the functions are in.
- The `win.webContents.openDevTools()` in `main.js` opens up the dev console automatically. Not something you'd want to do in production but useful for the example to watch the communication traffic. 
- The `console.log()` messages from `main.js` show up in the terminal that's running the app. Those from  `renderer.js` and `preload.js` show up in the app/browser dev console.
- This was a very helpful page for getting this all figured out, but didn't show bi-directional communication.