electron usare ipcRenderer anche con contextIsolation = true

AGIORNAMENTO : il codice  sembra funzionare correttamente :

ipcRenderer.on() esiste ma non riceve gli eventi credo che il problema sia in :

let new_ipcRenderer = {};
for(let pro in ipcRenderer){
         new_ipcRenderer[pro] =ipcRenderer[pro];
}

devo far in modo che le proprietà vengano copiate ricorsivamente sospeto in qualche prototipo di prototipo che non venga ricopiato come si deve .

nel frattempo segnalo che il seguente codice risolve il probleam anche se è una soluzione che non mi piace affatto :

contextBridge.exposeInMainWorld(
       "electron",
             {
                 ipcRenderer: {
                         ...ipcRenderer,
                          on: (channel, func) =>ipcRenderer.on(channel, (event, args)      =>func(null,args))
                                         }
              }
);
———————————————————–
Articolo originale:

Muovendo i primi passi con nel programmare in JavaScript usando Electron mi sono imbatturo in un problema che mi ha richiesto un bel pò di gratacapi per essere risolto .

in pratica per comunicare fra il main process ed il rendering process ci sono le ottime libreire di electronjs cioè ipcRenderer e ipcMain il problema sorge lato cliente con contextIsolation = true e tutte le variabili di ambiente settate di default in electronjs .

la soluzione sarebbe passare tutto ipcRenderer usando il preload attraverso contextBridge di electronjs che permette di far passare un ogetto da un ambiente all’altro in fase di precaricamento in questo modo :

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

contextBridge.exposeInMainWorld(
      "electron",
             {
                      ipcRenderer: ipcRenderer
              }
         );


Però se faccio così mi trovo a poter utilizzare la funzione ipcRenderer.send()che mi permette di inviare messaggi a ipcMain

ma invece ipcRenderer.on() non ne vuole sapere di funzionare .
indagando scopro che la funzione on() a differenza di Send() è una funzione di Prototipo e pertando non viene passata correttamente in quanto applica la clonazione strutturata , il limite di questo metodo appunto è che non copia le funzioni prototipo prototype, e quindi come si fa ?
in pratica dovedo trovare il modo di clonare “male” un ogetto e facendo in modo che le sue funzioni prototipo fossero passate non come tali ma come funzioni normali .
il frammento di codice che mi ha permesso di risolvere utto è stato il seguente :
//questo codice fa in modo che tutte le funzioni prototipo vengano copiate come se fossero funzioni normali
let new_ipcRenderer = {};
for(let pro in ipcRenderer){
         new_ipcRenderer[pro] =ipcRenderer[pro];
}
contextBridge.exposeInMainWorld(
     "electron",
          {
                   ipcRenderer: new_ipcRenderer
          }
);
nota : le best practices di Electron suggeriscono altro come vedi questa guida :
ma ritengo che ci siano casi in cui il contextIsolation = true non sia necessario ma che sia comunque un peccato non usarlo per poi usare una sola libreira che mi serve, ritengo che in questo contesto la mi ascelta sia un ottoimo compromesso fra semplicità e sicurezza .
segue codice completo :
File index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
    <!-- questo tag qui sotto abilita il Content-Security-Policy in breve csp se vi sono problemi nell'acceso alle risorse valutare di rimuoverlo -->
    <meta http-equiv="Content-Security-Policy" content="script-src 'self';"> 
    <title>Document</title>
</head>
<body>
    <button id="ipc_asincrono">ipc asincrono</button>
    <button id="ipc_sincrono">ipc sincrono</button>
    <div id="messaggio_ipc">per ora nessun messaggio </div>

    <script defer src="./js/web_renderer.js"></script>
</body>
</html>
file: main.js
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');

const createWindow = () => {
    const win = new BrowserWindow({
      width: 800,
      height: 600,
      webPreferences: {
        preload: path.join(__dirname, 'preload.js'),
        nodeIntegration: false,
        nodeIntegrationInWorker: false,
        contextIsolation: true,
        enableRemoteModule: false, 
        sandbox: true,
        webSecurity: true
      }
    })
  
    win.loadFile('index.html');
    win.webContents.openDevTools();// apre la console di debug di chroome
  }


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

 
  app.on('window-all-closed', () => {
    if (process.platform !== 'darwin'){
      app.quit()
    }
  })


  //END FASE DI AVVIO E CREAZIONE DELLE FINESTRA

  // START gestione mssaggio IPC

  
  ipcMain.on('messaggio_asincrono', (event, args) => {
    console.log(args);
    event.sender.send('ris_messaggio_asincrono', 'riposta asincrona a messagio asincrono');
  });

  // ricevuto messagio sincrono
  ipcMain.on('messaggio_sincrono', (event, args) => {
    console.log(args);
    event.returnValue = 'risposta sincrona a mesagio sincrono';
  });

  // END gestione mssaggio IPC

 

file preload.js
const {ipcRenderer, contextBridge } = require('electron');

let new_ipcRenderer = {};
console.log(new_ipcRenderer);
for(let pro in ipcRenderer){
  new_ipcRenderer[pro] = ipcRenderer[pro];
}

contextBridge.exposeInMainWorld(
  "electron",
  {
      ipcRenderer: new_ipcRenderer
  }
);

 

file web_renderer.js
window.addEventListener('load', (event) => {
    console.log('page is fully loaded');
    console.log(window.electron.ipcRenderer);

    const ipc_asincrono = document.querySelector("#ipc_asincrono");
    const ipc_sincrono = document.querySelector("#ipc_sincrono");
    const messaggio_ipc = document.querySelector('#messaggio_ipc');

    ipc_asincrono.addEventListener('click', (event) => {        
        window.electron.ipcRenderer.send('messaggio_asincrono', "ecco a te un messaggio asincrono")
    });

    // mi metto in ascolta di un eventuale risposta asincrona 
    window.electron.ipcRenderer.on('ris_messaggio_asincrono', (event, args) => {
        messaggio_ipc.innerHTML = args;
    });

    ipc_sincrono.addEventListener('click', (event) => { 
        let risposta = window.electron.ipcRenderer.sendSync('messaggio_sincrono', "ecco a te un messagio sincrono");
        messaggio_ipc.innerHTML = risposta;
    });
    
});