Developing App - JVM
This guide will help you create and understand how to develop and deploy a simple Wire Application written in a JVM language (Java, Kotlin, Scala). The SDK you will be using will simplify encryption/decryption, and http client calls to the Wire backend, leaving you to care about your business logic.
Note that the SDK takes care of security in transit and partially for securing cryptographic data stored by the SDK in the filesystem. However, as you will have access to decrypted messages and identifiers of conversations and teams, it is up to you to secure them.
Prerequisites
- Java 17 or higher
- Kotlin 2.x.x if you are using Kotlin
- An application registered with Wire (to obtain an API token)
- File system access for storing cryptographic keys and data
Adding the SDK to Your Project
- Gradle
- Maven
dependencies {
implementation("com.wire:wire-apps-jvm-sdk:0.0.1")
}
<dependency>
<groupId>com.wire</groupId>
<artifactId>wire-apps-jvm-sdk</artifactId>
<version>0.0.1</version>
</dependency>
Initializing the SDK
App registration through Team Management isn’t available yet. For now, create an account manually for your app and configure the required environment variables.
Environment Variables
# Credentials for the Wire account you just created
WIRE_SDK_EMAIL=your_email@domain.com
WIRE_SDK_PASSWORD=dummyPassword
# Open Settings, scroll to Profile link, and copy the user ID and domain
# Example of Profile Link:
# https://account.wire.com/user-profile/?id=abcd-1234-efgh-5678@my.domain.link
WIRE_SDK_USER_ID=abcd-1234-efgh-5678
Since app registration is not implemented yet, set applicationId and apiToken to arbitrary values.
For example:
- applicationId:
UUID.randomUUID() - apiToken: myApiToken
Constructor parameters
The SDK needs to be initialized with your application's credentials, the backend host, a key for the cryptographic material and your event handler implementation:
| Parameter | Description |
|---|---|
applicationId + apiToken | Universally unique identifier (UUID) of the app and a token that allows access to Wire-server endpoints. Available in Team Management after registering an app |
apiHost | URL of the backend that hosts Wire. For Wire Cloud, use https://prod-nginz-https.wire.com |
cryptographyStorageKey | Cryptographic key used to encrypt local storage. See how to generate cryptographically secure key |
wireEventsHandler | Your implementation of WireEventsHandler abstract class |
Initializing an instance of WireAppSdk is enough to get access to local stored teams and conversations and to send messages or similar actions.
However, to establish a long-lasting connection with the backend and receive all the events targeted to you Application, you need to call the startListening() method.
The startListening() method keeps a background thread running until explicitly stopped or the application terminates.
Complete Example
Here's a complete example showing how to initialize the SDK and handle basic received events:
- Kotlin
- Java
fun main() {
val wireAppSdk = WireAppSdk(
applicationId = UUID.fromString("YOUR_APPLICATION_ID"),
apiToken = "YOUR_API_TOKEN",
apiHost = "YOUR_API_HOST",
cryptographyStorageKey = yourGeneratedSecureKeyByteArray, // Must be 32 bytes
object : WireEventsHandlerSuspending() {
override suspend fun onTextMessageReceived(wireMessage: WireMessage.Text) {
println("Text message received: $wireMessage")
// Add your message handling logic here, like storing the message,
// sending back another message, or triggering some workflow
}
}
)
// Start the SDK
wireAppSdk.startListening()
}
public class Main {
public static void main(String[] args) {
final var wireAppSdk = new WireAppSdk(
UUID.fromString("YOUR_APPLICATION_ID"),
"YOUR_API_TOKEN",
"YOUR_API_HOST",
yourGeneratedSecureKeyByteArray,
new WireEventsHandlerDefault() {
@Override
public void onTextMessageReceived(@NotNull WireMessage.Text wireMessage) {
System.out.println("Text message received: $wireMessage");
// Add your message handling logic here, like storing the message,
// sending back another message, or triggering some workflow
}
}
);
// Start the SDK
wireAppSdk.startListening();
}
}
For simplicity the subclassing of WireEventsHandler is done inline as an anonymous class, but you can create a separate class for it, especially if you handle events in a complex way:
- Kotlin
- Java
class MyWireEventsHandler : WireEventsHandlerSuspending() {
private val logger = LoggerFactory.getLogger(MyWireEventsHandler::class.java)
override suspend fun onTextMessageReceived(wireMessage: WireMessage.Text) {
logger.info("Text message received: $wireMessage")
}
}
public class MyWireEventsHandler extends WireEventsHandlerDefault {
private static final Logger logger = LoggerFactory.getLogger(MyWireEventsHandler.class);
@Override
public void onTextMessageReceived(@NotNull WireMessage.Text wireMessage) {
logger.info("Text message received: $wireMessage");
}
}
NOTE: Your application can simply call startListening() and a new thread is created and will keep the Application running and receiving events. To stop it, just close the Application (Ctrl+C/Cmd+C) or call stopListening()
Echoing a received message
In your onTextMessageReceived implementation from MyWireEventsHandler you can echo a message as:
- Kotlin
- Java
override suspend fun onTextMessageReceived(wireMessage: WireMessage.Text) {
val message = WireMessage.Text.createReply(
conversationId = wireMessage.conversationId,
text = "${wireMessage.text} -- Sent from the SDK",
mentions = wireMessage.mentions,
originalMessage = wireMessage
)
// The manager is accessible through the inherited WireEventsHandler class.
// It is used to manage the Wire application's lifecycle and communication with the backend.
manager.sendMessageSuspending(message = message)
}
@Override
public void onTextMessageReceived(@NotNull WireMessage.Text wireMessage) {
final WireMessage reply = WireMessage.Text.createReply(
wireMessage.conversationId(),
wireMessage.text() + " -- Sent from the SDK",
wireMessage.mentions(),
wireMessage.linkPreviews(),
wireMessage,
null);
// The manager is accessible through the inherited WireEventsHandler class.
// It is used to manage the Wire application's lifecycle and communication with the backend.
getManager().sendMessage(reply);
}
Conclusion
With this basic setup you now have a simple Wire App.
You can check other events in Wire Events
Troubleshooting
- Enable DEBUG logging on the SDK if you are developing an Application and want to test it in a safe environment. Set the log level to DEBUG in your logging framework for the package
com.wire.sdk(e.g. for Logback<logger name="com.wire.sdk" level="DEBUG" />). - If you switch between different Wire environments, you may need to delete the
storage/apps.dbdirectory to avoid conflicts - For connection issues, verify your API token, host URL and if your deployed app has access to the public network (firewalls, docker ports, etc.)
- When running into cryptography issues, ensure your storage key is consistent between app restarts
- The SDK is designed to be thread-safe. The
startListening()andstopListening()methods are synchronized to prevent concurrent modifications to the SDK state. However at this moment, only using a single Wire Application instance has been tested. - If you are using Spring Boot or similar frameworks that force dependency versions, you might need to add explicit dependency management for the SDK dependencies to avoid version conflicts. For example you might force something like
implementation("org.jetbrains.kotlin:kotlin-stdlib:2.2.21")if Spring forces an older version which is incompatible with the SDK. - In case you need to build the SDK locally, you can skip the signing option by running:
./gradlew publishToMavenLocal -PskipSigning=true(Keep in mind to includerepositories { mavenCentral() }in yourbuild.gradle.ktsfile in the app where you use the locally built SDK)
Additional Resources
For any issue, requests or improvements, let us know by contacting us or creating a new issue on GitHub