Full-featured SIP router with multi-provider trunking, browser softphone via WebRTC, real-time Opus/G.722/PCM transcoding in Rust, RNNoise ML noise suppression, Kokoro neural TTS announcements, and a Lit-based web dashboard with live call monitoring and REST API.
6.0 KiB
ts/sip — SIP Protocol Library
A zero-dependency SIP (Session Initiation Protocol) library for Deno / Node. Provides parsing, construction, mutation, and dialog management for SIP messages, plus helpers for SDP bodies and URI rewriting.
Modules
| File | Purpose |
|---|---|
message.ts |
SipMessage class — parse, inspect, mutate, serialize |
dialog.ts |
SipDialog class — track dialog state, build in-dialog requests |
helpers.ts |
ID generators, codec registry, SDP builder/parser |
rewrite.ts |
SIP URI and SDP body rewriting |
types.ts |
Shared types (IEndpoint) |
index.ts |
Barrel re-export |
Quick Start
import {
SipMessage,
SipDialog,
buildSdp,
parseSdpEndpoint,
rewriteSipUri,
rewriteSdp,
generateCallId,
generateTag,
generateBranch,
} from './sip/index.ts';
SipMessage
Parsing
import { Buffer } from 'node:buffer';
const raw = Buffer.from(
'INVITE sip:user@example.com SIP/2.0\r\n' +
'Via: SIP/2.0/UDP 10.0.0.1:5060;branch=z9hG4bK776\r\n' +
'From: <sip:alice@example.com>;tag=abc\r\n' +
'To: <sip:bob@example.com>\r\n' +
'Call-ID: a84b4c76e66710@10.0.0.1\r\n' +
'CSeq: 1 INVITE\r\n' +
'Content-Length: 0\r\n\r\n'
);
const msg = SipMessage.parse(raw);
// msg.method → "INVITE"
// msg.isRequest → true
// msg.callId → "a84b4c76e66710@10.0.0.1"
// msg.cseqMethod → "INVITE"
// msg.isDialogEstablishing → true
Fluent mutation
All setter methods return this for chaining:
const buf = SipMessage.parse(raw)!
.setHeader('Contact', '<sip:proxy@192.168.1.1:5070>')
.prependHeader('Record-Route', '<sip:192.168.1.1:5070;lr>')
.updateContentLength()
.serialize();
Building requests from scratch
const invite = SipMessage.createRequest('INVITE', 'sip:+4930123@voip.example.com', {
via: { host: '192.168.5.66', port: 5070 },
from: { uri: 'sip:alice@example.com', displayName: 'Alice' },
to: { uri: 'sip:+4930123@voip.example.com' },
contact: '<sip:192.168.5.66:5070>',
body: sdpBody,
contentType: 'application/sdp',
});
// Call-ID, From tag, Via branch are auto-generated if not provided.
Building responses
const ok = SipMessage.createResponse(200, 'OK', incomingInvite, {
toTag: generateTag(),
contact: '<sip:192.168.5.66:5070>',
body: answerSdp,
contentType: 'application/sdp',
});
Inspectors
| Property | Type | Description |
|---|---|---|
isRequest |
boolean |
True for requests (INVITE, BYE, ...) |
isResponse |
boolean |
True for responses (SIP/2.0 200 OK, ...) |
method |
string | null |
Request method or null |
statusCode |
number | null |
Response status code or null |
callId |
string |
Call-ID header value |
cseqMethod |
string | null |
Method from CSeq header |
requestUri |
string | null |
Request-URI (second token of start line) |
isDialogEstablishing |
boolean |
INVITE, SUBSCRIBE, REFER, NOTIFY, UPDATE |
hasSdpBody |
boolean |
Body present with Content-Type: application/sdp |
Static helpers
SipMessage.extractTag('<sip:alice@x.com>;tag=abc') // → "abc"
SipMessage.extractUri('"Alice" <sip:alice@x.com>') // → "sip:alice@x.com"
SipDialog
Tracks dialog state per RFC 3261 §12. A dialog is created from a dialog-establishing request and updated as responses arrive.
UAC (caller) side
// 1. Build and send INVITE
const invite = SipMessage.createRequest('INVITE', destUri, { ... });
const dialog = SipDialog.fromUacInvite(invite, '192.168.5.66', 5070);
// 2. Process responses as they arrive
dialog.processResponse(trying100); // state stays 'early'
dialog.processResponse(ringing180); // state stays 'early', remoteTag learned
dialog.processResponse(ok200); // state → 'confirmed'
// 3. ACK the 200
const ack = dialog.createAck();
// 4. In-dialog requests
const bye = dialog.createRequest('BYE');
dialog.terminate();
UAS (callee) side
const dialog = SipDialog.fromUasInvite(incomingInvite, generateTag(), localHost, localPort);
CANCEL (before answer)
const cancel = dialog.createCancel(originalInvite);
Dialog states
'early' → 'confirmed' → 'terminated'
Helpers
ID generation
generateCallId() // → "a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5"
generateCallId('example.com') // → "a3f8b2c1...@example.com"
generateTag() // → "1a2b3c4d5e6f7a8b"
generateBranch() // → "z9hG4bK-1a2b3c4d5e6f7a8b"
SDP builder
const sdp = buildSdp({
ip: '192.168.5.66',
port: 20000,
payloadTypes: [9, 0, 8, 101], // G.722, PCMU, PCMA, telephone-event
direction: 'sendrecv',
});
SDP parser
const ep = parseSdpEndpoint(sdpBody);
// → { address: '10.0.0.1', port: 20000 } or null
Codec names
codecName(9) // → "G722/8000"
codecName(0) // → "PCMU/8000"
codecName(101) // → "telephone-event/8000"
Rewriting
SIP URI
Replaces the host:port in all sip: / sips: URIs found in a header value:
rewriteSipUri('<sip:user@10.0.0.1:5060>', '203.0.113.1', 5070)
// → '<sip:user@203.0.113.1:5070>'
SDP body
Rewrites the connection address and audio media port, returning the original endpoint that was replaced:
const { body, original } = rewriteSdp(sdpBody, '203.0.113.1', 20000);
// original → { address: '10.0.0.1', port: 8000 }
Architecture Notes
This library is intentionally low-level — it operates on individual messages and dialogs rather than providing a full SIP stack with transport and transaction layers. This makes it suitable for building:
- SIP proxies — parse, rewrite headers/SDP, serialize, forward
- B2BUA (back-to-back user agents) — manage two dialogs, bridge media
- SIP testing tools — craft and send arbitrary messages
- Protocol analyzers — parse and inspect SIP traffic
The library does not manage sockets, timers, or retransmissions — those concerns belong to the application layer.