00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

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 contextBridge sono 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