Modules
Architecture of Halcyon library is based on plugins (called modules). Every feature like authentication, sending and receiving messages or contact list management is implemented as module. Halcyon contains all modules in single package (at least for now), so no need to add more dependencies.
To install module you have to use install
function:
import tigase.halcyon.core.builder.createHalcyon
import tigase.halcyon.core.xmpp.modules.discovery.DiscoveryModule
val halcyon = createHalcyon {
install(DiscoveryModule)
}
Most of modules can be configured. Configuration may be passed in install
block:
import tigase.halcyon.core.builder.createHalcyon
import tigase.halcyon.core.xmpp.modules.discovery.DiscoveryModule
val halcyon = createHalcyon {
install(DiscoveryModule) {
clientName = "My Private Bot"
clientCategory = "client"
clientType = "bot"
}
}
By default, function createHalcyon()
automatically add all modules. If you want to configure your own set of modules, you have to disable this feature and add required plugins by hand:
import tigase.halcyon.core.builder.createHalcyon
import tigase.halcyon.core.xmpp.modules.discovery.DiscoveryModule
val halcyon = createHalcyon(installAllModules = false) {
install(DiscoveryModule)
install(RosterModule)
install(PresenceModule)
}
Note
Despite of the name, with install
you can also configure preinstalled modules!
Halcyon modules mechanism is implementing modules dependencies, it means that if you install module (for example) MIXModule
, Halcyon automatically install modules RosterModule
, PubSubModule
and MAMModule
with default configuration.
There is also set of aliases, to make configuration of popular modules more comfortable.
import tigase.halcyon.core.builder.createHalcyon
import tigase.halcyon.core.builder.bind
val halcyon = createHalcyon() {
bind {
resource = "my-little-bot"
}
}
In this example we used bind{}
instead of install(BindModule){}
.
List of aliases:
Alias |
Module name |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SaslModule & Sasl2Module
SaslModule
and Sasl2Module
provides mechanism to authenticate user in XMPP server.
Our current implementation supports a set of mechanisms to do that:
SCRAM-SHA-1
&SCRAM-SHA-1-PLUS
SCRAM-SHA-256
&SCRAM-SHA-256-PLUS
SCRAM-SHA-512
&SCRAM-SHA-512-PLUS
PLAIN
All SCRAM
mechanisms with PLUS
allow to bind authentication process with TLS channel. It makes authentication
process more secure and protect against man-in-the-middle attack.
SCRAM mechanisms in Halcyon supports three types on channel binding: tls-unique
, tls-exporter
and tls-server-end-point
.
Unfortunately, because of limitation of Java TLS API implementation, by default only tls-server-end-point
is
supported.
To enable other channel binding types, you have to use BouncyCastle based TLS processor. It is provided by separate module, so you need to import it to your project
implementation("tigase.halcyon:halcyon-bouncycastle:$HalcyonVersion")
and configure socket connector:
import tigase.halcyon.core.builder.createHalcyon
import tigase.halcyon.core.builder.socketConnector
import tigase.halcyon.core.connector.socket.BouncyCastleTLSProcessor
val halcyon = createHalcyon {
socketConnector {
tlsProcessorFactory = BouncyCastleTLSProcessor
}
}
SaslModule vs Sasl2Module
Sasl2Module
does exactly the same what SaslModule
. The only difference is that Sasl2Module
is used with
Bind 2 mechanism.
Events
SASLEvent
has three subtypes of events:
SASLStarted
fired when authentication process begins, it also provides used mechanism;SASLSuccess
fired when authentication process is finished with success;SASLError
fired on authentication error, it provides sasl error type and optional human readable description.
PresenceModule
Module for handling received presence information.
Install and configure
To install or configure preinstalled Presence module, call function install
inside Halcyon configuration (see Modules):
import tigase.halcyon.core.builder.createHalcyon
import tigase.halcyon.core.xmpp.modules.presence.PresenceModule
import tigase.halcyon.core.xmpp.modules.presence.InMemoryPresenceStore
val halcyon = createHalcyon {
install(PresenceModule) {
store = InMemoryPresenceStore()
}
}
The only one configuration property store
allows to use own implementation of presence store.
Setting own presence status
After connection is established, PresenceModule automatically sends initial presence.
To change own presence status, you should use sendPresence function.`
import tigase.halcyon.core.xmpp.modules.presence.PresenceModule
halcyon.getModule(PresenceModule)
.sendPresence(
show = Show.Chat,
status = "I'm ready for party!"
)
.send()
It is also possible to send direct presence, only for specific recipient:
import tigase.halcyon.core.xmpp.modules.presence.PresenceModule halcyon.getModule(PresenceModule) .sendPresence( jid = "[email protected]".toJID(), show = Show.DnD, status = "I'm doing my homework!" ) .send()
Presence subscription
Details of managing presence subscription are explained in XMPP specification. Here we simply show how to subscribe and unsubscribe presence with Halcyon library.
All subscriptions manipulation may be done with single sendSubscriptionSet function:
import tigase.halcyon.core.xmpp.modules.presence.PresenceModule halcyon.getModule(PresenceModule) .sendSubscriptionSet(jid = "[email protected]".toJID(), presenceType = PresenceType.Subscribe) .send()
Depends on action you want, you have to change presenceType
parameter:
- Subscription request:
Use
PresenceType.Subscribe
to send subscription request to given JabberID.- Accepting subscription request:
Use
PresenceType.Subscribed
to accept subscription request from given JabberID.- Rejecting subscription request:
Use
PresenceType.Unsubscribed
to reject subscription request or cancelling existing subscription to our presence from given JabberID.- Unsubscribe contact:
Use
PresenceType.Unsubscribe
to cancel your subscription of given JabberID presence.
Note
Remember that subscription manipulation can affect your roster content.
Checking presence
When you develop application, probably you will want to check presence of your contact, to see if he is available.
Halcyon provides few function for that: getBestPresenceOf
returns presence with highest priority (in case if there are few entities under the same bare JID);
getPresenceOf
returns last received presence of given full JID. You can also check list of all entities resources logged as single bare JID with getResources
function.
Because determining of contact presence using low-level XMPP approach is not so intuitive, we introduced TypeAndShow
. It joins presence stanza type and show extension in single set of enums.
import tigase.halcyon.core.xmpp.modules.presence.PresenceModule import tigase.halcyon.core.xmpp.modules.presence.typeAndShow val contactStatus = halcyon.getModule(PresenceModule) .getBestPresenceOf("[email protected]".toBareJID()) .typeAndShow()
Thanks to it, contactStatus
value will contain easy to show contact status like online, offline, away, etc.
Events
Module can fire two types of events:
PresenceReceivedEvent
is fired when any Presence stanza is received by client. Event contains JID of sender, stanza type (copied from stanza) and whole received stanza.ContactChangeStatusEvent
is fired when received stanza changes contact presence (all subscriptions requests are ignored). Event contains JID of sender, human readable status description, current presence with highest priority and just received presence stanza. Note thatpresence
in this event may contain stanza received long time ago. Current event is caused by receiving presence from entity with lower priority.
RosterModule
Module for managing roster (contact list).
Install and configure
To install or configure preinstalled Roster module, call function install
inside Halcyon configuration (see Modules):
import tigase.halcyon.core.builder.createHalcyon
import tigase.halcyon.core.xmpp.modules.roster.RosterModule
import tigase.halcyon.core.xmpp.modules.roster.InMemoryRosterStore
val halcyon = createHalcyon {
install(RosterModule) {
store = InMemoryRosterStore()
}
}
The only one configuration property store
allows to use own implementation of roster store.
Retrieving roster
When connection is established, client automatically requests for latest roster, so no additional actions are required.
Most modern XMPP server supports roster versioning. Thanks to it, client do not have to receive whole roster from server (which can be large). So we recommend, to implement own RosterStore to keep current roster content between client launches.
Manipulating roster
To add new contact to you roster you have to call addItem
function:
import tigase.halcyon.core.xmpp.modules.roster.RosterModule
import tigase.halcyon.core.xmpp.modules.roster.RosterItem
halcyon.getModule(RosterModule)
.addItem(
RosterItem(
jid = "[email protected]".toBareJID(),
name = "My friend",
)
)
.send()
Warning
Remember, that (as described in RFC) after call (and send) roster modification request, your local store will not be updated immediately. Roster store is updated only on server request!
When roster item is saved in your roster store, Halcyon fires RosterEvent.ItemAdded
event.
To modify existing roster item, you have to call exactly the same addItem
function:
import tigase.halcyon.core.xmpp.modules.roster.RosterModule
import tigase.halcyon.core.xmpp.modules.roster.RosterItem
halcyon.getModule(RosterModule)
.addItem(
RosterItem(
jid = "[email protected]".toBareJID(),
name = "My best friend!",
)
)
.send()
The difference is that after local store update Halcyon fires RosterEvent.ItemUpdated
event.
Last thing is removing items from roster:
import tigase.halcyon.core.xmpp.modules.roster.RosterModule
import tigase.halcyon.core.xmpp.modules.roster.RosterItem
halcyon.getModule(RosterModule)
.deleteItem("[email protected]".toBareJID())
.send()
When item will be removed from local store, Halcyon fires RosterEvent.ItemRemoved
event.
Events
Roster module can fires few types of events:
RosterEvent
is fired when roster item in your local store is modified by server request. There are three sub-events:ItemAdded
,ItemUpdated
andItemRemoved
.RosterLoadedEvent
inform us that roster data loading is finished. It is called only after retrieving roster on client request.RosterUpdatedEvent
is fired, when processing roster data from server is finished. I will be triggered after requesting roster from server and after processing set of roster item manipulations initiated by server.
DiscoveryModule
This module implements XEP-0030: Service Discovery.
Install and configure
To install or configure preinstalled Discovery module, call function install
inside Halcyon configuration (see Modules):
import tigase.halcyon.core.builder.createHalcyon
import tigase.halcyon.core.xmpp.modules.discovery.DiscoveryModule
val halcyon = createHalcyon {
install(DiscoveryModule) {
clientCategory = "client"
clientType = "console"
clientName = "Code Snippet Demo"
clientVersion = "1.2.3"
}
}
The DiscoveryModule
configuration is provided by interface DiscoveryModuleConfiguration
.
The
clientCategory
andclientType
properties provides information about category and type of client you develop.
List of allowed values you can use is published in Service Discovery Identities document.
The
clientName
andclientVersion
properties contains human readable software name and version.
Note
If you change client name and version, it is good to update node
name in EntityCapabilitiesModule.
Discovering information
Module provides function info
to prepare request to get information about given entity:
import tigase.halcyon.core.xmpp.modules.discovery.DiscoveryModule
import tigase.halcyon.core.xmpp.toJID
halcyon.getModule(DiscoveryModule)
.info("tigase.org".toJID())
.response { result ->
result.onFailure { error -> println("Error $error") }
result.onSuccess { info ->
println("Received info from ${info.jid}:")
println("Features " + info.features)
println(info.identities.joinToString { identity ->
"${identity.name} (${identity.category}, ${identity.type})"
})
}
}
.send()
In case of success, module return DiscoveryModule.Info
class containing information about requested JID and node, list of received identities and list of features.
Discovering list
Second feature provided by module is discovering list of items associated with an entity. It is implemented by the items
function:
import tigase.halcyon.core.xmpp.modules.discovery.DiscoveryModule
import tigase.halcyon.core.xmpp.toJID
halcyon.getModule(DiscoveryModule)
.items("tigase.org".toJID())
.response { result ->
result.onFailure { error -> println("Error $error") }
result.onSuccess { items ->
println("Received info from ${items.jid}:")
println(items.items.joinToString { "${it.name} (${it.jid}, ${it.node})" })
}
}
.send()
In case of success, module return DiscoveryModule.Items
class containing information about requested JID, node and list of received items.
Events
After connection to server is established, module automatically requests for for features of user account and server.
When Halcyon receives account information, then AccountFeaturesReceivedEvent
event is fired. In case of receiving XMPP server information, Halcyon fires ServerFeaturesReceivedEvent
event.
EntityCapabilitiesModule
This module implements XEP-0115: XMPP Ping.
Install and configure
To install or configure preinstalled EntityCapabilities module, call function install
inside Halcyon configuration (see Modules):
import tigase.halcyon.core.builder.createHalcyon
import tigase.halcyon.core.xmpp.modules.caps.EntityCapabilitiesModule
val halcyon = createHalcyon {
install(EntityCapabilitiesModule) {
node = "http://mycompany.com/bestclientever"
cache = MyCapsCacheImplementation()
storeInvalid = false
}
}
The EntityCapabilitiesModule
configuration is provided by interface EntityCapabilitiesModuleConfig
.
The
node
is URI to identify your software. As default library useshttps://tigase.org/halcyon
URI.With
cache
propery you can use own implementation of capabilities cache store, for example JDBC based, to keep all received capabilities between your application restarts.The
storeInvalid
property allow to force storing received capabilities with invalid versification string. By default, it is set tofalse
.
For more information about the possible consequences of disabling the validation verification string, refer to the Security Considerations chapter.
Getting capabilities
You can get entity capabilities based on the presence received.
import tigase.halcyon.core.xmpp.modules.caps.EntityCapabilitiesModule
val caps = halcyon.getModule(EntityCapabilitiesModule)
.getCapabilities(presence)
The primary use of the module is to define a list of features of the client with whom communication is taking place. After receiving presence from client we can determine features implemented by it:
import tigase.halcyon.core.xmpp.modules.caps.EntityCapabilitiesModule
val caps = halcyon.getModule(EntityCapabilitiesModule)
.getCapabilities(presence)
PingModule
This module implements XEP-0199: XMPP Ping.
Install
To install or configure preinstalled Discovery module, call function install
inside Halcyon configuration (see Modules):
import tigase.halcyon.core.builder.createHalcyon
import tigase.halcyon.core.xmpp.modules.PingModule
val halcyon = createHalcyon {
install(PingModule)
}
This module has no configuration options.
Note
If module will not be installed, other entities will not be able to ping application.
Pinging entity
Module provides function ping
to prepare request for ping given entity:
import tigase.halcyon.core.xmpp.modules.PingModule
import tigase.halcyon.core.xmpp.toJID
halcyon.getModule(PingModule)
.ping("tigase.org".toJID())
.response { result ->
result.onSuccess { pong -> println("Pong: ${pong.time}ms") }
result.onFailure { error -> println("Error $error") }
}
.send()
In the case of success, module returns PingModule.Pong class containing information about measured response time.