Service Discovery (Disco)
Service Discovery (Disco), defined in XEP-0030, allows XMPP entities to discover information about other entities on the network, including their capabilities, features, and associated items.
Overview
Service Discovery enables:
- Discovering server capabilities and features
- Finding available services and components
- Querying entity identities and features
- Browsing hierarchical service structures
Discovering Information
Query an entity for its capabilities and features:
final info = DiscoInformation();
final iq = IQ(generateID: true)
..type = 'get'
..to = JabberID('server@example.com')
..payload = info;
final result = await iq.send(whixp.transport);
if (result.payload is DiscoInformation) {
final disco = result.payload as DiscoInformation;
// List all features
for (final feature in disco.features) {
print('Feature: ${feature.variable}');
}
// List all identities
for (final identity in disco.identities) {
print('Identity: ${identity.name} (${identity.category}/${identity.type})');
}
}
Discovering Items
Query an entity for its associated items (child services or components):
final items = DiscoItems();
final iq = IQ(generateID: true)
..type = 'get'
..to = JabberID('server@example.com')
..payload = items;
final result = await iq.send(whixp.transport);
if (result.payload is DiscoItems) {
final discoItems = result.payload as DiscoItems;
for (final item in discoItems.items) {
print('Item: ${item.jid} - ${item.name} (node: ${item.node})');
}
}
Querying Specific Nodes
Query information about a specific node:
final info = DiscoInformation(node: 'specific-node');
final iq = IQ(generateID: true)
..type = 'get'
..to = JabberID('server@example.com')
..payload = info;
final result = await iq.send(whixp.transport);
Adding Identities
When responding to disco queries, you can add identities:
final info = DiscoInformation()
..addIdentity('My Client', 'client', type: 'mobile');
final iq = IQ(generateID: true)
..type = 'result'
..payload = info;
Adding Features
Add features to your disco response:
final info = DiscoInformation()
..addFeature([
'urn:xmpp:mam:2',
'urn:xmpp:sm:3',
'urn:xmpp:csi:0',
]);
final iq = IQ(generateID: true)
..type = 'result'
..payload = info;
Complete Example
Here's a complete example of discovering server capabilities:
Future<void> discoverServer() async {
// Discover server information
final info = DiscoInformation();
final infoIq = IQ(generateID: true)
..type = 'get'
..payload = info;
final infoResult = await infoIq.send(whixp.transport);
if (infoResult.payload is DiscoInformation) {
final disco = infoResult.payload as DiscoInformation;
print('Server Features:');
for (final feature in disco.features) {
print(' - ${feature.variable}');
}
print('\nServer Identities:');
for (final identity in disco.identities) {
print(' - ${identity.name} (${identity.category}/${identity.type})');
}
}
// Discover server items
final items = DiscoItems();
final itemsIq = IQ(generateID: true)
..type = 'get'
..payload = items;
final itemsResult = await itemsIq.send(whixp.transport);
if (itemsResult.payload is DiscoItems) {
final discoItems = itemsResult.payload as DiscoItems;
print('\nServer Items:');
for (final item in discoItems.items) {
print(' - ${item.jid} - ${item.name ?? "Unnamed"}');
// Optionally discover each item
if (item.node != null) {
final itemInfo = DiscoInformation(node: item.node);
final itemInfoIq = IQ(generateID: true)
..type = 'get'
..to = item.jid
..payload = itemInfo;
final itemInfoResult = await itemInfoIq.send(whixp.transport);
// Process item information...
}
}
}
}
Checking for Specific Features
Check if an entity supports a specific feature:
Future<bool> supportsFeature(String featureNamespace) async {
final info = DiscoInformation();
final iq = IQ(generateID: true)
..type = 'get'
..payload = info;
final result = await iq.send(whixp.transport);
if (result.payload is DiscoInformation) {
final disco = result.payload as DiscoInformation;
return disco.features.any(
(feature) => feature.variable == featureNamespace,
);
}
return false;
}
// Usage
final supportsMAM = await supportsFeature('urn:xmpp:mam:2');
if (supportsMAM) {
print('Server supports Message Archive Management');
}
Responding to Disco Queries
If you're building a component or service, you can respond to disco queries:
whixp.addEventHandler<IQ>('iq', (iq) {
if (iq == null) return;
if (iq.type == 'get' && iq.payload is DiscoInformation) {
final response = IQ(generateID: true)
..type = 'result'
..to = iq.from
..id = iq.id
..payload = DiscoInformation()
..addIdentity('My Service', 'component', type: 'generic')
..addFeature([
'urn:xmpp:mam:2',
'urn:xmpp:sm:3',
]);
whixp.send(response);
}
});
Best Practices
- Cache results: Cache disco results to avoid repeated queries
- Check before using: Always check if a feature is supported before using it
- Handle errors: Always handle errors when querying disco information
- Use appropriate nodes: Query specific nodes when you need detailed information
- Respond accurately: If responding to queries, ensure your responses accurately reflect your capabilities
Service Discovery is essential for building interoperable XMPP applications that can adapt to different server capabilities.