Openfire Logo

moxxmpp: A Minimal Working Example (in Dart)

Introduction

This document provides a minimal working example of a client implementation using the moxxmpp library, making it connect to a running Openfire server.

Topics that are covered in this document:

Background

moxxmpp is a pure Dart library that was originally built for the Moxxy XMPP client. It has since been separated and can be used standalone.

This guide describes how to use moxxmpp to connect to Openfire. It provides nothing more than a minimal working example, intended as a stepping stone to for client developers that get started with a new project.

Preparations

In this example, a client connection will be made against a running Openfire server. For ease of configuration, the 'demoboot' setup of Openfire is used.

The 'demoboot' setup of Openfire allows one to start a fresh installation of Openfire into a certain provisioned state, without running any of the setup steps. When running in 'demoboot' mode:

To start Openfire in 'demoboot' mode, you can invoke the Openfire executable using the -demoboot argument, as shown below.

Starting Openfire in 'demoboot' mode.
$ ./bin/openfire.sh -demoboot

Code

To start the project, create a file named pubspec.yaml in an empty directory, and copy in the code below.

Example pubspec.yaml file
name: example
description: A moxxmpp sample for Openfire.
version: 1.0.0

environment:
  sdk: '>=3.0.0 <4.0.0'

dependencies:
  logging: ^1.0.2
  moxxmpp:
    hosted: https://git.polynom.me/api/packages/Moxxy/pub
    version: 0.4.0
  moxxmpp_socket_tcp:
    hosted: https://git.polynom.me/api/packages/Moxxy/pub
    version: 0.4.0

Next, create a file named main.dart in an empty directory, and copy in the code below.

Example main.dart file
import 'package:logging/logging.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';

/// By default, moxxmpp expects the server to present a valid TLS certificate that
/// the system trusts. In this case, however, the certificate is self-signed, meaning
/// that the system does not trust it. To work around this issue, we create a sub-class
/// of the [TCPSocketWrapper] that bypasses the validity check and accepts any TLS
/// certificate that the server gives us. In production, you absolutely do not want to
/// do this.
class SelfSignedTCPSocketWrapper extends TCPSocketWrapper {
  SelfSignedTCPSocketWrapper() : super(false);

  @override
  bool onBadCertificate(dynamic certificate, String domain) {
    return true;
  }
}

/// The JID we want to authenticate as.
final xmppUser = JID.fromString('jane@example.com');

/// The password to authenticate with.
const xmppPass = 'secret';

/// The [xmppHost]:[xmppPort] server address to connect to.
/// In a real application, one might prefer to use [TCPSocketWrapper]
/// with a custom DNS implementation to let moxxmpp resolve the XMPP
/// server's address automatically. However, if we just provide a host
/// and a port, then [TCPSocketWrapper] will just skip the resolution and
/// immediately use the provided connection details.
const xmppHost = 'localhost';
const xmppPort = 5222;

void main(List args) async {
  Logger.root.level = Level.ALL;
  Logger.root.onRecord.listen((record) {
    print('${record.level.name}|${record.time}: ${record.message}');
  });

  // This class manages every aspect of handling the XMPP stream.
  final connection = XmppConnection(
    // A reconnection policy tells the connection how to handle an error
    // while or after connecting to the server. The [TestingReconnectionPolicy]
    // immediately triggers a reconnection. In a real implementation, one might
    // prefer to use a smarter strategy, like using an exponential backoff.
    TestingReconnectionPolicy(),

    // A connectivity manager tells the connection when it can connect. This is to
    // ensure that we're not constantly trying to reconnect because we have no
    // Internet connection. [AlwaysConnectedConnectivityManager] always says that
    // we're connected. In a real application, one might prefer to use a smarter
    // strategy, like using connectivity_plus to query the system's network connectivity
    // state.
    AlwaysConnectedConnectivityManager(),

    // This kind of negotiator tells the connection how to handle the stream
    // negotiations. The [ClientToServerNegotiator] allows to connect to the server
    // as a regular client. Another negotiator would be the [ComponentToServerNegotiator] that
    // allows for connections to the server where we're acting as a component.
    ClientToServerNegotiator(),

    // A wrapper around any kind of connection. In this case, we use the [SelfSignedTCPSocketWrapper]. It wraps
    // [TCPSocketWrapper], which uses a dart:io Socket/SecureSocket to connect to the server. If you want, you can also
    // provide your own socket to use, for example, WebSockets or any other connection
    // mechanism.
    SelfSignedTCPSocketWrapper(),
  )..connectionSettings = ConnectionSettings(
      jid: xmppUser,
      password: xmppPass,
      host: xmppHost,
      port: xmppPort,
    );

  // Register a set of "managers" that provide you with implementations of various
  // XEPs. Some have interdependencies, which need to be met. However, this example keeps
  // it simple and just registers a [MessageManager], which has no required dependencies.
  await connection.registerManagers([
    // The [MessageManager] handles receiving and sending  stanzas.
    MessageManager(),
  ]);

  // Feature negotiators are objects that tell the connection negotiator what stream features
  // we can negotiate and enable. moxxmpp negotiators always try to enable their features.
  await connection.registerFeatureNegotiators([
    // This negotiator authenticates to the server using SASL PLAIN with the provided
    // credentials.
    SaslPlainNegotiator(),
    // This negotiator attempts to bind a resource. By default, it's always a random one.
    ResourceBindingNegotiator(),
    // This negotiator attempts to do StartTLS before authenticating.
    StartTlsNegotiator(),
  ]);

  // Set up a stream handler for the connection's event stream. Managers and negotiators
  // may trigger certain events. The [MessageManager], for example, triggers a [MessageEvent]
  // whenever a message is received. If other managers are registered that parse a message's
  // contents, then they can add their data to the event.
  connection.asBroadcastStream().listen((event) {
    if (event is! MessageEvent) {
      return;
    }

    // The text body (contents of the  element) are returned as a
    // [MessageBodyData] object. However, a message does not have to contain a
    // body, so it is nullable.
    final body = event.extensions.get()?.body;
    print('[<-- ${event.from}] $body');
  });

  // Connect to the server.
  final result = await connection.connect(
    // This flag indicates that we want to reconnect in case something happens.
    shouldReconnect: true,
    // This flag indicates that we want the returned Future to only resolve
    // once the stream negotiations are done and no negotiator has any feature left
    // to negotiate.
    waitUntilLogin: true,
  );

  // Check if the connection was successful. [connection.connect] can return a boolean
  // to indicate success or a [XmppError] in case the connection attempt failed.
  if (!result.isType()) {
    print('Failed to connect to server');
    return;
  }
}

Finally, build and run the test client, using the instructions below.

Build and run moxxmpp test client
$ dart pub get
$ dart run main.dart

If all goes well, this will print a short exchange of XMPP data.

Note that this example disables important security features. You should not use this for anything important!

Further Reading

Please use the links below to find more information.