Integrate Instant Messenger into a Low-Code Environment

There are a lot of different ways to communicate inside companies. In smaller businesses it’s more likely to just talk to each other or send emails. However, if the company grows there is a point where companies need to offer additional ways to exchange information. One of these additional technologies would be instant messaging. There are multiple providers of such applications, most of them are hosted outside of your company. With Intrexx it’s possible to integrate the free real time collaboration server Openfire for XMPP(Jabber) into your company portal using a jQuery XMPP plugin. Openfire can be hosted in your own environment, used for free and is therefor a good alternative for instant messaging.

Openfire is a real time collaboration server which enables you to provide instant messaging inside your company without the need of external services. This server contains its own user- and contact-management. In order to use Openfire, Intrexx users need to be transferred to Openfire somehow. Also there needs to be some interface to access the server over the web.

Install and Activate Rest-API and HTTP-Bind

A lot of plugins are available for Openfire, one in specific is needed to communicate to Openfire from the outside and this is the Rest-API-plugin. This plugin for example enables you to manage users and their contacts. This plugin needs to be installed and activated to be able to manage Openfire users out of Intrexx.

Another feature we need is the http-bind. This feature allows us to keep a connection via http in order to sent and receive instant messages. Since Intrexx doesn’t have a push mechanism yet, the communication needs to be done by a third-party-library which directly communicates with Openfire.

This third-party-library is the jQuery XMPP-plugin I mentioned earlier.

In my previous post “Include JavaScript-Library in your Intrexx Application” I showed you how to include a JavaScript-library.

Copy Intrexx users and contacts to Openfire

But first things first. Without users in Openfire we can’t send any messages.

On the Openfire Rest-API documentation page there are several examples on how to use this interface. On the Intrexx side I use Groovy and the Apache Commons HttpComponents project to communicate with this API. In my example I connect using the Openfire Secret for authentication. After this I retrieve a List of all Openfire users having the custom IsIntrexxUser property set to true. I compare this list to a list of Intrexx users that are allowed to use Instant messaging. The users that don’t exist in Openfire are then created using the Rest-API.

import org.apache.http.impl.client.*
import org.apache.http.client.*
import org.apache.http.*
import org.apache.http.client.methods.*
import org.apache.http.auth.*
import org.apache.http.entity.*
// query to retrieve Openfire users
String url = "http://localhost:9090/plugins/restapi/v1/users?propertyKey=IsIntrexxUser&propertyValue=true";
// define Openfire authentication credentials for Rest-API
CredentialsProvider provider = new BasicCredentialsProvider();
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("admin", "******");
provider.setCredentials(AuthScope.ANY, credentials);
// initialize HttpClient with credentials
HttpClient client = HttpClientBuilder.create()
  .setDefaultCredentialsProvider(provider)
  .build();
// initialize GET call
HttpGet request = new HttpGet(url)
// add request header to GET call
request.addHeader("Accept", "application/json")
request.addHeader("Authorization", "0ThGJi3xWGlE11sR")
// execute GET call
HttpResponse response = client.execute(request);
// receive and parse JSON result of GET call
def result = g_json.parse(new InputStreamReader(response.getEntity().getContent()))
// initialize list of Openfire users
def ofUsers = []
// iterate through Openfire user list
result["user"].each{
	// add user to user list
	if(it.properties != null){
		ofUsers.add(it.username.toLowerCase())
	}
}
// query to create and modify Openfire users
url = "http://localhost:9090/plugins/restapi/v1/users"
// initialize POST call
HttpPost pRequest = new HttpPost(url);
// add request header
pRequest.addHeader("Content-Type", "application/json");
pRequest.addHeader("Authorization", "0ThGJi3xWGlE11sR");
// get all Intrexx-users who are member of certain usergroup
def ixUsers = g_om.getMembers("6AA80844C3C99EF93BF4536EB18605BF86FDD3C5", true)
// iterate through all found users
ixUsers.each{
	// add user to Openfire if not already exists
	if(!ofUsers.contains(it.loginName.toLowerCase())){
		// fill Groovy map with user-data
		def user = [:]
		user["username"] = it.loginName.toLowerCase()
		user["password"] = it.guid.toLowerCase()
		user["name"] = it.fullName
		user["email"] = it.emailBiz
		user["properties"] = [:]
		user["properties"]["property"] = []
		def key1 = [:]
		key1["@key"] = "IsIntrexxUser"
		key1["@value"] = "true"
		user["properties"]["property"].add(key1)
		// cast map to JSON String
		def jsonString = g_json.stringify(user)
		StringEntity params =new StringEntity(jsonString)
		pRequest.setEntity(params)
		// execute API call
		response = client.execute(pRequest)
	}
}

A user can only communicate with people he knows, so the needs to be some kind of buddylist for the user to manage. These buddies also need to be transferred to Openfire.

import org.apache.http.impl.client.*
import org.apache.http.client.*
import org.apache.http.*
import org.apache.http.client.methods.*
import org.apache.http.auth.*
import org.apache.http.entity.*

import de.uplanet.scripting.groovy.util.Safely

def conn = g_dbConnections.systemConnection
def stmt = null
def rs   = null

