IPC Electron
StreamooS utilise deux canaux de communication distincts entre ses trois processus (main, preload, renderer) et les overlays. Ce document décrit comment chaque couche communique.
Vue d'ensemble
Règles fondamentales
- Le renderer ne peut jamais importer de modules Node.js / Electron
- Le renderer ne peut jamais appeler
ipcRendererdirectement - Toute communication passe par
window.streamoos(pont preload) - Les overlays passent uniquement par WebSocket (jamais par IPC)
Preload Bridge (window.streamoos)
Le script preload (app/src/preload/index.cts) expose via contextBridge une API typée que le renderer peut appeler de manière sécurisée :
// app/src/preload/index.cts
contextBridge.exposeInMainWorld("streamoos", {
sendCommand: (feature: string, action: string, data?: unknown) =>
ipcRenderer.invoke("command", { feature, action, data }),
onUpdate: (callback: (update: FeatureUpdate) => void) =>
ipcRenderer.on("update", (_, update) => callback(update)),
// ... autres méthodes
});Dans le renderer :
// app/src/renderer/
const state = await window.streamoos.sendCommand("timer", "get_state");
window.streamoos.onUpdate((update) => {
if (update.feature === "timer") {
/* ... */
}
});Canaux IPC (app/src/shared/ipc.ts)
Les canaux sont definis dans IPC_CHANNELS dans app/src/shared/ipc.ts et partages entre main et preload :
Application
| Canal | Direction | Description |
|---|---|---|
app/ping | renderer -> main | Verification de sante |
app/status | renderer -> main | Statut de l'app (serveur WS, overlays, fenetres) |
Commandes de features
| Canal | Direction | Description |
|---|---|---|
feature/command | renderer -> main | Envoyer une commande a un handler |
feature/update | main -> renderer | Diffusion d'etat (mirror des updates WS) |
feature/error | main -> renderer | Notification d'erreur de feature |
ws/event | main -> renderer | Evenements runtime WebSocket (connexion/deconnexion client, commandes) |
Twitch
| Canal | Direction | Description |
|---|---|---|
twitch/auth-start | renderer -> main | Demarrer le flux OAuth PKCE Twitch |
twitch/auth-status | main -> renderer | Callback du resultat d'auth |
twitch/disconnect | renderer -> main | Deconnecter Twitch |
Donnees utilisateur
| Canal | Direction | Description |
|---|---|---|
favorites/get / favorites/set | renderer <-> main | Preference de radio favorite |
settings/get / settings/set | renderer <-> main | Reglages de l'app (theme, langue, presets) |
profile/get / profile/set | renderer <-> main | Profil utilisateur (nom, avatar) |
profile/upload-avatar | renderer -> main | Upload d'image avatar (dialogue fichier) |
Telecommande
| Canal | Direction | Description |
|---|---|---|
remote/generate-pin | renderer -> main | Generer un nouveau PIN + URLs remote |
Overlays
| Canal | Direction | Description |
|---|---|---|
overlay/open | renderer -> main | Ouvrir un overlay integre dans une fenetre externe |
custom-overlay/list | renderer -> main | Lister les overlays personnalises |
custom-overlay/add | renderer -> main | Importer un overlay personnalise |
custom-overlay/remove | renderer -> main | Supprimer un overlay personnalise |
custom-overlay/open | renderer -> main | Ouvrir un overlay personnalise |
Handler Registry (Main)
Chaque feature implémente l'interface FeatureHandler (app/src/main/handlers/types.ts) :
interface FeatureHandler {
readonly feature: string;
handle(action: string, data: unknown): Promise<unknown>;
getState(): FeatureState;
}HandlerRegistry (app/src/main/handlers/registry.ts) route les commandes vers le bon handler :
ipcMain.handle('command', async (event, { feature, action, data }) => {
// 1. Vérification de l'origine (event.senderFrame)
// 2. Validation Zod du payload
// 3. Dispatch vers FeatureHandler
// 4. emitUpdate() → IPC + WS simultanément
})emitUpdate - synchronisation simultanée
Les overlays et le dashboard reçoivent toujours le même état au même moment.
Sécurité IPC
Vérification de l'origine expéditeur
Chaque handler ipcMain vérifie que le message vient bien d'une frame connue :
// ❌ Ne jamais faire
ipcMain.handle('command', async (event, data) => { ... })
// ✓ Vérification systématique
ipcMain.handle('command', async (event, data) => {
if (!isAllowedSender(event.senderFrame)) return;
// ...
})Mapping défensif des payloads
Ne jamais spreader un payload IPC inconnu :
// ❌
store.update({ ...payload });
// ✓ Mapping explicite des champs attendus
store.update({
volume: payload.volume,
track: payload.track,
});Validation Zod
Tous les payloads entrants (IPC et WS) passent par safeParse Zod avant traitement. Un payload invalide retourne une erreur sans crasher le process.
Types partagés (app/src/shared/)
Les types IPC sont définis dans app/src/shared/ipc.ts et importables dans main, preload et renderer :
import type { FeatureUpdate, CommandPayload } from "@shared/ipc";app/src/shared/ n'a aucune dépendance runtime - TypeScript uniquement.
