COMAPI

Suggest Edits

The One API Overview

 

Comapi offers one all encompassing messaging API which we refer to as the "One" API.
This API lets you send messaging to your customers across all supported channels simply, using a RESTful service and JSON data.

In addition to giving you multi channel messaging support through one simple API, you can optionally use the Branch features to intelligently message your users using the channels you specify in your preference order, therefore saving costs and improving users experiences.

The following sections explain how you can use the "One" API:

Suggest Edits

Calling the One API

 

End Points

The following endpoints (URLs) are available for the "One" API for HTTP POSTing to.

Which method to use?

Use the batch submission if you have more than one message to send at a time, otherwise use the single submission.

We will only accept connections using TLS 1.1 or above

For security reasons we do not accept SSL connections negotiated using TLS 1.0 anymore. Almost all OS's support TLS 1.1 or higher, and we recommend using TLS 1.2 if supported. This is usually done automatically for you, but on versions of .Net prior to 4.6 TLS 1.2 is supported but is not the default protocol, and needs to be set explicitly.

ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;

See this article for more information.

Single Message Submission

The endpoint for the web service is: https://api.comapi.com/apispaces/apiSpaceId/messages

Send a single message request object using JSON e.g.

{
  "body": "Sending is easy with Comapi",
  "to": {
    "phoneNumber": "447123123123"
  },
  "rules": [
    "sms"
  ]
}

You can find more details about the end point here, but please read this section first so that you are familiar with how the API works.

Batch Message Submission

The endpoint for the web service is: https://api.comapi.com/apispaces/apiSpaceId/messages/batch

Send a batch of message request objects using a JSON array e.g.

[
  {
    "body": "This is message 1",
    "to": {
      "profileId": "dave@acme.com",
      "phoneNumber": "447123123123"
    },
    "rules": [
      "fbMessenger",
      "sms"
    ]
  },
  {
    "body": "This is message 2",
    "to": {
      "profileId": "bob@acme.com",
      "phoneNumber": "447234234234"
    },
    "rules": [
      "fbMessenger",
      "sms"
    ]
  }
]

You can find more details about the end point here, but please read this section first so that you are familiar with how the API works.

Content Type

The content type for the HTTP requests should be set to application/json using the Content-Type HTTP header:

Content-Type: application/json

Security

Secure communications

The "One" API is only accessible via secure communications using 2048 bit TLS encryption.

Granular authorisation

The authorisation to invoke methods on the API is provided by claims in JWT access tokens that can be created by sufficiently privileged users in the Comapi Portal. Tokens may be revoked at any time, and can be assigned permissions individually to allow least privilege best practices to be easily implemented.

See our Quick Start guide for more details on how to setup a security token.

Authorising web service calls

To use your access token to authorise calls to Comapi web services you should ensure that your token is included in the Authorization HTTP header as a Bearer for example:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3NjYzZGU4OC02MTRlLTQ1NTYtYmNiYi1mMDJlZjk4OWFlN2UiLCJpc3MiOiJodHRwczovL2FwaS5jb21hcGkuY29tL2FjY2Vzc3Rva2VucyIsImF1ZCI6Imh0dHBzOi8vYXBpLmNvbWFwaS5jb20iLCJhY2NvdW50SWQiOjM5Njk0LCJhcGlTcGFjZUlkIjoiNTMzNjQxOTgtM2YzZi00NzIzLWFiOGYtNzA2ODBjMTExM2IxIiwicGVybWlzc2lvbnMiOlsiY29udjpycHUiLCJjb252OnJwciIsImNvbnY6cmEiLCJjb252OndwdSIsImNvbnY6d3ByIiwiY29udjpkcHUiLCJjb252OmRwciIsImNvbnY6ZGEiLCJjb252bXNnOnIiLCJjb252bcgHtyIoLCJjaGFuOnIiLCJjaGFuOnciLCJjaGFuOmQiLCJwcm9mOnJhIiwicHJvZjpybyIsInByb2Y6d2EiLCJwcm9mOndvIiwicHJvZjpkYSIsInByb2Y6ZG8iLCJtc2c6c21zOnMiLCJtc2c6ZmJtc2c6cyIsIm1zZzphcHBtc2c6cyIsIm1zZzpjdXN0b206cyIsIm1zZzpyIiwiYXBpczpybyJdLCJzdWIiOiI3NjYzZGU4OC02MTRlLTQ1NTYtYmNiYi1mMDJlZjk4OWFlN2UiLCJwcm9maWxlSWQiOiJDb21hcGlfU3VwcG9ydCIsIm5hbWUiOiJNZXlhLVN1cHBvcnQiLCJpYXQiOjE0ODcxODUxNzB9.vRx3nQyeg66eZLeKZ03R1Npcayul_XdipvoObBF_M2I
POST /apispaces/53364198-3f3f-4723-ab8f-70680c1113b1/messages HTTP/1.1
Host: api.comapi.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3NjYzZGU4OC02MTRlLTQ1NTYtYmNiYi1mMDJlZjk4OWFlN2UiLCJpc3MiOiJodHRwczovL2FwaS5jb21hcGkuY29tL2FjY2Vzc3Rva2VucyIsImF1ZCI6Imh0dHBzOi8vYXBpLmNvbWFwaS5jb20iLCJhY2NvdW50SWQiOjM5Njk0LCJhcGlTcGFjZUlkIjoiNTMzNjQxOTgtM2YzZi00NzIzLWFiOGYtNzA2ODBjMTExM2IxIiwicGVybWlzc2lvbnMiOlsiY29udjpycHUiLCJjb252OnJwciIsImNvbnY6cmEiLCJjb252OndwdSIsImNvbnY6d3ByIiwiY29udjpkcHUiLCJjb252OmRwciIsImNvbnY6ZGEiLCJjb252bXNnOnIiLCJjb252bcgHtyIoLCJjaGFuOnIiLCJjaGFuOnciLCJjaGFuOmQiLCJwcm9mOnJhIiwicHJvZjpybyIsInByb2Y6d2EiLCJwcm9mOndvIiwicHJvZjpkYSIsInByb2Y6ZG8iLCJtc2c6c21zOnMiLCJtc2c6ZmJtc2c6cyIsIm1zZzphcHBtc2c6cyIsIm1zZzpjdXN0b206cyIsIm1zZzpyIiwiYXBpczpybyJdLCJzdWIiOiI3NjYzZGU4OC02MTRlLTQ1NTYtYmNiYi1mMDJlZjk4OWFlN2UiLCJwcm9maWxlSWQiOiJDb21hcGlfU3VwcG9ydCIsIm5hbWUiOiJNZXlhLVN1cHBvcnQiLCJpYXQiOjE0ODcxODUxNzB9.vRx3nQyeg66eZLeKZ03R1Npcayul_XdipvoObBF_M2I
Content-Type: application/json

Need a access token?

See our quick start guide for a guide to creating access tokens in the Comapi portal

Suggest Edits

Addressing your messages

 

The Comapi "One" API makes reaching your customers easy by allowing you to pass multiple ways of identifying your target customer in the to block of the request, plus it will automatically use relevant information from a customers Comapi profile in order to send via a specified channel.

e.g. If you pass a profileId only in the to block and wanted to send via Facebook Comapi would automatically look at the customers profile to see if they have a fbMessengerId and use it.

Profiles

Comapi can store contact profiles for you to make it easier to send across multiple channels as you can retain important details such as phone numbers, Facebook Ids, emails etc... and then have Comapi automatically utilise them when required to send via a channel such as Facebook.

To ensure Comapi creates or updates a profile record for a contact ensure you include a profileId in the to section of your requests. The profileId value can be any unique identifier for the contact, so if your system uses email to identify customers pass that, or customer numbers etc...

{
  "body": "The phoneNumber will be stored against the profileId",
  "to": {
    "profileId": "dave.smith@acme.com",
    "phoneNumber": "447123123123"
  },
  "rules": [
    "sms"
  ]
}
{
  "body": "This will still work even though no phoneNumber is being passed!",
  "to": {
    "profileId": "dave.smith@acme.com"
  },
  "rules": [
    "sms"
  ]
}

Profiles are useful for the Facebook Messenger channel

You need to use profiles when using the Facebook Send to Messenger plugin, as we use the profileId to save the unique Facebook Messenger Id Facebook sends to Comapi when a customer clicks on the Send to Messenger button. To send to this customer using Facebook Messenger simply ensure you pass the profileId in the to section. Find out more in the Facebook channel details.

Suggest Edits

Message sending across channels

 

The Comapi "One" API automatically creates a compatible message for each channel based on the text content held in the body property, therefore removing the need to specify a custom message body for each channel. This makes sending simple text based messages simple.

You can optionally specify a title property, which will be used to describe the message on channels that support the concept.

Merge Fields

In both the title and body properties you can optionally use merge fields to merge in properties from the Comapi profile you are addressing the message to. To do this simply add {{ profile property name }} anywhere in the properties e.g.

{
  "body": "Hello {{profile.name}}, thanks for signing up today.",
  "title": "{{profile.name}} welcome",
  "to": {
    "phoneNumber": "447123123123"
  },
  "rules": [
    "sms"
  ]
}

If you are creating a new profile with the send, then the merge field will allow the merging of the data in the to property, and if no property value can be found a blank string will replace the token.

How do I find my profile property names?

You can find your profile property names by calling retrieving you profile schema by calling this web service

Watch out for length restricitions

Some channels may have maximum message length restrictions. If your message exceeds then it will be truncated. Use a customBody to provide alternative content for these channels

Suggest Edits

Custom messages

 

The "One" API enables you to optionally define a more specific message body for a channel, allowing you to utilise all of the features supported by that channel. If you don't define a customBody for a channel Comapi will automatically create a message body using the text in the body property.

See each channels section for more information on what content to populate the customBody property with e.g. Facebook's custom body

Some examples of custom body content are:

{
  "body": "Test from Comapi",
  "to": {
    "profileId": "**YOUR USER PROFILE ID**"
  },
  "customBody": {
    "fbMessenger": {
      "attachment": {
        "type": "image",
        "payload": {
          "url": "https://scontent.xx.fbcdn.net/v/t1.0-1/p200x200/17156020_1871286216424427_1662368582524349363_n.jpg?oh=22685c22a19fc2e28e69634e6a920972&oe=592FD3D1"
        }
      }
    }
  },
  "rules": [
    "fbMessenger"
  ]
}
{
  "body": "Test from Comapi",
  "to": {
    "phoneNumber": "447123123123"
  },
  "customBody": {
    "sms": {
      "from": "Comapi",
      "to": "447123123123",
      "body": "Hello, this is a test message",
      "deliverAfter": "2017-03-01T12:57:00.543Z",
      "clientRef": "124546"
    }
  },
  "rules": [
    "sms"
  ]
}

We make it simple

If you only want to define a customBody for some of the channels you are targeting Comapi will automatically create a message body for the other channels based on the text in the body property.

Merge Fields

In custom bodies you can optionally use merge fields to merge in properties from the Comapi profile you are addressing the message to. To do this simply add {{ profile property name }} anywhere in the properties.

If you are creating a new profile with the send, then the merge field will allow the merging of the data in the to property, and if no property value can be found a blank string will replace the token.

In addition to the profile merge fields you can reference the {{body}} and {{title}} properties of the request to avoid duplication. e.g.

{
  "body": "Hi {{profile.name}}, this body text can be merged in the customBody!",
  "to": {
    "profileId": "joe.blogs@acme.com"
  },
  "customBody": {
    "fbMessenger": {
      "text": "{{body}}"
    }
  },
  "rules": [
    "fbMessenger",
    "sms"
  ]
}
Suggest Edits

Multi part messages

 

The "One" API can send multi part messages to any channel allowing you to easily add images, files and any other MIME type attachment to your messages.

How to create a multi part message

You will not need to specify a body property in your call to the API as you will be specifying the message content using the messageParts property. The messageParts property should contain an array of the parts you want to use to create your message.

Choose your message content source

The "One" API will use either a body property or messageParts property to create your message content but not both! Any custom bodies that have been defined will override the message parts for that channel.

Part 1 is the text for the message

The first part in your array will be used for the text in your message and so is usually the MIME type text/plain, but could be text/html but this will only work with channels that support HTML.

Additional parts

You can add any number of additional parts to your call of any valid MIME type; common uses will be adding images and files. The data for the parts can be defined either inline in the data property or in a URL in the url property. If you have large files we recommend using the URL approach and if necessary uploading the files to our content hosting service to obtain a URL for accessing the file.

Attachments using URLs

When attaching a file using a URL in the message part please ensure the following properties are populated and no others, as we will calculate the name and size from the file at the URL:

  • type
  • url

Attachments using Base64

When attaching a file using a Base64 data string in the message part please ensure the following properties are populated and no others, as we will to understand what the file should look like:

  • name
  • type
  • data
  • size (Optional)

messageParts structure

The messageParts property is an array of messagePart as defined below:

messagePart

Property
Type
Description

name*

string

The name of your part, usually the file name

type*

string

The MIME type for the part e.g. image/png

url

string

The URL to the file data.

If this isn't specified then the data property must be

data

string

The base64 encoded data for the file.

If this isn't specified then the url property must be

size

string

The size in bytes of the data for the message part; optional.

Choose URL or base64 data

You must choose to get your file data from either a URL or inline using base64 encoded data but not both!

Examples:

{
  "title": "Your order has been dispatched",
  "to": {
    "email": "dave@somewhere.com"
  },
  "rules": [
    "email"
  ],
  "messageParts": [
    {
      "name": "Body html",
      "type": "text/html",
      "data": "<h2>Item Repaired</h2><p>Your return number: <b>ABC1245</b> has been repaired and is being retuned to you</p>"
    },
    {
      "type": "image/png",
      "url": "http://cdn.dnky.co/3rdparty/comapi/images/laptop.png"
    }
  ],
  "metadata": {
    "data": "My correlation data"
  }
}
{
  "title": "Your order has been dispatched",
  "to": {
    "phoneNumber": "447123123123"
  },
  "rules": [
    "sms"
  ],
  "messageParts": [
    {
      "name": "Body text",
      "type": "text/plain",
      "data": "Your order: ABC1245 has been dispatched."
    },
    {
      "type": "image/png",
      "url": "http://cdn.dnky.co/3rdparty/comapi/images/laptop.png"
    }
  ],
  "metadata": {
    "data": "My correlation data"
  }
}
Suggest Edits

Channel selection

 

You can easily send a message to a single channel by including a rules array with a single channel listed. For example:

"rules": [
    "sms"
  ]
{
  "body": "Test from Comapi",
  "to": {
    "phoneNumber": "447123123123"
  },
  "rules": [
    "sms"
  ]
}

Please see our Branch section for more information about how you instruct Comapi to use multiple channels.

Supported channels are:

SMS

Send and receive SMS globally, with optional delivery receipts

Get started with the docs here

Facebook Messenger

Send messages via Facebook Messenger and reach an audience of over 1 billion monthly active users.

Get started with the docs here

Email

Use email to engage and inform your users, or use in conjunction with Branch as a great fallback channel due to its universal reach.

Get started with the docs here

App Messaging

App messaging allows you to build secure messaging and chat services into your native and web applications. Use App messaging for P2P chat, B2C chat or A2P messaging for alerts reminders and notifications.

Get started with the docs here

Inbox

Inbox is a complete platform for engaging and communicating to your customers via any mobile or native application. Inbox gives you the power to send rich HTML content and push notifications into apps.

Get started with the docs here

Custom Channels

If we don’t have it covered or you want to use existing suppliers you can use our custom channel to call your existing providers API or call any other communication channel. Total flexibility without the hassle of changing.

Get started with the docs here

Suggest Edits

Additional options

 

Setting Expiry

For time sensitive messages you can specify a expiry date and time where Comapi won't deliver you message after. Where a channel supports message expiry the expiry time will be passed on automatically.

To set an expiry set the following property:

Property
Type
Description

expiresOn

date-time

Date/Time (in UTC, ISO 8601 format)

{
  "body": "Test from Comapi",
  "to": {
    "phoneNumber": "447123123123"
  },
  "expiresOn": "2017-03-16T17:05:02Z",
  "rules": [
    "sms"
  ]
}
"expiresOn": "2017-03-16T17:05:02Z"

Others...

These other properties may be of use:

Property
Type
Description

title

string

The title to use to describe the message (if supported by the channel)

conversationId

string

Optional conversationId, used to correlate messages within Comapi and on any external channels, if supported

metadata

object

Custom metadata relating to the message that will be echoed back to you on any webhook events related to the message.

Can be used to correlate events back to sends, and aid webhook processing efficiency.

Ideal for use with webhooks for reporting and billing data collection.

channelOptions

object

Allows easy configuration of popular options for a channel without the need for a full customBody. See each channels details for further details.

{
  "body": "Hi, just letting you know that your order #1234 has been dispatched",
  "to": {
    "phoneNumber": "447123123123"
  },
  "rules": [
    "sms"
  ],
  "metadata": {
    "data": "My correlation data",
    "moreData": "You can hold any structured JSON data!"
  }
}
Suggest Edits

Message send response

 

The response from the API changes depending on whether you sent a single message or a batch of message, please refer to the appropriate section below:

Single Submission Response Details

Successful Submission Response

Upon a successful send to the "One" API you will receive a HTTP 201 and a JSON body in the following format:

Property
Type
Description

messageId

string

Unique id for this message

sentOn

string

Date/Time (in UTC, ISO 8601 format) that the message sent was accepted

status

string

One of:

  • processing
  • failed
  • sent

statusDetails

object

Specific details related to the current status.

_createdOn

string

Date/Time (in UTC, ISO 8601 format) that the message was created

_createdBy

string

The profile id that created the message

_updatedOn

string

Date/Time (in UTC, ISO 8601 format) that the message was last updated

_updatedBy

string

The profile id that last updated the message

{
  "_createdOn": "2017-03-17T14:22:40.633Z",
  "_updatedOn": "2017-03-17T14:22:40.633Z",
  "_createdBy": "access:3dd1558a-9db3-406a-8460-cbe5100a7628",
  "_updatedBy": "access:3dd1558a-9db3-406a-8460-cbe5100a7628",
  "to": {
    "phoneNumber": "447123123123"
  },
  "senderProfileId": "M0rsy",
  "body": "I love Comapi",
  "rules": [
    "sms"
  ],
  "sentOn": "2017-03-17T14:22:40.627Z",
  "channelOptions": {
    "sms": {
      "from": "Comapi",
      "allowUnicode": false
    }
  },
  "status": "processing",
  "statusDetails": {
    "channelId": "sms"
  },
  "messageId": "d8e296bb-97c4-4495-b98f-1e32e0b11628"
}

Failed Submission

If there are issues with the submitted request a HTTP 400 will be returned with the body describing the validation errors.

Getting the final status for a message

Once submitted Comapi will process your message and try to deliver it down one of the channels specified. It is still possible the message can fail at this stage if no channels accept the message. To find the final status of a message you can query it using the Get Status method, or register a Webhook to have data pushed to your systems.

Full API Specification

Please see the full API specification for further details about sending messages with the "One" API

Batch Submission Response Details

Successful Submission Response

Upon a successful send to the "One" API you will receive a HTTP 202 and a JSON body. The JSON will be an array of objects in the following format, one for each message submitted in the batch:

Property
Type
Description

index

int

The index of the message send request in the original batch submission this response is for

messageId

string

The unique identifier for the message. This id can be used to request status details and correlate inbounds and receipts to the original message

Whats happens after sending a batch of messages

The messages will be processed as fast as possible after submission but it may take a few seconds before this process is finished and the message if available for status updates. You can get updates on your messages statuses asynchronously using our webhooks system.

[
  {
    "index": 0,
    "messageId": "58bde0da-b380-4cb2-8dd1-ca74fc7a0cda"
  },
  {
    "index": 1,
    "messageId": "0aee96a8-09c9-4192-bf6d-c5f20a323956"
  }
]

Failed Submission

If there are issues with the submitted request a HTTP 400 will be returned with the body describing the validation errors.

Full API specification

Please see the full API specification for further details about sending messages with the "One" API

Suggest Edits

Inbound messages and receipts

 

All feedback from channels including receipts and inbound messages are converted to a canonical set of events. This means that you only have to implement one webhook in order to receive information about all channels and future channels supported by Comapi!

To find out more about webhooks and the events that can be forwarded to your systems please see the following section: Webhooks

 

This section covers the channels the Comapi "One" API supports:

Channel Identifier

In Comapi this channel can be referenced using: sms

This identifier can be used in the rules array, and customBody sections.

Sending an SMS

Sending an SMS via the "One" API is very simple all you required to so is ensure that your channel rules include the sms channel and you specify either a phoneNumber in international format or a profileId with a phone number in the to section of your request, and provide a body message e.g.

{
  "body": "Sending is easy with Comapi",
  "to": {
    "phoneNumber": "447123123123"
  },
  "rules": [
    "sms"
  ]
}

Message Length

The maximum length of SMS messages supported is 2000 characters.

Localiser & Stitching

In the basic SMS send example above you will notice we have not defined what phone number or identifier the SMS is sent from, so how can this work? The answer is that Comapi will automatically assign a suitable from phone number for the destination country from a pool of numbers. If your message is sent from a pool number, that number is then stitched to the destination phone number, and will be used for all future SMS communications within a 60 day period - keeping the individual user experience consistent.

Channel Options

The following additional channel options can be used to control the SMS channels most common options. To use the channel options create a object with your options in the requests channelOptions section in the sms property:

Property
Type
Description

from

string

The phone number, alpha identifier, or short code you are sending from

allowUnicode

boolean

This toggles whether having Unicode characters in the message body will be allowed or not

Please read why Unicode is important below before using this setting

{
  "body": "Sending SMS with Comapi is easy!",
  "to": {
    "phoneNumber": "447123123123"
  },
  "channelOptions": {
  	"sms": {
  		"from": "Comapi",
  		"allowUnicode": true
  	}
  },
  "rules": [
    "sms"
  ]
}

Possible from values

You can send SMS from the following types of originator:

Type
Example
Description

Mobile number

447123123123

A mobile number in international format. These can be rented for your desired country from Comapi

