XMPP Service Discovery (XEP-0030)
(require xmpp/xep-0030) | package: XMPP |
This module implements XMPP Service Discovery (XMPP-0030), an XMPP protocol extension for discovering information about other XMPP entities.
1 Example
Example:
> ; We will define a component with two entities, of the form: ; ; my-component.example.net ; ├ node0@my-component.example.net ; └ node1@my-component.example.net ; ; With the following configuration: ; ; my-component.example.net: ; Name: My component ; Category: component ; Type: generic ; ; node0@my-component.example.net: ; Name: First node ; Category: account ; Type: registered ; ; node1@my-component.example.net: ; Name: Second node ; Category: account ; Type: registered ; (require xmpp) ; ; Let us connect to the server as a component. ; (define-values (host port domain secret) (values "example.net" 5255 "my-component.example.net" "COMPONENT!SECRET")) (define conn (xmpp-connect-component host port domain secret)) (xmpp-set-handler conn) ; ; First we set up disco#info ; ; We start the disco#info service handler on this connection (define disco-info (xmpp-provide-disco-info conn)) ; Let us create a disco#info object for the component itself (define disco-info-root (make-xmpp-disco-info (xmpp-connection-jid conn) "")) ; Let us add an <identity/> value (add-xmpp-disco-info-identity! (xmpp-disco-info-identity "My component" "component" "generic") disco-info-root) ; And advertise the disco#info feature (add-xmpp-disco-info-feature! (xmpp-disco-info-feature "http://jabber.org/protocol/disco#info" "") disco-info-root) ; We add the disco-info object to the disco#info handler. ; After this, the component will start replying to any ; incoming disco#info queries. ; ; NOTE: we could have also added it first and then configured it. (add-xmpp-disco-info-entity! disco-info-root disco-info) ; ; Now we set up disco#items ; ; We start the disco#items service handler (define disco-items (xmpp-provide-disco-items conn)) ; We create a disco#items object holding the component's child items (define disco-items-root (xmpp-disco-items (xmpp-connection-jid conn) "" '())) ; We add it to the handler (we can do this before or after configuring ; the object's items). (add-xmpp-disco-items-entity! disco-items-root disco-items) ; We tell the root's disco#info to also advertise disco#items capability (add-xmpp-disco-info-feature! (xmpp-disco-info-feature "http://jabber.org/protocol/disco#items" "") disco-info-root) ; We define the first node (define di0 (xmpp-disco-item (string->jid "node0@my-component.example.net") "" "")) ; We define the second node (define di1 (xmpp-disco-item (string->jid "node1@my-component.example.net") "" "")) ; We add them to the disco#items object (add-xmpp-disco-item! di0 disco-items-root) (add-xmpp-disco-item! di1 disco-items-root) ; ; And now we add the disco#info for each of the two nodes ; ; We can also chain the different calls: (add-xmpp-disco-info-entity! (add-xmpp-disco-info-feature! (xmpp-disco-info-feature "http://jabber.org/protocol/disco#info" "") (add-xmpp-disco-info-identity! (xmpp-disco-info-identity "First node" "registered" "account") (xmpp-disco-info (xmpp-disco-item-jid di0) (xmpp-disco-item-node di0) '() '() '()))) (add-xmpp-disco-info-entity! (add-xmpp-disco-info-feature! (xmpp-disco-info-feature "http://jabber.org/protocol/disco#info" "") (add-xmpp-disco-info-identity! (xmpp-disco-info-identity "Second node" "registered" "account") (xmpp-disco-info (xmpp-disco-item-jid di1) (xmpp-disco-item-node di1) '() '() '()))) disco-info))
When the above component is queried, the conversation will be something like this:
<iq type=’get’ id=’a1197a’ to=’my-component.example.net’> <query xmlns=’http://jabber.org/protocol/disco#info’/> </iq>
<iq type=’get’ id=’a1198a’ to=’my-component.example.net’> <query xmlns=’http://jabber.org/protocol/disco#items’/> </iq>
<iq type=’result’ id=’a1197a’ from=’my-component.example.net’ to=’user@example.com/laptop-129’> <query xmlns=’http://jabber.org/protocol/disco#info’> <identity name=’My component’ type=’generic’ category=’component’/> <feature var=’http://jabber.org/protocol/disco#items’/> <feature var=’http://jabber.org/protocol/disco#info’/> </query> </iq>
<iq type=’result’ id=’a1198a’ from=’my-component.example.net’ to=’user@example.com/laptop-129’> <query xmlns=’http://jabber.org/protocol/disco#items’> <item jid=’node1@my-component.example.net’/> <item jid=’node0@my-component.example.net’/> </query> </iq>
<iq type=’get’ id=’a1199a’ to=’node1@my-component.example.net’> <query xmlns=’http://jabber.org/protocol/disco#info’/> </iq>
<iq type=’get’ id=’a119aa’ to=’node0@my-component.example.net’> <query xmlns=’http://jabber.org/protocol/disco#info’/> </iq>
<iq type=’result’ id=’a1199a’ from=’node1@my-component.example.net’ to=’user@example.com/laptop-129’> <query xmlns=’http://jabber.org/protocol/disco#info’> <identity name=’Second node’ type=’account’ category=’registered’/> <feature var=’http://jabber.org/protocol/disco#info’/> </query> </iq>
<iq type=’result’ id=’a119aa’ from=’node0@my-component.example.net’ to=’user@example.com/laptop-129’> <query xmlns=’http://jabber.org/protocol/disco#info’> <identity name=’First node’ type=’account’ category=’registered’/> <feature var=’http://jabber.org/protocol/disco#info’/> </query> </iq>
1.1 Nested hierarchy
Continuing from the previous example, we illustrate how to add items deeper in the hierarchy.
In this case, we will add two nodes under node0@my-component.example.net, named “config” and “user”:
> (add-xmpp-disco-items-entity! ; This is one item (add-xmpp-disco-item! (xmpp-disco-item (xmpp-disco-item-jid di0) "user" "User management") ; This is another (add-xmpp-disco-item! (xmpp-disco-item (xmpp-disco-item-jid di0) "config" "Configuration") ; This is the container for the items. The root node for these items is ; ‘di0‘ (defined in the previous example). (xmpp-disco-items (xmpp-disco-item-jid di0) (xmpp-disco-item-node di0)))) ; This is the disco#items handler disco-items) ; We indicate that ‘node0@my-component.example.net‘ now also supports ; disco#items. (add-xmpp-disco-info-entity! (add-xmpp-disco-info-feature! "http://jabber.org/protocol/disco#items" (xmpp-disco-info (xmpp-disco-item-jid di0) (xmpp-disco-item-node di0) '() '() '())) disco-info)
1.2 JSON data
Alternatively, it might be more convenient to load the configuration from a JSON object (see Racket’s JSON module).
For instance, the above configuration can be represented as:
{ "info": { "features": [ { "node": null, "var": "http://jabber.org/protocol/disco#items" }, { "node": null, "var": "http://jabber.org/protocol/disco#info" } ], "identity": [ { "category": "My component", "name": "generic", "type": "component" } ] }, "items": [ { "info": { "features": [ { "node": null, "var": "http://jabber.org/protocol/disco#info" } ], "identity": [ { "category": "Second node", "name": "account", "type": "registered" } ] }, "items": [], "jid": "node1@my-component.example.net", "name": null, "node": null }, { "info": { "features": [ { "node": null, "var": "http://jabber.org/protocol/disco#info" } ], "identity": [ { "category": "First node", "name": "account", "type": "registered" } ] }, "items": [], "jid": "node0@my-component.example.net", "name": null, "node": null } ], "jid": "my-component.example.net", "name": null, "node": null }
We could then load the configuration as:
> (require xmpp json) ; ; Let us connect to the server as a component. ; (define-values (host port domain secret) (values "example.net" 5255 "my-component.example.net" "COMPONENT!SECRET")) (define conn (xmpp-connect-component host port domain secret)) (xmpp-set-handler conn) ; ; First we set up disco#info ; ; We start both disco#info and disco#items service ; handlers on this connection (define disco (xmpp-provide-disco conn)) (define config (string-append "{\"info\":{\"features\":[{\"node\":null,\"var\":\"http://jabb" "er.org/protocol/disco#items\"},{\"node\":null,\"var\":\"http:/" "/jabber.org/protocol/disco#info\"}],\"identity\":[{\"category\"\n " ":\"My component\",\"name\":\"generic\",\"type\":\"component\"" "}]},\"items\":[{\"info\":{\"features\":[{\"node\":null,\"var\"" ":\"http://jabber.org/protocol/disco#info\"}],\"identity\":[{\"" "category\":\"Second node\",\"name\":\"account\",\"type\":\"reg" "istered\"}]},\"items\":[],\"jid\":\"node1@my-component.example" ".net\",\"name\":null,\"node\":null},{\"info\":{\"features\":[{" "\"node\":null,\"var\":\"http://jabber.org/protocol/disco#info\"\n " "}],\"identity\":[{\"category\":\"First node\",\"name\":\"acco" "unt\",\"type\":\"registered\"}]},\"items\":[],\"jid\":\"node0@" "my-component.example.net\",\"name\":null,\"node\":null}],\"jid" "\":\"my-component.example.net\",\"name\":null,\"node\":null}")) (add-xmpp-disco! (string->jsexpr config) (invalid-jid) "" disco)
2 Reference
2.1 disco
procedure
(xmpp-provide-disco conn) → (xmpp-disco-provider?)
conn : xmpp-connection?
procedure
(add-xmpp-disco! json jid node disco) → (xmpp-disco-provider?)
json : jsexpr? jid : jid? node : string? disco : xmpp-disco-provider?
Example:
> ; Use (invalid-jid) "" to load the configuration ; at the root of the tree. (add-xmpp-disco! json-data (invalid-jid) "" disco)
procedure
(xmpp-disco->jsexpr disco jid [node]) → (jsexpr?)
disco : xmpp-disco-provider? jid : jid? node : string? = ""
Example:
> ; Export the entire configuration tree (xmpp-disco->jsexpr disco (xmpp-connection-jid conn) "")
'#hasheq( |
(info . #hasheq( |
(features . (#hasheq((node . null) |
(var . "http://jabber.org/protocol/disco#info")) |
#hasheq((node . null) |
(var . "http://jabber.org/protocol/disco#items")))) |
(identity . (#hasheq((category . "My component") |
(name . "generic") |
(type . "component")))))) |
(items . (#hasheq( |
(info . #hasheq( |
(features . (#hasheq((node . null) |
(var . "http://jabber.org/protocol/disco#info")))) |
(identity . (#hasheq((category . "First node") |
(name . "account") |
(type . "registered")))))) |
(items) |
(jid . "node0@my-component.example.net") |
(name . null) |
(node . null)) |
#hasheq( |
(info . #hasheq( |
(features . (#hasheq((node . null) |
(var . "http://jabber.org/protocol/disco#info")))) |
(identity . (#hasheq((category . "Second node") |
(name . "account") |
(type . "registered")))))) |
(items) |
(jid . "node1@my-component.example.net") |
(name . null) |
(node . null)))) |
(jid . "my-component.example.net") |
(name . null) |
(node . null)) |
struct
(struct xmpp-disco-provider (disco-info disco-items))
disco-info : xmpp-disco-info-provider? disco-items : xmpp-disco-items-provider?
2.2 disco#info
procedure
(xmpp-provide-disco-info conn) → (xmpp-disco-info-provider?)
conn : xmpp-connection?
The returned xmpp-disco-info-provider mutable object is used to configure the actual disco#info data.
procedure
(add-xmpp-disco-info-entity! entity provider) → (xmpp-disco-info-provider?) entity : xmpp-disco-info? provider : xmpp-disco-info-provider?
Example:
> (require xmpp) ; We start the disco#info service handler on this connection (define disco-info (xmpp-provide-disco-info conn)) ; Let us create a disco#info object for the component itself ; (assume the component's name is my-component.example.net). (define disco-info-root (xmpp-disco-info (string->jid "my-component.example.net") "")) ; Now we can add disco#info data to my-component.example.net … (add-xmpp-disco-info-identity! (xmpp-disco-info-identity "Racket XMPP" "component" "generic") disco-info-root) (add-xmpp-disco-info-feature! ; Features are plain-text strings "http://jabber.org/protocol/disco#info" disco-info-root)
(add-xmpp-disco-info-entity! disco-info-root disco-info)
procedure
(add-xmpp-disco-info-identity! identity entity) → (xmpp-disco-info-entity?) identity : xmpp-disco-info-identity? entity : xmpp-disco-info-entity?
procedure
(add-xmpp-disco-info-feature! feature entity) → (xmpp-disco-info-entity?) feature : string? entity : xmpp-disco-info-entity?
procedure
(make-xmpp-disco-info jid node) → (xmpp-disco-info?)
jid : jid? node : string?
struct
(struct xmpp-disco-info-provider (entities))
entities : (listof (xmpp-disco-info?))
struct
(struct xmpp-disco-info (jid node identities features form))
jid : jid? node : string? identities : (listof xmpp-disco-info-identity?) features : (listof non-empty-string?) form : (listof (list/c non-empty-string? (listof string?)))
struct
(struct xmpp-disco-info-identity (name category type))
name : string? category : non-empty-string? type : non-empty-string?
struct
(struct xmpp-disco-info-feature (var node))
var : string? node : string?
2.3 disco#items
procedure
(xmpp-provide-disco-items conn) → (xmpp-disco-items-provider?)
conn : xmpp-connection?
The returned xmpp-disco-items-provider mutable object is used to configure the actual disco#items data.
procedure
(add-xmpp-disco-items-entity! entity provider) → (xmpp-disco-items-provider?) entity : xmpp-disco-items? provider : xmpp-disco-items-provider?
Example:
> (require xmpp) ; We start the disco#items service handler on this connection (define disco-items (xmpp-provide-disco-items conn)) ; Let us create a disco#items object for the component itself ; (assume the component's name is my-component.example.net). (define disco-items-root (xmpp-disco-items (string->jid "my-component.example.net") "")) ; Now we can add disco#items data to my-component.example.net … (add-xmpp-disco-item! (xmpp-disco-item (string->jid "node0@" "my-component.example.net") "" "First node") disco-items-root) (add-xmpp-disco-items-entity! disco-items-root disco-items)
procedure
(add-xmpp-disco-item! item entity) → (xmpp-disco-items?)
item : xmpp-disco-item? entity : xmpp-disco-items?
procedure
(make-xmpp-disco-items jid node) → (xmpp-disco-items?)
jid : jid? node : string?
struct
(struct xmpp-disco-items-provider (entities))
entities : (listof (xmpp-disco-items?))
struct
(struct xmpp-disco-items (jid node items))
jid : jid? node : string? items : (listof xmpp-disco-item?)
struct
(struct xmpp-disco-item (jid node name))
jid : jid? node : string? name : string?