Développement Web

Développer un client SIP WebRTC avec SIP.js pas à pas

11 دقائق للقراءة
Guide principal : Téléphonie IP avancée : 3CX, Issabel et softphones WebRTC. Ce tutoriel suppose un serveur Asterisk prêt pour WebRTC ; sinon, voyez activer WebRTC sur Asterisk.

SIP.js est l’autre grande bibliothèque JavaScript pour la téléphonie dans le navigateur. Là où JsSIP mise sur la simplicité immédiate, SIP.js propose une API moderne, typée pour TypeScript, et surtout une classe de haut niveau appelée SimpleUser qui réduit la construction d’un softphone à une poignée de méthodes : connecter, s’enregistrer, appeler, raccrocher. Ce tutoriel construit un client SIP WebRTC complet avec cette approche, idéale pour les projets structurés et les applications écrites en TypeScript. Chaque étape est expliquée avant d’être codée.

Prérequis

  • Un serveur Asterisk configuré pour WebRTC avec un endpoint dont vous avez l’identifiant et le mot de passe.
  • L’URL du WebSocket sécurisé : wss://votre-domaine:8089/ws.
  • Un environnement avec un bundler (Vite, webpack…) pour utiliser les modules, ou une version packagée pour le navigateur.
  • Une page servie en HTTPS (exigence des navigateurs pour le microphone).
  • Niveau : avancé. Temps estimé : 1 heure.

Pourquoi choisir SIP.js

JsSIP et SIP.js font fondamentalement le même travail, et le choix relève souvent du contexte du projet. SIP.js brille dans trois situations. D’abord, dans les projets TypeScript : la bibliothèque est entièrement typée, ce qui offre l’autocomplétion et la détection d’erreurs à la compilation. Ensuite, lorsqu’on veut une API à deux niveaux : SimpleUser pour aller vite, et l’API bas niveau UserAgent pour un contrôle fin quand le besoin se précise. Enfin, dans les architectures modernes à base de modules ES et de bundlers, où SIP.js s’intègre naturellement.

SimpleUser est le point d’entrée recommandé. Cette classe encapsule toute la mécanique SIP et WebRTC derrière des méthodes explicites. On ne manipule plus directement les sessions et les transports : on appelle connect, register, call, et la bibliothèque s’occupe du reste. C’est ce qui rend SIP.js accessible malgré la richesse de son API sous-jacente.

Étape 1 — Installer et importer SIP.js

SIP.js se distribue comme un paquet npm que l’on importe dans son projet. Avec un bundler moderne, on l’ajoute aux dépendances puis on importe l’espace de noms Web, qui contient la classe SimpleUser destinée au navigateur.

npm install sip.js

Une fois la dépendance installée, l’import se fait en une ligne dans votre fichier JavaScript ou TypeScript. L’espace de noms Web regroupe les outils spécifiques au navigateur, dont SimpleUser.

import { Web } from "sip.js";

Cet import donne accès à Web.SimpleUser et à son type d’options Web.SimpleUserOptions. Si vous travaillez sans bundler, une version packagée pour le navigateur existe également et expose les mêmes objets. À ce stade, la bibliothèque est prête à être utilisée.

Étape 2 — Préparer l’élément audio

Comme tout softphone, le client a besoin d’un endroit pour diffuser la voix de l’interlocuteur. SimpleUser attache automatiquement le flux distant à un élément <audio> que vous lui désignez : c’est l’un de ses grands avantages, il gère lui-même cette plomberie.

<audio id="remoteAudio" autoplay></audio>

Il suffit de placer cet élément dans la page et de récupérer sa référence en JavaScript pour la passer dans les options à l’étape suivante. Contrairement à une approche bas niveau, vous n’aurez pas à écouter manuellement l’arrivée du flux : SimpleUser s’en charge dès que la référence à l’élément lui est fournie.

Étape 3 — Construire le SimpleUser

Le cœur du client est l’objet SimpleUser, construit avec deux arguments : l’URL du serveur WebSocket, et un objet d’options qui décrit l’identité de l’utilisateur, l’élément audio de sortie et les identifiants d’authentification.

const server = "wss://votre-domaine:8089/ws";

const options = {
  aor: "sip:webrtc_client@votre-domaine",
  media: {
    remote: {
      audio: document.getElementById("remoteAudio")
    }
  },
  userAgentOptions: {
    authorizationUsername: "webrtc_client",
    authorizationPassword: "un_mot_de_passe_robuste"
  }
};

const simpleUser = new Web.SimpleUser(server, options);