Short Code

60006

A short numeric identifier that requires companies to register for. These can be rented for your desired country from Comapi

Alpha

COMAPI

String value between 2-11 characters of 0-9, a-z, A-Z, hyphen, underscore, full-stop and space

Why is Unicode important with SMS

SMS by default is limited to a small character set called the GSM character set. If you body contains characters outside of the GSM character set it will require the allowUnicode channel option to be enabled if you want to be able to send the message.

Unicode SMS messages can cost up to 3 times more!

Unicode messages require over double the amount of data to be sent over mobile networks and therefore the numbers of characters you can fit into a SMS segment is severely affected i.e. A message using 160 characters which only uses the GSM character set will cost 1 SMS, whereas if it has a Unicode character the same message will cost 3 SMS.

Please see our knowledge base article on how message lengths are calculated for SMS.

Custom Body

The Comapi "One" API automatically creates a text based SMS message if you only define the body property when sending a message, but you can specify advanced options if you use the customBody property and define a sms object within it using the details below:

Property
Type
Description

to*

string

The phone number to send to. This should be in international format (e.g. 447123123456), or if countryIsoCode is specified, in local format (e.g. 07123123456)

from*

string

A string containing the from value. See the possible options above

countryIsoCode

string

An optional country in ISO 3166-1 alpha-3 format. If specified, the to field can contain a number in local format, e.g. GBR would allow 07123123456

body*

string

The message text

deliverAfter

datetime

A date time specifying when we should try to deliver the message. This must be a UTC time in ISO 8601 format, e.g. 2015-10-21T13:28:06.419Z

validUntil

datetime

A date time specifying how long we should try to deliver the message for. This must be a UTC time in ISO 8601 format, e.g. 2015-10-21T13:28:06.419Z

filterSetName

string

If you have enabled Intelligent Filtering on your account, in the portal, you can specify which filter set is applied to this message.

rejectUnicode

boolean

If set to true, any messages containing Unicode characters will not be sent.

Please read why Unicode is important below before using this setting. If not sure we recommend it is set to true

Examples of sends using SMS custom bodies are:

{
  "body": "Test from Comapi",
  "to": {
    "profileId": "dave@acme.com"
  },
  "customBody": {
    "sms": {
      "from": "Acme Corp",
      "to": "447123123123",
      "body": "Hello, this is a message using a SMS customBody",
      "deliverAfter": "2017-03-01T12:57:00.543Z",
      "rejectUnicode": "true"
    }
  },
  "rules": [
    "sms"
  ]
}

Cannot use a customBody and channelOptions together

It's not possible to define a customBody and channelOptions together in the same request as Comapi doesn't know which settings to use, so either use the universal send with channelOptions or define everything using a customBody

Receipts and Inbounds

To receive feedback or inbound messages from SMS sends please see the following:

Inbound SMS

Allows you to receive SMS messages sent from phones to your Comapi hosted mobile numbers, short codes or keywords. Messages are delivered to a URL of your choosing using Comapi's webhook system. See the Inbound event in the Message Events section for more details.

Receipts

If you need to to know the status of messages you've sent using one of our APIs, you can request that delivery receipts are forwarded to a URL of your choosing using Comapi's webhook system. See the events in the Message Events section for more details on the receipt events you can receive.

You can receive the following types of receipts:

  • Sent
  • Delivered
  • Expired
  • Failed

Click-through Forwarding

If your system needs to know whether URLs inserted into SMS messages have been clicked, check how long the clicker took between receiving the message and clicking the URL or discover details about the clicker's operating system, you can have details forwarded to a URL of your choosing.

Check out our quick starts...

Check out our Sending SMS quick start for simple code example of how to send SMS with Comapi

Suggest Edits

Facebook

Using Facebook with Comapi

 

Channel Identifier

In Comapi this channel can be referenced using: fbMessenger

This identifier can be used in the rules array, and customBody sections.

Enabling the Facebook Channel

See our Facebook channel setup guide for more information on how to setup Comapi for Facebook Messenger sending.

Obtaining a Facebook Messenger Id

There are two pieces of data that Facebook Messenger uses to match a target customer for a message and they are:

  • Facebook Messenger Id - A unique numeric identifier for a Facebook user and your Facebook page combination
  • Mobile Phone Number - Use a mobile phone number in international format as long as you have enabled Customer Matching on your Facebook page

Not all Facebook users enter a mobile number

Phone number matching for Facebook Messenger can only work if the target Facebook user has entered their mobile number into their Facebook profile.

Sending using Customer Matching (Phone number matching)

Facebook has currently suspended the use of this feature

Facebook is reviewing the ability to use phone numbers to send Facebook Messenger messages, and therefore this option is currently unavailable.

If you choose to use the customers phone number to identify them on Facebook and can provide the customers first and last names then Comapi will automatically pass these on to Facebook with the phone number. This greatly increases the chance of Facebook matching the phone number correctly as the name and phone number matching gives them much more confidence that you are communicating with your intended customer.

You should pass the following fields in the to section of the send request:

Property
Type
Description

phoneNumber*

string

The international format phone number for the customer e.g. 447123123123

firstName

string

The first name of the customer

lastName

string

The last name of the customer

Sending using a Facebook Messenger Id

To guarantee being able to send a message to a customer via Facebook Messenger you must obtain a unique Facebook Messenger Id for them and your page combination. This can be achieved in the following ways:
1) The customer elects to receive messages using the Facebook Send to Messenger plugin from a web page
2) The customer sends your Facebook page a direct message via Messenger
3) The customer clicks on the Message Us Facebook plugin and sends a message

Using the Facebook Web Plugins

Facebook provides web plugins you can easily add to your web site at appropriate points to invite customers to allow you to interact via Facebook Messenger with them. The plugins are:

  • Message Us - takes the person directly to Messenger and allows them to initiate a conversation with you
  • Send to Messenger - allows you to initiate a conversation with them in Messenger by providing a Facebook Messenger id for the customer via a webhook call
  • Checkbox Plugin - Not currently supported
You can find out more about the Facebook web plugins <a href="https://developers.facebook.com/docs/messenger-platform/plugin-reference" target="_new">here</a>

You can find out more about the Facebook web plugins here

In order to use the Send to Messenger plugin with Comapi you can use one of the following approaches:
1) Pass the profile id of the user securely with the Send to Messenger data and let Comapi update the customer profile with their Facebook Messenger Id in the fbMessengerId attribute
2) Capture and record the Facebook Messenger Id yourselves for use with the Comapi "One" API

Using the Send to Messenger web plugin with Comapi

To use the Facebook Send to Messenger plugin with Comapi we recommend doing the following:

Create secure meta data to use with the plugin

In order for Comapi to be able to update the correct user profile with the Facebook messenger Id received we need you to attach some secure meta data to the Facebook plugin call. To generate this data is a simple web service call as follows:

1) Ensure you have setup your Facebook page with Comapi as detailed in the Facebook Messenger channel setup guide
2) From your server side call the Facebook Meta Data Service passing the profileId you want to attach the Facebook Messenger Id to, this is usually the logged user id. This service generates the secure meta data Comapi can recognise.
3) Ensure that the secure meta data created in step 2 is passed into the Send to Messenger web control in the data-ref property e.g:

<div class="fb-send-to-messenger" 
  messenger_app_id="336037380081042" 
  page_id="PAGE_ID" 
  data-ref="**Add your secure meta data from step 1 here**" 
  color="blue" 
  size="standard">
</div> 

4) Ensure that the Send to Messenger control is implemented in your web site according to the instructions. Note Comapi will handle the Opt-in callback from Facebook for you, so no need to do this, and the messenger_app_id is set to 336037380081042

How to find your Facebook pages page_id

There are many ways to do this you can find mentioned on the internet but the easiest is to use this website: https://findmyfbid.com

No `Send to Messenger` control rendered

The Facebook Send to Messenger web plugin only renders if it is happy with the parameters passed to it. Things to check are:

  • messenger_app_id is set to Comapi's app id 336037380081042
  • Your Facebook page has been configured in the Comapi portal as the Facebook channel
  • Your Facebook page is published
  • You have implemented the Facebook web plugin code correctly

Capturing the Facebook messenger Id yourself

Please see the following Facebook documentation about the Facebook web plugins for further instructions on how to achieve this, but you will be required to create a Facebook application and a web hook to receive the opt in data from Facebook.

Where can I find a customer Facebook Id after they have opted in?

Comapi will automatically receive Facebook Ids as your users contact your Facebook page or click on Send to messenger widgets and store their Facebook Id in the customers Comapi profile in the fbMessengerId attribute.

If Comapi cannot find any secure meta data with a Facebook opt-in or inbound message it will automatically create a profile for the user, so you can message them. If it does find secure meta data it will decode this and store the Facebook Id and information against the profile specified in the meta data.

In addition to storing the Facebook Id on the profile Comapi will automatically retrieve the following details from Facebook:

  • First name
  • Last name
  • Profile picture
  • Locale
  • Timezone
  • Gender

This additional information is held in the facebook section of the users profile.

Sending a Facebook Messenger message

The Comapi One API allows you to address customers on Facebook using one or more of the following:

  • Customer Profile Id - This is the easiest option, Comapi will automatically store the customer Facebook Id against their profile when they opt in to messages using the Send to Messenger widget or send an inbound message, and then use it when necessary
  • Phone Number Matching - If you have enabled Customer Matching on your Facebook page simply pass a phone number in international format, but we also highly recommend passing the customers firstName and lastName to increase the chance of a match significantly!
  • Facebook Messenger Id - If you have already captured the customers Facebook Messenger Id for your Facebook page you can use this

Comapi will always use a Facebook Messenger Id in preference to the phone number or profile if included in the API call.

To test your channel you can call the Comapi "One" API targeting the Facebook channel. To do this easily you could use a tool such as Postman or create the code in the language of your choice using the API reference docs.

The following request JSON can be used to perform a send with text content:

{
  "to": {
    "profileId": "dave@acme.com"
  },
  "body": "My customer message via Facebook Messenger using a profile id",
  "rules": [
    "fbMessenger"
  ]
}
{
  "to": {
    "phoneNumber": "447123123123",
    "firstName": "Dave",
    "lastName": "Smith"
  },
  "body": "My customer message via Facebook Messenger using phone number matching",
  "channelOptions": {
    "fbMessenger": {
      "messagingType": "NON_PROMOTIONAL_SUBSCRIPTION"
    }
  },
  "rules": [
    "fbMessenger"
  ]
}
{
  "to": {
    "fbMessengerId": "11112223333444444"
  },
  "body": "My customer message via Facebook Messenger using explicit fbMessengerId",
  "channelOptions": {
    "fbMessenger": {
      "messagingType": "NON_PROMOTIONAL_SUBSCRIPTION"
    }
  },
  "rules": [
    "fbMessenger"
  ]
}
{
  "to": {
    "fbMessengerId": "11112223333444444",
    "mobileNumber": "441234123123",
    "firstName": "Dave",
    "lastName": "Smith"
  },
  "body": "My customer message via Facebook Messenger",
  "channelOptions": {
    "fbMessenger": {
      "messagingType": "NON_PROMOTIONAL_SUBSCRIPTION"
    }
  },
  "rules": [
    "fbMessenger",
    "sms"
  ]
}

You must specify either a message tag or type for subscription messages

If you are not directly replying to an inbound message on the Facebook Messenger channel then you will need to specify either a messageTag or messagingType in your channel options as Facebook consider this a subscription message. In addition to setting your message type or tag your Facebook page must be approved for subscription messaging. To submit your page for subscription messaging approval please follow these instructions.

Channel Options

The following additional channel options can be used to control the email channels most common options. To use the channel options create a object with your options in the requests channelOptions section in the fbMessenger property:

Property
Type
Description

messageTag

string

Send message with one of predefined tag specified by Facebook to increase deliverability for certain categories of messages.

See the Facebook Tags documentation for more details.

messagingType

string

You need to specify a message type if your message if not a direct response to a customers inbound message e.g. it is a notification message and you haven't specified a messageTag.

You should use the message type NON_PROMOTIONAL_SUBSCRIPTION for these messages to be sent. Please review the Messenger Platform Policy for more information on Facebook message types.

{
  "to": {
    "profileId": "dave@acme.com"
  },
  "body": "Good news, your order #12345 has shipped.",
  "channelOptions": {
    "fbMessenger": {
      "messageTag": "SHIPPING_UPDATE"
    }
  },
  "rules": [
    "fbMessenger"
  ]
}
{
  "to": {
    "profileId": "dave@acme.com"
  },
  "body": "The jacket you wanted is now back in stock.",
  "channelOptions": {
    "fbMessenger": {
      "messagingType": "NON_PROMOTIONAL_SUBSCRIPTION"
    }
  },
  "rules": [
    "fbMessenger"
  ]
}

Custom Body

Facebook Messenger is capable of sending many types of messages including:

The Comapi "One" API automatically creates a text based Facebook message if you only define the body property when sending a message, but you can send any of the Facebook message body types if you use the customBody property and define a fbMessenger object within it that complies with the Facebook Graph API's message object as defined in the Facebook docs. Essentially you can pass any Facebook message type you desire if you define this property, or let Comapi automatically create you a basic text message.

Examples of sends using Facebook custom bodies are:

{
  "body": "Test from Comapi",
  "to": {
    "profileId": "**YOUR USER PROFILE ID**"
  },
  "customBody": {
    "fbMessenger": {
      "attachment": {
        "type": "image",
        "payload": {
          "url": "https://scontent.xx.fbcdn.net/v/t1.0-1/p200x200/17156020_1871286216424427_1662368582524349363_n.jpg?oh=22685c22a19fc2e28e69634e6a920972&oe=592FD3D1"
        }
      }
    }
  },
  "rules": [
    "fbMessenger"
  ]
}
{
  "body": "Test from Comapi",
  "to": {
    "profileId": "**YOUR USER PROFILE ID**"
  },
  "customBody": {
    "fbMessenger": {
    "text":"Pick a color:",
      "quick_replies":[
        {
          "content_type":"text",
          "title":"Red",
          "payload":"DEVELOPER_DEFINED_PAYLOAD_FOR_PICKING_RED"
        },
        {
          "content_type":"text",
          "title":"Green",
          "payload":"DEVELOPER_DEFINED_PAYLOAD_FOR_PICKING_GREEN"
        }
      ]
    }
  },
  "rules": [
    "fbMessenger"
  ]
}
{
  "body": "Test from Comapi",
  "to": {
    "profileId": "**YOUR USER PROFILE ID**"
  },
  "customBody": {
    "fbMessenger": {
      "attachment": {
        "type": "template",
        "payload": {
          "template_type": "receipt",
          "recipient_name": "Stephane Crozatier",
          "order_number": "12345678902",
          "currency": "USD",
          "payment_method": "Visa 2345",
          "order_url": "http://petersapparel.parseapp.com/order?order_id=123456",
          "timestamp": "1428444852",
          "elements": [
            {
              "title": "Classic White T-Shirt",
              "subtitle": "100% Soft and Luxurious Cotton",
              "quantity": 2,
              "price": 50,
              "currency": "USD",
              "image_url": "http://clipart-library.com/images/rcjKk9yni.png"
            },
            {
              "title": "Classic Gray T-Shirt",
              "subtitle": "100% Soft and Luxurious Cotton",
              "quantity": 1,
              "price": 25,
              "currency": "USD",
              "image_url": "http://clipart-library.com/images/yTkAjXk9c.png"
            }
          ],
          "address": {
            "street_1": "1 Hacker Way",
            "street_2": "",
            "city": "Menlo Park",
            "postal_code": "94025",
            "state": "CA",
            "country": "US"
          },
          "summary": {
            "subtotal": 75,
            "shipping_cost": 4.95,
            "total_tax": 6.19,
            "total_cost": 56.14
          },
          "adjustments": [
            {
              "name": "New Customer Discount",
              "amount": 20
            },
            {
              "name": "$10 Off Coupon",
              "amount": 10
            }
          ]
        }
      }
    }
  },
  "rules": [
    "fbMessenger"
  ]
}

Receipts and Inbounds

To receive feedback or inbound messages from Facebook sends please see the following:

Inbound Messages

Allows you to receive Facebook Messenger messages sent from phones or Facebook to your Facebook page. Messages are delivered to a URL of your choosing using Comapi's webhook system. See the Inbound event in the Message Events section for more details.

Receipts

If you need to to know the status of messages you've sent using one of our "One" API, you can request that delivery receipts are forwarded to a URL of your choosing using Comapi's webhook system. See the events in the Message Events section for more details on the receipt events you can receive.

You can receive the following types of receipts:

  • Sent
  • Delivered
  • Read
  • Failed

Check out our quick starts...

Check out our Using Facebook quick start to see a simple code example of how to use Facebook with Comapi

Channel Identifier

In Comapi this channel can be referenced using: nativePush

This identifier can be used in the rules array, and customBody sections.

Channel Setup

To setup Comapi in order to use the Push channel please ensure you have completed these steps:

  1. You integrate the App Messaging Foundation SDK into your mobile apps in order to register the required push token details and profile id to send messages to the app(s)
  2. Configure your native push channels and authentication scheme in the App Messaging channel setup as described here. You will need to configure FCM for Android and APNS for iOS.

Sending messages

Sending native push messages is simple using the Comapi "One" API; simply ensure that when you want to send a message using the "One" API in your rules or ruleSet you include the nativePush channel, and follow the guidance below for creating the rest of the request:

Addressing your messages

When you integrate the App Messaging Foundation SDK into your mobile app you will need to provide a callback for the SDK to authorise and identify app users. The callback should issue a JWT using the settings you specified, and importantly include the id or the user (by default in the sub claim) which will be used as Comapi's profile id for the user. You should ensure you start a session with the SDK which will call the auth callback in your app and the App Messaging Foundation SDK will now acquire the native push tokens automatically from the device and upload them against the profile your JWT claims to represent.

Now to target who a App Messaging message is delivered to simply include the profileId for the user in the to section of the request. This will send a push message to mobile devices associated with the profile id.

Integrating the App Messaging Foundation SDK into your App

Integrating the App Messaging Foundation SDK into your App

Message content

You will need to specify a body and title for your push messages. The body is the main body text of your message and we recommend keeping this to 160 characters to ensure it can be displayed in full on most mobile devices. The title will be shown above the body text on Android and concatenated to the front of the body text on iOS, and is used to give context to the message.

Examples:

{
  "body": "A push message sent from Comapi",
  "title": "Comapi news!",
  "to": {
    "profileId": "dave@acme.com"
  },
  "rules": [
    "nativePush"
  ]
}

Badge Counts / Custom Behaviours

Please use a customBody to define advanced / device specific features such as iOS badge counts or custom data payloads to drive deep links etc...

Channel Options

The nativePush channel has no channel options.

Custom Body

The Comapi "One" API automatically creates basic psuh messages for you if you define the body and title properties when sending a message, but you can specify advanced options if you use the customBody property and define a nativePush object within it using the details below:

Property
Type
Description

alert*

messageAlert object

The specifics of the push message alert

messageAlert object

Property
Type
Description

title

string

Title for the notification to send to all platforms. Note that this is ignored if any details are set in platforms

body

string

Body for the notification to send to all platforms. Note that this is ignored if any details are set in platforms

platforms

messageAlertPlatforms object

The platform specific details for the alert. If specified, these are set verbatim and alert.title / alert.body values are ignored

messageAlertPlatforms

Property
Type
Description

apns

messageAlertApns object

The APNS native channel push message specific details

fcm

messageAlertFcm object

The FCM native channel push message specific details

messageAlertApns

Property
Type
Description

badge

integer

The value to display on the application badge

sound

string

The soundfile to play on the device when the notification arrived

alert*

string

The alert to display on the device when the notification arrives

payload

object

The data payload to send to the device

messageAlertFcm

Property
Type
Description

collapse_key

object

The collapse key to send to FCM

data

object

The payload to send to the device

notification*

messageAlertFcmNotification object

Details of the notification to display

messageAlertFcmNotification

Property
Type
Description

title*

string

The title to display

body*

string

The body to display

icon

string

The icon to display

Examples of sends using custom bodies are:

{
  "to": {
    "profileId": "dave@acme.com"
  },
  "customBody": {
    "nativePush": {
      "alert": {
        "platforms": {
          "apns": {
            "alert": "Push message send from Comapi",
            "badge": 1
          },
          "fcm": {
            "notification": {
              "title": "Comapi news!",
              "body": "Push message send from Comapi"
            }
          }
        }
      }
    }
  },
  "rules": [
    "nativePush"
  ]
}

Receipts and Inbounds

To receive feedback Push channel sends please see the following:

Receipts

If you need to to know the status of messages you've sent using one of our "One" API, you can request that delivery receipts are forwarded to a URL of your choosing using Comapi's webhook system. See the events in the Message Events section for more details on the receipt events you can receive.

You can receive the following types of receipts, as the Push channel in a fire and forget style communication:

  • Sent
  • Failed - If a push cannot be sent to any of the profiles devices
Suggest Edits

App Messaging

 

Channel Identifier

In Comapi this channel can be referenced using: appMessaging

This identifier can be used in the rules array, and customBody sections.

Sending messages

Sending in-app messages via App Messaging is simple using the Comapi "One" API; simply ensure that in your rules or ruleSet you include the appMessaging channel, and follow the guidance below:

Addressing your messages

To target who a App Messaging message is delivered to include a profileId in the to section of the request. For more information on profile Ids see the The One API Overview.

Conversations

Messages are grouped together by a conversationId into conversations. If you don't specify a conversationId on a send a unique one will automatically be assigned therefore starting a new conversation. If you reference an existing conversationId then the new message will be appended to that conversation.

{
  "body": "App Messaging send from Comapi",
  "to": {
    "profileId": "dave@acme.com"
  },
  "rules": [
    "appMessaging"
  ]
}
{
  "body": "App Messaging send from Comapi",
  "to": {
    "profileId": "dave@acme.com"
  },
  "conversationId": "dave@acme.com:Acme Support",
  "rules": [
    "appMessaging"
  ]
}

Channel Options

The following additional channel options can be used to control the App Messaging channels most common options. To use the channel options create a object with your options in the requests channelOptions section in the appMessaging property:

