Integrations Framework
  • 28 Aug 2022
  • 8 Minutes to read
  • Contributors
  • Dark
    Light
  • PDF

Integrations Framework

  • Dark
    Light
  • PDF

Scope

Integrating the Vodia PBX with other software can significantly increase the value for businesses. Integrations are available from version 69 on.

Examples include

  • Logging calls with customers
  • Opening pop-up screens for faster user interaction
  • Tracking user activity on timesheet software

The framework for this contains the following parts:

  • Backend code. This code handles the interaction with other software.
  • User frontend code. This part collects the neccessary settings from the user front end and stores them in the user settings. This might include a username for a CRM system and a password.
  • Tenant frontend code. This part does the same for tenant-level information. This might contain a default CRM account, or credentials to push information into a CRM system.

A single tenant may use multiple integrations at the same time. For example while one set of extensions may use a CRM system, another set might be using a trouble ticket system.

Integration Definition

Integrations are managed through the system level web page. An integration must have a name for display purposes, for example "Our CRM System". An integration must also have a identifier that can be used to uniquely identify the integration in the front end and backend in JavaScript and in the HTML code, for example "ourcrmsys". A safe way to choose an identifier is to stick to lower case alphanumeric characters.

Tenant code

The tenant code will be used in the tenant administration web interface to collect the information needed for the integration. It consists of three text fields:

  • HTML. This code will be inserted in the accordion when the tenant administrator selects the integration. It is not a independent web page; it is just a part of the page and typically starts with a div tag.
  • CSS. This piece contains the stylesheet information required for properly displaying the HTML code. In many cases this part can remain empty.
  • JavaScript. This code will take care about presenting and collecting the information for the integration. Because it runs in the browser, all modern browser features can be used.

HTML example

The following example collects a token and a password from the tenant administrator:

