# Flux WebRTC détaillé Ce document décrit pas-à-pas comment deux clients AnyDrop établissent une connexion P2P. ## Acteurs - **Alice** — émetteur (veut envoyer un fichier) - **Bob** — récepteur - **Signaling** — serveur WebSocket AnyDrop - **STUN** — `stun.l.google.com:19302` - **TURN** — fallback si nécessaire ## Scénario 1 : Alice et Bob sur le même Wi-Fi ### 1. Connexion au signaling ``` Alice ──WS──▶ Signaling : { type: "hello" } Signaling ──▶ Alice : { type: "welcome", peerId: "a1", displayName: "Renard Bleu" } Bob ──WS──▶ Signaling : { type: "hello" } Signaling ──▶ Bob : { type: "welcome", peerId: "b2", displayName: "Tigre Rouge" } ``` Le serveur voit que les deux connexions proviennent de la **même IP publique** (la box d'Arthur) → il les place dans la room LAN `hash(IP)`. ### 2. Annonce mutuelle ``` Signaling ──▶ Alice : { type: "peer-joined", peerId: "b2", displayName: "Tigre Rouge" } Signaling ──▶ Bob : { type: "peer-joined", peerId: "a1", displayName: "Renard Bleu" } ``` Chaque client affiche un pair cliquable dans l'UI. ### 3. Alice déclenche l'envoi Alice drop un fichier sur "Tigre Rouge". Son client crée une `RTCPeerConnection` en mode initiator : ```ts const pc = new SimplePeer({ initiator: true, trickle: true }); pc.addStream(file); // simplifié ``` ### 4. Collecte des ICE candidates Le navigateur d'Alice : - Regarde ses interfaces réseau → trouve `192.168.1.42` → candidate **host** - Interroge STUN → apprend son IP publique `82.x.x.x:54321` → candidate **srflx** (server reflexive) - Si TURN configuré → réserve une allocation → candidate **relay** ### 5. Échange SDP via signaling ``` Alice ──▶ Signaling : { type: "signal", to: "b2", data: } Signaling ──▶ Bob : { type: "signal", from: "a1", data: } ``` Bob reçoit l'offer, crée sa propre `RTCPeerConnection` (non-initiator) : ``` Bob ──▶ Signaling : { type: "signal", to: "a1", data: } Signaling ──▶ Alice : { type: "signal", from: "b2", data: } ``` Les nouveaux ICE candidates découverts après coup sont envoyés par la même route (trickle ICE). ### 6. Connectivity checks WebRTC teste toutes les paires de candidates (A.host × B.host, A.srflx × B.host, etc.) avec des paquets STUN binding request. Sur même LAN, **A.host ↔ B.host répond en premier** → ce chemin est sélectionné. Durée typique : **500 ms à 2 s**. ### 7. DataChannel ouvert ```ts peer.on('connect', () => { peer.send(JSON.stringify({ name: "photo.jpg", size: 2458112, mime: "image/jpeg", id: "f1" })); streamFileInChunks(file); }); ``` Le transfert se fait **directement via le Wi-Fi local** — débit proche du lien physique (WiFi 6 : 500 Mo/s possibles). ### 8. Fin Chunk final `{ eof: true, id: "f1" }`, les deux pairs ferment la RTCPeerConnection ou la gardent ouverte pour un prochain envoi. --- ## Scénario 2 : Alice partage un lien public à Charlie (autre réseau) ### 1. Alice génère un code Alice clique sur "Partager publiquement". Son client demande au serveur : ``` Alice ──▶ Signaling : { type: "create-public-room" } Signaling ──▶ Alice : { type: "public-room-created", code: "x7k", url: "https://anydrop.arthurbarre.fr/x7k" } ``` L'UI affiche le QR code + l'URL + le code en gros. ### 2. Charlie ouvre l'URL Charlie sur son téléphone scanne le QR → son navigateur ouvre `/x7k`. Son client se connecte au signaling avec l'intention de rejoindre la room `x7k` : ``` Charlie ──▶ Signaling : { type: "hello", joinCode: "x7k" } Signaling ──▶ Charlie : { type: "welcome", peerId: "c3", ... } Signaling ──▶ Alice : { type: "peer-joined", peerId: "c3", displayName: "Ours Vert" } Signaling ──▶ Charlie : { type: "peer-joined", peerId: "a1", displayName: "Renard Bleu" } ``` ### 3. Négociation WebRTC (comme au scénario 1) Sauf que maintenant les candidates **host** (IPs locales) ne se joindront jamais (réseaux différents). Ce sont les candidates **srflx** (IP publique via STUN) qui vont s'apparier : - NAT d'Alice fait un "hole punching" via STUN - NAT de Charlie pareil - Les paquets UDP se croisent et établissent le tunnel Ça fonctionne dans **~80% des cas** avec juste STUN. Sinon → fallback TURN (relay). ### 4. Transfert Même logique de chunks que scénario 1, mais débit limité par la bande passante internet (typiquement 10-100 Mo/s selon connexions). --- ## Pourquoi le P2P parfois échoue (→ TURN) - NAT symétrique (certaines box 4G, firewalls d'entreprise) : le port change à chaque destination → STUN impuissant - Firewall corporate bloquant UDP sortant - Dans ces cas, TURN relaie le trafic via un serveur intermédiaire (coût bande passante réel) On détecte l'échec de connectivité via `iceConnectionState === "failed"` et on peut afficher un message à l'utilisateur ou retenter avec TURN. --- ## Sécurité - **DTLS obligatoire** : WebRTC refuse d'ouvrir un DataChannel non chiffré - Les clés DTLS sont négociées dans le SDP, échangées via le signaling - Un attaquant sur le signaling pourrait théoriquement faire du MITM en modifiant les empreintes DTLS dans le SDP → pour une V2, on peut ajouter une vérification out-of-band (ex : afficher une "safety number" à comparer) - Pour la V1, on s'appuie sur HTTPS/WSS pour sécuriser le signaling