Property
Type
Description

from

from object

The profile Id the message is being sent from, if not set the profile Id from the access token is used

from object

Property
Type
Description

profileId*

string

The profile Id the message is being sent from, if not set the profile Id from the access token is used

name

string

The name to display to the user

{
  "body": "How can we help you today?",
  "to": {
    "profileId": "dave@acme.com"
  },
  "channelOptions": {
    "appMessaging": {
      "from": 
      {
      	"profileId": "Acme-Support",
      	"name": "Support"
      }
    }
  },
  "conversationId": "dave@acme.com:Acme Support",
  "rules": [
    "appMessaging"
  ]
}

Custom Body

The Comapi "One" API automatically creates a text based App Messaging message if you only define the body property when sending a message, but you can specify advanced options if you use the customBody property and define a appMessaging object within it using the details below:

Property
Type
Description

from

from object

Who the message is being sent from

metadata

object

Any JSON meta data you want to send with the message, useful for driving custom experiences

parts*

array of messagePart

The parts that form your message i.e. the content

alert

messageAlert object

Details of the alert to be displayed on receipt of the message

from object

Property
Type
Description

profileId*

string

The profile Id the message is being sent from, if not set the profile Id from the access token is used

name

string

The name to display to the user

messagePart object

Property
Type
Description

data*

string

String representationof the message part, for non text MIME types use base64

type

string

The MIME type (if applicable) of the part

name

string

A name for the part

url

string

A URL associated with the message part

size

integer

Size of the message part in bytes

messageAlert object

Property
Type
Description

title

string

Title for the notification to send to all platforms. Note that this is ignored if any details are set in platforms

body

string

Body for the notification to send to all platforms. Note that this is ignored if any details are set in platforms

platforms

messageAlertPlatforms object

The platform specific details for the alert. If specified, these are set verbatim and alert.title / alert.body values are ignored

messageAlertPlatforms

Property
Type
Description

apns

messageAlertApns object

The APNS native channel push message specific details

fcm

messageAlertFcm object

The FCM native channel push message specific details

messageAlertApns

Property
Type
Description

badge

integer

The value to display on the application badge

sound

object

The soundfile to play on the device when the notification arrived

alert

string

The alert to display on the device when the notification arrives

payload

object

The payload to send to the device

messageAlertFcm

Property
Type
Description

collapse_key

object

The collapse key to send to FCM

data

object

The payload to send to the device

notification

messageAlertFcmNotification object

Details of the notification to display

messageAlertFcmNotification

Property
Type
Description

title

string

The title to display

body

string

The body to display

icon

string

The icon to display

Examples of sends using custom bodies are:

{
  "body": "App Messaging send from Comapi",
  "to": {
    "profileId": "dave@acme.com"
  },
  "conversationId": "dave@acme.com:Acme Support",
  "customBody": {
    "appMessaging": {
      "from": "Acme Corp",
      "alert": {
        "title": "Acme Support Update",
        "body": "You have a new message on your support ticket, click here to read it"
      },
      "parts": [
        {
          "data": "Your item has arrived and your account has been credited $50.00"
        }
      ]
    }
  },
  "rules": [
    "appMessaging"
  ]
}

Cannot use a customBody and channelOptions together

It's not possible to define a customBody and channelOptions together in the same request as Comapi doesn't know which settings to use, so either use the universal send with channelOptions or define everything using a customBody

Receipts and Inbounds

To receive feedback or inbound messages from App Messaging sends please see the following:

Inbound Messages

Allows you to receive App Messaging messages sent. Messages are delivered to a URL of your choosing using Comapi's webhook system. See the Inbound event in the Message Events section for more details.

Receipts

If you need to to know the status of messages you've sent using one of our "One" API, you can request that delivery receipts are forwarded to a URL of your choosing using Comapi's webhook system. See the events in the Message Events section for more details on the receipt events you can receive.

You can receive the following types of receipts:

  • Sent
  • Delivered
  • Read
  • Failed

Channel Identifier

In Comapi this channel can be referenced using: email

This identifier can be used in the rules array, and customBody sections.

Sending messages

Sending email messages is simple using the Comapi "One" API; simply ensure that in your rules or ruleSet you include the email channel, and follow the guidance below:

Addressing your messages

To target who a App Messaging message is delivered to include a email in the to section of the request. For more information on profile Ids see the The One API Overview.

Subject lines

If you specify a title it will be used as a subject line for your emails.

The email body

You can specify the content of the email either as plain text, HTML or both. The messages body property holds the plain text version, the channelOptions.email.htmlBody can optionally hold the HTML version of the message.

Attachments

If you would like to add attachments to your emails then you will need to define your message content using Multi part messages instead of the body property.

From name

By default Comapi will use the From name specified in the email channel configuration page which typically would be your company name. You can override this using the channel option fromName as described below, to identify departments etc...

Attachments

You can send attachments with your email such as images, files or any other valid MIME type. If you choose to add attachments then you must define the whole email as a series of message parts, and not set a body as the message parts will be used to construct this. The message parts are added as an array to the messageParts property, and ensure that your first message part is the email body content in either HTML (text/html) or plain text (plain/text), and then any other attachments you want. See the attachments example below for an example, and the message parts documentation for further details on how to use these.

{
  "body": "Email send from Comapi",
  "to": {
    "email": "dave@somewhere.com"
  },
  "rules": [
    "email"
  ]
}
{
  "body": "Your order: ABC1245 has been dispatched.",
  "title": "Your order has been dispatched",
  "to": {
    "email": "dave@somewhere.com"
  },
  "channelOptions": {
    "email": {
      "htmlBody": "<h2>Order Dispatched</h2><p>Order number: <b>ABC1245</b> has been dispatched</p>"
    }
  },
  "rules": [
    "email"
  ]
}
{
  "body": "Your order: ABC1245 has been dispatched.",
  "title": "Your order has been dispatched",
  "to": {
    "email": "dave@somewhere.com"
  },
  "channelOptions": {
    "email": {
      "fromName": "Acme Logistics"
    }
  },
  "rules": [
    "email"
  ],
  "metadata": {
    "data": "My correlation data"
  }
}
{
  "title": "Your order: ABC1245 has been dispatched.",
  "to": {
    "email": "dave@somewhere.com"
  },
  "channelOptions": {
    "email": {
      "fromName": "Acme Logistics",
      "addUnsubscribeFooter": true,
      "htmlUnsubscribeFooter": "<a href='%unsubscribe_url%'>Click here</a> to unsubscribe."
    }
  },
  "rules": [
    "email"
  ],
  "messageParts": [
    {
      "name": "Body html",
      "type": "text/html",
      "data": "<h2>Item Repaired</h2><p>Your return number: <b>ABC1245</b> has been repaired and is being retuned to you</p>"
    },
    {
      "type": "image/png",
      "url": "http://cdn.dnky.co/3rdparty/comapi/images/laptop.png"
    }
  ],
  "metadata": {
    "data": "My correlation data"
  }
}
{
  "title": "Email sends with Comapi",
  "to": {
    "email": "dave@somewhere.com"
  },
  "channelOptions":{
  "email":{
  	"fromName": "Comapi",
  	"addUnsubscribeFooter": true,
  	"unsubscribeFooter": "<a href='%unsubscribe_url%'>Click here</a> to unsubscribe."
  }
  },
  "rules": [
    "email"
  ],
  "messageParts":[
  	{
  		"name":"Body html",
  		"type":"text/html",
  		"data":"<h2>Image Attached</h2><p>You can attach files using both a URL or Base64 data.</p>"
  	},
  	{
  		"name":"comapi.png",
  		"type":"image/png",
  		"size": 2560,
  		"data":"iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAMAAADW3miqAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAkxQTFRFAAAAK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwK5jwKZfwKpfwLJjwbLf1ptT5qNX5cbr1Lpnwa7f17fb+////8/n+e7/2KpjwNZ3xxuP71uv8PaHxRqXy5vP98Pj+VKzzKJfw2Oz85fP9SKbyk8v3/f7/LprwUarzntD4ZLT0OJ7xksr30un81Or8mM34PqHxQ6Tyy+b75fL9OZ/xO6DxgsL2qdX5i8f3QKLyvN763e79TqnyL5rwp9T5/v7//v//xOL7QaPyvd/6j8n3YrP08vn+lMv3NJ3xs9r6kcr3KJbwicb3u976MZvwnM/4+/3/5PL9Vazzer72r9j5LJnwotL4+fz/3+/9ZrX0QqPy0en86/X+mc348vj+cLn1LZnwZ7X0odL4N57xX7H05/T9z+f8brj1fcD29fr+hsT2d7318fj+/P7/Np3xR6by7/f+bbj1/P3/pNP5Mpvwt9z6abb06PT97Pb+T6nztdv6XrH04/H97vf+1ev8rdf5NJzxcrv1pdP5T6rzut76bLj1isb3PKDxzOb7weH70Oj8UKrzrtj51er83O79V67zTany6vX+Y7P0MJrw6fT9arf1c7v1S6fyZbT0/O9MXQAAADV0Uk5TAAAbc8Tx8MNyGARS+/rAUQJp72hJ60IQtq4PV/TzVqqp4d+oVbIO6kHuZ725SBpxwv7BcBeegkssAAAAAWJLR0RA/tlc2AAAAAlwSFlzAAAuIgAALiIBquLdkgAAAl1JREFUOMt91PdfEzEUAPCcImqtWgs40CLgxI2aFyxgo6EW5RRbaBWqYqmKUlARrVZQEUHUKu6Je++99/jHvPRK7y6M91PyPt/c3cslDyEkSQMGJg3CvUTy4CFDJQkpIZmGJZlxHzF8xEiToiTLKCvuJ6wpFglJqWliHoCANksbLaExY0WTt8ieX1Cozcelo/ETxOcsdlC6ZClLJGwZaKJgWJGTKrHMRRKpTJQlPqh4OUcrSuREKguJ1RPXSo5WlWrfbkZCXQB4tVtBdo8ubUBQVm73gm/N2opKP2EyA9ITwbr1lG7wythXhQnbGKgObiIiAv9m/jVbaoAp9W/dVktDdfXEiKB4ey1HdAevS97ZwMe7mAGRxt1UjT0chffGxvs8ekQi+5tU03yAFx8+yGukh1p0iPgOt6rmSJvyLmUn2o8qkw4vaIi1HHOr5viJMIuePBXoDJ92njl7zrAF5xtUc+FilMmXLtMr1VflomtVoENQ0KWaroCHsevNyujGzVsEuk9CDBXeVk1TkDG4czc2vndf+3kxFH0Qy7vtjSz88JG64HGZgKCNr24t7yRQ8kQ1oafPmBFh8txBQy8iBF5WxHf0lUt3zLs387X9zVuA0ndx42zXmQRSLggh9e/j5sNHGfeCeEQ+xc1no8G64ws1Har58tVozChbQ9++q3/4BxgMztZfqZ/53Pz6LRg8CWXYEhPir6PU8YcIxjYZpU/RHeC/wcp/eYLBmVONDQNkkEUzbbrSVSwpObifyJlh4Q3KlDqz7yY2a7ZJbXXSnLnzknN7ilzr/AULeTv8D0g5OX09OeL2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA0LTEwVDExOjU3OjI0KzAwOjAwFBYLiAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNC0xMFQxMTo1NzoyNCswMDowMGVLszQAAABGdEVYdHNvZnR3YXJlAEltYWdlTWFnaWNrIDYuNy44LTkgMjAxNC0wNS0xMiBRMTYgaHR0cDovL3d3dy5pbWFnZW1hZ2ljay5vcmfchu0AAAAAGHRFWHRUaHVtYjo6RG9jdW1lbnQ6OlBhZ2VzADGn/7svAAAAGHRFWHRUaHVtYjo6SW1hZ2U6OmhlaWdodAAxOTIPAHKFAAAAF3RFWHRUaHVtYjo6SW1hZ2U6OldpZHRoADE5MtOsIQgAAAAZdEVYdFRodW1iOjpNaW1ldHlwZQBpbWFnZS9wbmc/slZOAAAAF3RFWHRUaHVtYjo6TVRpbWUAMTQ5MTgyNTQ0NFCEz4QAAAAPdEVYdFRodW1iOjpTaXplADBCQpSiPuwAAABWdEVYdFRodW1iOjpVUkkAZmlsZTovLy9tbnRsb2cvZmF2aWNvbnMvMjAxNy0wNC0xMC8zOGIzNWFjOWViZjFhZTU2YzVkZmE2MmU0ZmNmMmE4Zi5pY28ucG5n/L7enQAAAABJRU5ErkJggg=="
  	}],
  "metadata": {"data": "My correlation data"}
}

Channel Options

The following additional channel options can be used to control the email channels most common options. To use the channel options create a object with your options in the requests channelOptions section in the email property:

Property
Type
Description

fromName

string

The from name to use for the email. If not specified it will default to the from name set in the channel settings on the Comapi portal.

htmlBody

string

The email body in HTML format, if specified both a text/plain and text/html version of the email will be sent

mailboxName

string

The mailbox name to use. A mailbox is a virtual mailbox to allow partitioning of your email between departments

Default from channel configuration will be used if not specified.

trackOpens

boolean

Indicates whether read receipts should be generated when emails are opened (uses a tracking image).

The default is true

addUnsubscribeFooter

boolean

Indicates whether a footer with an unsubscribe link should be added to the message.

The default is false

htmlUnsubscribeFooter

string

Custom content for the unsubscribe footer in the HTML version of the email. Defaults will be applied if not specified. The token
%unsubscribe_url% should be used in the string where you want the unsubscribe URL merged.

unsubscribeFooter

string

Custom content for the unsubscribe footer in the plain text version of the email. Defaults will be applied if not specified. The token
%unsubscribe_url% should be used in the string where you want the unsubscribe URL merged.

{
  "body": "Your order for pink fluffy slippers has been dispatched!",
  "to": {
    "email": "dave@somewhere.com"
  },
  "channelOptions": {
    "email": {
      "fromName": "Support",
      "htmlBody": "<p>Your order for <font color='pink'>pink fluffy slippers</font> has been dispatched!</p>",
      "trackOpens": false,
      "addUnsubscribeFooter": true,
      "unsubscribeFooter": "If you don't want anymore shipping updates change your contact options in the portal or click on the following link: %unsubscribe_url%",
      "htmlUnsubscribeFooter": "<hr/><p>If you don't want anymore shipping updates change your contact options in the portal or <a href='%unsubscribe_url%'>click here</a> to unsubscribe.</p>"
    }
  },
  "rules": [
    "email"
  ]
}
{
  "body": "Your order for pink fluffy slippers has been dispatched!",
  "title": "Your order has been dispatched",
  "to": {
    "email": "dave@somewhere.com"
  },
  "channelOptions": {
    "email": {
      "fromName": "Acme Support",
      "addUnsubscribeFooter": true,
      "unsubscribeFooter": "Click on the following link to unsubscribe: %unsubscribe_url%"
    }
  },
  "rules": [
    "email"
  ]
}

Unsubscribe mechanism

If you opt to add a unsubscribe link to your email using the addUnsubscribeFooter and unsubscribeFooter channel options or simply embedding the token %unsubscribe_url% in your emails body, if your customer clicks on it, all subsequent emails sent to that email address will be stopped and a Failed receipt returned.

Custom Body

The email channel doe not support custom bodies, all features are covered in the main options and channel options.

Receipts and Inbounds

To receive feedback or inbound messages from email sends please see the following:

Inbound Messages

Allows you to receive email messages sent. Messages are delivered to a URL of your choosing using Comapi's webhook system. See the Inbound event in the Message Events section for more details.

To send an message in to Comapi via email you can use the email address that is shown in the portals email channel configuration page which will look something like this:
general|xwcQMBWDhLpNZAVFq1nspx@stage-mail.comapi-email.com

Comapi Portal's email channel settings

Comapi Portal's email channel settings

Receipts

If you need to to know the status of messages you've sent using one of our "One" API, you can request that delivery receipts are forwarded to a URL of your choosing using Comapi's webhook system. See the events in the Message Events section for more details on the receipt events you can receive.

You can receive the following types of receipts:

  • Sent
  • Delivered
  • Read
  • Failed

Tracking email opens

If the trackOpens channel option is set to false then you will not receive Delivered or Read receipts. The default is to track opens of emails.

Suggest Edits

Custom Channels

 

Channel Identifier

In Comapi custom channels can be referenced using there custom channel ids that are shown in the Custom Channels tab of the Branch settings for your API Space (see below highlighted in yellow)

This identifier can be used in the rules array, and customBody sections.

Sending messages

Sending custom channel messages is simple using the Comapi "One" API; simply ensure that in your rules or ruleSet you include the custom channel id, and follow the guidance below:

Addressing your messages

To target who a custom channel message is delivered to ensure you include either the field referred to in the custom channels Id field name property or a profileId with this information in the to section of the request. For more information on profile Ids see the The One API Overview.

Title and Body

The title and body fields of the request will be used to create the custom channel request, so ensure you specify these.

{
  "body": "A message via custom channels",
  "title": "A email from Comapi",
  "to": {
    "email": "info@comapi.com"
  },
  "rules": [
    "custom_email"
  ]
}

Channel Options

The channel options section is not used with custom channels.

Custom Body

You can specify a custom body for a custom channel by adding a property to the customBody section with the same name as your custom channel id. The value must be a string so if passing JSON please ensure you escape it to create a string.

If specified this will be used instead of your defined body template for the custom channel, but you must still provide a to section identifier, we suggest you provide a profileId, which is simply your unique identifier for your user.

{
  "to": {
    "profileId": "info@comapi.com"
  },
  "customBody": {
	"custom_email": "{  \"personalizations\": [    {      \"to\": [        {          \"email\": \"info@comapi.com\"        }      ]    }  ],  \"from\": {    \"email\": \"m0rsy@comapi.com\"  },  \"subject\": \"Custom bodies with custom channels\",  \"content\": [    {      \"type\": \"text\/html\",      \"value\": \"Hi from Comapi\"    }  ]}"
  },
  "rules": [
    "custom_email"
  ]
}

Receipts and Inbounds

You can create two way custom channel integrations with Comapi, but you will be responsible in creating the simple adaptor layer between the 3rd party systems feedback mechanisms and Comapi.

Receiving data from custom channels

Receiving data from custom channels

Your adaption layer will need to receive the data from the 3rd party system however they specify, such as a web page, and then call on to Comapi to record the data.

Comapi provides two web methods to allow receipts and inbound messages to be recorded in the system, so analytics can be recorded and receipts and inbound messages sent via our webhooks to your integrations. The web methods are:

Receipts

To record receipt values for a custom channel for a message you need to call the Messages web services status update method
with the Comapi messageId and status.

Where can I find the messageId?

Hopefully the 3rd party channel you are integrating will allow meta data or a message reference to be added to the request. If so you can use the {{messageId}} token when setting up the custom channels body template to map the Comapi message id in. This will them often be echoed on any inbound data.

See our Custom Channels section for more info.

Inbound Messages

To record inbound messages from your 3rd party channel you need to call the Messages services inbound method.

You will need to populate the from and to objects with the identifiers used by the channel, and if possible provide a text only version of the message in the body property. The channel Data object is used to record the raw unaltered body provided by the channel, so it is available for display or processing later.

Suggest Edits

Branch

Using Branch to find the best channel to contact your customer

 

Can you use Branch?

You will need the Comapi Toolkit Pro subscription to be able to use Branch.

Branch works in conjunction with The "One" API and lets you set rules to prioritise the order of channels in which a message to a customer can be sent. Branch will work through these channels in priority order trying to find the best channel in order to send your message automatically.

To find out more see the Branch overview in the Get Started section.

How to use Branch

To use Branch features with a message send is very simple, you just specify more than one channel to send to in your preference order, which can be done explicitly in the JSON or you can refer to a Branch rule set to allow easy tweaking to the Branch rules without a code change later. To find out more about Branch rule set setup see the Branch channel setup guide.

Explicit Rules

To use Branch with explicitly set channels is very simple, simply include more than one channel id in the rules array in your message send in the priority order required. For example:

"rules": [
  "fbMessenger",
  "sms"
  ]
{
  "body": "Branch send from Comapi",
  "to": {
    "profileId": "dave@acme.com",
    "phoneNumber": "447123123123"
  },
  "rules": [
    "fbMessenger",
    "sms"
  ]
}

Configured Rules

To use a reference to a Branch rule set simply replace the usual rules section with a ruleSet property with it value set to the rule set id you want to use.

"ruleSet": "Facebook-First"
{
  "body": "Branch send from Comapi",
  "to": {
    "profileId": "dave@acme.com",
    "phoneNumber": "447123123123"
  },
  "ruleSet": "Facebook-First"
}

Don't confuse Branch

Ensure you specify either a rules or ruleSet section but not both!

Suggest Edits

App Messaging Overview

 

What is App Messaging

App Messaging allows you to build secure messaging and chat services into your native and web applications. Use App Messaging for P2P chat, B2C chat or A2P messaging for alerts reminders and notifications. Create user profiles, start conversations and send any type of message. We have done the heavy lifting for you, so you don’t carry the cost of development or platform support.

Profiles

Comapi automatically creates a profile for each individual user using any unique identifier you want, allowing you to pass profile and user information/data to the platform. Profile information can then be used by other Comapi services to segment and target users. You can also add, update and get the profile's information using the our APIs.

Start Conversations

Conversations are like subscriptions to a channel. You can start a conversation with one user, groups of users or any defined target. You can then use permissions to set access rights and manage conversations. Conversations are stored on our platform, enabling synchronisation across any of a customer's devices. This provides a continuous customer experience within your application. You have total control over how you want your conversations to be managed.

Send Messages

Send any type of message. Comapi supports all usual formats (text, image, video, html). Messages can also be any other MIME type, data payload or native operating system services such as location. Send multi-part messages to make rendering of rich content easy, and easily automate processes by sending data along with the message.

 

You must integrate the App Messaging SDK to create profiles to send to

For a user to become able to send and receive messages via App Messaging you will need to integrate at least one of the client SDKs into your mobile apps or websites. The SDKs will create a Comapi profile for the user and store the necessary device details to communicate with the user and device.