try
{
	// prepare statement to select all buddies for a specific user
	stmt = g_dbQuery.prepare(conn, "SELECT STR_BUDDY FROM DATAGROUP('1BBD033039FDAE2C8764449FA2E8903F13DB3895') WHERE STR_USER=?")
	// initialize Openfire authentication for Rest API
	CredentialsProvider provider = new BasicCredentialsProvider()
	UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("admin", "******")
	provider.setCredentials(AuthScope.ANY, credentials)
	//initialize HttpClient
	HttpClient client = HttpClientBuilder.create()
	  .setDefaultCredentialsProvider(provider)
	  .build()
	//read all users of a specific group
	def ixUsers = g_om.getMembers("6AA80844C3C99EF93BF4536EB18605BF86FDD3C5", true)
	ixUsers.each{
		// set parameter of prepared statement
		stmt.setString(1, it.guid)
		// execute prepared statement
		rs = stmt.executeQuery()
		// define url to access user roster
		String url = "http://localhost:9090/plugins/restapi/v1/users/${it.getLoginName().toLowerCase()}/roster"
		// initialize Post request
		HttpPost pRequest = new HttpPost(url)
		// add request header
		pRequest.addHeader("Content-Type", "application/xml")
		pRequest.addHeader("Authorization", "0ThGJi3xWGlE11sR")

		while (rs.next())
		{
			def buddy = g_om.getUser(rs.getStringValue(1), true)
			//initialize Post Content
			StringEntity params =new StringEntity("""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
		<rosterItem>
		    <jid>${buddy.getLoginName().toLowerCase()}@xxtendvb</jid>
		</rosterItem>""")
			// set Post Content
			pRequest.setEntity(params)
			// execute Post request
			response = client.execute(pRequest)
		}
		rs   = Safely.close(rs)
	}
}
finally
{
	rs   = Safely.close(rs)
	stmt = Safely.close(stmt)
}

Implement jQuery XMPP chat

On the jQuery plugin page, there is an example of a basic chat which I reuse and adapt to what I need in Intrexx. Username and password are already clear, so these elements can be deleted. The contact-list doesn’t need to show the username, but the fullname. The HTML/CSS needs to be adjusted a little bit, but in general this already works.

var thisUser = "";

function init_xmpp_client(userGuid, userName){
	Loader.loadJsFileOnDemand("./include/custom/xmpp/jquery.xmpp.js");
	var chat = $("<div style='position: fixed; bottom: 0;width:100%' id='chat' />");
	$("body").append(chat);
	connectXMPP(userGuid,userName);
	return true;
}

function connectXMPP(userGuid, userName){
	var jid = userName+"@xxtendvb";
	var password = userGuid;
	var logContainer = $("#log");
	var contactList = $("#contacts");

	var url ="http://xxtendvb:7070/http-bind/";
	$.xmpp.connect({url:url, jid: jid, password: password,
		onConnect: function(){
			logContainer.html("Connected");
			$.xmpp.setPresence(null);
			thisUser = $.xmpp.jid+"/"+$.xmpp.sid;
		},
		onPresence: function(presence){
			var jid = presence.from.split("/");
			var id = MD5.hexdigest(jid[0]);
			var con = $("#contact"+id);
			if(con.length == 0){
				var contact = $("<li id='contact"+id+"'>");
				contact.append("<a href='javascript:void(0)'>"+ jid[0] +"</a>");
				contact.find("a").click(function(){
						var conversation = $("#"+id);
						if(conversation.length == 0)
							openChat({to:jid[0]});
				});
				contactList.append(contact);
			}
		},
		onDisconnect: function(){
			logContainer.html("Disconnected");
		},
		onMessage: function(message){
			var jid = message.from.split("/");
			var id = MD5.hexdigest(jid[0]);
			var conversation = $("#"+id);
			if(conversation.length == 0){
				openChat({to:jid[0]});
			}
			conversation = $("#"+id);
			conversation.find(".conversation").append("<div>"+ jid[0] +": "+ message.body +"</div>");
		},onError:function(error){
			alert(error.error);
		}
	});
}

function disconnectXMPP(){
	$.xmpp.disconnect();
}

function openChat(options){
	var jid = options.to.split("/");
	var id = MD5.hexdigest(jid[0]);
	var chat = $("<div style='border: 1px solid #000000; float:left; display: inline-block;' id='"+id+"'><div style='border: 1px solid #000000;'>Chat with "+jid[0]+"</div><div style='height:150px;overflow: auto;' class='conversation'></div><div><input type='text' /><button>Send</button></div></div>");
	var input = chat.find("input");
	var sendBut = chat.find("button");
	var conversation = chat.find(".conversation");
	sendBut.click(function(){
		$.xmpp.sendMessage({to:options.to, body: input.val()});
		conversation.append("<div>"+ $.xmpp.jid +": "+ input.val() +"</div>");
		input.val("");
	});
	$("#chat").append(chat);
}

Test communication between two Intrexx users

To test the functionality of this whole construct you can open two Browser instances to login as two different users, or you can use another Third-party-tool like Pidgin to login to Openfire and chat with the user logged in to Intrexx.

XMPP Chat Example in Intrexx 8
XMPP Chat Example in Intrexx 8

Conclusion

Integrating an XMPP client in a low-code environment is a good example why it is so important to have ways of adding new features with programming. Low-Code enables you to add all kinds of functionality to your platform. So as long as the platform doesn’t offer all the features you need, this is a good way of adding them by yourself.

This post was written 2017 in an earlier version of this blog on http://xxtend.net . By now Intrexx offers Push functionality using WebSockets, which means it is possible to add Instant messaging with much less coding now. I’ll dig into that in the future as well.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.