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');
var message = channel.createMessage('Hi! How are you');

// Long form:
var message = conversation.createMessage({
    parts: [{
        body: 'Hi! How are you',
        mimeType: 'text/plain'
    }]
});
var message = channel.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.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.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.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.

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.

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):

  • name: If sent by a Bot or service that does not have a userId, the name property will be used to provide a service name that is defined by your app via Layer’s Platform API’s sender.name field.
  • userId: Name 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. Unless the Message comes from a bot or service.
  • 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. Unless the Message comes from a bot or service.userId and name properties:
function renderSender(message) {
  return "<div class='sender'><" + (lookupDisplayNameFor(message.sender.userId) || message.sender.name) + "</div>";
}

Note that the lookupDisplayNameFor() method is something you will frequently need to provide unless your User ID is also a displayable name.

If you have configured your Identity Token to provide a displayName and avatarUrl then you can instead use

function renderSender(message) {
  return "<div class='sender'><img src='" + message.sender.avatarUrl + "'>" +
    message.sender.displayName + "</div>";
}

Rendering the Message Parts

A Message can have multiple Message Parts, each with its own MIME Type. Typically a Message with multiple Message Parts is not simply sending content, each of which is to be independently rendered. For example, if I send a picture an text, perhaps that text simply goes below the image… or perhaps it goes under the image with indentation to indicate that its associated with the image. Perhaps it even hovers over the image. The Message Parts are linked, and completely unlike sending the Image in one message and the text in a separate message.

To handle this, you may want to write Message Handlers and a Handler Registry:

window.layerMessageHandlers = [];
window.registerLayerMessageHandler = function(testFunction, handlerFunction) {
  window.layerMessageHandlers.push({testFunction: testFunction, handlerFunction: handlerFunction});
}
function renderParts(message) {
  var handler = window.layerMessageHandlers.filter(function(handler) {
    return handler.testFunction(message);
  })[0];
  var html = handler(message);
  return html;
}

Now we can write a simple Text Handler:

window.registerLayerMessageHandler(
  // Test if this handler will handle the Message
  function(message) {
    return (message.parts.length === 1 && message.parts[0].mimeType === 'text/plain');
  },

  // Render the Message
  function(message) {
    return  '<div class="message-part message-part-text-plain">' + message.parts[0].body + '</div>';
  }
});

And we can write a Location handler that displays a Map and some descriptive text:

window.registerLayerMessageHandler(
  // Test if this handler will handle the Message
  function(message) {
    var textParts = message.parts.filter(function(part) { return part.mimeType === 'text/plain' });
    var locationParts = message.parts.filter(function(part) { return part.mimeType === 'location/coordinates' });
    return (message.parts.length === 2 && textParts.length && locationParts.length);
  },

  // Render the Message
  function(message) {
    var textPart = message.parts.filter(function(part) { return part.mimeType === 'text/plain' })[0];
    var locationPart = message.parts.filter(function(part) { return part.mimeType === 'location/coordinates' })[0];
    return '<div class="message-part message-part-text-location">' +
             '<img src="https://some-mapping-service.com/latlong=' + locationPart.body + '" />' +
             '<span>' + textPart.body + '</span>' +
           '</div>';
  }
});

Note that part.body will either be a Blob or a string. Which type will be used is controlled by the static property layer.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(\+.+)?$/];

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.

Channels Announcements