Keep on reading to learn how to integrate App Messaging SDKs into your apps and websites, or for information about our server side APIs see:

 

The following sections will take you through using the App Messaging Foundation Android SDK

There is also a repository containing some sample apps here ...

Suggest Edits

Installing the SDK

 
compile 'com.comapi:foundation:1.2.0'
<dependency>
    <groupId>com.comapi</groupId>
    <artifactId>foundation</artifactId>
    <version>1.2.0</version>
</dependency>

Then

  • initialise SDK with API Space id and authentication token provider
  • start session when you obtain token from your authentication provider
  • listen for realtime events, and push messages
  • call message and profile services
Suggest Edits

Initialise

 

To initialise the SDK, you will need a few pre-requisites. A configured API Space and an authentication provider that can generate a JWT that matches the auth scheme configured for your API Space. Set both of them when building configuration object. This is the only required configuration setup. To use the SDK you will also need to provide couple event listeners.

ComapiConfig config = new ComapiConfig()
   .apiSpaceId("<API_SPACE_ID>")
   .authenticator(new ChallengeHandler());

e.g. of the authentication challenge handler (needs to extend ComapiAuthenticator class)

public class ChallengeHandler extends ComapiAuthenticator {
    @Override
    public void onAuthenticationChallenge(AuthClient authClient, 
       ChallengeOptions options) {
          authClient.authenticateWithToken(/*get token using options.getNonce()*/));
    }
}

onCreate in Application

The initialisation needs to be performed in onCreate method of Android Application class.

You can use APIs in two versions reactive and with callbacks. Callback version initialise through Comapi class and the access to APIs is through ComapiClient. For reactive java alternative use RxComapi and RxComapiClient classes.

public class MyApplication extends Application {

    @Override
    public void onCreate()
    {
        super.onCreate();
      
        Comapi.initialiseShared(this, config, new Callback<ComapiClient>() {
            @Override
            public void success(ComapiClient client) {
               //Use client instance to communicate with services
            }

            @Override
            public void error(Throwable t) {
               //Error
            }
        });
    }
}
public class MyApplication extends Application {

    @Override
    public void onCreate()
    {
        super.onCreate();
      
        RxComapi.initialiseShared(this, config)
           .subscribeOn(Schedulers.io())
           .observeOn(AndroidSchedulers.mainThread())
           .subscribe(new Action1<RxComapiClient>() {
                    @Override
                    public void call(RxComapiClient client) {
                       //Use client instance to communicate with services
                    }
                }, new Action1<Throwable>() {
                    @Override
                    public void call(Throwable t) {
                        //Error
                    }
                });
    }
}

Both of the above calls will create singleton client instances that you can obtain through

Comapi.getShared(); 
// OR
RxComapi.getShared();

If you don't want SDK to keep the reference to the client instance use

Comapi.initialise(...);
// OR
RxComapi.initialise(...);

instead and store the client yourself.

Add listeners

When your app is in the foreground it keeps socket connection open and listens for live update of your profile and conversations for which you are a participant. Register for the incoming events with the ComapiConfig object and pass it to registration method.

ComapiConfig config = new ComapiConfig()
   .apiSpaceId("<API_SPACE_ID>") //required
   .authenticator(new ChallengeHandler()) //required
   .pushMessageListener(/* extends PushMessageListener */)
   .messagingListener(/* extends MessagingListener */)
   .profileListener(/* extends ProfileListener */)
   .stateListener(/* extends StateListener */);

Advanced options

FCM

If you don't want SDK to manage FCM registration and token

config.fcmEnabled(false);

Logging

You can set the logging level separately for internal file and console output and network calls to OFF, FATAL, ERROR, WARNING, INFO, DEBUG. By default they are set to warning.

ComapiConfig config = new ComapiConfig().logConfig(
new LogConfig()
   .setFileLevel(LogLevel.DEBUG)
   .setConsoleLevel(LogLevel.DEBUG)
   .setNetworkLevel(LogLevel.DEBUG));

You can also set custom limit for the size of internal log files

new ComapiConfig().logSizeLimitKilobytes(limit);

Proxy

If your test app connects through a proxy provide the Uri like this

new ComapiConfig().apiConfiguration(new APIConfig()
       .proxy("http://xx.xxx.xx.xxx:xxxx"));
Suggest Edits

Start session

 

Initialisation through Comapi (RxComapi) class gives you access to client object used for any further communication with the SDK. See initialisation for details.

In order to communicate with Comapi services you need to start session in SDK. This is needed only once per user log in, after that SDK will be re-authenticating session until you end session.

Start

To create or log in user to Comapi services use

client.service().session().startSession(new Callback<Session>(){/* implement */});
rxClient.service().session().startSession()
   .subscribe(new Action1<Session>(){/* implement */}, 
      new Action1<Throwable>(){/* implement */});

This will ask ComapiAuthenticator (provided when initialising) for a JWT token then SDK will create session server side for profile id obtained from the token. Any subsequent call to services will use same authentication details.

Stop

To close currently opened session use

client.service().session().endSession(
   new Callback<ComapiResult<Void>>(){/* implement */});
rxClient.service().session().endSession()
   .subscribe(new Action1<ComapiResult<Void>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});
Suggest Edits

Client APIs

 

You can use Comapi client instance obtained in Initialisation to access SDK APIs.

session

client.getSession().getProfileId();
client.getSession().isSuccessfullyCreated();

services

// Messaging related service calls
client.service().messaging();

// User profile related service calls
client.service().profile();

APIs

Depending on the client you initialised ComapiClient or RxComapiClient you will have access to callbacks or reactive APIs. Reactive version returns Observables you need to subscribe to. For callback version pass callback as the last parameter and the request will be performed in the background delivering result in UI thread.

All service calls will return ComapiResult<T> objects that give access to

// True if {@link #getCode()} ()} is in the range [200..300).
result.isSuccessful()

// Response data
result.getResult()

// HTTP status message
result.getMessage()

// service call error details
result.getErrorBody()

// HTTP status code.
result.getCode()

// ETag describing version of the data.
result.getETag()

Logs

client.getLogs().subscribe(new Action1<String>() {
                    @Override
                    public void call(String logs) {
                       //Internal logs
                    }
                }, new Action1<Throwable>() {
                    @Override
                    public void call(Throwable t) {
                        //Error
                    }
                });

For large logs data a good alternative is to copy them to a provided file from which they can be read gradually or e.g. send for inspection

client.copyLogs(file).subscribe(new Action1<File>() {
                    @Override
                    public void call(File logs) {
                       //Internal logs
                    }
                }, new Action1<Throwable>() {
                    @Override
                    public void call(Throwable t) {
                        //Error
                    }
                });

SDK state

client.getState();

You can find the list of codes in GlobalState class.

Suggest Edits

Listen to events

 

Realtime events are delivered to the SDK via a websocket. These events can be subscribed to via the following methods

ComapiConfig config = new ComapiConfig()
   .messagingListener(listener /* extends MessagingListener */)
   .profileListener(listener /* extends ProfileListener */);
client.addListener(listener /* extends MessagingListener */);
client.addListener(listener /* extends ProfileListener */);
client.removeListener(listener /* extends MessagingListener */);
client.removeListener(listener /* extends ProfileListener */);

MessagingListener and ProfileListener are abstract with empty implementation of onEventName methods. Override some or all of them to receive socket events from foundation SDK. Real time events are available only when the application is in foreground (visible to the user). If app is put to the background use FCM to communicate messages to the user. Update the internal state after app is put back to foreground using services APIs.

Available events

Event Name
Description

ProfileUpdateEvent

Sent when a user's profile is updated.

MessageSentEvent

Sent when a new message appeared in a conversation. This event will also be delivered to the message sender.

MessageDeliveredEvent

Sent when one of participants updated the message status to 'delivered'.

MessageReadEvent

Sent when one of participants updated the message status to 'read'.

ParticipantAddedEvent

Sent when a participant is added to a conversation. When a conversation is created, this event will also fire with the owner's profileId.

ParticipantUpdatedEvent

Sent when a participant role is updated in a conversation.

ParticipantRemovedEvent

Sent when a participant is removed from a conversation.

ConversationDeleteEvent

Sent when a conversation is deleted.

ConversationUpdateEvent

Sent when a conversation details were updated.

ConversationUndeleteEvent

Sent when a conversation is restored.

ParticipantTypingEvent (from v1.0.2)

Sent when 'isTyping' method has been called informing conversation participants that the user started typing a new message

ParticipantTypingOffEvent (from v1.0.2)

Sent when 'isTyping' method has been called informing conversation participants that the user stopped typing a new message

// Event unique identifier.
getEventId()

// Event name/type.
getName()

In addition specific event type

// Profile unique identifier.
getProfileId();

//Time when the update event was published.
getPublishedOn();

//Revision of the profile details on the server.
getRevision();

//Gets profileId of an user that performed this update.
getCreatedBy();

//Gets profile update details.
getPayload();

// Tag specifying server data version.
getETag();
// Message unique identifier
getMessageId();

// Unique, monotonically increasing number of event for this conversation
getConversationEventId()

// Custom message metadata (sent when client sends a message)
getMetadata();

// Message sender
getFromWhom();

// Message sender defined internally on server (shouldn't be visible inside the app)
getSentBy();

// When the message was sent
getSentOn();

// Conversation unique identifier
getConversationId();

// Parts of the message with data, type, name and size 
getParts();

// Alert definitions with FCM and APNS push platforms
getAlert();
// Message unique identifier
getMessageId();

// Conversation unique identifier
getConversationId();

// Profile unique identifier
getProfileId();

// When the message was marked as delivered
getTimestamp();

// Unique, monotonically increasing number of event for this conversation
getConversationEventId();
// Same as MessageDeliveredEvent
// Conversation unique identifier
getConversationId();

// Profile unique identifier
getProfileId();

// Role definition for this participant in this conversation
getRole();
// Same as ParticipantAddedEvent
// Same as ParticipantAddedEvent
// Conversation unique identifier
getConversationId() 

// Conversation name
getConversationName();

// Conversation decription
getDescription();

// Role definitions for 'owner' and 'participant'
getRoles();

// Tag specifying server data version.
getETag();
// Conversation unique identifier
getConversationId();

// When the conversation was deleted
getDeletedOn();

// Tag specifying server data version.
getETag();
// Conversation unique identifier
getConversationId();

// Conversation details, same as ConversationUpdateEvent
getConversation();

// Tag specifying server data version.
getETag();
// Conversation unique identifier
getConversationId();

// Get profile id of an participant who started typing a new message
getProfileId();
// Conversation unique identifier
getConversationId();

// Get profile id of an participant who stopped typing a new message
getProfileId();
Suggest Edits

Push messages

 

Comapi SDK is automatically registering you to receive push messages through FCM.

There are few places where the messages can be obtained: PushMessageListener registered when initialising SDK, system tray, Intent used to open Launcher Activity.

App state
Notification
Data
Both

Foreground

PushMessageListener

PushMessageListener

PushMessageListener

Background

System tray

PushMessageListener

Notification: system tray; Data: in extras of the intent.

PushMessageListeners simply redirects messages from internally registered FirebaseMessagingService (see Firebase)

Suggest Edits

Messaging service

 
service = client.service().messaging();

Depending on the client you initialised ComapiClient or RxComapiClient you will have access to callbacks or reactive profile APIs. Reactive version returns Observables you need to subscribe to. For callback version pass callback as the last parameter and the request will be performed in the background delivering result in UI thread.

data consistency

In order to manage concurrent updates of a conversation from many devices/websites use ETag. When obtaining conversation details from Comapi services you can find an ETag in ComapiResult object. It describes what version of server data you got. When you want to update this data pass the same ETag with the new details. If the server data will change before your next update the services will not allow such modification until you obtain the recent version of the data and pass correct ETag with the next update. This way you can keep consistency of the data across many devices.

messaging service API

create conversation

ConversationCreate conversation = ConversationCreate.builder()
   // Unique conversation identifier.
   .setId("1234")
   // Description
   .setDescription("This is my first conversation")
   // Name 
   .setName("Awesome chat")
   // Is this conversation visible to users not being participants.
   .setPublic(false)
   /* Sets what permissions 'owner' and 'participant' will have in this conversation
      You can set if they will can: add new participants, remove participants, send messages.
      By default 'owner' and 'participant' can send messages, can add participants, cannot remove participants.
      Here we added CanRemoveParticipants permission for the 'owner'. */
   .setRoles(new Roles(Role.builder().setCanRemoveParticipants().build(), new Role()))
   .build();

Then pass this object to Comapi services

service.createConversation(conversation,
   new Callback<ComapiResult<ConversationDetails>>(){/* implement */});