<div class="row">
  <div class="form-group has-feedback">
    <label class="col-sm-6 control-label">[[dom_actionurl#user]]</label>
    <div class="col-sm-6">
      <input class="form-control" type="text" name="token" pattern="[a-z]+">
      <span class="glyphicon form-control-feedback" aria-hidden="true"></span>
    </div>
  </div>
</div>
<div class="row">
  <div class="form-group has-feedback">
    <label class="col-sm-6 control-label">[[dom_actionurl#pass1]]</label>
    <div class="col-sm-6">
      <input class="form-control" type="password" name="password">
      <span class="glyphicon form-control-feedback" aria-hidden="true"></span>
    </div>
  </div>
</div>

The HTML code is using two rows in div tags according to Bootstrap, the framework that the tenant code is using. As with the other web pages it uses language items embedded in [[ and ]] tags. For many integrations there will sufficient to just use plain text without the need to translate them into multiple languages.

If there would be any need to style elements, that styling can be either done inline or in the CSS part of the integration. It is suggested to use classnames that start with the integration identifiert to avoid naming conflicts in the web page, for example ourcrmsys-token.

JavaScript example

In order to load the part correctly, there is a piece of JavaScript that will parse the settings and put them into the right place. For saving there is another piece of code that will encode the settings. The following example illustrates how this works:

'use strict';

integrations.ourcrmsys = {
  // Load the public information:
  load: function(form, data) {
    form.querySelector('[name="token"]').value = data.token || ''
    form.querySelector('[name="password"]').value = ''
  },
  // Save returns an array with the public and private information:
  save: function(form) {
    return [
    {
      token: form.querySelector('[name="token"]').value
    },
    form.querySelector('[name="password"]').value
    ]
  }
}

The integration must be an object inside the global integrations object which is set up outside of the integration itself. The object must use the identifier for the integration. This object must have two entries:

  • load. The load entry must be a function that takes the form element and the settings for the integration. The form element can be used to easily find the right fields in the HTML code. In order to avoid conflicts with other integrations, the elements should not use id. For example name tags are better to avoid problems. The data argument contains the data that was stored for the integration with that tenant. It is guaranteede to be an object even if it is not available. Because the object may have no data, it is important to provide default values when loading the form. Sensitive information like passwords are not loaded and must be cleared when this functions is being called.
  • save. The save entry is used to store input from the tenant administrator. It takes like the load entry the form as argument. It returns an Array, where the first entry is the public information and the second entry the sensitive information for the integration. If there is no sensitive information, the function can return just the public information (unless it is an Array).

User frontend code

The user front end consists like the tenant code of three parts: HTML code, CSS code and JavaScript. The main difference is that in the front end, Bootstrap and jQuery are not available.

HTML example

The following example shows how to present a username prompt in the part for the integration:

<pbx-setting-wrapper>
  <pbx-label>[[usr_crm#user]]</pbx-label>
  <pbx-input class="form-control" type="text" name="username" />
</pbx-setting-wrapper>
<pbx-setting-wrapper>
  <pbx-label>[[usr_crm#pass1]]</pbx-label>
  <pbx-input class="form-control" type="password" name="password" />
</pbx-setting-wrapper>
<pbx-setting-wrapper>
  <pbx-label>[[usr_crm#addr]]</pbx-label>
  <pbx-input class="form-control" type="text" name="address" />
</pbx-setting-wrapper>
<pbx-button-wrapper>
  <pbx-button type="submit" appearance="contained" icon="fa-regular fa-floppy-disk" class="save">[[save]]</pbx-button>
</pbx-button-wrapper>

The styling is done by the general user front end style. The easiest way to have styling consistent with other user input elements is to look at those templates.

JavaScript example

The following example shows how to present a username prompt in the part for the integration:

//
//  Example integration code for user level
//

// Load the content of the form
export const load = (form, data) => {
  form.querySelector('[name="username"]').value = data.username || '';
  form.querySelector('[name="address"]').value = data.address || '';
}

// Generate the content that should be saved when the user hits the save button:
export const save = (form, data) => {
  // Public information
  const pub = {
    username: form.querySelector('[name="username"]').value,
    address: form.querySelector('[name="address"]').value
  }

  // Private information
  const pri = {
    password: form.querySelector('[name="password"]').value
  }
  
  // Write the password only if the user has entered something:
  if (pri.password) return [ pub, pri ];
  return pub;
}

The JavaScript code structure is similar to the structure in the tenant part. The integration object is in pbx.integrations.

Backend code

The backend code is executed on the server. Because this is not a browser environment, there is only a smaller functionality available, for more details see Backend JavaScript. The following lines show an example of backend code:

function oncallhungup(data) {
  console.trace('SCRIPT', 5, data.domain, system.format("Call hung up with data %s", JSON.stringify(data)));
}

function onhttp(data, callback) {
  console.trace('SCRIPT', 5, data.domain, system.format("Received http request with data %s", JSON.stringify(data)));
  callback(200, "Ok", "application/json", "true");
}

The following callbacks are available:

  • function oncallreceived(data): This function is called when an incoming call is ringing.
  • function oncallmissed(data): This function is called when a call was missed.
  • function oncallanswered(data): This function is called when an incoming call is answered.
  • function oncallhungup(data): This function is called when an incoming call, that has connected, has now disconnected.
  • function oncalldialedringing(data): This function is called when an outbound call is ringing.
  • function oncalldialedmissed(data): This function is called when an outbound call was not answered.
  • function oncalldialedanswered(data): This function is called when an outbound call was answered.
  • function oncalldialed(data): This function is called when an outbound call has connected and is now disconnected.
  • function oncallerrorrejected(data): This function is called when an inbound call was rejected.
  • function oncallerrornotavailable(data): This function is called when an inbound call was not available.
  • function oncallerrorinvalid(data): This function is called when an inbound call was invalid.
  • function onhttp(data, callback): This function is called when the system receives a request on HTTP/HTTPS that matches the prefix for the integration. The function must take one argument that contains an object with information about the method, the scheme, the body, the identifier for the integration, the url (which is the filename of the request), the domain (if available) and the headers (an array with the headers). This function must call the callback to deliver the result of the request. The callback can be asynchronous, e.g. after performing a query.

All of the above callbacks (except onhttp) take as argument an object with the following entries:

  • call: An object representing the current call.
  • leg: An object representing the current call leg.
  • user: The user ID. This is a integer number, not the name of the account.

Loading and saving state

The backend can load and save variables that are set from the frontend with the two following functions. These functions are only available in the integrations framework and will automatically store the domain or extension variable that is associated with the integration ID.

function loadIntr(domain, user, name, secret): This function can be used to load the value of the entry with the specific name. The username can be empty "" to access the domain data. If secret is true the private information will be retrieved, otherwise the public information.
function saveIntr(domain, user, name, value, secret): This function can be used to save the value of the entry with the specific name.

Starting and ending calls

If there is a need to start or end a call, there are two functions available.

function startCall(args): This function starts a new call and returns the ID for the call. The args are an object with the following entries:

  • domain: This entry contains the DNS name or number of the tenant in which the call will be started.
  • user: This entry contains the alias name or number of the account that is starting the call. It is optional if the from entry is available.
  • from: This field contains number that starts the call. It is only required if there is no user entry.
  • ani: This optional entry explicitly set the ANI to be used for the call.
  • to: This entry defines the number to be called.
  • timeout: The timeout for the user to connect the call, the default is 60 seconds.
  • connect: When this entry is true, the system will attempt to automatically connect the user with the call.

function endCall(id, code, expl, forced): This function can be used to disconnect a call. The id argument is the ID returned from the startCall function. The code is the optional SIP code for ending the call, and the expl the optional disconnect text. If forced is true, the call will be disconnected forcefully. The default values are 200, "Request Terminated" and false.


Was this article helpful?