The following sections takes you through using the App Messaging Foundation Javascript SDK found in Github here:
There is also a repository containing some sample apps here ...
Installing the SDK
The 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(`App messaging SDK 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("App Messaging SDK 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.
Initialise
To initialise the SDK, you will need a few pre-requisites ...
-
Setup the Push / App Messaging configuration is Engagement Cloud, see the instructions here for more information on setting up App Messaging.
-
Your API Space Id for your configuration, see the instructions here for more information
-
An authentication provider that can generate a JWT that matches the authorisation scheme configured for your API Space.
Note: 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";
// SDK 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 the SDK
*/
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
The Auth Challenge function you pass when initializing the SDK needs to generate and return a JWT via the answerAuthenticationChallenge method.
There are two ways of creating a JWT for users, you can:
Use the Session API to create a JWT
This is a convenient way to create an anonymous user JWT, simply call the Session API to generate an anonymous JWT. You can pass a user id, which is unique per user or device, and a nonce to ensure the JWT is the one you requested.
Self Issued JWT
With this option you must create a valid JWT matching the settings you entered into the App Messaging configuration; you have total control of the user id and claims. There are plenty of code examples of how to create JWT online, and a lot of good information and code libraries can be found at the JWT home page.
The JWT
The following parameters are needed when generating a JWT for the SDK and they are:
- Issuer
- Audience
- Shared Secret
- 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.
These parameters can be defined and obtained from within the portal in the Push channel profile settings area. See the Push / App Messaging setup pages for more information.
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, {utf8: "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);
});
}
Start a session
To call onto any of the backend services, a valid session is required. This is what the authChallenge is for. Whenever the SDK 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);
});
Conversations
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 conversationConfig = new ConversationBuilder().withName("Support").withDescription("Support related chat").withUsers(["johnSmith", "joeBloggs"]);
sdk.services.appMessaging.createConversation(conversationConfig)
.then(conversationInfo => {
console.log("conversation created", conversationInfo);
})
.catch(error => {
fail("conversation creation failed: ", error);
});
Classical
var conversationConfig = new COMAPI.ConversationBuilder().withName("Support").withDescription("Support related chat").withUsers(["johnSmith", "joeBloggs"]);
sdk.services.appMessaging.createConversation(conversationConfig)
.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 :
profileId
- the profileId of the user to addrole
- 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 |
Query a list of conversations
This is how you would query for list of ALL conversations that a user is a member of ...
sdk.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.services.appMessaging.getConversation("CA29B56B-30D6-4217-9C99-577AA7525B92")
.then(conversation => {
console.log("getConversation() succeeded", conversations);
})
.catch(error => {
console.log("getConversation() failed", error);
})
Querying Conversation 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 real time 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.
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 |
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[];
}
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
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
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
Websocket 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;
}
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.
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.