Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/strophe/strophejs/llms.txt

Use this file to discover all available pages before exploring further.

Strophe.js uses a handler-based model for reacting to incoming data. Rather than polling or using a single catch-all event listener, you register fine-grained handlers that fire only when stanzas matching specific criteria arrive. Two distinct handler types exist: stanza handlers that are triggered by incoming XML elements, and timed handlers that fire at regular intervals regardless of incoming traffic. Both types are managed through the Connection instance.

Stanza Handlers

A stanza handler is a callback registered with conn.addHandler(). Every incoming stanza is tested against all registered handlers. Any handler whose filter criteria match the stanza will have its callback invoked.

connection.addHandler()

connection.addHandler(
  handler: (stanza: Element) => boolean,
  ns: string | null,
  name: string | null,
  type: string | string[] | null,
  id: string | null,
  from: string | null,
  options?: HandlerOptions
): Handler
The return value is a Handler reference used later to unregister the handler via deleteHandler().

Parameters

handler
(stanza: Element) => boolean
required
The callback function. It receives the matched XML Element as its only argument. Return true to keep the handler registered (persistent). Return false to automatically remove it after this single invocation (one-shot).
ns
string | null
The XML namespace to match against. Strophe.js checks both the element’s own xmlns attribute and the xmlns of any direct child elements. Pass null to match stanzas regardless of namespace.
name
string | null
The element tag name to match (e.g. 'message', 'iq', 'presence'). Pass null to match any element name.
type
string | string[] | null
The type attribute value to match (e.g. 'chat', 'get', 'set', 'result', 'error'). An array of strings can be passed to match any of several types. Pass null to match regardless of type.
id
string | null
The id attribute value to match. Used to correlate a specific response to a request. Pass null to match any id.
from
string | null
The from attribute value to match. By default this is a full JID comparison. Pass null to match any sender.
options
HandlerOptions
An optional object controlling matching behaviour:
  • matchBareFromJid (boolean, default false): When true, the from filter is compared using only the bare JID (node@domain), ignoring the resource. Strophe.js strips the resource from both the registered from value and the incoming stanza’s from attribute before comparing.
  • ignoreNamespaceFragment (boolean, default false): When true, URL fragments (#suffix) are stripped from namespace URIs before comparison. Useful for protocols like XHTML-IM where namespace variants share a common base.

connection.deleteHandler()

connection.deleteHandler(handRef: Handler): void
Removes a previously registered stanza handler. The handRef must be the value returned by addHandler():
const ref = conn.addHandler(myCallback, null, 'message', 'chat', null, null);

// Later, when you no longer want to receive messages:
conn.deleteHandler(ref);

Handler Return Value Semantics

The boolean return value from your callback controls the handler’s lifetime:
Return valueEffect
trueHandler remains registered and will fire again on future matching stanzas.
falseHandler is automatically removed after this invocation (one-shot).
Stanza handlers only run while connection.authenticated is true. Handlers registered before authentication is complete are queued and processed once the session is established. System-internal handlers (used by Strophe.js itself) are exempt from this restriction.

Timed Handlers

A timed handler fires periodically at a fixed interval, independent of any incoming stanzas. This is useful for implementing keep-alive pings, periodic presence updates, or polling-style logic.

connection.addTimedHandler()

connection.addTimedHandler(
  period: number,
  handler: () => boolean
): TimedHandler
period
number
required
The interval in milliseconds between invocations.
handler
() => boolean
required
The callback function. Takes no arguments. Return true to reschedule the handler for another period milliseconds. Return false to cancel it.
Returns a TimedHandler reference for use with deleteTimedHandler().

connection.deleteTimedHandler()

connection.deleteTimedHandler(handRef: TimedHandler): void
Removes a previously registered timed handler before its next scheduled fire:
const pingTimer = conn.addTimedHandler(30000, sendPing);

// Stop the timer when leaving a room:
conn.deleteTimedHandler(pingTimer);

Examples

Handling All Incoming Chat Messages

A persistent handler that prints every incoming chat message:
conn.addHandler(
  (stanza: Element): boolean => {
    const from = stanza.getAttribute('from');
    const body = stanza.querySelector('body');
    if (body) {
      console.log(`Message from ${from}: ${body.textContent}`);
    }
    return true; // stay registered
  },
  null,       // any namespace
  'message',  // <message> elements only
  'chat',     // type="chat" only
  null,       // any id
  null        // any sender
);

Filtering by XML Namespace

Listen only for stanzas that carry a specific extension namespace (e.g. XEP-0085 Chat State Notifications):
conn.addHandler(
  (stanza: Element): boolean => {
    const composing = stanza.querySelector('composing');
    if (composing) {
      const from = stanza.getAttribute('from');
      console.log(`${from} is typing…`);
    }
    return true;
  },
  'http://jabber.org/protocol/chatstates', // match this namespace on any child
  'message',
  null,
  null,
  null
);

One-Shot IQ Response Handler

Register a handler that fires exactly once when a matching IQ result arrives, then removes itself:
import { $iq } from 'strophejs';

const id = conn.getUniqueId('version');

// Register the one-shot handler BEFORE sending the IQ
conn.addHandler(
  (stanza: Element): boolean => {
    const name = stanza.querySelector('name')?.textContent;
    const version = stanza.querySelector('version')?.textContent;
    console.log(`Server software: ${name} ${version}`);
    return false; // remove after first match
  },
  'jabber:iq:version',
  'iq',
  'result',
  id,   // match only the response to our specific request
  null
);

conn.send(
  $iq({ type: 'get', id, to: 'example.com' })
    .c('query', { xmlns: 'jabber:iq:version' })
);

Matching by Bare JID

Receive messages from any resource belonging to a contact (e.g. phone, laptop, web):
conn.addHandler(
  (stanza: Element): boolean => {
    console.log('Message from contact:', stanza.getAttribute('from'));
    return true;
  },
  null,
  'message',
  'chat',
  null,
  'bob@example.com', // bare JID
  { matchBareFromJid: true }
);

Periodic Ping Timer

Send an XMPP ping every 60 seconds to keep the connection alive:
import { $iq } from 'strophejs';

conn.addTimedHandler(60000, (): boolean => {
  const id = conn.getUniqueId('ping');
  conn.sendIQ(
    $iq({ type: 'get', id, to: conn.domain })
      .c('ping', { xmlns: 'urn:ietf:params:xml:ns:xmpp-ping' }),
    () => console.log('Ping acknowledged'),
    () => console.warn('Ping timed out')
  );
  return true; // reschedule
});

One-Shot Timed Handler

Schedule a single deferred action — for example, auto-setting presence to away after 5 minutes of inactivity:
import { $pres } from 'strophejs';

let idleTimer = conn.addTimedHandler(300000, (): boolean => {
  conn.send(
    $pres()
      .c('show').t('away').up()
      .c('status').t('Idle')
  );
  return false; // fire once, then remove
});

// Reset the timer on user activity:
function onUserActivity() {
  conn.deleteTimedHandler(idleTimer);
  idleTimer = conn.addTimedHandler(300000, () => {
    conn.send($pres().c('show').t('away').up().c('status').t('Idle'));
    return false;
  });
}

Important Notes

Stanza handlers run synchronously during the stanza processing loop. Avoid performing slow or blocking operations inside a handler. If you need async work, capture a reference to the stanza and process it asynchronously — but be aware the connection’s handler queue continues to advance while your async work is pending.
Use sendIQ() and sendPresence() instead of manually building one-shot handlers for request/response patterns. These helpers internally call addHandler() with the correct id matching and manage cleanup on both success and timeout.

Build docs developers (and LLMs) love