Javascript

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 ...

  1. Setup the Push / App Messaging configuration is Engagement Cloud, see the instructions here for more information on setting up App Messaging.

  2. Your API Space Id for your configuration, see the instructions here for more information

  3. 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 :

  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.

EventDetails
conversationDeletedThe conversation has been deleted
conversationUndeletedThe conversation has been un-deleted
conversationUpdatedThe conversation has been updated (name / description changed)
participantAddedA participant has been added to a conversation
participantRemovedA participant has been removed from a conversation
participantTypingA 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

EventDetails
conversationMessage.sentA message has been sent to the conversation
conversationMessage.deliveredA participant has marked a message as delivered
conversationMessage.readA 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

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

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 NameEvent PayloadDescription
conversationDeletedIConversationDeletedEventDataSent when a conversation is deleted
conversationUndeletedIConversationUndeletedEventDataSent when a conversation is undeleted
conversationUpdatedIConversationUpdatedEventDataSent when a conversation is updated
participantAddedIParticipantAddedEventDataSent when a participant is added to a conversation. When a conversation is created, this event will also fire with the owner's profileId.
participantRemovedIParticipantRemovedEventDataSent 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.
participantTypingIParticipantTypingEventDataSent when a participant is typing in a conversation
profileUpdatedIProfileUpdatedEventSent when a user's profile is updated
conversationMessageEventIConversationMessageEventThis 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.