.. _web_contacts:
Web Service Contacts
====================
.. contents::
:local:
:depth: 3
Overview
--------
Acrobits apps may use an external web service to load user's contacts (address book). Web Service Contact Source can be
used instead of native phone address book, or both phone address book, web service contacts and also CardDAV Contact
Source can be used together. In case multiple contact sources are defined, the GUI lets the user switch between
different sources.
Web Service contacts are obtained in two steps:
#. The app calls the web service and receives response. This response is expected to be in JSON format and should contain an array of contact objects.
If you are already working with an existing contacts web service that returns an XML response, you may still use that depending on the response format.
Please refer to :ref:`contacts-transformation` and :ref:`contacts-wscontactsxpath` to learn more about utilising an existing XML contacts web service.
#. Transform the returned response into an internal json contact representation used by the app
The apps will call the web service periodically at a configurable interval (the default is 180 seconds).
Contacts Source Web Service
---------------------------
Parameter templates for Contacts Source Web Service can use any variables from :ref:`global-params`
and :ref:`account-params` scopes.
The web service is configured on global level, either by Cloud Softphone portal, or by setting the appropriate prefKeys
directly for apps which use Acrobits SDK. The properties follow the general Acrobits Web Service Definition model, with
prefix ``wsContacts``:
``wsContactsUrl``
~~~~~~~~~~~~~~~~~
Contains the URL, including URL scheme, of the web service, possibly also with query string.
Example:
.. code-block:: xml
https://example.com/contacts/?u=%account[username]%&p=%account[password]%
``wsContactsMethod``
~~~~~~~~~~~~~~~~~~~~
The HTTP method to use. Typically ``GET`` or ``POST``. The default is ``GET``.
``wsContactsPostData``
~~~~~~~~~~~~~~~~~~~~~~
If filled in, the app will use POST request to call the web service.
Example ( for application/x-www-form-urlencoded):
.. code-block:: none
u=%account[username]%&p=%account[password]%
Example ( for application/json ):
.. code-block:: json
{
"username" : "%account[username]%",
"password" : "%account[password]%"
}
``wsContactsContentType``
~~~~~~~~~~~~~~~~~~~~~~~~~
Specifies the value of Content-Type header to be sent in POST request. If not specified, the app will default to
``application/x-www-form-urlencoded``
``wsContactsAuthUsername``, ``wsContactsAuthPassword``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In case your server uses HTTP authentication, you can provide the username and password here. The apps support
``Basic`` and ``Digest`` authentication, but note that ``Basic`` authentication will only work when using https url
scheme. The app will not send passwords over plaintext connection.
Example:
.. code-block:: xml
%account[username]%
%account[password]%
``wsContactsRefresh``
~~~~~~~~~~~~~~~~~~~~~
The period in seconds which determines how often will the app call the web service. The default is 180 seconds. The
web service will only be called when the app is running in foreground.
.. _contacts-wscontactsxpath:
``wsContactsXPath``
~~~~~~~~~~~~~~~~~~~
In case the web service response returns XML which contains other information than just the list of contacts, this
parameter tells the app where to look for contact data. See the examples for more information.
``wsContactsKeywordMapping``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Defines the transformation between the format returned by the web service and :ref:`contacts_json`.
Response
--------
The response will be considered successful if the HTTP response code is 2xx. All other responses are silently
ignored.
In case the successful response contains ``Last-Modified`` header, the app will remember it and send its value in
the next request in ``If-Modified-Since`` header. The server is expected to return ``304 Not Modified`` if the
contacts have not changed since the last request.
.. important::
It is highly recommended to include the ``Last-Modified`` header in the response and return ``304 Not Modified``
response in case the contacts have not changed. The contacts can be quite big and fully reloading them periodically
(the default period is only 3 minutes!) can consume very significant bandwidth.
Example
-------
Setup
~~~~~
::
wsContactsUrl = https://example.com/contacts
wsContactsMethod = POST
wsContactsContentType = application/json
wsContactsPostData = {"username" : "%account[username]%","password" : "%account[password]%"}
request
~~~~~~~
.. code-block:: http
POST /example/contacts HTTP/1.1
Host: example.com
Connection: close
Cache-Control: max-age=0
User-Agent: CloudSoftphone/1.5.6
Content-Type: application/json
If-Modified-Since: Wed, 21 Oct 2017 06:28:00 GMT
{"username" : "johndow","password" : "12345"}
response
~~~~~~~~
.. code-block:: http
HTTP/1.1 200 OK
Date: Sun, 15 Mar 2015 00:46:17 GMT
Server: Apache/2.4.7 (Ubuntu)
Content-Length: 3141
Content-Type: application/json
Last-Modified: Wed, 21 Oct 2017 07:28:00 GMT
{
"contacts": [
{
"avatar" : "http://example.com/image.png",
"largeAvatar" : "http://example.com/largerImage.png",
"birthday" : "1998-06-14",
"checksum" : "DFC1490F899CB9FD",
"contactEntries" : [
{
"entryId" : "0",
"label" : "home",
"type" : "tel",
"uri" : "555-610-6679"
},
{
"entryId" : "1",
"label" : "work",
"type" : "tel",
"uri" : "+420 276 285 602"
}
],
"contactAddresses" : [
{
"addressId" : "0",
"label" : "home",
"city" : "Tiburon",
"country" : "USA",
"countryCode" : "us",
"state" : "CA",
"street" : "1747 Steuart Street",
"zip" : "94920"
}
],
"contactId" : "E94CD15C-7964-4A9B-8AC4-10D7CFB791FD",
"displayName" : "David Taylor",
"fname" : "David",
"lname" : "Taylor",
"notes" : "Plays on Cole's Little League Baseball Team"
},
{
"birthday" : "2012-10-15",
"checksum" : "45d8bb50a8c04c91da7a96116611cc3b",
"contactEntries" : [
{
"entryId" : "0",
"label" : "home",
"type" : "tel",
"uri" : "2300"
}
],
"contactAddresses" : [
{
"addressId" : "0",
"city" : "East Michellefurt",
"country" : "Guam",
"countryCode" : "NI",
"label" : "home",
"state" : "Nebraska",
"street" : "327 Jamie Pike Apt. 239",
"zip" : "11525"
}
],
"contactId" : "c181c63c0f3b42d69e71abf8c38064d4",
"company" : "Amazon",
"displayName" : "Brenda Patrick",
"fname" : "Brenda",
"lname" : "Patrick",
"notes" : "Plays on Cole's Little League Baseball Team."
}
]
}
.. important::
Each contact is a dictionary of key-value pairs, where keys are string identifiers and values are strings, or, in case
of ``contactEntries``, an array of dictionaries containing phone numbers, emails, urls of the contact.
Additionally, there is ``contactAddresses``, which is an array of dictionaries containing the addresses of the contact.
.. note::
The strings inside contacts should use UTF-8 encoding.
Keys
----
Keys in main Contact dictionary:
+--------------------+--------------------+--------------------+
| **contactId** | **displayName** | **checksum** |
+--------------------+--------------------+--------------------+
| **fname** | **mname** | **lname** |
+--------------------+--------------------+--------------------+
|**fnamePhonetic** |**mnamePhonetic** |**lnamePhonetic** |
+--------------------+--------------------+--------------------+
| **nick** | **namePrefix** | **nameSuffix** |
+--------------------+--------------------+--------------------+
| **company** |**departmentName** | **jobTitle** |
+--------------------+--------------------+--------------------+
| **birthday** | **notes** |**contactEntries** |
+--------------------+--------------------+--------------------+
|**contactAddresses**| | |
+--------------------+--------------------+--------------------+
Keys in Contact Entry dictionray:
+-----------------+-----------------+-----------------+-----------------+
| **entryId** | **type** | **uri** | **label** |
+-----------------+-----------------+-----------------+-----------------+
Keys in Contact Address dictionray:
+-----------------+-----------------+-----------------+-----------------+
| **street** | **city** | **state** | **zip** |
+-----------------+-----------------+-----------------+-----------------+
| **country** | **countryCode** | **label** | |
+-----------------+-----------------+-----------------+-----------------+
Detailed description of keys
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(except for those which are obvious)
``contactId``
Unique contact identifier which should not change. This field is required, contacts without this field will be
ignored.
.. note::
In addition to being required, contactId must be unique for each contact entry. If multiple contacts share the same contactId, web service contacts will not work.
``displayName``
String which will be shown in GUI when presenting the contact in in-call screen, contact list views etc.
``checksum``
A string value which should change whenever anything in the contact changes. In case you don't provide one, we will
generate it as SHA1 of all values in contact dictionary.
``fname``, ``mname``, ``lname``
First, Middle, Last names.
``fnamePhonetic``, ``mnamePhonetic``, ``lnamePhonetic``
Phonetic names, important for sorting contacts which use eastern alphabets (like Kanji).
``countryCode``
ISO2 country code.
``contactEntries``
Contains array of dictionaries with phone numbers, emails or uris for the contact.
``entryId``
Unique identifier of entry. It only has to be unique within the contact where it belongs.
``type``
Type of contact entry. One of: ``tel``, ``email``, ``url`` .
``label``
The label of contact entry. For example, ``home``, ``work``, ``mobile`` etc.
``uri``
The value of contact entry. The value depends on ``type``, it is either a phone number, email or URL.
``contactAddresses``
Contains array of dictionaries with addresses for the contact.
``addressId``
Unique identifier of address. It only has to be unique within the contact where it belongs.
``label``
The label of contact address. For example, ``home``, ``work`` etc.
.. _contacts-transformation-xml:
Transformation from XML
-----------------------
Example XML response
~~~~~~~~~~~~~~~~~~~~
In case the webservice returns a response in the following XML like below, we need to define a transformation which will map the xml nodes to Contacts JSON keys.
.. code-block:: xml
0
OK
https://example.com/avatar.png
https://example.com/largeAvatar.png
Jason
Bourne
224
1
d111a2e3-b806-40ce-955b-52c31996a4c7
2018-01-01 16:10:45
2018-01-01 17:20:13
East Michellefurt
Nebraska
11525
327 Jamie Pike Apt. 239
Guam
James
Bond
226
2
156eea0b-872f-4e44-844b-ee19c9b3fa46
2018-01-01 16:10:45
2018-03-05 17:22:10
In cases where the contacts tag is nested within other tags, we need to specify the ``wsContactsXPath`` to locate the contacts node correctly. The app will use the ``wsContactsXPath`` to get to the node with contact elements. For the above XML, the ``wsContactsXPath`` would be::
content/contacts
Defining the Transformation
~~~~~~~~~~~~~~~~~~~~~~~~~~~
To map the XML nodes to the JSON keys, we need to define the ``wsContactsKeywordMapping`` strings. For the above XML, we will use two different mappings:
.. _transformation-mapping-1:
**Mapping 1:**
.. code-block:: none
firstName=fname,lastName=lname,organization=company,uniqueId=contactId,organizationalUnit=departmentName,lastModified=checksum,extension=contactEntries:tel,homeCity=contactAddresses:city,homeState=contactAddresses:state,homeZipCode=contactAddresses:zip,homeCountry=contactAddresses:country,homeStreet=contactAddresses:street
The transformation will map the XML nodes to the JSON keys as follows:
.. _transformation-mapping-1-output:
.. code-block:: json
{
"contacts": [
{
"avatar" : "8FAA3B8F02636F20C551C26D04C0E817121CBA20",
"avatar_URL" : "https://example.com/avatar.png",
"checksum" : "2018-01-01 17:20:13",
"company" : "",
"contactEntries" : [
{
"entryId" : "0",
"type" : "tel",
"uri" : "224"
}
],
"contactAddresses": [
{
"addressId" : "0",
"city" : "East Michellefurt",
"state" : "CA",
"zip": "11525",
"country": "Guam",
"street": "327 Jamie Pike Apt. 239"
}
],
"contactId" : "d111a2e3-b806-40ce-955b-52c31996a4c7",
"creationDate" : "2018-01-01 16:10:45",
"departmentName" : "",
"fname" : "Jason",
"lname" : "Bourne",
"id" : "1",
"largeAvatar" : "07181300F304ED840D050AB428657AB46EA10A7E",
"largeAvatar_URL" : "https://example.com/largeAvatar.png"
},
{
"checksum" : "2018-03-05 17:22:10",
"company" : "",
"contactEntries" : [
{
"entryId" : "0",
"type" : "tel",
"uri" : "226"
}
],
"contactId" : "156eea0b-872f-4e44-844b-ee19c9b3fa46",
"creationDate" : "2018-01-01 16:10:45",
"departmentName" : "",
"fname" : "James",
"id" : "3",
"lname" : "Bond"
}
]
}
The transformation is a string with comma-separated entries in the form ``xmlNodeName=jsonKey``. In the above example, we use the correct key names for first and last name, company, department and contact identifier. The web service result provides ``lastModified`` value, which we can use as our internal ``checksum`` (the value will change whenever the contact data are modified).
.. _transformation-mapping-2:
**Mapping 2:**
This particular XML response uses only one phone number per contact and one address per contact. In order to map multiple phone numbers or addresses, we can use the following syntax:
.. code-block:: none
firstName=fname,lastName=lname,organization=company,uniqueId=contactId,organizationalUnit=departmentName,lastModified=checksum,extension=contactEntries_ext:tel,workNumber=contactEntries_work:tel,homeNumber=contactEntries_home:tel,homeCity=contactAddresses_home:city,homeState=contactAddresses_home:state,homeZipCode=contactAddresses_home:zip,homeCountry=contactAddresses_home:country,homeStreet=contactAddresses_home:street
Using this mapping it is possible to specify a ``label`` of the particular entry in ``contactEntries`` and ``contactAddresses``. It will create tree phone numbers with labels ``ext``, ``work`` and ``home`` with values from XML nodes ``extension``, ``workNumber`` and ``homeNumber`` and tree contact addresses with labels ``home`` and ``work`` respectively.
.. _transformation-mapping-2-output:
.. code-block:: json
{
"contacts": [
{
"avatar" : "8FAA3B8F02636F20C551C26D04C0E817121CBA20",
"avatar_URL" : "https://example.com/avatar.png",
"checksum" : "2018-01-01 17:20:13",
"company" : "",
"contactEntries" : [
{
"entryId" : "0",
"label" : "ext",
"type" : "tel",
"uri" : "224"
}
],
"contactAddresses": [
{
"addressId" : "0",
"label" : "home",
"city" : "East Michellefurt",
"state" : "CA",
"zip": "11525",
"country": "Guam",
"street": "327 Jamie Pike Apt. 239"
}
],
"contactId" : "d111a2e3-b806-40ce-955b-52c31996a4c7",
"creationDate" : "2018-01-01 16:10:45",
"departmentName" : "",
"fname" : "Jason",
"lname" : "Bourne",
"id" : "1",
"largeAvatar" : "07181300F304ED840D050AB428657AB46EA10A7E",
"largeAvatar_URL" : "https://example.com/largeAvatar.png"
},
{
"checksum" : "2018-03-05 17:22:10",
"company" : "",
"contactEntries" : [
{
"entryId" : "0",
"label" : "ext",
"type" : "tel",
"uri" : "226"
}
],
"contactId" : "156eea0b-872f-4e44-844b-ee19c9b3fa46",
"creationDate" : "2018-01-01 16:10:45",
"departmentName" : "",
"fname" : "James",
"id" : "3",
"lname" : "Bond"
}
]
}
.. _contacts-transformation-json:
Transformation from JSON
------------------------
The process of transforming JSON data involves mapping the fields in the input JSON to the desired output format. Below, we will illustrate this transformation using two examples of JSON responses. The mappings and outputs required for this transformation have already been defined earlier in the document and are referenced here for clarity.
Example JSON response
~~~~~~~~~~~~~~~~~~~~~
.. code-block:: json
{
"contacts": [
{
"avatar": "https://example.com/avatar.png",
"largeAvatar": "https://example.com/largeAvatar.png",
"firstName": "Jason",
"lastName": "Bourne",
"extension": "224",
"id": "1",
"uniqueId": "d111a2e3-b806-40ce-955b-52c31996a4c7",
"organization": "",
"organizationalUnit": "",
"creationDate": "2018-01-01 16:10:45",
"lastModified": "2018-01-01 17:20:13",
"homeCity": "East Michellefurt",
"homeState": "Nebraska",
"homeZipCode": "11525",
"homeStreet": "327 Jamie Pike Apt. 239",
"homeCountry": "Guam"
},
{
"firstName": "James",
"lastName": "Bond",
"extension": "226",
"id": "2",
"uniqueId": "156eea0b-872f-4e44-844b-ee19c9b3fa46",
"organization": "",
"organizationalUnit": "",
"creationDate": "2018-01-01 16:10:45",
"lastModified": "2018-03-05 17:22:10"
}
]
}
To transform the above JSON, we will use the mappings defined earlier. These mappings specify how each field in the input JSON should be transformed to the output format.
**Mapping 1:**
Mapping 1 provides a straightforward transformation where each field in the JSON is mapped directly to its corresponding field in the output. Refer to the detailed definition of Mapping 1 :ref:`here `
This mapping includes fields such as:
- ``firstName`` mapped to `fname`
- ``lastName`` mapped to ``lname``
- ``organization`` mapped to ``company``
- ``uniqueId`` mapped to ``contactId``
- ``lastModified`` mapped to ``checksum``
- ``extension`` mapped to ``contactEntries:tel``
- Address fields like ``homeCity``, ``homeState``, ``homeZipCode``, ``homeStreet``, and ``homeCountry`` mapped to ``contactAddresses``
See the detailed output for Mapping 1 :ref:`here `.
By applying :ref:`Mapping 1 `, the transformation ensures that the fields from the input JSON are correctly mapped and structured as per the requirements. This process helps in converting the input data into a standardized format, making it easier to handle and process.
**Mapping 2:**
Mapping 2 is used for more complex transformations, especially when there are multiple phone numbers or addresses per contact. This mapping introduces labels to distinguish between different types of phone numbers and addresses. Refer to the detailed definition of :ref:`Mapping 2 `.
This mapping includes fields such as:
- ``firstName`` mapped to ``fname``
- ``lastName`` mapped to ``lname``
- ``organization`` mapped to ``company``
- ``uniqueId`` mapped to ``contactId``
- ``lastModified`` mapped to ``checksum``
- Different types of phone numbers like ``extension``, ``workNumber``, ``homeNumber`` mapped to ``contactEntries_ext:tel``, ``contactEntries_work:tel``, ``contactEntries_home:tel``
- Different types of addresses like ``homeCity``, ``homeState``, ``homeZipCode``, ``homeStreet``, and ``homeCountry`` mapped to ``contactAddresses_home:city``, ``contactAddresses_home:state``, ``contactAddresses_home:zip``, ``contactAddresses_home:street``, ``contactAddresses_home:country``
See the detailed output for Mapping 2 :ref:`here `. This output illustrates how the input JSON fields are mapped to the corresponding output fields, including the use of labels for different types of phone numbers and addresses.