Décortiquons les options. L’aor (Address of Record) est l’identité SIP de l’utilisateur, équivalente à son adresse téléphonique dans le système. Le bloc media.remote.audio désigne l’élément qui jouera la voix reçue — c’est là que SimpleUser branchera automatiquement le flux distant. Enfin, userAgentOptions porte les identifiants d’authentification, qui doivent correspondre exactement à l’endpoint WebRTC déclaré dans Asterisk. Avec ces trois informations, l’objet est prêt.

Étape 4 — Connecter et s’enregistrer

SimpleUser sépare clairement deux opérations : se connecter au serveur (ouvrir le WebSocket) et s’enregistrer (annoncer sa disponibilité pour recevoir des appels). Les deux méthodes renvoient des promesses, ce qui permet de les enchaîner proprement avec await.

await simpleUser.connect();
await simpleUser.register();

L’appel à connect() établit la connexion WebSocket sécurisée avec Asterisk ; sa promesse se résout quand la connexion est prête. Ensuite, register() enregistre le client pour qu’il puisse recevoir des appels entrants. Si l’une de ces promesses est rejetée, c’est généralement le signe d’une URL erronée, d’un certificat refusé ou d’identifiants invalides — entourez ces appels d’un bloc de gestion d’erreurs pour informer l’utilisateur clairement.

Étape 5 — Passer un appel

Une fois connecté et enregistré, passer un appel tient en une seule méthode : call(), à laquelle on fournit la destination sous forme d’URI SIP. C’est aussi à cet instant que le navigateur demandera l’accès au microphone.

const target = "sip:101@votre-domaine";
await simpleUser.call(target);

SimpleUser établit l’appel, négocie le média chiffré, et attache automatiquement le flux reçu à l’élément audio désigné dans les options. Vous n’avez rien d’autre à faire : la voix de l’interlocuteur sort dès que la connexion est établie. C’est tout l’intérêt de l’approche SimpleUser par rapport à une gestion manuelle des flux, où il faudrait brancher soi-même l’audio distant.

Étape 6 — Recevoir un appel avec un délégué

Pour réagir aux appels entrants, SimpleUser utilise un délégué : un objet contenant des fonctions de rappel que la bibliothèque invoque quand un événement survient. La plus importante est onCallReceived, déclenchée à l’arrivée d’un appel.

simpleUser.delegate = {
  onCallReceived: async () => {
    await simpleUser.answer();
  }
};

Ici, dès qu’un appel arrive, le délégué appelle answer() pour décrocher automatiquement. Dans une application réelle, on afficherait plutôt une notification et un bouton « Décrocher » relié à answer(), afin de laisser l’utilisateur choisir. Le mécanisme du délégué centralise toutes les réactions aux événements dans un seul objet, ce qui rend le code lisible et facile à faire évoluer.

Étape 7 — Raccrocher et suivre les états

Terminer un appel se fait avec hangup(). Le délégué peut aussi recevoir d’autres rappels pour suivre le cycle de vie de l’appel — par exemple savoir quand il est établi ou terminé, afin de mettre à jour l’interface.

await simpleUser.hangup();

On relie typiquement cette méthode à un bouton « Raccrocher ». Pour une interface soignée, complétez le délégué avec des rappels signalant le début et la fin d’appel, et utilisez-les pour activer ou désactiver les boutons selon l’état. Un softphone qui reflète fidèlement son état évite les manipulations confuses et inspire confiance à l’utilisateur.

Étape 8 — Servir et tester

Compilez votre projet avec votre bundler, servez la page résultante en HTTPS, et ouvrez-la dans un navigateur. À l’ouverture, le code se connecte et s’enregistre ; surveillez la console pour confirmer qu’aucune promesse n’a été rejetée. Composez ensuite le numéro d’une autre extension et déclenchez l’appel : après l’autorisation du microphone, la conversation doit s’établir dans les deux sens. Testez aussi un appel entrant en appelant l’extension WebRTC depuis un autre poste, pour vérifier que le délégué décroche bien.

Connexion et enregistrement : deux notions distinctes

Une subtilité de SIP.js mérite qu’on s’y arrête, car elle évite bien des confusions. Se connecter et s’enregistrer sont deux choses différentes. La connexion établit le canal de transport — le WebSocket sécurisé — entre le navigateur et Asterisk. L’enregistrement, lui, est une opération du protocole SIP qui annonce au serveur « voici où me joindre » et permet de recevoir des appels entrants. On peut être connecté sans être enregistré : dans ce cas, on peut émettre des appels mais pas en recevoir.

