Vi presento il mio nuovo progetto BlackSession, questo è un browser nato per gestire più account su una stessa piattaforma. Se ad esempio dobbiamo gestire più account come: lavoro, personale, cliente ecc... richiederebbe normalmente browser separati o profili manuali.

BlackSession risolve questo problema dato che ogni profilo è assegnato ad una finestra completamente isolata, con cookie, local storage e sessioni in comune, tutto dentro una singola finestra.
I browser mainstream hanno i "profili" ma li trattano come finestre separate con overhead elevato. L'obiettivo di BlackSession è diverso cioè rendere il context switching immediato tramite tab colorate per profilo, proxy dedicati per ogni profilo e zero sovrapposizione di sessioni. Il tutto con un'interfaccia minimalista avendo come punto di forza la leggerezza.

Premesse prima di partire
Vediamo prima dei concetti fondamentali prima di leggere l'architettura del browser.
Electron è un framework open source che permette di creare applicazioni desktop native usando HTML, CSS, JavaScript. Combina due motori: Chromium per il rendering dell'interfaccia e Node.js per l'accesso al sistema operativo. Quindi come risultato avremo un'app che sembra un programma nativo ma è scritta interamente in JavaScript/TypeScript. Ad esempio VS Code, Figma e Discord sono tutti basati su Electron.
Main process e renderer process Electron divide l'applicazione in due processi separati.
main process = backend quindi accesso a Node.js e alle API di sistema, gestisce le finestre e i file.
renderer = frontend quindi HTML/CSS/React, non ha accesso diretto al filesystem o alle API native.
IPC (Inter-Process Communication) Dato che i due processi sono isolati questi comunicano tramite IPC in modo asincrono. Il renderer chiama funzioni esposte tramite un preload script (un bridge sicuro) e il main risponde. In BlackSession ogni azione UI come aprire un tab, cambiare proxy, navigare a un URL è una chiamata IPC.
ESM (ECMAScript Modules) è un sistema di moduli standard di JavaScript basato su import ed export. Quindi come CommonJS (require) ma evoluto. Nel codice ho incluso "type": "module" in package.json per abilitarlo in tutto il progetto (anche per main process di Electron).
Iniziamo!
Lo stack tecnologico è incentrato su WebContentsView successore di BrowserView presente nelle versioni più recenti di Electron. Ogni tab è una view separata inclusa sulla finestra principale e dimensionata dinamicamente al resize:
// electron/main.ts
function resizeView(view: WebContentsView) {
const { width, height } = mainWindow.getBounds();
view.setBounds({ x: 0, y: 60, width, height: height - 60 });
}
// throttle CPU/memoria per i tab in background
function setTabActive(view: WebContentsView, active: boolean) {
view.webContents.setBackgroundThrottling(!active);
view.webContents.setAudioMuted(!active);
}
Il vero isolamento si basa sulle session di Electron con partizioni persistenti. Ogni profilo ottiene la propria partizione persist:<profileId>, quindi Electron lo gestisce quasi come una sandbox separata su disco: cookie, cache, IndexedDB tutto separato.

Architettura:
- Main process: tab e gestione sessioni - Gestisce WebContentsView, proxy, IPC
- Renderer process: React + Tailwind UI - title bar, tab bar, address bar, modali
- Preload bridge: contextBridge API - IPC type-safe tra renderer e main
- Session store: persist:<profileId> - Isolamento completo per profilo
Altro punto di forza per BlackSession è che ogni profilo può avere un proxy dedicato (HTTP, HTTPS, SOCKS5):

Questo viene applicato a livello di session prima che la tab carichi qualsiasi risorsa. Le credenziali sono memorizzate in una Map indicizzata per partizione e iniettate tramite app.on('login'):
// proxy in session prima di caricare
async function applyProxy(partition: string, proxy?: ProxyConfig) {
const s = session.fromPartition(partition);
if (!proxy?.enabled || !proxy.host || !proxy.port) {
await s.setProxy({ proxyRules: '' });
return;
}
await s.setProxy({
proxyRules: `${proxy.type}://${proxy.host}:${proxy.port}`
});
if (proxy.username && proxy.password) {
proxyCredentials.set(partition, {
username: proxy.username,
password: proxy.password,
});
}
}
// auto auth per proxy che richiedono credenziali
app.on('login', (event, webContents, _details, authInfo, callback) => {
if (!authInfo.isProxy) return;
const partition = wcPartitionMap.get(webContents.id);
const creds = proxyCredentials.get(partition);
if (creds) {
event.preventDefault();
callback(creds.username, creds.password);
}
});
N.B. le credenziali sono indicizzate per nome di partizione (stringa) ma non per riferimento all'oggetto Session. Questo evita di lasciare dati inutili quando le session vengono ricreate tra restart.

La scelta di usare vite-plugin-electron mi ha permesso di avere un unico processo di build per entrambi i target (renderer e main) con hot-reload durante lo sviluppo (cioè poter fare modifiche al codice con il programma avviato). Il main process è scritto in TypeScript con type: "module" e compilato in ESM. Combo un po' caotica perché bisogna fare attenzione a __filename e __dirname che non disponibili nativamente in ESM (risolti con fileURLToPath(import.meta.url)).
Stack:
- Build con Vite 8 + electron-builder: Windows (NSIS) Linux (deb, tar.gz)
- UI React 19 + Tailwind 4: JIT, nessun file CSS generato a build
In ambito di gestione crash e cleanup di risorse ogni WebContentsView registra listener per render-process-gone e unresponsive, con alert dedicato. Alla chiusura del tab o della finestra, destroyTab() rimuove la view, chiama webContents.destroy() e pulisce entrambe le map ausiliarie per evitare memory leak:
function destroyTab(tab: Tab) {
wcPartitionMap.delete(tab.view.webContents.id);
try { mainWindow?.contentView.removeChildView(tab.view); } catch (_) {}
try {
tab.view.webContents.stop();
tab.view.webContents.destroy();
} catch (_) {}
tabs.delete(tab.id);
}
Il context menu è nativo! Al posto di implementare un menu contestuale in HTML (che non si integra con l'OS) quindi ci sarebbe problemi di compatibilità spesso e volentieri, ho sfruttato electron.Menu con scelte basate sull'elemento selezionato: taglia/copia/incolla se il focus è su un campo editabile, copia/apri-in-nuova-scheda se si è su un link. L'evento open-url-in-new-tab viene inviato dal main al renderer via IPC che apre il tab nel profilo corretto.
Oltre a questo il browser presenta diversi shortcuts interessati come ctrl +/- per lo zoom e ctrl t che apre la finestra dei profili creati dall'utente.

Utilizzo risorse: Google Chrome vs Blacksession (idling)
(Testato su Win 11)

Garantisco che questo browser è in continua fase di aggiornamento e dimostra che con Electron è assolutamente fattibile avere accesso alle API di sistema senza alcun problema (avendo come punto di forza il fattore cross platform). La separazione tra main e renderer con un bridge IPC tipizzato mi ha portato a scrivere il progetto in circa 700 righe di codice, incredibile per le funzioni che attualmente sono disponibili!
Architettura completa.
Il browser è scaricabile nel sito ufficiale: blacksession.org

Avete idee per futuri aggiornamenti?