sindresorhus / electron-store

Simple data persistence for your Electron app or module - Save and load user preferences, app state, cache, etc
MIT License
4.57k stars 148 forks source link

Please help in renderer process with contextIsolation and no remote etc #252

Closed thehans closed 1 year ago

thehans commented 1 year ago

I am trying to add electron-store to my project and have been struggling for days to get it to function at all, when using all the oppressive modern security defaults:

  nodeIntegration: false,
  contextIsolation: true,
  enableRemoteModule: false,
  sandbox: true,

Can anyone see what I might be doing wrong? Here is a rough summary of the code I'm trying to use

src/main/index.js

const { app, screen, ipcMain, BrowserWindow } = require('electron')
const isDev = require('electron-is-dev')
const path = require('path')
const Store = require('electron-store');

let win, store
app.whenReady().then(() => {

  store = new Store({ 
    defaults: { foo: 1, bar: 2, baz: 3 } 
  });

  // configuration (electron-store)
  ipcMain.on('config-load', () => store.store);
  ipcMain.on('config-get', store.get);
  ipcMain.on('config-set', store.set);
  ipcMain.on('config-has', store.has);

  const {width, height} = screen.getPrimaryDisplay().workAreaSize
  win = new BrowserWindow({
    width: width, height: height,
    icon: path.join(__dirname, '../favicon.png'),
    webPreferences: {
      preload: POD_CONTROLLER_PRELOAD_WEBPACK_ENTRY,
      nodeIntegration: false,
      contextIsolation: true,
      enableRemoteModule: false,
      sandbox: true,
    }
  })

  win.loadURL(POD_CONTROLLER_WEBPACK_ENTRY)
  if (isDev) {
    win.webContents.openDevTools()
  }
})

src/preload.js

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

contextBridge.exposeInMainWorld('config', {
  load: () => ipcRenderer.send('config-load'),
  get: (key, defaultValue) => ipcRenderer.send('config-get', key, defaultValue),
  set: (key, value) => ipcRenderer.send('config-set', key, value),
  has: (name) => ipcRenderer.send('config-has', name),
})

src/renderer/index.js

document.addEventListener('DOMContentLoaded', () => {
  let foo = window.config.get("foo");
  ...
})

I am able to build and start the app, but cannot read or write any config values.

I open a dev console and first try to see if I can view the whole store as one object:

>  window.config.load()
<- undefined

Then I test what happens if I try to read a value:

window.config.get("foo")

Screenshot from 2023-03-23 13-18-42

If I try to write a value:

window.config.set("bar", "ok")

Screenshot from 2023-03-23 13-20-09

And try to check for the existence of a value:

window.config.has("bar")

Screenshot from 2023-03-23 13-20-27

thehans commented 1 year ago

I finally resolved my issue. I had not realized that ipcRenderer.send is asynchronous, therefore it cannot return a value. (I'm still very new and learning a lot of electron/node ecosystem and APIs)

In order to do things synchronously, you have to use ipcRenderer.sendSync and the listener should set event.returnValue.

src/main/index.js

  ...
  ipcMain.on('config-load', (event) => { event.returnValue = store.get(); });
  ipcMain.on('config-get', (event, key, defaultValue) => { event.returnValue = store.get(key, defaultValue); });
  ipcMain.on('config-set', (event, key, value) => { store.set(key, value); });
  ipcMain.on('config-has', (event, key) => { event.returnValue = store.has(key); });

src/preload.js

...
contextBridge.exposeInMainWorld('config', {
  load: () => ipcRenderer.sendSync('config-load'),
  get: (key, defaultValue) => ipcRenderer.sendSync('config-get', key, defaultValue),
  set: (key, value) => ipcRenderer.sendSync('config-set', key, value),
  has: (key) => ipcRenderer.sendSync('config-has', key),
})

Now everything seems to be functioning as expected for me.