Cette distinction a des conséquences pratiques. Un softphone destiné uniquement à émettre des appels sortants — par exemple un bouton « Appeler le support » sur un site web — peut se contenter de connect() sans register(). À l’inverse, un poste d’agent qui doit recevoir des appels enchaînera toujours les deux. Comprendre cette séparation aide aussi à diagnostiquer : si les appels sortants passent mais que rien n’arrive en entrée, c’est souvent que l’enregistrement n’a pas eu lieu ou a expiré. SIP.js renouvelle automatiquement l’enregistrement, mais une coupure réseau peut le faire tomber le temps d’une reconnexion.

Sécurité : protéger les identifiants

Le piège de sécurité majeur d’un softphone navigateur est le même quelle que soit la bibliothèque : le mot de passe SIP transite dans le code livré au client. Tout ce qui se trouve dans le JavaScript d’une page est, par nature, lisible par quiconque inspecte la page. Coder en dur les identifiants d’une extension, c’est les offrir à n’importe quel visiteur, qui pourrait alors usurper le poste et passer des appels frauduleux à vos frais.

La parade consiste à ne jamais inscrire les identifiants permanents dans le code public. On les récupère depuis un point d’accès authentifié côté serveur, après que l’utilisateur s’est identifié dans l’application, ou bien on génère des identifiants éphémères, valables seulement le temps d’une session. Ainsi, même si quelqu’un inspecte la page, il n’obtient qu’un secret temporaire et limité. Cette précaution rejoint directement les principes de sécurisation des plateformes : un identifiant qui fuit est une porte ouverte à la fraude téléphonique, et la frontière entre développement et production se joue souvent sur ce détail.

SimpleUser ou l’API bas niveau ?

SimpleUser couvre la grande majorité des besoins, mais SIP.js expose aussi une API de bas niveau, articulée autour des classes UserAgent, Registerer et Inviter. Cette API offre un contrôle fin sur chaque étape du protocole : personnaliser les en-têtes SIP, gérer plusieurs appels simultanés avec des comportements distincts, intercepter des événements précis du protocole. Elle est plus verbeuse mais incontournable pour les applications avancées comme un standard multi-lignes ou un centre de contact intégré.

La bonne stratégie consiste à démarrer avec SimpleUser, qui vous met en production rapidement, et à ne descendre vers l’API bas niveau que lorsqu’un besoin précis le justifie. Les deux niveaux coexistent dans la même bibliothèque : passer de l’un à l’autre ne demande pas de tout réécrire, seulement d’adopter des objets plus granulaires là où c’est nécessaire. Cette progressivité est précisément ce qui distingue SIP.js et en fait un bon choix pour un projet appelé à grandir.

Erreurs fréquentes

Erreur Cause Solution
connect() rejette la promesse URL WebSocket erronée ou certificat refusé Vérifier wss://...:8089/ws et la validité du certificat
Enregistrement échoue Identifiants ne correspondant pas à l’endpoint Aligner authorizationUsername/Password sur l’endpoint Asterisk
Pas de son Élément audio non fourni dans les options Renseigner media.remote.audio avec un élément valide
Micro non demandé Page servie en HTTP Servir en HTTPS avec un certificat reconnu
Appel inter-réseaux qui échoue Absence de serveur TURN Déployer coturn et le configurer côté client

Tutoriels associés

Pour aller plus loin

Questions fréquentes

SIP.js fonctionne-t-il sans TypeScript ?
Oui. SIP.js est écrit en TypeScript mais s’utilise parfaitement en JavaScript pur. Les types apportent un confort supplémentaire en TypeScript, mais ne sont pas obligatoires pour profiter de la bibliothèque.

Faut-il un bundler obligatoirement ?
Non. Une version packagée pour le navigateur permet d’utiliser SIP.js via une simple balise de script. Le bundler reste recommandé pour un projet structuré, car il gère proprement les dépendances et l’optimisation.

SimpleUser gère-t-il la vidéo ?
Oui, en fournissant un élément vidéo dans les options média en plus de l’audio. SimpleUser attache alors le flux vidéo distant à cet élément, sur le même principe que l’audio.

Quand passer à l’API bas niveau ?
Lorsque SimpleUser ne suffit plus : gestion de plusieurs lignes simultanées avec des comportements différents, personnalisation des en-têtes SIP, ou intégration fine dans un centre de contact. Pour un softphone standard, SimpleUser couvre l’essentiel.

Service ITSkillsCenter

Application mobile Android et iOS

Création d'application mobile Android et iOS. À partir de 350 000 FCFA.

Démarrer mon projet
Publicité