> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/jitsi/lib-jitsi-meet/llms.txt
> Use this file to discover all available pages before exploring further.

# XMPP Signaling

> Understanding lib-jitsi-meet's XMPP/Jingle protocol implementation for WebRTC signaling

# XMPP Signaling

lib-jitsi-meet uses XMPP with the Jingle extension (XEP-0166) for WebRTC signaling. The library implements this using Strophe.js with custom plugins.

## Architecture Overview

The XMPP signaling layer consists of:

* **Strophe.js** - Core XMPP client library (Jitsi fork)
* **JingleConnectionPlugin** - Handles Jingle session management
* **JingleSessionPC** - Manages individual peer connections
* **Strophe Plugins** - Protocol extensions (disco, jingle, etc.)
* **SignalingLayer** - Abstraction for protocol details

Source: CLAUDE.md:94-99

## Jingle Protocol

Jingle (XEP-0166) is an XMPP extension for establishing peer-to-peer media sessions.

### Session Lifecycle

**1. Session Initiate**

```javascript theme={null}
case 'session-initiate': {
    logger.info('(TIME) received session-initiate:\t', now);
    
    const pcConfig = isP2P ? this.p2pIceConfig : this.jvbIceConfig;
    
    sess = new JingleSessionPC(
        sid,                          // Session ID
        iq.getAttribute('to'),        // Local JID
        fromJid,                      // Remote JID
        this.connection,              // XMPP connection
        this.mediaConstraints,        // Media constraints
        cloneDeep(pcConfig),          // ICE config
        isP2P,                        // P2P or JVB
        false                         // Not initiator
    );
    
    this.sessions[sess.sid] = sess;
    this.eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess, jingleElement, now);
    break;
}
```

Source: `modules/xmpp/strophe.jingle.js:181-200`

**2. Session Accept**

```javascript theme={null}
case 'session-accept': {
    const ssrcs = [];
    
    // Extract SSRCs from session-accept (P2P)
    findAll(iq, 'jingle>content').forEach(content => {
        const ssrc = getAttribute(findFirst(content, 'description'), 'ssrc');
        ssrc && ssrcs.push(ssrc);
    });
    
    logger.debug(`Received ${action} from ${fromJid} with ssrcs=${ssrcs}`);
    this.eventEmitter.emit(XMPPEvents.CALL_ACCEPTED, sess, jingleElement);
    break;
}
```

Source: `modules/xmpp/strophe.jingle.js:202-214`

**3. Transport Info**

ICE candidates are exchanged via `transport-info` actions:

```javascript theme={null}
case 'transport-info': {
    const candidates = _parseIceCandidates(
        findFirst(iq, 'jingle>content>transport')
    );
    
    logger.debug(`Received ${action} from ${fromJid} for candidates=${candidates.join(', ')}`);
    this.eventEmitter.emit(XMPPEvents.TRANSPORT_INFO, sess, jingleElement);
    break;
}
```

Source: `modules/xmpp/strophe.jingle.js:221-226`

**4. Session Terminate**

```javascript theme={null}
case 'session-terminate': {
    logger.info('terminating...', sess.sid);
    let reasonCondition = null;
    let reasonText = null;
    
    const reasonElement = findFirst(iq, ':scope>jingle>reason');
    
    if (reasonElement) {
        const firstReasonChild = reasonElement.children?.length > 0 
            ? reasonElement.children[0] : undefined;
        reasonCondition = firstReasonChild ? firstReasonChild.tagName : null;
        reasonText = getText(findFirst(iq, ':scope>jingle>reason>text'));
    }
    
    logger.debug(`Received ${action} from ${fromJid} disconnect reason=${reasonText}`);
    this.terminate(sess.sid, reasonCondition, reasonText);
    this.eventEmitter.emit(XMPPEvents.CALL_ENDED, sess, reasonCondition, reasonText);
    break;
}
```

Source: `modules/xmpp/strophe.jingle.js:228-245`

### Source Signaling

**Source Add**

```javascript theme={null}
case 'source-add':
    sess.addRemoteStream(findAll(iq, ':scope>jingle>content'));
    break;
```

Source: `modules/xmpp/strophe.jingle.js:250-252`

**Source Remove**

```javascript theme={null}
case 'source-remove':
    sess.removeRemoteStream(findAll(iq, ':scope>jingle>content'));
    break;
```

Source: `modules/xmpp/strophe.jingle.js:253-255`

### P2P vs JVB Detection

lib-jitsi-meet distinguishes P2P from JVB connections by checking the resource:

```javascript theme={null}
const isP2P = Strophe.getResourceFromJid(fromJid) !== 'focus';
```

Jicofo (the JVB focus component) always has the resource "focus", while P2P connections use participant resources.

Source: `modules/xmpp/strophe.jingle.js:152`

## JSON Message Encoding

For large conferences, lib-jitsi-meet uses JSON encoding to compress source information:

