Messages

Message objects represent individual messages. They belong to a conversation and consist of one or more pieces of content, known as Message Parts.

Push notification previews and sounds are set when you create each message. Each message keeps track of its delivery and read state for every participant in a conversation.

Creating a message

Message objects are created by calling conversation.createMessage() with either a string (for textual messages) or an object containing an array of MessagePart objects (see below):

// Short form:
var message = conversation.createMessage('Hi! How are you');

// Long form:
var message = conversation.createMessage({
    parts: [{
        body: 'Hi! How are you',
        mimeType: 'text/plain'
    }]
});

Message Parts

A message is comprised of one or more parts. You may use a multi-part message to send a photo along with a description, for example, or to send a message with a location.

You can send any type of data through Layer. To support this, Message Parts consist of a body string for the value and a MIME type string for the type. Each Message Part can be up to 2GB. The MIME type string simply describes the type of content.

Note

Message Parts that are 2KB or smaller are sent inline (the content is sent with the Message Part instance). Larger Message Parts have their content uploaded to dedicated storage using the Rich Content APIs.

// Creates a message part with a string of "Hi!..." and text/plain MIME type.
var messagePart = new Layer.Core.MessagePart({
    mimeType: 'text/plain',
    body: 'Hi! How are you'
});

// Creates a message part with an image;
// File object represents an object typically received via drag and drop
// or a file upload button (javascript File object is a subclass of Blob)
var imagePart = new Layer.Core.MessagePart({
    mimeType: 'image/jpeg',
    body: file
});

message.addPart(messagePart);
message.addPart(imagePart);

Your app can support custom MIME types. The following demonstrates sending location data:

// Creates an object with latitude and longitude
var location = JSON.stringify({
    lat: 25.43567,
    lon: 123.54383
});
var part = new Layer.Core.MessagePart({
    mimeType: 'text/location',
    body: location
});

message.addPart(part);

A messages:sent event is triggered when it has been received by the server:

message.on('messages:sent', function() {
  myToast('Your message has been sent');
});

However, as a Message may be sent while offline, it could take seconds or hours before that event triggers.

MessagePart access is implemented as a Messagepart property, and is accessed as a javascript Set instance. As a Set, it provides a forEach() method for accessing each part, but provides no index based access. Message provides the following methods for simplifying access to the parts:

  • MessagefindPart(): Returns a single matching Part: message.findPart(part => part.mimeType === 'frodo/dodo')
  • MessagefilterParts(): Returns an array of matching Part: message.filterParts(part => part.mimeType === 'frodo/dodo')
  • MessagemapParts(): Runs Array.map across the collection of Parts: message.mapParts(part => part.mimeType === 'frodo/dodo')
  • MessagegetRootPart(): Returns the most significant Message Part: var rootPart = message.getRootPart()
  • MessagefilterPartsByMimeType(): Returns all Message Parts with the specified MIME Type

Note

