Integrations Framework
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:
// Load the public information:
export const load = (form, data) => {
form.querySelector('[name="token"]').value = data.token || ''
form.querySelector('[name="password"]').value = ''
}
// Save returns an array with the public and private information:
export const save = (form) => {
return [
{
token: form.querySelector('[name="token"]').value
},
form.querySelector('[name="password"]').value
]
}
The script must have two entries:
load
. The load entry must be a function that takes the form element and the settings for the integration. Theform
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 useid
. For examplename
tags are better to avoid problems. Thedata
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 theload
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) => {
// 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 ongroup(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");
}
function ongroup(data)
The ongroup
function is called when there is a change to a group (queue or ring group). The data object contains the following entries:
domain
: An object that references the tenant. It contains theaddress
(e.g."tenant1.pbx.com"
), thename
(e.g."ACME trash removal"
) and theid
for the tenant (e.g.3
).state
: The state of the call, e.g."ringing"
.timestamps
: Timestamps for the main events of the call, e.g."start"
,"connect"
or"end"
in seconds since 1970.inbound
: A boolean value that indicates if this was an inbound call.ringing
(optional): A list of the agents that are currently ringing. For each agent, the system uses an object that includes thenumber
(e.g."41"
), aname
(e.g."Joe Doe"
) and theid
for the agent (e.g.3
).missed
(optional): A list of agents that missed the call, in the same format likeringing
.agent
(optional): The agent that is connected.caller
: The information about the caller as an object with thenumber
and thename
.callee
: The information about the callee as an object with thenumber
and thename
.group
: Information about the group as an object, including thetype
of the call, thenumber
and thename
.reason
(optional): If the call was disconnected, additional information as object including the disconnectcode
(e.g.487
) and the human readabletext
for the code (e.g."Disconnected"
).callid
: The call-ID for the call which should be unique across all calls.
function onagent(data)
The onagent
function is called when there is a change to an agent. The entries domain
, state
, timestamps
, inbound
, agent
, caller
, callee
, reason
, callid
are the same as for the ongroup call. Only the group
entry is different, it contains the number of the group.
function oncdr(data)
This function is called when the call has ended. It contains the following information about the call:
domain
: The address of the tenant.callid
: The unique call-ID for the call.from
: TheFrom
header for the call.to
: TheTo
header for the call.cmc
: The client matter code for the call (entered by the agent).comment
: A comment for the call (entered by the agent).category
: The category for the call (entered by the agent).rating
: The rating for teh call (entered by the caller).start
: The start timestamp.connect
: The time when the call connected (if it connected)end
: The end time stamprecordings
: An array containing objects about the recording for the call:time
: The time when the recording started.file
: The file location for the calllog
: The log entry for accessing the recording (e.g. a manager started playback)account
: The group account for the recording (e.g. the queue)extension
: The extension that was connected
states
: An array containing objects about the states the call went through:from
: TheFrom
header at that state of the callto
: TheTo
header at that state of the calllanguage
: The language code at that state of the callcode
: The state code for the state (e.g.200
)start
: The start time stamp for the statedurationivr
: The duration (in ms) how long IVR was playeddurationring
: The duration (in ms) how long ringback was playeddurationtalk
: The duration (in ms) how long the call was connected (including hold)durationhold
: The duration (in ms) how long the call was helddurationidle
: The duration (in ms) how long the agent was idle before accepting the callmissed
: An array if the extension ID that missed the callreason
: The reason code for redirecting the calltype
: The state type (e.g.acd
)account
: The account number for the group of the stateaccountuser
: The account ID for the group of the stateextension
: The extension number for the group of the stateextensionuser
: The extension ID for the group of the state
trunklegs
: An array containing information about the trunks involved in the call:direction
: The direction of the trunk callfrom
: TheFrom
header at that state of the callto
: TheTo
header at that state of the callremoteparty
: The remote party for the call (depending on the direction)localparty
: The local party for the calltrunk
: The trunk that was involvedcost
: The cost for the callstart
: The start time stamp for the callconnect
: The connect time stamp for the call (if connected)end
: The end time stamp for the callcode
: The SIP response code for the callipadr
: The IP address for the SIP packets from the trunkquality
: The quality report for the callextension
: The extension that triggered in the callcodec
: The codec that was used for the callmos
: The MOS estimate for the call
extensionlegs
: An array containing information about the extensions involved in the call:callid
: The call-ID for the callfrom
: TheFrom
header at that state of the callto
: TheTo
header at that state of the calldirection
: The direction of the trunk callextension
: The extension that was involved in the callredirect
: The redirect information for the callidle
: The idle duration for the agentstart
: The start time stamp for the callconnect
: The connect time stamp for the call (if connected)end
: The end time stamp for the callipadr
: The IP address for the extensionquality
:The quality report for the calltype
: An indication is the call was missed and if the user has marked the call as readdeleted
: A flag that indicates if the user marked this leg as deletedua
: The User-Agent for the callcodec
: The codec
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 thefrom
entry is available.from
: This field contains number that starts the call. It is only required if there is nouser
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
.