Preload Scripts
I preload scripts sono file JavaScript che vengono eseguiti nel Renderer Process prima che il contenuto della pagina web venga caricato. Hanno accesso sia alle API di Node.js che al contesto della pagina web, rendendoli il ponte ideale tra Main e Renderer Process.
Ruolo del Preload Script
Il preload script serve a:
- Esporre API sicure dal Main Process al Renderer Process
- Proteggere l’accesso alle API native del sistema
- Creare un layer di astrazione tra il codice web e le funzionalità di sistema
┌─────────────────────────────────────────────────┐
│ Renderer Process │
│ │
│ ┌──────────┐ contextBridge ┌───────────┐ │
│ │ preload.js│ ──────────────────►│ window.* │ │
│ │ │ │ (renderer)│ │
│ │ Node.js ✓ │ │ Node.js ✗ │ │
│ │ Electron ✓│ │ Web API ✓ │ │
│ └──────────┘ └───────────┘ │
└─────────────────────────────────────────────────┘
Creare un Preload Script Base
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
// Esponi API sicure al renderer tramite window.electronAPI
contextBridge.exposeInMainWorld('electronAPI', {
// Informazioni di sistema
platform: process.platform,
// Invocare funzioni nel Main Process
openFile: () => ipcRenderer.invoke('dialog:openFile'),
saveFile: (content) => ipcRenderer.invoke('dialog:saveFile', content),
// Ascoltare eventi dal Main Process
onMenuAction: (callback) => {
ipcRenderer.on('menu-action', (_event, action) => callback(action));
},
});
Collegare il Preload alla Finestra
Il preload script viene specificato nella configurazione della BrowserWindow:
// main.js
const { BrowserWindow } = require('electron');
const path = require('node:path');
const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true, // Obbligatorio per contextBridge
nodeIntegration: false, // Sicurezza: disabilitato
sandbox: true, // Sicurezza aggiuntiva (opzionale)
},
});
Importante: Il percorso del preload deve essere assoluto. Usa path.join(__dirname, ...) per costruirlo correttamente.
Usare le API Esposte nel Renderer
Dopo aver esposto le API con contextBridge, puoi accedervi nel renderer tramite l’oggetto window:
// renderer.js
document.getElementById('open-btn').addEventListener('click', async () => {
const filePath = await window.electronAPI.openFile();
console.log('File selezionato:', filePath);
});
// Ascoltare eventi dal menu
window.electronAPI.onMenuAction((action) => {
console.log('Azione menu:', action);
});
// Accedere a proprietà
console.log('Piattaforma:', window.electronAPI.platform);
Pattern Comuni
Esporre Operazioni CRUD
// preload.js
contextBridge.exposeInMainWorld('electronAPI', {
// Lettura
getSettings: () => ipcRenderer.invoke('settings:get'),
getSettingByKey: (key) => ipcRenderer.invoke('settings:getByKey', key),
// Scrittura
saveSettings: (settings) => ipcRenderer.invoke('settings:save', settings),
// Eliminazione
resetSettings: () => ipcRenderer.invoke('settings:reset'),
});
Esporre Listener con Cleanup
Per evitare memory leak, è importante poter rimuovere i listener:
// preload.js
contextBridge.exposeInMainWorld('electronAPI', {
onProgressUpdate: (callback) => {
const handler = (_event, progress) => callback(progress);
ipcRenderer.on('download:progress', handler);
// Restituisci una funzione di cleanup
return () => {
ipcRenderer.removeListener('download:progress', handler);
};
},
});
// renderer.js
const cleanup = window.electronAPI.onProgressUpdate((progress) => {
progressBar.style.width = `${progress}%`;
});
// Quando non serve più, rimuovi il listener
cleanup();
Esporre API con Validazione
// preload.js
contextBridge.exposeInMainWorld('electronAPI', {
writeFile: (filePath, content) => {
// Validazione nel preload per sicurezza
if (typeof filePath !== 'string' || filePath.length === 0) {
throw new Error('Percorso file non valido');
}
if (typeof content !== 'string') {
throw new Error('Il contenuto deve essere una stringa');
}
return ipcRenderer.invoke('fs:writeFile', filePath, content);
},
});
Cosa NON Fare nel Preload
Non esporre ipcRenderer direttamente
// ❌ PERICOLOSO - Non farlo mai!
contextBridge.exposeInMainWorld('electron', {
ipcRenderer: ipcRenderer,
});
// ❌ PERICOLOSO - Il renderer può inviare qualsiasi messaggio IPC
contextBridge.exposeInMainWorld('electron', {
send: (channel, data) => ipcRenderer.send(channel, data),
invoke: (channel, data) => ipcRenderer.invoke(channel, data),
});
Esporre solo canali specifici
// ✅ SICURO - Solo canali predefiniti
const ALLOWED_CHANNELS = ['dialog:openFile', 'dialog:saveFile', 'app:getVersion'];
contextBridge.exposeInMainWorld('electronAPI', {
invoke: (channel, ...args) => {
if (ALLOWED_CHANNELS.includes(channel)) {
return ipcRenderer.invoke(channel, ...args);
}
throw new Error(`Canale non autorizzato: ${channel}`);
},
});
Preload con TypeScript
Per avere type-safety nel renderer, definisci i tipi delle API esposte:
// preload.ts
import { contextBridge, ipcRenderer } from 'electron';
const electronAPI = {
openFile: (): Promise<string | null> =>
ipcRenderer.invoke('dialog:openFile'),
saveFile: (content: string): Promise<boolean> =>
ipcRenderer.invoke('dialog:saveFile', content),
platform: process.platform as NodeJS.Platform,
};
contextBridge.exposeInMainWorld('electronAPI', electronAPI);
// Esporta il tipo per il renderer
export type ElectronAPI = typeof electronAPI;
// global.d.ts - Dichiarazione globale per il renderer
import type { ElectronAPI } from './preload';
declare global {
interface Window {
electronAPI: ElectronAPI;
}
}
Context Isolation
Il preload script funziona correttamente solo con contextIsolation: true (default da Electron 12+). Questa opzione garantisce che:
- Il codice del preload e il codice della pagina web hanno scope separati
- Il preload non può essere manipolato dal codice della pagina
- Le API esposte con
contextBridgesono l’unico modo per comunicare
// Con contextIsolation: true (default, sicuro)
// Il preload ha il suo scope isolato
// window.myVar nel preload ≠ window.myVar nel renderer
// Con contextIsolation: false (INSICURO, da evitare)
// Il preload condivide lo scope col renderer
// Qualsiasi script nella pagina può accedere alle API di Node.js