While Layer does not place any restrictions on the MIME Type, Google and Apple dictate that the MIME Type string MUST conform to a */* convention. If the MIME Type does not contain a forward slash (/) you may have issues sending messages. For a comprehensive list of MIME Type values check out the IANA’s official registry of media types.

Message Content Updates

The Layer XDK is capable of receiving message content updates issued through the SAPI calls where content can be added, updated or removed. All these operations are seen as direct mutations on the Message and Message Part instances, but can also be captured as object changes or through the query controller (where available).

message.on('messages:change', function(evt) {
  const changes = evt.getChangesFor('parts');
  changes.forEach(function(change) {
    console.log(`${change.property} has changed from ${change.oldValue} to ${change.newValue}`);
  });
});

message.on('messages:part-added', function(evt) {
  console.log("Part added: ", evt.part);
});

message.on('messages:part-removed', function(evt) {
  console.log("Part removed: ", evt.part);
});

Sending a message

// Basic send:
message.send();

// Send with notifications:
message.send({
    title: 'New Message from Ping',
    text: 'Your device is now a machine that goes Ping',
    sound: 'ping.aiff'
});

The send() method takes an optional notification parameter that contains text, sound and title properties. These represent the message to be shown in a notification, as well as the sound to be played on the receiving device (only applies to mobile clients using our iOS or Android SDK).

Note

When sending Channel Messages, notification parameters are ignored; these are for Conversation Messages only.

Confirming delivery

The simplest way to confirm that a message has been delivered is to visit the Logs page of the Developer Dashboard. If a message was successfully sent, you’ll see a log like this:

May 02 2015 2:34:27pm Sync: User <USER_IDENTIFIER> created a message in conversation <CONVERSATION_IDENTIFIER>.

To view programatic reports of delivery confirmations:

var message = conversation.createMessage('Can you see me now?').send();

// Subscribe to changes in the Message's properties
message.on('messages:change', function(evt) {
    var changes = evt.getChangesFor('recipientStatus');
    var count = 0;

    // Typically there is only one change, but there may be more
    changes.forEach(function(change) {

        // Each change contains a before and after version of
        // the recipientStatus property, containing the status
        // of every participant.
        var oldRecipientStatuses = change.oldValue;
        var newRecipientStatuses = change.newValue;
        Object.keys(oldRecipientStatuses).forEach(function(userId) {
            // Any participant who was "sent" and is no longer "sent"
            // has either received OR received and read the message.
            if (oldRecipientStatuses[userId] === layer.Constants.RECEIPT_STATE.sent &&
                oldRecipientStatuses[userId] != newRecipientStatuses[userId]) {
                count++;
            }
        });
    });
    alert(count + ' more participants have received this message');
});

Note that the above code may be triggered multiple times as not all participants will receive the message at the same time

Receiving messages

The Layer SDK automatically receives incoming messages — you don’t need to force a sync or set up a polling system.

Messages are typically delivered to your Application via Queries. Typically your main interest in these received Messages is how to render them.

Rendering the Sender

The Message sender property is an object, containing the following properties (some require configuration before you can receive them):

  • userId: ID for the user as represented on your system
  • displayName: If you have embedded a Display Name in your Identity Tokens, or uploaded a display name for your users into Layer’s Servers, then this value of sender will be populated.
  • avatarUrl: If you have embedded an Avatar URL in your Identity Tokens, or uploaded URLs for your users into Layer’s Servers, then this value of sender will be populated.
function renderSender(message) {
  return `<div class='sender'><img src='${message.sender.avatarUrl}'>${message.sender.displayName}</div>`;
}

Working with the Message Parts

A Message can have multiple Message Parts, each with its own MIME Type. Message Parts typically form a collection of constructs that can be merged together into a useful whole. For example, an Image might consist of one Message part with the image data, and a second Message Part with EXIF data describing the image. A third Message Part might contain a caption and story about that Image. Together they form a single Message. The Layer XDK provides a system for tieing these Message Parts together into Message Type Models. These Models then have Views designed to render their contents without the View needing to parse and understand what a Message Part is.

Note

The part.body will either be a Blob or a string. Which type will be used is controlled by the static property layer.Core.MessagePart.TextualMimeTypes, which is a static array of Strings and Regular Expressions to compare a MIME Type against. Default value is:

MessagePart.TextualMimeTypes = [/^text\/.+$/, /^application\/json(\+.+)?$/, /\+json$/, /-json$/];

If a mimeType matches these regular expressions, its body will be converted as needed to a String, else it will always converted to a Blob. This behavior can be customized for your application:

// Add a single string to the tests; any mimeType exactly matching this will be treated as text rather than Blob:
MessagePart.TextualMimeTypes.push("location/coordinates");

// Add a regular expression to the tests; any mimeType matching this test will be treated as text rather than Blob
MessagePart.TextualMimeTypes.push(/lyrics\/.+/);

Recipient status

Layer keeps track of the recipient status of each message, for each participant in a conversation. Note that messages within channels do not track recipient status; only Conversation rather than Channel messages track this.

There are four possible statuses:

  • layer.Constants.RECEIPT_STATE.sent: The message has successfully reached the Layer service and is waiting to be synchronized with recipient devices.
  • layer.Constants.RECEIPT_STATE.delivered: The message has been successfully delivered to a recipient’s device.
  • layer.Constants.RECEIPT_STATE.read: The message has been marked as read by a recipient’s device (see below).

You can check a specific participant’s status:

// Get the status for a specific participant, given their User ID
var userId = 'userA'
var status = message.recipientStatus[userId];

The Layer Web SDK also provides readStatus and deliveryStatus properties, which provides a summary of each message’s read and delivery status. Each property can be one of the following values:

  • layer.Constants.RECIPIENT_STATE.NONE: The message has not been successfully read/received by participants.
  • layer.Constants.RECIPIENT_STATE.SOME: The message has been successfully read/received by some but not all participants.
  • layer.Constants.RECIPIENT_STATE.ALL: The message has been successfully read/received by all participants.
function renderStatus(message) {
  var description;
  switch (message.readStatus) {
      case layer.Constants.RECIPIENT_STATUS.NONE:
        description = 'unread';
        break;
      case layer.Constants.RECIPIENT_STATUS.SOME:
        description = 'read by some';
        break;
      case layer.Constants.RECIPIENT_STATUS.ALL:
        description = 'read';
        break;
  }
  return '<div class="receipt-status">' + description + '</div>';
}

Layer automatically updates the recipient status to sent, delivered, or invalid. The only update your app can perform is to mark a message as read:

message.isRead = true;

Your app can also mark all the messages in a conversation as read:

conversation.markAllMessagesAsRead();

Note that Messages within Channels do not yet support marking messages as read.

Conversations Announcements