rxService.createConversation(conversation)
   .subscribe(new Action1<ComapiResult<ConversationDetails>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

update conversation

ConversationUpdate update = ConversationUpdate.builder()
                .setDescription("New description")
                .setName("Different name")
                .build();

Then pass this object to Comapi services

service.updateConversation(conversationId, update, eTag,
   new Callback<ComapiResult<ConversationDetails>>(){/* implement */});
rxService.updateConversation(conversationId, update, eTag)
   .subscribe(new Action1<ComapiResult<ConversationDetails>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

delete conversation

service.deleteConversation(conversationId, eTag,
   new Callback<ComapiResult<Void>>(){/* implement */});
rxService.deleteConversation(conversationId, eTag)
   .subscribe(new Action1<ComapiResult<Void>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

get convesartion

To obtain conversation details call

service.getConversation(conversationId,
  new Callback<ComapiResult<ConversationDetails>>(){/* implement */});
rxService.getConversation(conversationId)
   .subscribe(new Action1<ComapiResult<ConversationDetails>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});
// is the conversation public or private
isPublic();

// unique identification number
getId();

// name of the conversation
getName();

// description of the conversation
getDescription();

// privileges of owner and participants in this conversation
getRoles();

query conversations

You can also obtain the list of all conversations:

service.getConversations(isPublic,
   new Callback<ComapiResult<List<Conversation>>>(){/* implement */});
rxService.getConversations(isPublic)
   .subscribe(new Action1<ComapiResult<List<Conversation>>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});
// is the conversation public or private
isPublic();

// unique identification number
getId();

// name of the conversation
getName();

// description of the conversation
getDescription();

// privileges of owner and participants in this conversation
getRoles();

// eTag to compare if local version of the data is the same as the one the server side
getETag();

// number of participant in conversation
getParticipantCount();

// latest event id of sent message in the conversation. If null there are no messages in the conversation
getLatestSentEventId();

remove conversation participants

Provide list of profile ids to be removed from conversation.

service.removeParticipants(conversationId, ids,
   new Callback<ComapiResult<Void>>(){/* implement */});
rxService.removeParticipants(conversationId, ids)
   .subscribe(new Action1<ComapiResult<Void>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

query conversation participants

Query all participants for particular conversation

 service.getParticipants(conversationId, 
   new Callback<ComapiResult<List<Participant>>>(){/* implement */});
rxService.getParticipants(conversationId)
   .subscribe(new Action1<ComapiResult<List<Participant>>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

add participants to conversation

Add new participants to conversation

service.addParticipants(conversationId, participants,
   new Callback<ComapiResult<Void>>(){/* implement */});
rxService.addParticipants(conversationId, participants)
   .subscribe(new Action1<ComapiResult<Void>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

send message in conversation

service.sendMessage(conversationId, body,
   new Callback<ComapiResult<MessageSentResponse>>(){/* implement */});
rxService.sendMessage(conversationId, body)
   .subscribe(new Action1<ComapiResult<MessageSentResponse>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

You can update the message status later using conversationId from ComapiResult result.

// globaly unique message identifier
getId();

// unique for conversation event identifier, monotonically increasing, can be used to order messages
getEventId();

The more advanced way of constructing a new message is through

Map<String, Object> data = new HashMap<>();
data.put("key","value");

Map<String, Object> fcm = new HashMap<>();
fcm.put("data", data);
fcm.put("notification", "{ \"title\":\"Message\", \"body\":\"Hi!\" } ");

Map<String, Object> apns = new HashMap<>();
apns.put("alert", "Hi!");

Part part = Part.builder()
               .setData("Hi")
               .setName("body")
               .setSize("Hi".length())
               .setType("text/plain")
               .build();

MessageToSend message = MessageToSend.builder()
   .setAlert(fcm, apns)
   .setMetadata(data)
   .addPart(part)
   .build();

In this way you can set details of FCM/APNS notifications to be sent to participants devices, some custom metadata, add more message parts with e.g. Base64 encoded images etc.

Then call

service.sendMessage(conversationId, message, 
   new Callback<ComapiResult<MessageSentResponse>>(){/* implement */});
rxService.sendMessage(conversationId, message)
   .subscribe(new Action1<ComapiResult<MessageSentResponse>>(){/* implement */}, 
      new Action1<Throwable>(){/* implement */});

You can update the message status later using conversationId from ComapiResult result.

update message status

MessageStatusUpdate update = MessageStatusUpdate.builder()
   .addMessageId("id")
   .setStatus(MessageStatus.delivered)
   .build();

List<MessageStatusUpdate> updates = new ArrayList<>();
updates.add();

Then pass the list with 'delivered' and 'read' statuses to the service

service.updateMessageStatus(conversationId, updates,
   new Callback<ComapiResult<Void>>(){/* implement */});
rxService.updateMessageStatus(conversationId, updates)
   .subscribe(new Action1<ComapiResult<Void>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

query conversation events

Introduced in v1.0.2.

Conversation events - sending a message and updating a message status can be obtained providing conversation event id to start from (and going forward) and an upper limit of events you are interested in.

service.queryConversationEvents(conversationId, from, limit,
   new Callback<ComapiResult<ConversationEventsResponse>>(){/* implement */});
rxService.queryConversationEvents(conversationId, from, limit)
   .subscribe(new Action1<ComapiResult<ConversationEventsResponse>>(){
      /* implement */},
      new Action1<Throwable>(){/* implement */});

In response you will get

/**
 * Gets Events in the order in which they were received.
 * This collection can contain following conversation events:
 * MessageSentEvent - new message received in the conversation
 * MessageDeliveredEvent - message status marked 'delivered'
 * MessageReadEvent - message status marked 'read'
 * Cast elements of this collection to one of these.
 */
getEventsInOrder();

// Parsed message sent events
getMessageSent();

// Parsed message status 'delivered' events
getMessageDelivered();

// Parsed message status 'read' events
getMessageRead();

See socket events for details.

query messages

List of messages in a conversation can be obtained providing conversation event id to start from (and going backwards) and an upper limit of messages you are interested in. This interface allows you to get messages from the end of the conversation one page at a time. This will allow you to implement the classic pull down interface to load more messages.

service.queryMessages(conversationId, from, limit, 
   new Callback<ComapiResult<MessagesQueryResponse>>(){/* implement */});
rxService.queryMessages(conversationId, from, limit)
   .subscribe(new Action1<ComapiResult<MessagesQueryResponse>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

The result is a list of messages and orphaned events associated with them. The orphaned events are the events updating messages for conversation event id higher then 'from' - so for messages obtained e.g. in previous 'paging calls'. This events need to be applied to this newer messages.

// Messages conforming to the query.
getMessages();

/* Latest event id in this conversation that 
was taken into account constructing the query result. */
getLatestEventId();

/* Earliest event id in this conversation that 
was taken into account constructing the query result. */
getEarliestEventId();

// Events updating messages for conversation event id later/higher then 'from'.
getOrphanedEvents();

Getter messagesQueryResponse.getMessages() returns list of MessageReceived:

// Message unique identifier.
getMessageId();

/* Unique, monotonically increasing number of event 
of this message and conversation. */
getSentEventId();

// Message sender.
getFromWhom();

/* Message sender defined internally on server 
(shouldn't be visible inside the app). */
getSentBy();

// When the message was sent.
getSentOn();

// Conversation unique identifier
getConversationId();

// Key is the profile identifier the value is either 'delivered' or 'read'
getStatusUpdate();

Getter messagesQueryResponse.getOrphanedEvents() returns list of OrphanedEvent:

// Conversation event id.
getConversationEventId()

// Event name.
getName();

// Unique event identifier.
getEventId()

// Id of the updated message.
getMessageId();

// Id of the conversation for which message was updated.
getConversationId();

// Profile id of the user that changed the message status.
getProfileId();

// Gets time when the message status changed.
getTimestamp();

// True if this event is of type message delivered.
isEventTypeDelivered()

// True if this event is of type message read.
isEventTypeRead();

send user is typing event

Introduced in v1.0.2.

To send an information to conversation participants that the user started or finished typing a new message call

service.isTyping(conversationId, isTyping, 
   new Callback<ComapiResult<Void>>(){/* implement */});
rxService.isTyping(conversationId, isTyping)
   .subscribe(new Action1<ComapiResult<Void>>(){/* implement */},
              new Action1<Throwable>(){/* implement */});

Upload content data

Introduced in v1.1.1

To upload content data

service.uploadContent(folder, contentData,
   new Callback<ComapiResult<UploadContentResponse>>(){/* implement */});
rxService.uploadContent(folder, contentData)
   .subscribe(new Action1<ComapiResult<UploadContentResponse>>(){
     /* implement */},
              new Action1<Throwable>(){/* implement */});

The content data can be created based on a file, byte array and base64 string

contentData = ContentData.create(file, type, name);
contentData = ContentData.create(byte[], type, name);
contentData = ContentData.create(base64, type, name);

The UploadContentResponse will contain the full url to the file in the cloud. You can for e.g. send a message with a message Part containing this url pointing to an attachment data.

Suggest Edits

Profile service

 
service = client.service().profile();

Depending on the client you initialised ComapiClient or RxComapiClient you will have access to callbacks or reactive profile APIs. Reactive version returns Observables you need to subscribe to. For callback version pass callback as the last parameter and the request will be performed in the background delivering result in UI thread.

data consistency

In order to manage concurrent updates of an profile from many devices/websites use ETag. When obtaining profile details from COMAPI services you can find an ETag in ComapiResult object. It describes what version of server data you got. When you want to update this data pass the same ETag with the new profile details. If the server data will change before your next update the services will not allow such modification until you obtain the recent version of the data and pass correct ETag with the next update. This way you can keep consistency of the data across many devices.

profile details

Get profile details of an user registered in the same API Space as the SDK

service.getProfile(profileId,
   new Callback<ComapiResult<Map<String, Object>>>(){/* implement */});
rxService.getProfile(profileId)
   .subscribe(new Action1<ComapiResult<Map<String, Object>>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

Result contains containing a string-object map of properties associated with a given user profile.

query profiles

Query profiles registered on API Space

service.queryProfiles(queryString,
   new Callback<ComapiResult<List<Map<String, Object>>>>(){/* implement */});
rxService.queryProfiles(queryString)
   .subscribe(new Action1<ComapiResult<List<Map<String, Object>>>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

In result you will get a string-object map with a list of profile properties, each associated with a user profile conforming to provided query.

See query syntax. You can use QueryBuilder helper class to construct valid query string.

e.g.

String queryString = new QueryBuilder().addExists("email").build();

and queryProfiles will look for all profiles containing a field named 'email'.

update profile

Update your user profile providing custom map of values that should be associated with it.

service.updateProfile(profileDetailsMap, eTag,
   new Callback<ComapiResult<List<Map<String, Object>>>>(){/* implement */});
rxService.updateProfile(profileDetailsMap, eTag)
   .subscribe(new Action1<ComapiResult<List<Map<String, Object>>>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

result contains updated details.

patch profile

Applies patch for an user profile. This method won't erase previous profile details with different keys.

service.patchProfile(profileId, profileDetailsMap, eTag,
   new Callback<ComapiResult<List<Map<String, Object>>>>(){/* implement */});
rxService.patchProfile(profileId, profileDetailsMap, eTag)
   .subscribe(new Action1<ComapiResult<List<Map<String, Object>>>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

The following sections will take you through using the App Messaging Foundation iOS SDK

There is also a repository containing some sample apps here ...

Suggest Edits

Installing the SDK

 

To integrate Comapi Foundation into your Xcode project using CocoaPods, specify it in your Podfile:

use_frameworks!

pod 'ComapiFoundation'

Then, run the following command:

$ pod install

After that just insert in the Swift file:

import ComapiFoundation

and you are good to go.

Suggest Edits

Initialise

 

To initialise the Comapi SDK, you will need a few pre-requisites listed below:

  • A configured API Space
  • An authentication provider that can generate a JWT that matches the auth scheme configured for your Api Space.
  • The generated JWT must include the provided nonce as a claim in the generated JWT
import ComapiFoundation

let config = ComapiConfig(apiSpaceId: "<API_SPACE_ID>",
              						authenticationDelegate: ChallengeHandler())
let client = Comapi.initialise(config: config)
                          

Where <API_SPACE_ID> is your configured ApiSpaceID obtained from Comapi.

For convenience use of shared instance of ComapiClient, please use initialisation

Comapi.initialiseSharedInstance(config: config)

ChallengeHandler is just a class which implements following method:

class ChallengeHandler: AuthenticationDelegate {
    
    func client(_ client: ComapiClient,
                didReceiveAuthenticationChallenge authenticationChallenge: AuthenticationChallenge,
                completion continueWithToken: @escaping (String?) -> Void) {
/*
* 		Here goes your custom method to obtain a token with a nonce
* 	  call `continueWithToken` passing token as a parameter
*/
          getToken(authenticationChallenge.nonce) { token in
                  continueWithToken(token)
          }
		}
}

The JWT token needs to include claims from the authentication panel in the dashboard, which can be found at Channels -> App Messaging.

Here's an example implementation of a token generator in Swift using JSONWebToken

import JWT

class JWTokenGenerator {

    static func generate(tokenFor nonce: String) -> String {
				let now = Date()
        
        /* JWT expiry date */
        let exp = Calendar.current.date(byAdding: .day, value: 30, to: now)!

        /* JWT data */
        let secretKey = "testsecret".data(using: .utf8)!

        /* JWT headers */
        let headers = ["typ" : "JWT"]

        /* JWT claims */
        let claims = ["nonce" : nonce, 
                      "sub" : "testid",
                      "iss" : "testissuer",
                      "aud" : "testaudience",
                      "iat" : now.timeIntervalSince1970,
                      "exp" : exp.timeIntervalSince1970] as [String : Any]
        
        let token = JWT.encode(claims: claims, algorithm: 	Algorithm.hs256(secretKey), headers: headers)
        
        return token
    }
}

              
/* Note that this should preferably be generated by your backend, the app should only retreieve the token through an HTTP call */

For more information on JWT visit this link.

Retrieving the Client

After initialisation you can call the singleton object from anywhere in your code:

Comapi.shared()

Add event listener

When your app is in the foreground it keeps socket connection open and listens for live update of your profile and conversations for which you are a participant. Register the delegate for the incoming events with the ComapiClient object. The delegate should conform to the EventListener protocol.

client.addEventDelegate(self)

To receive the event implement following method from EventListener protocol:

func client(client: ComapiClient, didReceiveEvent event: Event) {
        switch event {
          case .conversation: //handle `conversation` event
          case .conversationMessage: // handle `conversationMessage` event
          case .profile: // handle `profile` event
          default: return
        }
    }

Please note that you can register multiple listeners in your code, to handle events in different parts of your application. The events are broadcasted to all registered delegates.

To remove event delegate call:

client.removeEventDelegate(self)

passing the current subscribed delegate as a parameter.

Advanced options

You can set up the logging level for the SDK using the log configuration:

Log.consoleDestination.minimumLevel = .error
Log.fileDestination.minimumLevel = .info

Following log levels are defined in LogLevel swift file :

  • verbose
  • debug
  • info
  • warning
  • error
Suggest Edits

Client APIs

 

You can use Comapi client instance obtained in Initialisation to access SDK APIs.

Session

client.profileId
client.isSessionSuccesfullyCreated

Service

Accessing services is done by calling methods from the proper group:

client.services.profile
client.services.messaging
client.services.session

All service calls will return Result<T, Error: Swift.Error> Swift enumeration that gives access to response data. To properly handle it you can use following code in the completion block:

client.services.messaging.getMessages(forConversationID: conversation.id, limit: 0, from: nil) {
                (result) in

                switch result {
                	case .success(let value):
                   print(value.messages ?? "")

                	case .failure(let error):
                    print("error: ",error.localizedDescription)
                    /* handle error here */
                }
Suggest Edits

Listen to events

 

WebSocket

Whenever ComapiClient is configured and authenticated, a new session is started which opens a WebSocket connection. This allows for a two-way real-time communication between the client and the server. For more information on WebSocket visit the official RFC site. When open, the socket provides live updates for the profile and conversations which you are a participant of. You can subscribe for these updates to update your views and models.

Add event listener

Register the delegate for the incoming events with the ComapiClient object. The delegate should conform to the EventListener protocol.

client.addEventDelegate(self)

To receive the event implement following method from EventListener protocol:

func client(client: ComapiClient, didReceiveEvent event: Event) {
        switch event {
          case .conversation: //handle `conversation` event
          case .conversationMessage: // handle `conversationMessage` event
          case .profile: // handle `profile` event
          default: return
        }
    }

Please note that you can register multiple listeners in your code, to handle events in different parts of your application. The events are broadcasted to all registered delegates.

client.removeEventDelegate(self)

Available events

Event type
Event subtype

profile

update

Sent when a user's profile is updated.

conversation

participantAdded

Sent when a participant is added to a conversation. When a conversation is created, this event will also fire with the owner's profileId.

conversation

participantUpdated

Sent when a participant role is updated in a conversation.

conversation

participantRemoved

Sent when a participant is removed from a conversation.

conversation

delete

Sent when a conversation is deleted.

conversation

update

Sent when a conversation details were updated.

conversation

undelete

Sent when a conversation is restored.

conversation

create

Sent when new conversation was created.

conversation

participantTyping

Sent when one of the participants is typing new message

conversation

participantTypingOff

Sent when the other participant stopped typing

conversationMessage

delivered

Sent when one of participants updated the message status to 'delivered'.

conversationMessage

read

Sent when one of participants updated the message status to 'read'.

conversationMessage

sent

Sent when a new message appeared in a conversation. This event will also be delivered to the message sender.

For all events you can access id, apiSpace and name properties.

For other events, there are other properties you can access, as follows:

profile.profileId

Conversation

Below are listed properties specific for the conversation event subtypes.

conversation.conversationId
conversation.payload.profileId
conversation.payload.role
conversation.conversationId
conversation.payload.profileId
conversation.payload.role
conversation.conversationId
conversation.payload.profileId
conversation.payload.role
conversation.conversationId
conversation.payload.date
conversation.profileId
conversation.payload.roles
conversation.payload.isPublic
conversation.payload.participants
conversation.conversationId
conversation.payload.description
conversation.payload.roles
conversation.profileId
conversation.payload.roles
conversation.payload.isPublic
conversation.payload.participants
conversation.account
conversation.payload.profileId
covenrsation.payload.conversationId
conversation.account
conversation.payload.profileId
covenrsation.payload.conversationId

ConversationMessage

conversationMessage.messageId
conversationMessage.conversationId
conversationMessage.profileId
conversationMessage.timestamp
conversationMessage.messageId
conversationMessage.conversationId
conversationMessage.profileId
conversationMessage.timestamp
conversationMessage.messageId
conversationMessage.metadata
conversationMessage.parts
conversationMessage.alert
Suggest Edits

Push messages

 

In order to use Comapi built-in support for the Push Notifications, you need to configure your app to support the PUSH notification, as you would normally do. To benefit from the Comapi Push Notifications you need to pass the deviceToken which your appliacation has registered with and call setPushToken method, e.g:

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    if let tokenString = String(data: deviceToken, encoding: .utf8) {
        try? Comapi.shared().setPushToken(tokenString) { _ in
        		// completion
        }
    }
    
    // rest of your push notification code
}
Suggest Edits

Start session

 

Start

Comapi starts new session when the authentication flow is finished. Providing proper ChallengeHandler and successfully initialising the ComapiClient starts new session automatically, no further customization is needed.

However, if you want to start session explicitly, you can use startSession method:

client.services.session.startSession(completion: { 
		// do any of the subsequent calls or any other action after initialisation
}) { error in
    // error ocurred
}

Stop

You can end the session just by calling:

client.services.session.endSession { (result) in 
		switch result {
    case .failure(let error):
        // error occurred
    case .success(let result):
        // success
    }
}
Suggest Edits

Messaging services

 

Messaging service API

Conversations

In order to properly create conversation, you need to set up correct attributes for the Owner and the participants. The whole procedure is listed in example below:

let ownerAttributes = RoleAttributes(canSend: true, canAddParticipants: true, canRemoveParticipants: true)
let participantAttributes = RoleAttributes(canSend: true, canAddParticipants: false, canRemoveParticipants: false)
let roles = Roles(owner: ownerAttributes , participant: participantAttributes )
let participant = ConversationParticipant(id: "Avarage Joe", role: "owner")
let newConversation = NewConversation(id:"{id}", name: "Awesome chat", conversationDescription: "My first conversation", roles: roles, isPublic: false, participants: [participant])

self.client.services.messaging.createConversation(newConversation) { result in
    switch result {
    case .failure(let error):
        // error occurred
    case .success(let result):
        // success
    }
}

To query all conversation call the method:

client.services.messaging.getAllConversations { (result) in
    switch result {
    case .failure(let error):
        // error occurred
    case .success(let result):
        // success
    }
}

Or if you want just to query the specific one:

client.services.messaging.getConversation("{id}") { result in
		switch result {
    case .failure(let error):
        // error occurred
    case .success(let result):
        // success
    }
}

Updating and deleting is as easy as calling dedicated SDK methods:

let conversationUpdate = ConversationUpdate(id: "{id}", name: "Name", conversationDescription: "description", roles: roles, isPublic: true)

client.services.messaging.updateConversation("{id}", conversationUpdate) { result in
		switch result {
    case .failure(let error):
        // error occurred
    case .success(let result):
        // success
    }
}
client.services.messaging.deleteConversation("{id}") { result in
		switch result {
    case .failure(let error):
        // error occurred
    case .success(let result):
        // success
    }
}

Participants

client.services.messaging.getParticipants(forConversationId: "{id}") { result in
		switch result {
    case .failure(let error):
        // error occurred
    case .success(let result):
        // success
    }
}
let participant = ConversationParticipant(id: "{id}", role: "participant")
client.services.messaging.addParticipants(forConversationId: conversationId, participants: [participant]) { result in
		switch result {
    case .failure(let error):
        // error occurred
    case .success(let result):
        // success
    }
}
client.services.messaging.removeParticipants(forConversationId: "{id}", participants: [participant]) { result in
		switch result {
    case .failure(let error):
        // error occurred
    case .success(let result):
        // success
    }
}

Messages

Querying messages is done with the method getMessages:

client.services.messaging.getMessages(forConversationID: "{id}", limit: 1, from: nil) { (result) in 
		switch result {
    case .failure(let error):
        // error occurred
    case .success(let result):
        // success
    }
}

If you want to query messages from specific message id, set the id as from parameter.

To send a message you need to create SendableMessage struct and then call sendMessage method.

let text = "Hello!"
let metadata = MessageMetadata(myMessageID: UUID().uuidString)
let part = MessagePart(name: "",
                       type: "text/plain",
                        url: nil,
                       data: text,
                       size: text.utf8.count)
                           
let message = SendableMessage(metadata: metadata, parts: [part])

client.services.messaging.send(message: message, toConversationWithId: "{id}") { (result) in
		switch result {
    case .failure(let error):
        // error occurred
    case .success(let result):
        // success
    }      
}
client.services.messaging.updateStatus(forMessagesWithIds: ["{ids_list}"], to: .read, inConversationWithId: "{id}", withTimestamp: Date()) { result in
		switch result {
    case .failure(let error):
        // error occurred
    case .success(let result):
        // success
    }
}

Events

client.services.messaging.queryEvents(forConversationID: "{id}", limit: 10) { result in
		switch result {
    case .failure(let error):
        // error occurred
    case .success(let result):
        // success
    }
}
Suggest Edits

Profile services

 
client.services.profile.getProfile("{profile_id}") { result in
		switch result {
    case .failure(let error):
        // error occurred
    case .success(let result):
        // success
    }
}
client.services.profile.updateProfile("{profile_id}", attributes: attributes) { result in
		switch result {
    case .failure(let error):
        // error occurred
    case .success(let result):
        // success
    }
}

You can query profiles using search criteria. Just specify the list of query elements and pass it as argument to queryProfile method

let query = [QueryBuilder.QueryElements( "name", .equal, "Han Solo")]

client.services.profile.queryProfiles(queryElements: query) { result in
		switch result {
    case .failure(let error):
        // error occurred
    case .success(let result):
        // success
    }
}
client.services.profile.patchProfile("{id}", attributes: attributes) { result in
		switch result {
    case .failure(let error):
        // error occurred
    case .success(let result):
        // success
    }
}

You can also use convenience methods to operate on current user profile:

client.services.profile.updateCurrentProfile(attributes: attributes) { _ in
		switch result {
    case .failure(let error):
        // error occurred
    case .success(let result):
        // success
    }
} 
client.services.profile.patchCurrentProfile(attributes: attributes) { _ in
		switch result {
    case .failure(let error):
        // error occurred
    case .success(let result):
        // success
    }
}
Suggest Edits

JavaScript

 

The following sections will take you through using the App Messaging Foundation Javascript SDK

There is also a repository containing some sample apps here ...

Suggest Edits

Install the SDK

How to install the sdk into your app

 

Installation

Comapi SDK can be installed from either NPM or Bower depending on your intended usage.

If you are integration into a classical javascript project and you just want to include a script that exposes some global objects in your page, then use Bower.

If you are using a project that utilises ES6 modules i.e angular2, ionic2 etc., then use NPM.

NPM

Install SDK ...

npm install @comapi/sdk-js-foundation --save

Import into your code and access SDK methods ...


import { Foundation } from "@comapi/sdk-js-foundation"

class MyClass{
    public displayVersion(){
        console.log(`Comapi version: ${Foundation.version}`);
    }
}

Bower

Install package from bower ...

bower install comapi-sdk-js-foundation

Include the script somewhere ...

<script src="bower_components/comapi-sdk-js-foundation/dist/comapi-foundation.js"></script>

There is also a minified version comapi-foundation.min.js available.

For all subsequent classical snippets, I will assume that this script has been included

Access SDK methods ...

console.log("Comapi version: " + COMAPI.Foundation.version);

Use of ES6 Promises

ES6 Promises are extensively used within this SDK. Depending on what browsers you are targeting, you may need to include a poly-fill for this. Several of these are available online.

Suggest Edits

Initialise

 

SDK Initialisation

To initialise the SDK, you will need a few pre-requisites ...

1) A configured API space

2) An authentication provider that can generate a jwt that matches the auth scheme configured for your Api Space.

3) The generated JWT must include the provided nonce as a claim in the generated jwt

ES6

Here is a typescript sample using ES6 import syntax (available from the npm package)

// some app specific imports
import { AppSettings } from "../settings";
import { AuthService } from "./auth";

// Comapi class / interface imports
import { Foundation, ComapiConfig, IAuthChallengeOptions } from "@comapi/sdk-js-foundation"

export class ComapiService {

    public sdk: Foundation;

    private authChallenge(options: IAuthChallengeOptions, answerAuthenticationChallenge) {
        this._authService.getToken(options.nonce)
            .then((token) => {
                answerAuthenticationChallenge(token);
            });
    }

    constructor(private _authService: AuthService) { }

    /**
     * Public method to encapsulate up the initialisation of Comapi 
     */
    public initialise(): Promise<Foundation> {

        return new Promise((resolve, reject) => {

            if (this._authService.isAuthenticated()) {

                let comapiConfig = new ComapiConfig()
                    .withApiSpace(AppSettings.APP_SPACE_ID)
                    // Note the this pointer binding so I can access this._authService in the authChallenge calllback
                    .withAuthChallenge(this.authChallenge.bind(this));

                Foundation.initialise(comapiConfig)
                    .then((sdk) => {
                        this.sdk = sdk;
                        console.log("foundation interface created");
                        resolve(sdk);
                    })
                    .catch((error) => {
                        console.error("initialise failed", error);
                        reject(error);
                    });
            } else {
                reject("Not logged in");
            }
        });
    }
}

Classical

function authChallenge(options, answerAuthenticationChallenge) {

    authService.getToken(options.nonce)
        .then((token) => {
            answerAuthenticationChallenge(token);
        })
        .catch(error=>{
            answerAuthenticationChallenge(null);            
        });
}

var comapiConfig = new COMAPI.ComapiConfig()
    .withApiSpace(appConfig.apiSpaceId)
    .withAuthChallenge(authChallenge);

COMAPI.Foundation.initialise(comapiConfig)
    .then(function (sdk) {
        console.log("Foundation interface created", sdk);
    })
    .catch(function (error) {
        $log.error("paragonService: failed to initialise", error);
    });

Advanced options

The above examples initialised the SDK with minimal configuration. You can customise the sdk behaviour with the following optional settings

logRetentionTime

When the SDK uses indexedDB to persist logs, they are purged on SDK initialisation.
this value represents the number of hours to keep logs for - the default value is 24

logLevel

This parameter controls what level of logging to perform.

export enum LogLevels {
    None,
    Error,
    Warn,
    Debug
};

The default setting is to only log errors.

logPersistence

This parameter controls whether and where to persist log data.
The historic data is available via a call to Foundation.getLogs()

export enum LogPersistences {
    None,
    IndexedDB,
    LocalStorage
};

The default setting is to use local storage.
IndexedDB is more performant but may require a poly-fill.

Authentication

JWT

The Comapi Auth Challenge needs to generate and return a jwt via the answerAuthenticationChallenge method.

There are 4 pieces fo data that need to be specified in the Comapi portal for the ApiSpace auth settings

1) Issuer
2) Audience
3) Shared Secret
4) ID Claim

A cryptographic nonce is used as part of the authentication flow. This is passed into the authChallenge (options.nonce) and needs to be added as a claim in the generated jwt.

The below sample uses jsrsasign to dynamically create a client side jwt ...

function authChallenge (options, answerAuthenticationChallenge) {
    // Header
    var oHeader = { alg: 'HS256', typ: 'JWT' };
    // Payload
    var tNow = KJUR.jws.IntDate.get('now');
    var tEnd = KJUR.jws.IntDate.get('now + 1day');
    var oPayload = {
        sub: "john smith",
        nonce: options.nonce,
        iss: "https://my-issuer.com",
        aud: "https://my-audience.com",
        iat: tNow,
        exp: tEnd,
    };
    var sHeader = JSON.stringify(oHeader);
    var sPayload = JSON.stringify(oPayload);
    var sJWT = KJUR.jws.JWS.sign("HS256", sHeader, sPayload, "my shared secret");
    answerAuthenticationChallenge(sJWT);
}

This node express method uses the njwt package. and achieves the same as above but server - side

/**
 * @Params {string} req.body.username
 * @Params {string} req.body.password
 * @Params {string} req.body.nonce
 */
app.post('/authenticate', function (req, res, next) {

    // TODO: authenticate username & password ...

    var claims = {
        iss: "https://my-issuer.com",
        sub: req.body.username,
        nonce: req.body.nonce,
        aud: "https://my-audience.com"
    }

    var jwt = njwt.create(claims, "my shared secret");
    var token = jwt.compact();
    res.json({ jwt: token });
});

The following auth challenge could be used in conjunction with the above node endpoint ..

function authChallenge (options, answerAuthenticationChallenge) {

    $http.post("/authenticate", { 
            username: "johnSmith" 
            password: "Passw0rd!",
            nonce: options.nonce })
        .then(function (response) {
            answerAuthenticationChallenge(response.data.token);
        })
        .catch(function (error) {
            answerAuthenticationChallenge(null);
        });
}

Sessions

To call onto any of the Comapi services, a valid session is required. This is what the authChallenge is for. Whenever Comapi needs a session and it doesn't have a currently active one, it will run through the auth flow as part of session creation.

You can explicitly start a session or the SDK will create one on the fly the first time you call a method that requires one

To start a session manually as part of the initialisation flow, you can do the following ...

Foundation.initialise(comapiConfig)
    .then(sdk => {
        console.log("sdk initialised", sdk);        
        return sdk.startSession();
    })
    .then(sessionInfo => {
        console.log("session started", sessionInfo);
    })
    .catch(error => {
        console.error("Something went wrong", error);
    });
Suggest Edits

Create conversation

 

Creating a conversation

The first thing you probably want to do is create a conversation and add some participants.
I will assume you have an initialised SDK at this point.

To create a conversation, you should use the ConversationBuilder class in conjunction with the createConversation() method.

A unique ConversationId is required to create a conversation. This is up to the integrator to provide.
The ConversationBuilder interface will automatically create a guid for this when it is instantiated.
You can override this behaviour and specify your own id using the withId("myConversationId") method.

Note that the methods on this interface can be chained together. See the SDK docs for a complete list of options.

ES6

Here is an ES6 sample


import { ConversationBuilder } from "@comapi/sdk-js-foundation";

let conversation = new ConversationBuilder().withName("Support").withDescription("Support related chat").withUsers(["johnSmith", "joeBloggs"]);

sdk.services.appMessaging.createConversation(channelDetails)
    .then(conversationInfo => {
        console.log("conversation created", conversationInfo);
    })
    .catch(error => {
        fail("conversation creation failed: ", error);
    });

Classical

var conversation = new COMAPI.ConversationBuilder().withName("Support").withDescription("Support related chat").withUsers(["johnSmith", "joeBloggs"]);

sdk.services.appMessaging.createConversation(channelDetails)
    .then(function(conversationInfo) {
        console.log("conversation created", conversationInfo);
    })
    .catch(function(error) {
        fail("conversation creation failed: ", error);
    });

These samples are basically the same bar the import of ConversationBuilder which is available from the COMAPI global.

Adding participants to a conversation

Participants can be added when the conversation is created or on the fly at a later date. To add a participant, you need to specify 2 pieces of information :

1) profileId - the profileId of the user to add
2) role - the role of the participant ["member"|"owner"]

i.e.

    var participants = [
        {
            id: "johnSmith",
            role: "member",
        }, {
            id: "joeBloggs",
            role: "member",
        }
    ];

the addParticipantsToConversation() method takes a conversationId and an array of participant objects

return sdk.services.appMessaging.addParticipantsToConversation(conversationInfo.id, participants)
    .then(result => {
        console.log("addMembersToConversation() succeeded", result);
    })
    .catch(error => {
        console.error("addMembersToConversation() failed", error);
    });

Removing participants from a conversation

To delete a participant, you use the deleteParticipantsFromConversation() method. You can specify a list of users to remove.

sdk.services.appMessaging.deleteParticipantsFromConversation(conversationInfo.id, ["joeBloggs", "johnSmith"])
    .then(result => {
        console.log("deleteParticipantsFromConversation() succeeded", result);
    })
    .catch(error => {
        console.error("deleteParticipantsFromConversation() failed", error);
    });

Listing participants in a conversation

To query all the participants in a conversation, you use the getParticipantsInConversation method.
An array of participants is returned in the Promise result.

sdk.services.appMessaging.getParticipantsInConversation(conversationInfo.id)
    .then(result => {
        console.log("getParticipantsInConversation() succeeded", result);
    })
    .catch(error => {
        console.error("getParticipantsInConversation() failed", error);
    });

Deleting a conversation

To delete a conversation, you simply use the deleteConversation() method.

sdk.services.appMessaging.deleteConversation(conversationInfo.id)
    .then(result => {
        console.log("deleteConversation() succeeded", result);
    })
    .catch(error => {
        console.error("deleteConversation() failed", error);
    });

Conversation related events

All of these methods will generate events which can be handled in your app. There is a specific events section where I will cover this in more detail particularly in terms of event payloads. Events are especially useful when there is more than one user of your app, so all devices / users get notified of any conversation related changes.

Event Details
conversationDeleted The conversation has been deleted
conversationUndeleted The conversation has been un-deleted
conversationUpdated The conversation has been updated (name / description changed)
participantAdded A participant has been added to a conversation
participantRemoved A participant has been removed from a conversation
participantTyping A participant is typing in a conversation
Suggest Edits

Send message

 

Sending a message

Basic plain text message

The next thing that you are going to want to do is send a message to your newly created conversation.

You can use the MessageBuilder interface to build your message. This abstracts away the complexities of JSON formatting.

The simplest message that can be sent is plain text, so lets start with that ...

ES6

import { MessageBuilder } from "@comapi/sdk-js-foundation"

let message = new MessageBuilder().withText("Hello world");

sdk.services.appMessaging.sendMessageToConversation(channelDetails.id, message)
    .then(result => {
        console.log("sendMessageToConversation() succeeded", result);
    })
    .catch(error => {
        console.error("sendMessageToConversation() failed", result);
    });

Classic

var message = new COMAPI.MessageBuilder().withText("Hello world");

sdk.services.appMessaging.sendMessageToConversation(channelDetails.id, message)
    .then(function(result){
        console.log("sendMessageToConversation() succeeded", result);
    })
    .catch(function(error){
        console.error("sendMessageToConversation() failed", result);
    });

Message parts

A message in Comapi messaging consists of an array of message parts. In the above sample, there was a single part of type text/plain. You can have as many parts as you like and each part can be of the format of your choosing. The underlying structure of a message part is as follows (all the properties are optional - you can use as you see fit):

export interface IMessagePart {
    /**
     * The message part name
     */
    name?: string;
    /**
     * The message Part Type
     */
    type?: string;
    /**
     * The message URL
     */
    url?: string;
    /**
     * Te message data
     */
    data?: string;
    /**
     * The size of the data 
     */
    size?: number;
}

There are 2 helper methods available to allow more complex parts to be added:

withData

Here we will create a message with 2 parts, the first part being plain text, the second being an image attachment.
Note that the data type field is completely up to the integrator to use as they see fit. I have just adopted Mime types for these samples.


    // some data uri for an image ...
    let data = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="

    let message = new MessageBuilder()
        .withText("Check out this image ...")
        .withData("image/png", data);

withPart

If you woud like to manually create the parts yourself, then you can do the following


    let textPart = {
        data: "Check out this image ...",
        type: "text/plain",
        size: 24 // Note:  size is optional, use as required
    };

    let imagePart = {        
        data: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",
        type: "image/png",
    };

    let message = new MessageBuilder()
        .withPart(textPart)
        .withPart(imagePart);

Push Notifications

If you would like to have a push notification sent with the message, you can specify a generic message for both FCM & APNS and further customise the platform specific payloads ...

Generic settings

    let message = new MessageBuilder()
        .withText("Hello world")
        .withPush("Hi there");

Platform Specific overrides


let apnsAlert = {
    badge: 1,
    sound: "ping.aiff",
    alert: "hello"
};

let fcmAlert = {
    notification: {
        body: ";-)",
        title: "hello"
    }
};    

let message = new MessageBuilder()
    .withText("Hello world")
    .withPush("Hi there")
    .withFcmAlert(fcmAlert)
    .withApnsAlert(apnsAlert);

Metadata

You can also send some metadata along with the message. This can be any object.

let message = new MessageBuilder()
    .withText("Hello world")
    .withMetadata({prop1: "val1", prop2: 2});

Events

Event Details
conversationMessage.sent A message has been sent to the conversation
conversationMessage.delivered A participant has marked a message as delivered
conversationMessage.read A participant has marked a message as read
Suggest Edits

Query conversations

 

Query a list of conversations

This is how you would query for list of ALL conversations that a user is a member of ...

sdk.foundation.services.appMessaging.getConversations()
    .then(conversations => {
        console.log("getConversations() succeeded", conversations);
    })
    .catch(error => {
        console.log("getConversations() failed", error);
    })

There are also some optional arguments to getConversations():

/**
 * Function to get all conversations (not any messages) the user is a participant in
 * @method ConversationManager#getConversations 
 * @param {ConversationScope} [scope] - the conversation scope ["public"|"participant"]
 * @param {string} [profileId] - The profileId to search with
 * @returns {Promise} 
 */

Query a specific conversation

To query the details of a specific conversation with a known conversationId, you can do the following ...

sdk.foundation.services.appMessaging.getConversation("CA29B56B-30D6-4217-9C99-577AA7525B92")
    .then(conversation => {
        console.log("getConversation() succeeded", conversations);
    })
    .catch(error => {
        console.log("getConversation() failed", error);
    })
Suggest Edits

Query conversation messages

 

Querying Messages

This interface allows you to get messages from the end of the conversation one page at a time. This will allow you to implement the classic pull down to load more interface.

This method uses a continuation token manage the paging. If you want to enumerate from the end, you don't specify a token. As part of the response, a continuation token is returned along with the messages. This is fed back in the next time we want to query for messages. If the wrong token is specified, the method will return an error and you wi have to go beck to the end of the list of messages.

Here is the first call to the api - I have specified a conversationId and a page size of 100 ...

//  Call the getMessages() api for the first time
sdk.services.appMessaging.getMessages("CA29B56B-30D6-4217-9C99-577AA7525B92", 100 )
    .then(response => {
        console.log("getMessages() succeeded", response);
        console.log("Here are your messages ...", response.messages);
        console.log("Here is your continuation token, store this and use the next time you call this method", response.continuationToken);        
    })
    .catch(response => {
        console.error("getMessages() failed", error);
    });

To query the previous page of messages, you need to feed in the continuation token that was returned the lst time you queried this conversation. The token is specific to this conversation only.

// Call the getMessages() api for the second time
// 
sdk.services.appMessaging.getMessages("CA29B56B-30D6-4217-9C99-577AA7525B92", 100, continuationToken )
    .then(response => {
        console.log("getMessages() succeeded", response);
        console.log("Here are your messages ...", response.messages);
        console.log("Here is an updated continuation token", response.continuationToken);
    })
    .catch(response => {
        console.error("getMessages() failed", error);
    });

You can keep doing this until you reach the beginning of the conversation ...

When the continuation token returned is <= 0, you have all the messages

Here is the definition of the response returned via a promise from the call to getMessages()

export interface IGetMessagesResponse {
    continuationToken?: number;
    earliestEventId?: number;
    latestEventId?: number;
    messages: IConversationMessage[];
}

The eventId's will be used later when we handle incoming events from the web-socket.

The foundation SDK doesn't deal with any kind of conversation persistence - that is up to you manage.
You may choose to just query messages from the end of the conversation every time and not bother persisting anything.

Applying realtime events to conversation Messages stored locally

Whether you choose to store your messages in some database, or just in-memory you will need to deal with new events that arrive.
These events will take the form of new messages and messages getting marked as delivered / read. I will deal with this in detail in the websocketEvents section.

Suggest Edits

Query events

 

Querying events

Conversations messages and their relative statuses are generated from an immutable event store. To build up a conversation, we play through the events and that is projected into an ordered list of messages. These events are available to the client either from the web-socket or from a call to sdk.services.appMessaging.getConversationEvents().

The live web-socket events are delivered to the app in realtime and need to be applied to the local conversation message store. If the client has any gaps, they can query a range of events using getConversationEvents(). The event payload is the same whether it is received from the web-socket of from this api method.

To query events, we can do the following ....

// retrieve up to 100 events from position 0
sdk.services.appMessaging.getConversationEvents("5D21F17C-B2EC-4622-848E-5A2A916953EA", 0, 100)
    .then(events => {
        console.log("getConversationEvents() succeeded", events);
    })
    .catch(error => {
        console.error("getConversationEvents() failed", error);
    });

Applying events

After you have initially loaded up a conversation, it becomes your responsibility to process the incoming web-socket events / query events from getConversationEvents() and update the messages accordingly.

There are 3 events that may need processing depending on whether you intend to mark messages as delivered / read.

conversationMessage.sent

This event signifies that a new message has been posted to the conversation. If this message wasn't sent by you, you should send a status update marking this message as delivered. You will also want to add this message to your local message store. You can identify the position to insert this message by looking at the sentEventid property on the messages in your message store and the conversationEventId property on the sent event. Messages should be ordered based on this sequence.

conversationMessage.read

This event signifies that a someone has marked a message as read.

conversationMessage.delivered

This event signifies that a someone has marked a message as delivered. Messages get automatically marked as delivered when you query messages with sdk.services.appMessaging.getMessages().

sdk.on("conversationMessageEvent", function (event) {

    // Check that we haven't processed this event already ....
    // when we call getMessages(), part of the response are 2 properties:
    // earliestEventId and latestEventId.
    // we need to maintain these and adjust when we query messages

    if(event.conversationEventId > earliestEventId ||
       event.conversationEventId < latestEventId){
        console.log("event already seen", event);
        return;
    }

    switch (event.name) {
        case "conversationMessage.sent":
        // add message to local conversation store 
        // also end a messageStatus update of delivered for this message
        // You can send another update of read when you display the message to the user
        break;

        case "conversationMessage.read":
        // update statusUpdates in locally stored message object (if found) to reflect message is read by event.payload.profileId
        break;

        case "conversationMessage.delivered":
        // update statusUpdates in locally stored message object (if found) to reflect message was delivered to event.payload.profileId
        break;
    }
});

StatusUpdates

Status updates are stored against a statusUpdates property on a message and is of the following structure. The status will either be delivered or read. if it is read, it implied to also be delivered.

{
    "alex": {
        "status": "read",
        "on": "2016-10-19T11:52:29.704Z"
    },
    "dave": {
        "status": "delivered",
        "on": "2016-10-19T11:00:29.704Z"
    },
}

IGetMessagesResponse

export interface IGetMessagesResponse {
    continuationToken?: number;
    earliestEventId?: number;
    latestEventId?: number;
    messages: IConversationMessage[];
}
Suggest Edits

Websocket events

 

Events

Realtime events are delivered to the SDK via a web-socket. These events can be subscribed to via the following methods ...

Subscribe to an event

sdk.on("profileUpdated", function (event) {
    console.log("profileUpdated", event);
});

Unsubscribe from an event

sdk.off("profileUpdated");

Complete list of events

Event Name Event Payload Description
conversationDeleted IConversationDeletedEventData Sent when a conversation is deleted
conversationUndeleted IConversationUndeletedEventData Sent when a conversation is undeleted
conversationUpdated IConversationUpdatedEventData Sent when a conversation is updated
participantAdded IParticipantAddedEventData Sent when a participant is added to a conversation. When a conversation is created, this event will also fire with the owner's profileId.
participantRemoved IParticipantRemovedEventData Sent when a participant is removed to a conversation. App needs to check whether the participant is the current user and locally remove the conversation from the UI.
participantTyping IParticipantTypingEventData Sent when a participant is typing in a conversation
profileUpdated IProfileUpdatedEvent Sent when a user's profile is updated
conversationMessageEvent IConversationMessageEvent This event is sent for all conversation message related activity. It encapsulates the sent, delivered and read events. It is defined like this so you can handle web-socket conversation message events and events requested from sdk.services.appMessaging.getConversationEvents() seamlessly.

IConversationDeletedEventData

export interface IConversationDeletedEventData {
    conversationId: string;
    createdBy: string;
    timestamp: string;
}

IConversationUndeletedEventData

export interface IConversationUndeletedEventData {
    conversationId: string;
    createdBy: string;
    timestamp: string;
}

IConversationUpdatedEventData

export interface IConversationUpdatedEventData {
    conversationId: string;
    createdBy: string;
    name: string;
    description: string;
    roles: IConversationRoles;
    isPublic: boolean;
    timestamp: string;
}

IParticipantAddedEventData

export interface IParticipantAddedEventData {
    conversationId: string;
    createdBy: string;
    profileId: string;
    role: string;
    timestamp: string;
}

IParticipantRemovedEventData

export interface IParticipantRemovedEventData {
    conversationId: string;
    createdBy: string;
    profileId: string;
    timestamp: string;
}

IParticipantTypingEventData

export interface IParticipantTypingEventData {
    conversationId: string;
    createdBy: string;
    profileId: string;
    timestamp: string;
}

IProfileUpdatedEvent

export interface IProfileUpdatedEvent {
    eTag: string;
    profile: any;
}

IConversationMessageEvent

This event encapsulates sent, delivered and read events.

export interface IConversationMessageEvent {
    eventId: string;
    // name field will be ["conversationMessage.sent" | "conversationMessage.read" | "conversationMessage.delivered"]
    name: string;
    conversationId: string;
    conversationEventId: number;
    // payload will differ based on event name ...
    payload: IMessageSentPayload | IMessageStatusUpdatePayload;
}

// payload for conversationMessage.sent
export interface IMessageSentPayload {
    messageId: string;
    metadata: any;
    context: any;
    parts: IMessagePart[];
    alert: IMessageAlert;
}

// payload for conversationMessage.delivered, conversationMessage.read
export interface IMessageStatusUpdatePayload {
    messageId: string;
    conversationId: string;
    profileId: string;
    timestamp: string;
}
Suggest Edits

Message status updates

 

Message status updates

Message status updates are sent by the application when a message is received or when the message has been read.
If you query for messages using sdk.services.appMessaging.getMessages(), they will be automatically marked as delivered.
It is the application's responsibility to mark New messages received from the web socket.
It is also the application's responsibility to mark the messages as read whenever this occurs. (This is optional functionality)

To send a message status update, you can use the MessageStatusBuilder interface.
You will also need the conversationId of the conversation that you you wish to send the status update to.

ES6 implementation of marking a message as delivered

import { MessageStatusBuilder } from "@comapi/sdk-js-foundation";

// Note I am using the version that takes a single messageId in this sample
let status = new MessageStatusBuilder().deliveredStatusUpdate("C984814D-B714-4DC8-8DFF-33C29082ACEA");

// we can send multiple updates of different types with this method, hence the array ...
sdk.services.appMessaging.sendMessageStatusUpdates(conversationId, [status]);

Classical implementation of marking a message as read

// Note I am using the version that takes a list of messageId's in this sample
var status = new COMAPI.MessageStatusBuilder().readStatusUpdates(["C984814D-B714-4DC8-8DFF-33C29082ACEA", "88E43FA4-9705-44F5-8DE6-4B9DD5E46DF3"]);

sdk.services.appMessaging.sendMessageStatusUpdates(conversationId, [status]);

See the API documentation for the full list of methods available. There are methods for single or multiple updates for both read and delivered statuses

Suggest Edits

Profile API

 

Profile API

When a user is created, a profile is also automatically generated.
The application can store user specific information in the profile.

Querying the user's profile

sdk.profile.getMyProfile()
    .then(function (profile) {
        console.log("getMyProfile() succeeded", profile);
    })
    .catch(function (error) {
        console.error("getMyProfile() failed", error);
    });

Updating the user's profile

sdk.services.profile.updateMyProfile(profile)
    .then(function (updatedProfile) {
        console.error("updateMyProfile() succeeded", updatedProfile);
    })
    .catch(function (error) {
        console.error("updateMyProfile() failed", error);
    });

eTags

The SDK internally uses eTags to prevent simultaneous updates of the profile from overwriting each other.

If you want to update a profile, you need to query it first, make the appropriate changes to it and then update it. When the profile is queried, an eTag is internally stored and passed back in the update. If they don't match, an error will be returned. This means that the state of the profile has changed since it was queried and what we tried to update is now stale.

Suggest Edits

Typescript

 

Typescript

This library was written in typescript, hence all entities that the SDK deals with have an associated interface.

Assuming you installed the SDK via npm, you can access all of the relevant interfaces as required.

Here is a simple example showing how you would go about importing an interface ...

import { 
    Foundation,
    IConversationMessage
} from "@comapi/sdk-js-foundation"

export class MessagesHelper {

    /**
     * Method to determine whether a message has been read or not by  user
     * @param {IConversationMessage} message - the message to check
     * @param {string} profileId - the profileId to check read status against 
     */
    public static isMessageRead(message: IConversationMessage, profileId: string):boolean {

        var isRead = false;

        // if the message was sent by this person then skip ...
        if (message.context.from.id !== profileId) {
            // are there status updates and is there one for this user
            if (message.statusUpdates && message.statusUpdates[profileId]) {
                // is it read ?
                if (message.statusUpdates[profileId].status === "read") {
                    isRead = true;
                }
            }
        } else {
            // the user who wrote the message obviously read it ...
            isRead = true;
        }

        return isRead;    
    }
}

If you look in node_modles/@comapi/foundation/src/interfaces.d.ts, you will see what interfaces are available.

 

Cordova Integration

The Javascript App Messaging Foundation SDK can easily be incorporated into a Cordova app.
There is no specific Comapi plugin as such as all the necessary functionality required to configure push notifictions are present in the core sdk. You just need to install the SDK into your app.

See the Installation section for details of how to do this.

For Ionic 2, follow the NPM guidelines.

For Ionic 1 or similar, follow the Bower guidelines.

You will however need a push notification plugin. The SDK implements push in a standard way so you can just install phonegap-plugin-push or use something similar.

Here are some snippets showing how to send the registrationId to Comapi

Retrieving the push registrationId is an asynchronous task that will be performed AFTER the cordova deviceready event has fired.
This needs to be passed to Comapi and can only be passed after the SDK is initialised and a valid session has been created.
There is potential for a race condition here so i will split the two tasks apart and save the registrationId to localStorage for the purposes of this snippet.

Getting the registrationId

This snippet is using the Push plugin from Ionic Native to retrieve the registrationId and setup a notification handler. There is no Comapi code in here.

import { Push } from 'ionic-native';

platform.ready().then(() => {

    let push = Push.init({
        ios: {
        alert: "true",
        badge: true,
        sound: 'false'
        }
    });

    push.on('registration', (data) => {
        console.log("got a registrationId", data.registrationId);
        localStorage.setItem("registrationId", data.registrationId);
    });

    push.on('notification', (data) => {
        console.log("got a pushNotification", data);
    });

}

Sending the registrationId to Comapi

This snippet shows how to send the registrationId to Comapi.
The assumption is that you have an initialised SDK and a valid session at this point. Note the Environment import.

import { Environment } from "@comapi/sdk-js-foundation";

// Put this somewhere appropriate in your app (at the end of you initialisation/login flow)

let registrationId = localStorage.getItem("registrationId");
// skip if registrationId hasn't been collected
if(registrationId){

    // There are separate methods to call depending on platform ...
    if (platform.is('ios')) {

        // You will need to create an APNS cert. in the apple developer portal.
        // Then you must upload it to your API space in the Comapi portal.
        // Can be a development or production cert, hence the environment parameter
        sdk.device.setAPNSPushDetails(">>> My Bundle Id <<<", Environment.development, registrationId)
        .then(result => {
            console.log("setAPNSPushDetails() succeeded", result);
        })
        .catch(error => {
            console.error("setAPNSPushDetails() failed", error);
        });

    }else if(platform.is('android')){

        sdk.device.setFCMPushDetails(">>> My Package Name <<<", registrationId)
        .then(result => {
            console.log("setFCMPushDetails() succeeded", result);
        })
        .catch(error => {
            console.error("setFCMPushDetails() failed", error);
        });

    }
}else{
    console.error("no registrationId ;-(");
}

Push Payloads

When you send a message, you can individually craft the platform specific payloads.
They will be received in the notification event handler shown in the first snippet.

 

Can you use the Chat API?

You will need the Comapi Toolkit Pro subscription to be able to use the Chat API, or Comapi Toolkit if you just want to use our SaaS Chat tool.

The Chat API is a logic layer for creating and managing chats across multiple channels. It is built upon the App Messaging channel and is the same API used to power our Chat SaaS tool.

We make the API available for integration if you need to provide omni channel chat functionality into your own products and services. You will require a Comapi Toolkit Pro subscription to utilise the API.

The API consists of the following services:

Chat service

This service provides all the functions needed to create and manage chats.
The API details can be found here.

Chat Message service

This service provides all the functions needed to send and retrieve messages on a chat.
The API details can be found here.

Chat Configuration service

This service provides all the functions needed to create and manage chat teams and the channel configuration for them.
The API details can be found here.

Web hook events

Concepts

The following concepts should be understood to help you to understand how the Chat API works:

Teams

You must have a least one team defined in order to create a chat, as chats are created when a new message is received and is routed to a team using the channels associated with the team.

Channels

Chat can be used with the following channels:

  • Web chat
  • App Messaging
  • SMS
  • Email
  • Facebook
  • Twitter (Coming soon)

Starting Chats

Chats are started when a new message is received from via a channel associated with a team where there is no active chat with the sender already, otherwise the message is appended to the ongoing chat.

Response Routing

A chat exists on a single channel at any one time but can move between channels during its lifetime. When a chat is transferred to another team any responses are automatically routed back via the channel the message was received on, therefore allowing all teams to participate in a chat even if they don't have the channel the message started on associated with them. This allows the creation of a team structure where there are a few teams that handle the first line contact and then route the chat onward to specialist teams.

Chats

A chat is the container for all communication in the Chat API. A chat can be created, transferred or deleted.

Maximum Participants

A chat may only have 2 participants at any one time, the customer and agent.

Chat ownership

A chat is assigned to either a team for automatic assignment to an available agent or to a agent directly.

Closing chats

To ensure a new chat is started next time a customer contacts you once a conversation is complete you must ensure that the chat is closed.

Try our chat tool

Use our chat tool to see the Chat API in action and get a better feel for how it operates.

Suggest Edits

Web Chat Widget

 

You can add a pre-built chat widget to your websites with ease which will give you an out of the box solution for customer chat using only a few lines of code included in each of your web sites pages. The widget can have its colours customised from the Comapi portal and operates as a black box solution in isolation in your web page. The widget looks like this:

The Comapi web chat widget

The Comapi web chat widget

Configuring Web Chat

You can configure the web chat widget in the Comapi Portal in the by selecting Channels -> Web Chat from the menu.

Web Chat configuration

Web Chat configuration

Widget Customisation

You can customise the look and feel of the widget on this tab by setting the colour scheme used and text colour etc...

Usage

On this tab you can copy and paste the widget code snippets for adding to your web pages with or without an authentication callback. See below for more details on authentication callbacks.

Authentication

The web chat widget can be run in two modes:

  • Anonymous - The widget creates a unique anonymous profile for each customer so that you can chat with your customers even if they haven't signed in or registered
  • Authenticated - The widget calls back to your system in order to identify who it is representing.

Anonymous mode

Anonymous mode will automatically create a unique profile id for each customer who visits your website and allow them to communicate with you via the web chat widget with ease. All you need to do to use this mode is to copy the widget code snippet from the Web Chat configuration screens Usage tab into your web site. The code snippet will be needed on each page you want the web chat widget on, and usually it is simply added to the master page template so it exists on all pages.

Authenticated mode

Authenticated mode will callback to your code for you to provide a JWT that identifies the user on the website. Typically this mode would be used when the chat widget is used with a web site that supports signing in, so that the identity used by web chat matches the logged in user. If your website already uses JWTs then these can be reused with the web chat widget as long as you configure the App Messaging security settings to match your systems e.g. shared secret, nonce etc...

<script>
    var comapiConfig = {
         apiSpace: '{Your API Space Id}',
         urlBase: 'https://api.comapi.com' 
    };
    (function(d, s, id){
        var js, cjs = d.getElementsByTagName(s)[0];
        if (d.getElementById(id)) {return;}
        js = d.createElement(s); js.id = id;
        js.src = '//cdn.dnky.co/widget/bootstrap.js';
        cjs.parentNode.insertBefore(js, cjs);
    }(document, 'script', 'comapi-widget'));
</script>
<script>
    var comapiConfig = {
         apiSpace: '{Your API Space Id}',
         urlBase: 'https://api.comapi.com',
         authCallback: function (nonce, answerAuthenticationChallenge){
            // TODO: generate a JWT and return 
            var jwt = ''; 
            answerAuthenticationChallenge(jwt); 
        } 
    };
    (function(d, s, id){
        var js, cjs = d.getElementsByTagName(s)[0];
        if (d.getElementById(id)) {return;}
        js = d.createElement(s); js.id = id;
        js.src = '//cdn.dnky.co/widget/bootstrap.js';
        cjs.parentNode.insertBefore(js, cjs);
    }(document, 'script', 'comapi-widget'));
</script>

Logging the widget out

If you have the concept of logging out of your website and need the chat widget to clear it cached settings so that a different user can use the widget on next login then please add the following JavaScript snippet to your log out client code:

// Tell the Comapi chat widget to unload itself
if (window.COMAPI_WIDGET_UNLOAD) {
	window.COMAPI_WIDGET_UNLOAD();
}

Advanced Options

Overriding Data Storage

The Webchat widget stores a small amount of context data on the clients browser using HTML 5 local storage mechanism, so it can maintain a consistent profile id between website visits and restore the users messaging history.

It is possible to override where this context data is stored by passing in storage method overrides in the chat widgets config. Reasons to do this include using alternate persistence mechanisms, or to change the widget to use session storage so each visit to a website starts a new user and therefore clears message history.

To override the storage methods for the Webchat widget pass the following object in the configuration for the widget:

storage: {
    removeItem: function(key){
       // The code to remove an item from storage using the "key"
  },
    getItem: function(key){
       // The code to retrieve an item from storage using the "key"
  },
    setItem: function(key, value){
       // The code to store an item in storage using the "key" and "value"
  }
}

e.g.

<script>
   window.comapiConfig = {
       apiSpace: 'fa24cf6e-4352-4b26-a71a-c3032bea7a01',
     	 storage: {
                removeItem: function(key){
                    return sessionStorage.removeItem(key);
                },
                getItem: function(key){
                    return sessionStorage.getItem(key);
                },
                setItem: function(key, value){
                    sessionStorage.setItem(key, value);
                }
            }
   };
   (function(d, s, id){
       var js, cjs = d.getElementsByTagName(s)[0];
       if (d.getElementById(id)) {return;}
       js = d.createElement(s); js.id = id;
       js.src = '//cdn.dnky.co/widget/bootstrap.js';
       cjs.parentNode.insertBefore(js, cjs);
   }(document, 'script', 'comapi-widget'));
</script>

Widget API

The widget exposes an API via some interfaces attached to the window object of the page you added the webchat widget to. The API methods are designed to allow deeper integration and automation with the webchat widget, including updating profile information for the webchat widgets active user.

All interfaces for the webchat widget API hang off window.COMAPI_WIDGET_API and are:

profile

The profile interface provides access to profile functionality for reading and updating the profile associated with the webchat widget.

getProfile

Function to get the current webchat widget user's profile.

Returns a promise with either the profile object for the user or a error object if it fails.

COMAPI_WIDGET_API.profile.getProfile()
    .then(function (profile) {
        console.log("getProfile() succeeded", profile);
    })
    .catch(function (error) {
        console.error("getProfile() failed", error);
    });

patchProfile

Function to update (patch) the user's profile. The profile will be merged (patched) with the properties passed in. Any other properties will remain unchanged.

Name Type Description
data object data to patch the profile with

Returns a promise with the updated profile object if successful, or an error object if an error occurs.

COMAPI_WIDGET_API.profile.patchProfile(data)
    .then(function (updatedProfile) {
        console.error("patchProfile() succeeded", updatedProfile);
    })
    .catch(function (error) {
        console.error("patchProfile() failed", error);
    });

updateProfile

Function to update the current webchat widget user's profile. The profile will be replaced with the properties passed in. Any previous properties will be removed!

Name Type Description
profile object the profile to update

Returns a promise with the updated profile object if successful, or an error object if an error occurs.

COMAPI_WIDGET_API.profile.updateProfile(profile)
    .then(function (updatedProfile) {
        console.error("updateProfile() succeeded", updatedProfile);
    })
    .catch(function (error) {
        console.error("updateProfile() failed", error);
    });

device

The device interface provides access to some device functionality, which is largely utilised with Cordova apps:

setFCMPushDetails (packageName, registrationId)

Function to set FCM push details for the current session

Name Type Description
packageName string the android package name of your cordova app
registrationId string the push registration id

Returns a promise indicating whether the call was successful or not.

COMAPI_WIDGET_API.device.setFCMPushDetails(packageName, registrationId)
    .then(function (result) {
        console.error("setFCMPushDetails() succeeded", result);
    })
    .catch(function (error) {
        console.error("setFCMPushDetails() failed", error);
    });

setAPNSPushDetails

Function to set APNS push details for the current session

Name Type Description
bundleId string the iOS bundleId of your cordova app
environment number the environment (development = 0, production = 1)
token string the device token

Returns a promise indicating whether the call was successful or not.

COMAPI_WIDGET_API.device.setAPNSPushDetails(details)
    .then(function (result) {
        console.error("setAPNSPushDetails() succeeded", result);
    })
    .catch(function (error) {
        console.error("setAPNSPushDetails() failed", error);
    });

removePushDetails

Function to remove push details for the current session

Returns a promise indicating whether the call was successful or not.

COMAPI_WIDGET_API.device.removePushDetails()
    .then(function (result) {
        console.error("removePushDetails() succeeded", result);
    })
    .catch(function (error) {
        console.error("removePushDetails() failed", error);
    });

widget

The widget interface allows you to programmatically show or hide the widget:

show

COMAPI_WIDGET_API.widget.show();

hide

COMAPI_WIDGET_API.widget.hide();

session

The session interface allows you access the current widgets session:

Returns a promise with the session info if successful or an error object if the call failed.

getSessionInfo

COMAPI_WIDGET_API.session.getSessionInfo()
    .then(function (sessionInfo) {
        console.error("getSessionInfo() succeeded", sessionInfo);
    })
    .catch(function (error) {
        console.error("removePushDetails() failed", error);
    });
Suggest Edits

Android Chat

 

The following sections will take you through using the Comapi Android Chat SDK

This SDK wraps the App Messaging Foundation SDK and provides additional functionalities that will make creating a chat app a lot easier.

It will automatically

  • manage conversations
  • manage socket reconnections
  • synchronise conversation data
  • manage sending attachments with messages and update message data with urls for the uploaded content
  • timely update persistence store when sending messages and filter out duplicates
  • mark messages as delivered
    and also
  • Replace message&events queries with simple paging API
Suggest Edits

Installing the SDK

 
compile 'com.comapi:chat:1.0.1'
<dependency>
    <groupId>com.comapi</groupId>
    <artifactId>chat</artifactId>
    <version>1.0.1</version>
</dependency>
Suggest Edits

Initialise

 

To initialise the SDK, you will need a few pre-requisites.

  • A configured API Space

  • authentication provider that can generate a JWT that matches the auth scheme configured for your API Space.

  • Implementation of the ChatStore. This implementation should perform db inserts and updates in a single transaction. Set a factory class for ChatStore objects in configuration object.

ChatConfig config = new ChatConfig()
   .apiSpaceId("<API_SPACE_ID>")
   .authenticator(authenticator)
   .store(storeFactory);

authenticator needs to extend ComapiAuthenticator class

authenticator = new ComapiAuthenticator() {
    @Override
    public void onAuthenticationChallenge(AuthClient authClient, 
       ChallengeOptions options) {
          // get token using options.getNonce()
          authClient.authenticateWithToken(token));
    }
}

store factory needs to extend StoreFactory<ChatStore> class

store = new StoreFactory<ChatStore>() {
      @Override
      protected void build(StoreCallback<ChatStore> callback) {
        // create ChatStore implementation that suppeort single tranasaction mechanism
        callback.created(store);
      }
   }

to initialise SDK and obtain client instance as a result (you will be responsible for storing the client instance in the app)

ComapiChat.initialise(application, chatConfig, new Callback<ComapiChatClient>(){/* implement */});

if you would like the SDK to store a static instance for you use below initialise method instead

ComapiChat.initialiseShared(application, chatConfig, new Callback<ComapiChatClient>(){/* implement */});

// to get the client instance somwhere else
ComapiChat.getShared();

if you prefer reactive APIs then you can use one of these

ComapiChat.initialise(application, chatConfig).subscribe(new Action1<ComapiChatClient>(){/* implement */}, 
      new Action1<Throwable>(){/* implement */});

or

 ComapiChat.initialiseShared(application, chatConfig)
 .subscribe(new Action1<ComapiChatClient>(){/* implement */}, 
      new Action1<Throwable>(){/* implement */});
Suggest Edits

Chat Store

 

Messaging events and service responses will be redirected to a store interface implementation inside your app that you register on SDK initialisation.

First step is to provide StoreFactory<ChatStore> instance when initialising the SDK. This should return an instance of ChatStore class with implementation of its abstract methods.

// Sets persistence store factory. 
//ChatStore instance will queue db changes and perform them when {@link ChatStore#endTransaction} has been called internally.
chatConfig.store(new StoreFactory<ChatStore>() {
	@Override
	protected void build(StoreCallback<ChatStore> callback) {
		callback.created(new ChatStoreImplementation());
	}
});

// Initialise SDK in onCreate method in Application class.
ComapiChat.initialise(app, chatConfig, callback);
// Implementation of chat store class that provides a way to store data relevant both to SDK and the app.
public class ChatStoreImplementation extends ChatStore {
  // implementation off all abstract methods used to modify you db by Comapi Chat SDK.
}

This implementation should queue changes in a single pending transaction and perform the db inserts/update when 'endTransaction' method is called. The underlying data should be synchronised since the transactions can be performed in different threads.

SDK will internally call below methods to start queuing changes and to tell the store to apply the changes.

// begin transaction, from this point calling other methods of this class will queue store updates and inserts to be executed when {@link ChatStore#endTransaction()} on same instance is called
beginTransaction();

// end transaction, execute queued store updates and inserts
endTransaction();

Below abstract methods that need to be implemented will be used by the SDK to keep the local chats data inside your database consistent with the server.

// get conversation
getConversation(String conversationId);

// get all conversations
getAllConversations();

// insert or update conversation in persistence store
upsert(ChatConversation conversation);

// update conversation
update(ChatConversationBase conversation);

// delete conversation
deleteConversation(String conversationId);

// insert or update message
upsert(ChatMessage message);

// update stored {@link ChatMessage} with a new {@link ChatMessageStatus}. The chat message is unique for a combination of messageId, profileId and {@link LocalMessageStatus} value
update(ChatMessageStatus status);

// delete all messages from persistence store that are related to given conversation
deleteAllMessages(String conversationId);

// delete message
deleteMessage(String conversationId, String messageId);

// delete all content of persistence store
clearDatabase();

The upsert and update methods can provide more informations than are required by get methods (this additional data can be safely ignored).

In-Memory ChatStore implementation sample

To implement in memory store we need to create some mechanism of updating the data in a single transaction. The operations triggered by the SDK to update db between the calls 'beginTransaction()' and 'endTransaction()' should be queued and executed after the endTransaction().

The example app can be found on Github.

  1. Lets create 'Transaction' object that can queue executable Tasks and performs them in an order after execute(data) method is called. You can find the example implementation here - Transaction.

  2. The data itself modified with the in Transaction class can be stored in couple maps with the keys being object ids for faster queries. The example implementation can be found here - ChatStoreData.

  3. The last thing is to extend ChatStore class from Comapi Chat SDK and override all it's methods listed in above paragraph. Update/delete/upsert methods should queue the changes stored in Transaction object and when SDK calls 'endTransaction()' method the ChatStoreImplementation will change the data state.

Now on initialisation of 'CoampiChat' in application class you need to set a factory of above ChatStoreImplementation classes link.

Firebase ChatStore implementation

Let's use some database-management system like Realm to store our chat data coming from Comapi Chat SDK.

This time the ChatStore implementation will be simpler since Realm will handle the transactions for us.

The example app can be found on Github.

  1. To work it will require Realm initialisation in Application class and Realm model classes for conversations, messages and message statuses - link.

  2. Then the ChatStoreImplementation will control Realm db update through a single Realm transaction.

  3. Now on initialisation of Comapi Chat in application class you need to set a factory of above ChatStoreImplementation.

  4. The Realm db adapters for the app UI components in this examples will be notified by Realm about any changes made by the SDK to conversations and/or messages.

Suggest Edits

Start session

 

Initialisation through ComapiChat class gives you access to client object used for any further communication with the SDK. See Initialise for details.

In order to communicate with Comapi services you need to start session in SDK. This is needed only once per user log in, after that SDK will be re-authenticating session until you end session.

Start

To register or log in user to Comapi services use

client.service().session().startSession(new Callback<Session>(){/* implement */});
rxClient.service().session().startSession()
   .subscribe(new Action1<Session>(){/* implement */}, 
      new Action1<Throwable>(){/* implement */});

This will ask ComapiAuthenticator (provided in ChatConfig) for a JWT token then SDK will create session server side for profile id obtained from the token. Any subsequent call to services will use same authentication details.

Stop

To close currently opened session use

client.service().session().endSession(
   new Callback<ComapiResult<Void>>(){/* implement */});

or

rxClient.service().session().endSession()
   .subscribe(new Action1<ComapiResult<Void>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});
Suggest Edits

Client APIs

 

You can use Comapi client instance obtained in Initialisation to access SDK APIs.

session

Session session = client.getSession().getSession();
// check if session is successfully started
session.isSuccessfullyCreated();

// user profile id for which the session was opened
session.getProfileId();

messaging

Messaging (conversations and messages) can be accessed through

// messaging related service calls
client.service().messaging();

or

// messaging related service calls
client.rxService().messaging();

messaging data

Messages and Conversations API calls in messaging service will manage persistence store updates internally. Participants are NOT persisted and need to be managed by the app if needed.

profiles

Profiles related APIs can be accessed through

// user profile related service calls
client.service().profile();

or

// user profile related service calls
client.rxService().profile();

profiles data

Profiles are NOT persisted and need to be managed by the app if needed.

Listeners

Profile, participants and typing events are not managed with store implementation and the app should register appropriate listeners for relevant events.

/* add events listener:
 * ProfileListener - changes to user profile details
 * ParticipantsListener - changes to participant list in conversation
 * TypingListener - user started/stopped typing in a conversation
 */
addListener(listener);

// remove events listener
removeListener(listener);
Suggest Edits

Listen to events

 

Profile, participants and typing events are not managed with store implementation and the app should register appropriate listeners for relevant events.

You can register listeners from client instance

/* add events listener:
 * ProfileListener - changes to user profile details
 * ParticipantsListener - changes to participant list in conversation
 * TypingListener - user started/stopped typing in a conversation
 */
client.addListener(listener);

// remove events listener
client.removeListener(listener);

or set them up in a configuration when initialising

ComapiConfig config = new ComapiConfig()
   .participantsListener(listener /* extends ParticipantsListener */)
   .profileListener(listener /* extends ProfileListener */)
   .typingListener(listener /* extends TypingListeners */);

Listeners classes are abstract with empty implementation of onEventName methods. Override some or all of them to receive socket events from foundation SDK. Real time events are available only when the application is in foreground (visible to the user). If app is put to the background use FCM to communicate messages to the user. Update the internal state after app is put back to foreground using services APIs.

Available events

Event Name
Description

ProfileUpdateEvent

Sent when a user's profile is updated.

ParticipantAddedEvent

Sent when a participant is added to a conversation. When a conversation is created, this event will also fire with the owner's profileId.

ParticipantUpdatedEvent

Sent when a participant role is updated in a conversation.

ParticipantRemovedEvent

Sent when a participant is removed from a conversation.

ParticipantTypingEvent

Sent when 'isTyping' method has been called informing conversation participants that the user started typing a new message

ParticipantTypingOffEvent

Sent when 'isTyping' method has been called informing conversation participants that the user stopped typing a new message

// event unique identifier
getEventId()

// event name/type
getName()

And the specific event types provides also

// profile unique identifier
getProfileId();

// time when the update event was published
getPublishedOn();

// revision of the profile details on the server
getRevision();

// gets profileId of an user that performed this update
getCreatedBy();

// gets profile update details
getPayload();

// tag specifying server data version
getETag();
// conversation unique identifier
getConversationId();

// profile unique identifier
getProfileId();

// role definition for this participant in this conversation
getRole();
// Same as ParticipantAddedEvent
// Same as ParticipantAddedEvent
// conversation unique identifier
getConversationId();

// get profile id of an participant who started typing a new message
getProfileId();
// conversation unique identifier
getConversationId();

// get profile id of an participant who stopped typing a new message
getProfileId();
Suggest Edits

Messaging service

 

You can choose to access two method versions - with callbacks and reactive.

// methods will accept callbacks to deliver result, the request will be performed in the background delivering result in UI thread.
service = client.service().messaging();
// methods will return observables you need to subscribe to
service = client.rxService().messaging();

messaging service API

create conversation

ConversationCreate request = ConversationCreate.builder()
   // Unique conversation identifier.
   .setId("1234")
   // Description
   .setDescription("This is my first conversation")
   // Name 
   .setName("Awesome chat")
   // Is this conversation visible to users not being participants.
   .setPublic(false)
   /* Sets what permissions 'owner' and 'participant' will have in this conversation
      You can set if they will can: add new participants, remove participants, send messages.
      By default 'owner' and 'participant' can send messages, can add participants, cannot remove participants.
      Here we added CanRemoveParticipants permission for the 'owner'. */
   .setRoles(new Roles(Role.builder().setCanRemoveParticipants().build(), new Role()))
   .build();

Then pass this object to Comapi services

service.createConversation(request,
   new Callback<ComapiResult<ChatResult>>(){/* implement */});

or

rxService.createConversation(conversation)
   .subscribe(new Action1<ComapiResult<ChatResult>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

update conversation

ConversationUpdate update = ConversationUpdate.builder()
                .setDescription("New description")
                .setName("Different name")
                .setRoles(roles)
                .setPublic(false)
                .build();

Then pass this object to Comapi services

service.updateConversation(conversationId, update, eTag,
   new Callback<ComapiResult<ChatResult>>(){/* implement */});

or

rxService.updateConversation(conversationId, update, eTag)
   .subscribe(new Action1<ComapiResult<ChatResult>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

delete conversation

service.deleteConversation(conversationId, eTag,
   new Callback<ComapiResult<Void>>(){/* implement */});

or

rxService.deleteConversation(conversationId, eTag)
   .subscribe(new Action1<ComapiResult<Void>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

synchronise store

The SDK will automatically synchronise local app data with the server when the app becomes foregrounded or socket reconnected. However we recommend to call below method when screen with list of conversations becomes visible or when user triggers refresh (e.g. by using swipe to refresh widget).

service.synchroniseStore(new Callback<Boolean>(){/* implement */});

or

rxService.synchroniseStore()
   .subscribe(new Action1<Boolean>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

You can also synchronise the local state of a single conversation

service.synchroniseConversation(conversationId, new Callback<Boolean>(){/* implement */});

or

rxService.synchroniseConversation(conversationId)
   .subscribe(new Action1<Boolean>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

conversation participants

Get all participants for particular conversation

 service.getParticipants(conversationId, 
   new Callback<List<ChatParticipant>>(){/* implement */});

or

rxService.getParticipants(conversationId)
   .subscribe(new Action1<List<ChatParticipant>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

add participants to conversation

Add new participants to conversation

service.addParticipants(conversationId, participants,
   new Callback<ChatResult>(){/* implement */});
rxService.addParticipants(conversationId, participants)
   .subscribe(new Action1<ChatResult>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

remove participants from conversation

Provide list of profile ids to be removed from conversation.

service.removeParticipants(conversationId, ids,
   new Callback<ChatResult>(){/* implement */});

or

rxService.removeParticipants(conversationId, ids)
   .subscribe(new Action1<ChatResult>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

send message in conversation

service.sendMessage(conversationId, body,
   new Callback<ChatResult>(){/* implement */});
rxService.sendMessage(conversationId, body)
   .subscribe(new Action1<ChatResult>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

you can also send messages with more parts, FCM/APNS push notifications, custom metadata:

Map<String, Object> data = new HashMap<>();
data.put("key","value");

Map<String, Object> fcm = new HashMap<>();
fcm.put("data", data);
fcm.put("notification", "{ \"title\":\"Message\", \"body\":\"Hi!\" } ");

Map<String, Object> apns = new HashMap<>();
apns.put("alert", "Hi!");

Part part = Part.builder()
               .setData("Hi")
               .setName("body")
               .setSize("Hi".length())
               .setType("text/plain")
               .build();

MessageToSend message = MessageToSend.builder()
   .setAlert(fcm, apns)
   .setMetadata(data)
   .addPart(part)
   .build();

Then call

service.sendMessage(conversationId, message, 
   new Callback<ComapiResult<MessageSentResponse>>(){/* implement */});

or

rxService.sendMessage(conversationId, message)
   .subscribe(new Action1<ComapiResult<MessageSentResponse>>(){/* implement */}, 
      new Action1<Throwable>(){/* implement */});

Sending message with attachments

service.sendMessage(conversationId, message, attachents,
   new Callback<ComapiResult<MessageSentResponse>>(){/* implement */});
rxService.sendMessage(conversationId, message, attachemnts)
   .subscribe(new Action1<ComapiResult<MessageSentResponse>>(){/* implement */}, 
      new Action1<Throwable>(){/* implement */});

where attachments can be created by one of the static constructors

Attachment.create(file, type, folder, name);
Attachment.create(byteArray, type, folder, name);
Attachment.create(base64string, type, folder, name);

update message status

You can mark the message as being read by the user, this information will be send to all participants of the coversation

service.markMessagesAsRead(conversationId, messageIds, 
   new Callback<ChatResult>(){/* implement */});

or

rxService.markMessagesAsRead(conversationId, messageIds)
   .subscribe(new Action1<ChatResult>(){/* implement */}, 
      new Action1<Throwable>(){/* implement */});

get message page

Chat SDK implents a logic to obtain the next page of messages stoarting from the latest messages and going backwards

service.getPreviousMessages(conversationId,
   new Callback<ChatResult>(){/* implement */});
rxService..getPreviousMessages(conversationId)
   .subscribe(new Action1<ChatResult>(){
      /* implement */},
      new Action1<Throwable>(){/* implement */});

send user is typing event

To send an information to conversation participants that the user started or finished typing a new message call

service.isTyping(conversationId, isTyping, 
   new Callback<ComapiResult<Void>>(){/* implement */});
rxService.isTyping(conversationId, isTyping)
   .subscribe(new Action1<ComapiResult<Void>>(){/* implement */},
              new Action1<Throwable>(){/* implement */});
Suggest Edits

Profile service

 
// methods will accept callbacks to deliver result, the request will be performed in the background delivering result in UI thread.
service = client.service().profile();
// methods will return observables you need to subscribe to
service = client.rxService().profile();

Reactive version returns Observables you need to subscribe to. For callback version pass callback as the last parameter and the request will be performed in the background delivering result in UI thread.

data consistency

In order to manage concurrent updates of an profile from many devices/websites use ETag. When obtaining profile details from COMAPI services you can find an ETag in ComapiResult object. It describes what version of server data you got. When you want to update this data pass the same ETag with the new profile details. If the server data will change before your next update the services will not allow such modification until you obtain the recent version of the data and pass correct ETag with the next update. This way you can keep consistency of the data across many devices.

profile details

Get profile details of an user registered in the same API Space as the SDK

service.getProfile(profileId,
   new Callback<ComapiResult<Map<String, Object>>>(){/* implement */});
rxService.getProfile(profileId)
   .subscribe(new Action1<ComapiResult<Map<String, Object>>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

Result contains containing a string-object map of properties associated with a given user profile.

query profiles

Query profiles registered on API Space

service.queryProfiles(queryString,
   new Callback<ComapiResult<List<Map<String, Object>>>>(){/* implement */});
rxService.queryProfiles(queryString)
   .subscribe(new Action1<ComapiResult<List<Map<String, Object>>>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

In result you will get a string-object map with a list of profile properties, each associated with a user profile conforming to provided query.

See query syntax. You can use QueryBuilder helper class to construct valid query string.

e.g.

String queryString = new QueryBuilder().addExists("email").build();

and queryProfiles will look for all profiles containing a field named 'email'.

update profile

Update your user profile providing custom map of values that should be associated with it.

service.updateProfile(profileDetailsMap, eTag,
   new Callback<ComapiResult<List<Map<String, Object>>>>(){/* implement */});
rxService.updateProfile(profileDetailsMap, eTag)
   .subscribe(new Action1<ComapiResult<List<Map<String, Object>>>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});

result contains updated details.

patch profile

Applies patch for an user profile. This method won't erase previous profile details with different keys.

service.patchProfile(profileId, profileDetailsMap, eTag,
   new Callback<ComapiResult<List<Map<String, Object>>>>(){/* implement */});
rxService.patchProfile(profileId, profileDetailsMap, eTag)
   .subscribe(new Action1<ComapiResult<List<Map<String, Object>>>>(){/* implement */},
      new Action1<Throwable>(){/* implement */});
Suggest Edits

JavaScript Chat

 

The following sections will take you through using the Comapi Javascript Chat SDK

This SDK wraps App Messaging Foundation SDK and provides additional functionalities that will make crating a chat app a lot easier.

It will automatically

  • manage conversations
  • synchronise conversation data
  • mark messages as delivered
Suggest Edits

Install the Chat SDK

 

Comapi SDK can be installed from either NPM or Bower depending on your intended usage.

If you are integration into a classical javascript project and you just want to include a script that exposes some global objects in your page, then use Bower.

If you are using a project that utilises ES6 modules i.e angular2, ionic2 etc., then use NPM.

NPM

Install SDK ...

npm install @comapi/sdk-js-chat --save

Import into your code and access SDK methods ...

import { ComapiChatClient } from "@comapi/sdk-js-chat"

class MyClass{
    public displayVersion(){
        console.log(`Comapi version: ${ComapiChatClient.version}`);
    }
}

Bower

Install package from bower ...

bower install comapi-sdk-js-chat

Include the script somewhere ...

<script src="bower_components/comapi-sdk-js-chat/dist/comapi-chat-client.js"></script>

There is also a minified version comapi-chat-client.min.js available.

For all subsequent classical snippets, I will assume that this script has been included

Access SDK methods ...

console.log("Comapi version: " + COMAPI_CHAT.ComapiChatClient.version);

Use of ES6 Promises

ES6 Promises are extensively used within this SDK. Depending on what browsers you are targeting, you may need to include a poly-fill for this. Several of these are available online.

Suggest Edits

Initialise the Chat SDK

 

To initialise the sdk, you need to setup a ComapiChatConfig object create an instance of the sdk and then call the initialise method passing in the config object.

ES6

import { IAuthChallengeOptions, LogPersistences, OrphanedEventPersistences } from '@comapi/sdk-js-foundation';
import { ComapiChatClient, ComapiChatConfig } from '@comapi/sdk-js-chat';

// We will discuss this in it's own section
import { ConversationStore } from '???';

export class ComapiService {

    private chatClient: ComapiChatClient;
    private comapiConfig: ComapiChatConfig;

    constructor(private _authService: AuthService, private _conversationStore ConversationStore) { 

        // create / initialise ComapiChatConfig
        this.comapiConfig = new ComapiChatConfig()
            .withStore(_conversationStore)
            .withApiSpace(">> YOUR APP SPACE ID <<<")
            .withAuthChallenge(this.authChallenge.bind(this));
    }

    /**
     * Auth Challenge.
     */ 
    private authChallenge(options: IAuthChallengeOptions, answerAuthenticationChallenge) {
        this._authService.getToken(options.nonce)
            .then((token) => {
                answerAuthenticationChallenge(token);
            });
    }


    public initialise(): Promise<boolean> {
        if (this.chatClient) {
            return Promise.resolve(false);
        } else {
            this.chatClient = new ComapiChatClient();
            return this.chatClient.initialise(this.comapiConfig);
        }
    }

    public uninitialise(): Promise<boolean> {
        return this.chatClient.uninitialise()
            .then(function () {
                this.chatClient = undefined;
                return true;
            });
    }

}

Advanced options

The above examples initialised the SDK with minimal configuration. You can customise the sdk behaviour with the following optional settings.

You can ue the following chainable methods to further configure the sdk:

withEventPageSize(eventPageSize: number)

This parameter defaults to 10

If a gap in the conversation messages is detected, the sdk will fill this up by querying the missing events in pages until the gap is filled. This parameter represents the page size.

withMessagePageSize(messagePageSize: number)

This parameter defaults to 10

withLazyLoadThreshold(lazyLoadThreshold: number)

This parameter defaults to 1

withMaxEventGap(maxEventGap: number)

This parameter defaults to 100

withAutoSynchronize(autoSynchronize: boolean)

This parameter defaults to true

Authentication

JWT

The Comapi Auth Challenge needs to generate and return a jwt via the answerAuthenticationChallenge method.

There are 4 pieces fo data that need to be specified in the Comapi portal for the ApiSpace auth settings

1) Issuer
2) Audience
3) Shared Secret
4) ID Claim

A cryptographic nonce is used as part of the authentication flow. This is passed into the authChallenge (options.nonce) and needs to be added as a claim in the generated jwt.

The below sample uses jsrsasign to dynamically create a client side jwt ...

function authChallenge (options, answerAuthenticationChallenge) {
    // Header
    var oHeader = { alg: 'HS256', typ: 'JWT' };
    // Payload
    var tNow = KJUR.jws.IntDate.get('now');
    var tEnd = KJUR.jws.IntDate.get('now + 1day');
    var oPayload = {
        sub: "john smith",
        nonce: options.nonce,
        iss: "https://my-issuer.com",
        aud: "https://my-audience.com",
        iat: tNow,
        exp: tEnd,
    };
    var sHeader = JSON.stringify(oHeader);
    var sPayload = JSON.stringify(oPayload);
    var sJWT = KJUR.jws.JWS.sign("HS256", sHeader, sPayload, "my shared secret");
    answerAuthenticationChallenge(sJWT);
}

This node express method uses the njwt package. and achieves the same as above but server - side

/**
 * @Params {string} req.body.username
 * @Params {string} req.body.password
 * @Params {string} req.body.nonce
 */
app.post('/authenticate', function (req, res, next) {

    // TODO: authenticate username & password ...

    var claims = {
        iss: "https://my-issuer.com",
        sub: req.body.username,
        nonce: req.body.nonce,
        aud: "https://my-audience.com"
    }

    var jwt = njwt.create(claims, "my shared secret");
    var token = jwt.compact();
    res.json({ jwt: token });
});

The following auth challenge could be used in conjunction with the above node endpoint ..

function authChallenge (options, answerAuthenticationChallenge) {

    $http.post("/authenticate", { 
            username: "johnSmith" 
            password: "Passw0rd!",
            nonce: options.nonce })
        .then(function (response) {
            answerAuthenticationChallenge(response.data.token);
        })
        .catch(function (error) {
            answerAuthenticationChallenge(null);
        });
}
Suggest Edits

Chat Conversation Store

 

The chat interface delegates responsibility of storing chat related data to a conversation store which is implemented by the integrator (we supply some reference implementations). When new messages arrive, new conversations are created, messages are marked as read, the sdk will update the conversation store via the following interface:

/**
 * 
 */
export interface IConversationStore {

    getConversations(): Promise<IChatConversation[]>;

    getConversation(conversationId: string): Promise<IChatConversation>;
    createConversation(conversation: IChatConversation): Promise<boolean>;
    updateConversation(conversation: IChatConversation): Promise<boolean>;
    deleteConversation(conversationId: string): Promise<boolean>;
    deleteConversationMessages(conversationId: string): Promise<boolean>;

    reset(): Promise<boolean>;
    // sdk calls this to see whether it needs to update / add the new message 
    getMessage(conversationId: string, messageId: string): Promise<IChatMessage>;
    // read / delivered info has been added, hand back to client to store ...
    updateMessageStatus(conversationId: string, messageId: string, profileId: string, status: string, timestamp: string): Promise<boolean>;
    // getMessageStatus(conversationId: string, messageId: string): Promise<boolean>;

    // new message added 
    createMessage(message: IChatMessage): Promise<boolean>;

    getMessages(conversationId: string): Promise<IChatMessage[]>;
}

We have implementations for in-memory and indexedDb stores that can be used. You could use these directly or use the source as a starting point to implement your own. The conversation stores are passed to the chat sdk during initialisation.

Here is an example of using the built-in memory store ...

ES6 Syntax

Note the imports

import { ComapiChatClient, ComapiChatConfig, MemoryConversationStore } from '@comapi/sdk-js-chat';

let store = new MemoryConversationStore()

let comapiConfig = new ComapiChatConfig()    
    .withStore(store)
    .withApiSpace(">>> Your API SPACE ID <<<")
    .withAuthChallenge(this.authChallenge.bind(this));

let chatClient = new ComapiChatClient();

chatClient.initialise(comapiConfig)
    .then(succeeded => {
        console.log("initialised!");
    });

Traditional Syntax

Note the use of the global COMAPI_CHAT object.

var store = new COMAPI_CHAT.MemoryConversationStore();

var comapiConfig = new COMAPI_CHAT.ComapiChatConfig()    
    .withStore(store)
    .withApiSpace(">>> Your API SPACE ID <<<")
    .withAuthChallenge(authChallenge);

var chatClient = new COMAPI_CHAT.ComapiChatClient()

chatClient.initialise(comapiConfig)
    .then(function(succeeded){
        console.log("initialised!");
    });

Implement your own

You may want to implement your own interface if you require to perform any custom action when new messages are delivered etc. The Angular 1.x based sample app implements its own store. The reason being that it uses $q instead of native Promises so that Angular's change detection seamlessly works. The angular 4 sample just uses the stock interface as Angular4 internally uses zone.js to monkey patch all async operations.

Both of these 2 angular samples rely on Angular's binding mechanism to keep the views up to date. If you are not using a framework that supports this kind of binding, you will need to hook into some of these store methods. You could publish an event that your view code could consume and subsequently redraw the view / append the new data.

Asynchronous methods

All of the store interface methods are asynchronous and return promises. The reason being that all of the native persistence interfaces supported by the browser are asynchronous.

Please note that if you implement your own interface, you run the risk of breaking the sdk if your code throws an exception or fails to resolve a promise.

Reference implementations

There are 2 reference implementations of stores available. You can use these as a starting point to a custom implementation

MemoryConversationStore
IndexedDBConversationStore

Suggest Edits

Chat Client APIs

 

The following interfaces are available from the chat client:
Please refer to the foundatin documentation for further details of these inherited interfaces.

Session

This interface is simply the foundation session service exposed.

chatClient.session.startSession()
    .then(function(session){
        console.log("Got a session");
    });

Profile

This interface is simply the foundation profile service exposed.

chatClient.profile.getMyProfile()
    .then(function(profile){
        console.log("Got my profile");
    });

Messaging

This is the chat messaging interface. It is documented in more detail here

Device

This interface is simply the foundation device service exposed.

this.chatClient.device.setAPNSPushDetails("my.bundle.id", Environment.development, ">> my push token <<")
    .then(function(succeeded){
        console.log("done");
    });

Channels

This interface is simply the foundation channels service exposed.

Foundation

This interface is simply the foundation interface exposed.

Suggest Edits

Chat Client Events

 

All events from the foundation sdk can be listened out for (although most are internally handled)
Here is an example of listening for a profile Updated event ...

Subscribe to an event

sdk.on("profileUpdated", function (event) {
    console.log("profileUpdated", event);
});

Unsubscribe from an event

sdk.off("profileUpdated");
Suggest Edits

Chat Client Messaging Service

 

This service provides the main functionality of the chat sdk. Here is a list of functions along with a description.

synchronize

This method synchronises the conversation store. It does this in the following manner:

Obtains a list of conversations that this user is a participant in from comapi backend.

Iterate over list of conversations comparing what is stored locally in conversation store. Local conversations are added / updated as necessary.

There is a concept of lazy loading with a configurable threshold - use the withLazyLoadThreshold() method on the ComapiChatConfig interface to specify the desired value. The sdk will order all local conversations by date and only synchronise the first n conversations specified by the lazyLoadThreshold.

If a conversation that doesn't get synchronised subsequently receives an event, (i.e. a new message) It will get synchronised at that point. It will also get synchronised with a call to getConversationInfo() - see below

getPreviousMessages

The sdk will only retrieve the last page of messages for a conversation when it synchronises. To implement an infinite scroll style interface you call this method. It will load another page of historic messages.

The page size is configurable - use the withMessagePageSize() method on the ComapiChatConfig interface to specify the desired value.

The method returns a boolean result via a promise. If you are at the beginning of the conversation (i.e you have all the messages) and you call this method the boolean result will be false.

getConversations

Method to return a list of all the conversations a user is part of.

chatClient.messaging.getConversations(conversationId)
    .then(conversations => {
        console.log("got conversations", conversations);
    });

getConversationInfo

Method to get conversation info to render a detail view.
It will return an object containing messages, conversation info and participant info.
The conversation will be synchronised if necessary during this call.

chatClient.messaging.getConversationInfo(conversationId)
    .then(info => {
        console.log("got info", info);
    });

an IChatConversationInfo object is returned via a Promise

export interface IChatConversationInfo {
    conversation: IChatConversation;
    messages?: IChatMessage[];
    participants?: IConversationParticipant[];
}

sendTextMessage

Method to send a simple text message to a conversation

chatClient.messaging.sendTextMessage("Hello world")
    .then(result => {
        console.log("sent!");
    });

sendMessage

Method to send a message to a conversation. This message needs to be created with the MessageBuilder helper object. THis gives you full control of the message and its constituent parts.

import { MessageBuilder } from '@comapi/sdk-js-chat';

let message = new MessageBuilder().withText("Hello world");

chatClient.messaging.sendMessage("your conversation id", message)
    .then(result => {
        console.log("sent!");
    });
var message = new COMAPI_CHAT.MessageBuilder().withText("Hello world");

chatClient.messaging.sendMessage("your conversation id", message)
    .then(function(result){
        console.log("sent!");
    });

See foundation sdk for more details of MessageBuilder

sendAttachment

Method to create an attachment via the content api, create a message part(s) using the attachment url and the optional message and send it.

var contentData = COMAPI_CHAT.ContentData.createFromFile(attachment);

chatClient.messaging.sendAttachment(conversationId, contentData)
    .then(function (result) {
        console.log("sent!");        
    });