Aller au contenu
HexaTransfer
Retour au blog
Approfondissements techniques

WebRTC transfert de fichiers : tutoriel navigateur à navigateur

Transférez des fichiers directement entre navigateurs avec WebRTC. Canaux de données, signalisation et configuration de connexion pair pour le partage en temps réel.

Le transfert de fichiers WebRTC déplace les octets directement entre deux navigateurs via un RTCDataChannel, sans serveur dans le chemin des données après la poignée de main initiale. Les éléments nécessaires : un canal de signalisation (WebSocket ou un petit relais quelconque) pour échanger offres SDP et candidats ICE, des serveurs STUN pour la découverte NAT, un repli TURN pour les NAT symétriques, et un canal de données configuré pour la livraison ordonnée et fiable. Une fois la connexion pair établie, vous faites channel.send() avec des chunks de 16 Ko à 256 Ko et regardez les octets affluer sur une association SCTP chiffrée DTLS 1.2 à environ la vitesse du réseau. Pour les organisations soucieuses de leur conformité RGPD et des recommandations ANSSI, l'architecture P2P navigateur présente un avantage réel : aucune donnée ne transite par un serveur tiers, aucune copie intermédiaire n'est créée.

Comment WebRTC vous amène réellement en pair à pair

WebRTC n'est pas magique — c'est ICE plus SDP plus DTLS plus SCTP empilés ensemble. L'expéditeur crée un RTCPeerConnection, ouvre un canal de données, génère une offre SDP, et l'envoie au destinataire via votre canal de signalisation. Le destinataire répond. Les deux parties échangent alors des candidats ICE (IP locale, IP réflexive via STUN, IP de relais via TURN) jusqu'à trouver un chemin fonctionnel. DTLS 1.2 effectue la poignée de main de bout en bout, SCTP monte dessus pour le streaming fiable, et vos octets commencent à affluer.

Le chiffrement est obligatoire et intégré. Vous ne pouvez pas le désactiver. C'est un avantage de sécurité significatif — contrairement aux WebSockets, vous n'avez pas besoin de vous souvenir d'envelopper quoi que ce soit dans de la crypto applicative pour protéger contre les espions réseau. Pour une vraie mise en œuvre de chiffrement de bout en bout contre votre propre serveur de signalisation, superposez un deuxième tour d'AES-256-GCM au-dessus, car un serveur de signalisation malveillant pourrait substituer son propre certificat DTLS.

Signalisation : la partie que WebRTC ne définit pas

WebRTC délibérément laisse la signalisation à votre charge. Un WebSocket sur votre serveur convient ; de même qu'une salle partagée postée dans une Firebase Realtime DB, ou même des chaînes SDP collées manuellement. Ce qui compte c'est que les deux pairs échangent finalement une offre, une réponse, et un flux de candidats ICE.

Serveur de signalisation minimal en Node :

const rooms = new Map();
wss.on('connection', (ws) => {
  ws.on('message', (raw) => {
    const msg = JSON.parse(raw);
    if (msg.type === 'join') {
      const room = rooms.get(msg.room) ?? new Set();
      room.add(ws); rooms.set(msg.room, room);
    } else {
      for (const peer of rooms.get(msg.room) ?? []) {
        if (peer !== ws) peer.send(raw);
      }
    }
  });
});

Moins de 20 lignes. Le serveur ne voit jamais les octets des fichiers — seulement les métadonnées SDP et ICE. Vous pouvez l'héberger sur un VPS à 5 € ou Cloudflare Workers et servir des centaines de transferts simultanés.

Établir la connexion pair

Créez la connexion avec le STUN public de Google plus un repli TURN :

const pc = new RTCPeerConnection({
  iceServers: [
    { urls: 'stun:stun.l.google.com:19302' },
    { urls: 'turn:turn.example.com:3478',
      username: 'user', credential: 'pass' }
  ]
});

Environ 15 à 25 % des connexions résidentielles se trouvent derrière des NAT symétriques que STUN ne peut pas traverser, donc TURN n'est pas optionnel pour un service en production. Faites tourner coturn sur un VPS avec suffisamment de bande passante pour gérer le trafic relayé, ou payez un fournisseur TURN géré comme Xirsys ou Twilio.

Ouvrez le canal de données côté offrant avant de créer l'offre :

const channel = pc.createDataChannel('file', {
  ordered: true, maxRetransmits: null
});
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
signaling.send({ type: 'offer', sdp: offer.sdp });

Ordonné + retransmissions illimitées vous donne une fiabilité équivalente à TCP. Le mode non ordonné est plus rapide mais nécessite un réassemblage au niveau applicatif.

Découper le fichier pour le canal

SCTP a une limite pratique de 256 Ko par message, et les anciens navigateurs peinent au-dessus de 16 Ko. Une valeur sûre par défaut est des chunks de 16 Ko lus depuis le fichier via File.slice() :

const chunkSize = 16 * 1024;
let offset = 0;
channel.bufferedAmountLowThreshold = 1024 * 1024;
function sendNext() {
  while (offset < file.size && channel.bufferedAmount < 4 * 1024 * 1024) {
    const chunk = file.slice(offset, offset + chunkSize);
    chunk.arrayBuffer().then((buf) => channel.send(buf));
    offset += chunkSize;
  }
}
channel.onbufferedamountlow = sendNext;
sendNext();