```javascript theme={null}
const jsonMessages = findAll(iq, 'jingle>json-message');

if (jsonMessages?.length) {
    let audioVideoSsrcs;
    
    logger.info(`Found a JSON-encoded element in ${action}, translating to standard Jingle.`);
    for (let i = 0; i < jsonMessages.length; i++) {
        // Translate JSON to standard Jingle source elements
        audioVideoSsrcs = expandSourcesFromJson(iq, jsonMessages[i]);
    }
    
    if (audioVideoSsrcs?.size) {
        const logMessage = [];
        for (const endpoint of audioVideoSsrcs.keys()) {
            logMessage.push(`${endpoint}:[${audioVideoSsrcs.get(endpoint)}]`);
        }
        logger.debug(`Received ${action} from ${fromJid} with sources=${logMessage.join(', ')}`);
    }
}
```

Source: `modules/xmpp/strophe.jingle.js:156-174`

## ICE Candidate Parsing

ICE candidates are extracted from transport elements:

```javascript theme={null}
function _parseIceCandidates(transport) {
    const candidates = findAll(transport, ':scope>candidate');
    const parseCandidates = [];
    
    // Extract candidate attributes
    candidates.forEach(candidate => {
        const attributes = candidate.attributes;
        const candidateAttrs = [];
        
        for (let i = 0; i < attributes.length; i++) {
            const attr = attributes[i];
            candidateAttrs.push(`${attr.name}: ${attr.value}`);
        }
        parseCandidates.push(candidateAttrs.join(' '));
    });
    
    return parseCandidates;
}
```

Source: `modules/xmpp/strophe.jingle.js:26-44`

## Session Management

### Creating P2P Sessions

```javascript theme={null}
newP2PJingleSession(me, peer) {
    const sess = new JingleSessionPC(
        RandomUtil.randomHexString(12),  // Random session ID
        me,                               // Local JID
        peer,                             // Remote JID
        this.connection,                  // XMPP connection
        this.mediaConstraints,            // Media constraints
        this.p2pIceConfig,                // P2P ICE config
        true,                             // Is P2P
        true                              // Is initiator
    );
    
    this.sessions[sess.sid] = sess;
    return sess;
}
```

Source: `modules/xmpp/strophe.jingle.js:277-292`

### Session Validation

**Session Not Found**

```javascript theme={null}
if (!sess) {
    ack.attrs({ type: 'error' });
    ack.c('error', { type: 'cancel' })
        .c('item-not-found', {
            xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'
        })
        .up()
        .c('unknown-session', {
            xmlns: 'urn:xmpp:jingle:errors:1'
        });
    logger.warn(`invalid session id: ${sid}`);
    this.connection.send(ack);
    return true;
}
```

Source: `modules/xmpp/strophe.jingle.js:99-114`

**JID Mismatch**

```javascript theme={null}
if (fromJid !== sess.remoteJid) {
    logger.warn('jid mismatch for session id', sid, sess.remoteJid, iq);
    ack.attrs({ type: 'error' });
    ack.c('error', { type: 'cancel' })
        .c('item-not-found', {
            xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'
        })
        .up()
        .c('unknown-session', {
            xmlns: 'urn:xmpp:jingle:errors:1'
        });
    this.connection.send(ack);
    return true;
}
```

Source: `modules/xmpp/strophe.jingle.js:117-132`

## Strophe.js Integration

lib-jitsi-meet uses a custom fork of Strophe.js with Jitsi-specific modifications.

### Connection Plugin Pattern

```javascript theme={null}
export default class JingleConnectionPlugin extends ConnectionPlugin {
    constructor(xmpp, eventEmitter, iceConfig) {
        super();
        this.xmpp = xmpp;
        this.eventEmitter = eventEmitter;
        this.sessions = {};
        this.jvbIceConfig = iceConfig.jvb;
        this.p2pIceConfig = iceConfig.p2p;
        this.mediaConstraints = {
            offerToReceiveAudio: true,
            offerToReceiveVideo: true
        };
    }
    
    init(connection) {
        super.init(connection);
        this.connection.addHandler(
            this.onJingle.bind(this),
            'urn:xmpp:jingle:1',  // Namespace
            'iq',                  // Stanza type
            'set',                 // IQ type
            null,                  // ID
            null                   // From
        );
    }
}
```

Source: `modules/xmpp/strophe.jingle.js:49-78`

### IQ Acknowledgment

All Jingle IQs must be acknowledged:

```javascript theme={null}
const ack = $iq({ 
    id: iq.getAttribute('id'),
    to: fromJid,
    type: 'result'
});

// ... process the Jingle action ...

this.connection.send(ack);
return true;  // Keep handler
```

Source: `modules/xmpp/strophe.jingle.js:91-94, 265-267`

## Media Constraints

Default media constraints for Jingle sessions:

```javascript theme={null}
this.mediaConstraints = {
    offerToReceiveAudio: true,
    offerToReceiveVideo: true
};
```

Source: `modules/xmpp/strophe.jingle.js:64-67`

## ICE Configuration

Separate ICE configurations for JVB and P2P:

```javascript theme={null}
constructor(xmpp, eventEmitter, iceConfig) {
    // ...
    this.jvbIceConfig = iceConfig.jvb;  // JVB connection config
    this.p2pIceConfig = iceConfig.p2p;  // P2P connection config
}
```

ICE config is cloned before use to prevent mutation:

```javascript theme={null}
const pcConfig = isP2P ? this.p2pIceConfig : this.jvbIceConfig;
sess = new JingleSessionPC(
    // ...
    cloneDeep(pcConfig),  // Deep clone prevents side effects
    // ...
);
```

Source: `modules/xmpp/strophe.jingle.js:62-63, 194`

## Strophe Plugins

lib-jitsi-meet includes several Strophe plugins for XMPP protocol extensions:

### Available Plugins

* **strophe.jingle.js** - Jingle session management (XEP-0166)
* **strophe.disco.js** - Service discovery (XEP-0030)
* **moderator.js** - Moderator functionality
* **Lobby.js** - Lobby/waiting room support
* **BreakoutRooms.js** - Breakout room management

Source: modules/xmpp/ directory listing

### Plugin Architecture

All plugins extend `ConnectionPlugin`:

```javascript theme={null}
import ConnectionPlugin from './ConnectionPlugin';

export default class MyPlugin extends ConnectionPlugin {
    init(connection) {
        super.init(connection);
        // Register handlers, etc.
    }
}
```

Source: `modules/xmpp/strophe.jingle.js:9, 49`

## XMPP Events

The Jingle plugin emits events via the EventEmitter:

```javascript theme={null}
import { XMPPEvents } from '../../service/xmpp/XMPPEvents';

// Session events
this.eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess, jingleElement, now);
this.eventEmitter.emit(XMPPEvents.CALL_ACCEPTED, sess, jingleElement);
this.eventEmitter.emit(XMPPEvents.CALL_ENDED, sess, reasonCondition, reasonText);
this.eventEmitter.emit(XMPPEvents.TRANSPORT_INFO, sess, jingleElement);
```

Source: `modules/xmpp/strophe.jingle.js:5, 199, 213, 225, 244`

<Warning>
  **Session ID Collision**

  If a `session-initiate` is received with an existing session ID, lib-jitsi-meet rejects it:

  ```javascript theme={null}
  if (sess !== undefined) {
      ack.attrs({ type: 'error' });
      ack.c('error', { type: 'cancel' })
          .c('service-unavailable', {
              xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'
          });
      logger.warn('duplicate session id', sid, iq);
      this.connection.send(ack);
      return true;
  }
  ```

  This prevents out-of-order message issues.

  Source: `modules/xmpp/strophe.jingle.js:133-146`
</Warning>

## XML Utilities

lib-jitsi-meet provides XML helpers for parsing Jingle IQs:

```javascript theme={null}
import { findAll, findFirst, getAttribute, getText } from '../util/XMLUtils';

// Find elements
const jingleElement = findFirst(iq, 'jingle');
const contents = findAll(iq, 'jingle>content');

// Get attributes
const sid = getAttribute(jingleElement, 'sid');
const action = getAttribute(jingleElement, 'action');

// Get text content
const reasonText = getText(findFirst(iq, ':scope>jingle>reason>text'));
```

Source: `modules/xmpp/strophe.jingle.js:7, 85-88, 239`

## XMPP Connection Management

The signaling layer is part of the broader XMPP architecture:

**Module Structure**

* **ChatRoom.js** - Multi-user chat room with presence
* **JingleSessionPC.js** - Jingle protocol for media negotiation
* **SignalingLayerImpl.js** - Abstraction layer for signaling
* **Strophe plugins** - Protocol extensions

**Transport Support**

* BOSH (Bidirectional-streams Over Synchronous HTTP)
* WebSocket

Source: CLAUDE.md:94-99

## Performance Considerations

**Session Timing**

```javascript theme={null}
const now = window.performance.now();
logger.info('(TIME) received session-initiate:\t', now);
```

lib-jitsi-meet tracks high-resolution timestamps for session events to measure signaling latency.

Source: `modules/xmpp/strophe.jingle.js:147, 182`

**P2P Optimization**

For P2P sessions, detailed logging is conditional:

```javascript theme={null}
isP2P && logger.debug(`Received ${action} from ${fromJid}`);
```

Source: `modules/xmpp/strophe.jingle.js:184`

## Next Steps

* [WebRTC Internals](/advanced/webrtc-internals) - Peer connection implementation
* [Codec Selection](/advanced/codec-selection) - Video codec negotiation
* [Network Optimization](/advanced/network-optimization) - ICE and connectivity
