Skip to content

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 ipcRenderer directement
  • 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 :

typescript
// 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 :

typescript
// 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 ​

CanalDirectionDescription
app/pingrenderer -> mainVerification de sante
app/statusrenderer -> mainStatut de l'app (serveur WS, overlays, fenetres)

Commandes de features ​

CanalDirectionDescription
feature/commandrenderer -> mainEnvoyer une commande a un handler
feature/updatemain -> rendererDiffusion d'etat (mirror des updates WS)
feature/errormain -> rendererNotification d'erreur de feature
ws/eventmain -> rendererEvenements runtime WebSocket (connexion/deconnexion client, commandes)

Twitch ​

CanalDirectionDescription
twitch/auth-startrenderer -> mainDemarrer le flux OAuth PKCE Twitch
twitch/auth-statusmain -> rendererCallback du resultat d'auth
twitch/disconnectrenderer -> mainDeconnecter Twitch

Donnees utilisateur ​

CanalDirectionDescription
favorites/get / favorites/setrenderer <-> mainPreference de radio favorite
settings/get / settings/setrenderer <-> mainReglages de l'app (theme, langue, presets)
profile/get / profile/setrenderer <-> mainProfil utilisateur (nom, avatar)
profile/upload-avatarrenderer -> mainUpload d'image avatar (dialogue fichier)

Telecommande ​

CanalDirectionDescription
remote/generate-pinrenderer -> mainGenerer un nouveau PIN + URLs remote

Overlays ​

CanalDirectionDescription
overlay/openrenderer -> mainOuvrir un overlay integre dans une fenetre externe
custom-overlay/listrenderer -> mainLister les overlays personnalises
custom-overlay/addrenderer -> mainImporter un overlay personnalise
custom-overlay/removerenderer -> mainSupprimer un overlay personnalise
custom-overlay/openrenderer -> mainOuvrir un overlay personnalise

Handler Registry (Main) ​

Chaque feature implémente l'interface FeatureHandler (app/src/main/handlers/types.ts) :

typescript
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 :

typescript
// ❌ 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 :

typescript
// ❌
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 :

typescript
import type { FeatureUpdate, CommandPayload } from "@shared/ipc";

app/src/shared/ n'a aucune dépendance runtime - TypeScript uniquement.

Released under the MIT License.