La marque bufferedAmount vous empêche de mettre des gigaoctets en file d'attente dans le tampon d'envoi SCTP et de manquer de mémoire. Quand le tampon descend sous 1 Mo, remplissez jusqu'à 4 Mo. Cela donne un débit proche de 40 à 80 Mo/s sur les réseaux locaux et 5 à 20 Mo/s sur le haut débit résidentiel typique.

Recevoir et écrire les octets sur le disque

Côté répondant, écoutez le canal entrant :

pc.ondatachannel = ({ channel }) => {
  const chunks = [];
  let received = 0;
  channel.onmessage = ({ data }) => {
    chunks.push(data);
    received += data.byteLength;
    updateProgress(received);
    if (received === expectedSize) finish(chunks);
  };
};

Pour les fichiers de plus de 500 Mo, n'accumulez pas en RAM. Utilisez l'API File System Access pour streamer directement sur le disque :

const handle = await window.showSaveFilePicker({ suggestedName: fileName });
const writable = await handle.createWritable();
channel.onmessage = async ({ data }) => writable.write(data);

Firefox et Safari ne prennent pas encore en charge showSaveFilePicker, donc repliez sur un téléchargement Blob + URL.createObjectURL pour ces navigateurs, plafonné à 2 Go.

Envoyer les métadonnées du fichier avant les octets

Le récepteur a besoin de connaître le nom, la taille et le type MIME du fichier avant que le flux d'octets commence. Utilisez une petite poignée de main JSON sur le canal de données :

channel.send(JSON.stringify({
  type: 'metadata', name: file.name,
  size: file.size, mime: file.type, sha256: fileHash
}));

Puis passez en mode binaire. Le récepteur bascule selon que data est une chaîne ou un ArrayBuffer. Incluez un SHA-256 du fichier pour la vérification d'intégrité post-transfert, et optionnellement une empreinte de clé si vous superposez AES-256-GCM applicatif au-dessus.

Gérer les défaillances de connexion

Les canaux de données WebRTC échouent de trois façons : ICE ne se complète jamais (NAT, blocage pare-feu), l'établissement DTLS échoue (désalignement d'horloge, problèmes de cert), ou la connexion se coupe en cours de transfert (mise en veille du portable, changement de réseau). Écoutez pc.oniceconnectionstatechange et agissez sur 'failed' ou 'disconnected'. Chrome maintient 'disconnected' pendant quelques secondes avant de passer à 'failed' ; Safari est moins patient.

En cas d'échec en cours de transfert, redémarrez ICE sans reconstruire toute la connexion :

await pc.restartIce();
const offer = await pc.createOffer({ iceRestart: true });
// renvoyez via la signalisation

Si le redémarrage échoue, repliez sur un upload reprenables via votre serveur — une architecture hybride P2P + serveur. Certains outils de transfert WebRTC comme Wormhole et justbeamit utilisent ce schéma car il gère les 15 % de conditions réseau où le pur P2P ne peut tout simplement pas fonctionner.

Ajouter le chiffrement de bout en bout contre votre propre serveur

Le DTLS intégré à WebRTC protège contre les attaquants réseau mais pas contre un serveur de signalisation malveillant ou compromis. Pour une vraie mise en œuvre de chiffrement de bout en bout, faites générer aux deux pairs une paire de clés ECDH P-256, échangez les clés publiques via un court code hors bande (QR ou phrase de 6 mots), dérivez un secret partagé via HKDF-SHA256, et chiffrez chaque message du canal de données avec AES-256-GCM avant d'appeler send. Ainsi même si le serveur de signalisation substitue des certs DTLS, il ne peut pas lire vos fichiers.

HexaTransfer utilise une architecture hybride — texte chiffré stocké côté serveur avec AES-256-GCM côté client — en échangeant la directivité pour un partage tolérant au mode hors ligne.

Où WebRTC gagne et perd

Le transfert de fichiers WebRTC brille quand les deux pairs sont en ligne simultanément, quand la confidentialité par rapport à votre propre serveur compte, et quand les fichiers sont suffisamment grands (100 Mo+) que les coûts de bande passante de relais seraient douloureux. Il perd quand les utilisateurs veulent envoyer et partir, quand les destinataires ouvrent le lien des heures plus tard, ou quand les destinataires sont sur des réseaux d'entreprise restrictifs qui bloquent STUN et TURN. Pour un outil de transfert généraliste, le pur P2P couvre confortablement environ 60 % des cas d'usage. Les 40 % restants ont besoin d'un repli sur serveur — ce qui explique pourquoi presque tous les produits de « transfert de fichiers P2P » ont un relais quelque part dans leur architecture.

Essayez-le sur https://hexatransfer.com — gratuit, sans compte, 10 Go maximum.

Envoyez vos fichiers volumineux en toute sécurité avec le chiffrement de bout en bout

Transférez des fichiers jusqu'à 10 Go gratuitement avec le chiffrement de bout en bout. Aucun compte requis. Vos fichiers sont chiffrés dans votre navigateur avant l'envoi — personne d'autre ne peut les lire.

Envoyer un fichier