BlackBerry Spark Communications Services Guide

Chats

Overview

A central feature of BlackBerry Spark Communications Services is the ability to allow applications to create chats and share rich messaging experiences. A chat is a shared space that allows many endpoints to contribute content in a series of individual messages. Typically, these messages represent user-visible content, but they can also contain arbitrary information that your application can consume.

Extensibility

The Spark Communications Services SDK excels at providing an instant messaging experience between human users, but its strengths extend far beyond that. The SDK and its APIs are designed to support applications that need mixed human-machine or machine-to-machine messaging with or without user interfaces.

A chat is the name that the SDK gives to a group of communicating endpoints, each representing an identity. Those messages are classified by application-specified tags and can contain arbitrary application-specified JSON content as well as optional text content and large binary attachments and more. A chat itself can also have application-specified JSON content associated with it.

Automated endpoints that use the SDK use chats to define communication contexts. For example, a temperature sensor endpoint might create a chat with a temperature reading recording server and then send custom temperature reading JSON messages within that chat for the server to receive and record in a database. Neither endpoint would have a UI display of the chat or its messages, which are just serving the role of a secured communications pathway.

Creating Chats

Chats are created by your application using the SDK. The user that creates a chat becomes the first participant. Participants can be invited to join the chat when it is created or they can be added later.

In the code snippet below we show how your application can create a chat between two participants and set its subject:

// Obtain an SDK instance.
const bbmsdk = ...;

// Invite other participants using their regIds
const regId1 = ...;
const regId2 = ...;
const invitees = [regId1, regId2];

// Give the chat a subject
const subject = 'My Chat Subject';

// Start a new chat
bbmsdk.messenger.chatStart({
    subject: subject,
    isOneToOne: false,
    invitees: invitees
  });
// Invite other participants using their regIds
final long regId1 = ...;
final long regId2 = ...;
ArrayList<ChatStart.Invitees> invitees = new ArrayList<>();
invitees.add(new ChatStart.Invitees().regId(regId1));
invitees.add(new ChatStart.Invitees().regId(regId2));

// Give the chat a subject
final String subject = "My Chat Subject";

// Create a cookie to track the chat start request
final String cookie = UUID.randomUUID().toString();

// Start a new chat
BBMEnterprise.getInstance().getBbmdsProtocol().send(new ChatStart(cookie, Lists.newArrayList(invitees), subject));
// Invite other participants using their regIds
NSNumber *regId1 = @(...);
NSNumber *regId2 = @(...);
NSMutableArray *invitees = [[NSMutableArray alloc] init];
for (NSNumber *regId in @[regId1, regId2]) {
    BBMChatStartMessage_Invitees *invitee = [[BBMChatStartMessage_Invitees alloc] init];
    invitee.regId = regId;
    [invitees addObject:invitee];
}

BBMChatStartMessage *chatStart = [[BBMChatStartMessage alloc] initWithInvitees:invitees];

// Give the chat a subject
chatStart.subject = @"My Chat Subject";

// Get the associated cookie to track the request
NSString *cookie = chatStart.cookie;

// Start a new chat.
[[BBMEnterpriseService service] sendMessageToService:chatStart];
// Invite other participants using their regIds
let regId1 : UInt64 = ...  
let regId2 : UInt64 = ...

//Create a list of invitees from our list of regIds
let invitees = [regId1, regId2].map( { (regId) -> BBMChatStartMessage_Invitees in
    let invitee = BBMChatStartMessage_Invitees()!
    invitee.regId = NSNumber(value: regId)
    return invitee
})

let chatStart = BBMChatStartMessage(invitees: invitees)

// Give the chat a subject
chatStart.subject = "My Chat Subject"

// Get the associated cookie to track the request
let cookie = chatStart.cookie

// Start a new chat.
BBMEnterpriseService.shared().sendMessage(toService: chatStart) 
    

See the APIs for creating chats for Android, iOS, Linux, and JavaScript.

Chat States

A chat can go through several states in its lifecycle. Its stable state is the Ready state. In that state, the chat's content has been fully restored by the endpoint and new messages can be sent and received. When in the Waiting or Restoring states, the chat will not receive messages, but it will accept messages from your application and queue them to be sent later.

Defunct Chats

In certain cases, a chat can enter the Defunct state. This means that the previously functional chat is no longer fully functional, and messages cannot be sent or received within it. The Defunct state exists so that your application has a chance to consume content that was shared in the chat before it became Defunct. Your application must explicitly remove a Defunct chat.

See the chat state APIs for Android, iOS, Linux, and JavaScript.

One-to-one Chats

By default, chats can have many participants. Your application can optionally specify participants when you create a chat, and it can add participants after a chat exists.

Your application can create a special kind of chat called one-to-one. One-to-one chats exist between two identities. The SDK makes sure that only a single one-to-one chat can exist between the two identities. When your application creates a one-to-one chat, the other participant must be specified, and other participants cannot be added later.

When one of the participants leaves the one-to-one chat, the other participant also leaves automatically and the chat becomes Defunct. At that point, your application can create a new one-to-one chat between the pair of participants. Your application can remove the Defunct one-to-one chat when you no longer require its content.

Displaying Chats

Chats: Chat List

The SDK allows your application to query the list of chats, and provides easy access to chat attributes that are typically used in building user interfaces.

A chat has:

See the APIs for getting a chat list for Android, iOS, Linux, and JavaScript.

Participants

Chats can have up to 250 participants. Only an existing participant can invite additional participants. A chat's invitePolicy determines if invites are further restricted to only chat administrators. Chat invitations work on the basis of a grant-and-join mechanism. This means that the inviting participant's endpoint, the BlackBerry Infrastructure, and the invited identity's SDK must all agree that the identity should be allowed into the chat. This mechanism is just one of the ways that Spark Communications Services makes sure that only authorized identities can join a chat and receive its messages.

In the code snippet below we show how your application can invite users to an existing chat:

// Obtain an SDK instance.
const bbmsdk = ...;

// Identify the user and the existing chat
const regId = "...";
const chatId = "...";

// Invite a user to an existing chat.
bbmsdk.messenger.chatInvite(chatId, regId);
// Identify the user and the existing chat
final long regId = ...;
final String chatId = "...";

ArrayList<ChatInvite.Invitees> invitees = new ArrayList<>();
invitees.add(new ChatInvite.Invitees().regId(regId));

// Invite user to existing chat
BBMEnterprise.getInstance().getBbmdsProtocol().send(new ChatInvite(chatId, invitees));
// Identify the user and the existing chat
NSNumber *regId = @(...);
NSString *chatId = @"...";

BBMChatInviteMessage_Invitees *invitee = [[BBMChatInviteMessage_Invitees alloc] init];
invitee.regId = regId;
BBMChatInviteMessage *invite = [[BBMChatInviteMessage alloc] initWithChatId:chatId invitees:@[invitee]];
[[BBMEnterpriseService sharedService] sendMessageToService:invite]; 
// Identify the user and the existing chat
let regId : UInt64 = ...
let chatId = "..."

let invitee = BBMChatInviteMessage_Invitees()!
invitee.regId = NSNumber(value: regId);
let invite = BBMChatInviteMessage(chatId: chatId, invitees: [invitee])
BBMEnterpriseService.shared().sendMessage(toService: invite) 
    

Each participant in a chat has access to all of the messages in the chat. This means that if one participant posts a message to the chat, the message is delivered to all of the other participants on each of their endpoints.

Because participants can join and leave chats multiple times, participants have a state. When a participant leaves a chat, the participant's messages aren't deleted. Instead, the participant's state is updated to indicate that they are no longer in the chat.

See the APIs for leaving a chat for Android, iOS, Linux, and JavaScript.

In addition to leaving chats, participants can mark chats as hidden. This state is local only to the endpoint, and the SDK will automatically un-hide the chat the next time a message is added to its history.

See the APIs for hiding a chat for Android, iOS, and Linux.

When a chat loses its last participant, it is deleted. A participant with the necessary capabilities (see the section below) can remove other participants from the chat and then leave the chat as a way to implement a delete chat functionality.

In the code snippet below we show how your application can delete a chat:

// Obtain an SDK instance.
const bbmsdk = ...;

// Identify the participants to remove and the existing chat
const participants = ["..."]; // List of user regId's.
const chatId = "...";

// Remove all other participants from the chat.
for(let participant of participants) {
  bbmsdk.messenger.participantRemove(chatId, participant);
}
// Leave the chat
bbmsdk.messenger.chatLeave(chatId);
// Identify the participants to remove and the existing chat
ArrayList<String> participants = ...; // List of user uris from ChatParticipant
final String chatId = "...";

// Remove all other participants from the chat
for (String participantUri : participants) {
  BBMEnterprise.getInstance().getBbmdsProtocol().send(new ParticipantRemove(chatId, participantUri));
}

// Leave the chat
BBMEnterprise.getInstance().getBbmdsProtocol().send(new ChatLeave(Lists.newArrayList(chatId)));
// Identify the participants to remove and the existing chat
NSArray *participants = ...;  // List of user uris from BBMChatParticipant
NSString *chatId = @"...";

// Remove all other participants from the chat
for(NSString *userUri in participants) {
    BBMParticipantRemoveMessage *remove = [[BBMParticipantRemoveMessage alloc] initWithChatId:chatId 
                                                                                      userUri:userUri];
    [[BBMEnterpriseService sharedService] sendMessageToService:remove];
}

// Leave the chat
BBMChatLeaveMessage *leave = [[BBMChatLeaveMessage alloc] initWithChatIds:@[chatId]];
[[BBMEnterpriseService sharedService] sendMessageToService:leave]; 
// Identify the participants to remove and the existing chat
let participants = ...   // List of user uris from BBMChatParticipant
let chatId = "..."

// Remove all other participants from the chat
for userUri in participants {
    let remove = BBMParticipantRemoveMessage(chatId: chatId, userUri: userUri)
    BBMEnterpriseService.shared().sendMessage(toService: remove)
}

// Leave the chat
let leave = BBMChatLeaveMessage(chatIds:[chatId])
BBMEnterpriseService.shared().sendMessage(toService: leave) 
    

Participants also report their most recent message delivery and message read confirmation timestamps. This information can be useful when, for example, your application wants to show the user when a participant last received or read messages in a chat.

Chats: Last Seen

Chat Administration

The creator of a chat is automatically an administrator of that chat. Chat administrators have additional capabilities that regular participants do not have. Administrators can:

  • Remove other participants from the chat without their consent.
  • Promote other participants to be administrators.
  • Demote other participants from administrators to regular participants.
  • Always invite other participants (see below).

When a chat is created, its invitation policy is set by its creator to either allow all participants to invite others or to allow only administrators to invite others. This policy can be changed by an administrator at any time. When a chat has an administrators-only invitation policy but no administrators, then additional participants cannot be added to the chat.

In the code snippet below we show how to promote, demote, and remove participants from a chat:

// Obtain an SDK instance.
const bbmsdk = ...;

// Identify the participant and the existing chat
const participant = "...";
const chatId = "...";

// Promote a participant to be a chat administrator
bbmsdk.messenger.participantPromote(chatId, regId);

// Demote a chat administrator to be a regular participant
bbmsdk.messenger.participantDemote(chatId, regId);

// Remove a participant from the chat
bbmsdk.messenger.participantRemove(chatId, regId);
// Identify the participant and the existing chat
final String userUri = "...";
final String chatId = "...";

// Promote a participant to be a chat administrator
BBMEnterprise.getInstance().getBbmdsProtocol().send(new ParticipantPromote(chatId, userUri));

// Demote a chat administrator to be a regular participant
BBMEnterprise.getInstance().getBbmdsProtocol().send(new ParticipantDemote(chatId, userUri));

// Remove a participant from the chat
BBMEnterprise.getInstance().getBbmdsProtocol().send(new ParticipantRemove(chatId, userUri));
// Identify the participant and the existing chat
NSString *userUri = @"...";
NSString *chatId = @"...";

// Promote a participant to be a chat administrator
BBMParticipantPromoteMessage *promote = [[BBMParticipantPromoteMessage alloc] initWithChatId:chatId 
                                                                                     userUri:userUri];
[[BBMEnterpriseService sharedService] sendMessageToService:promote];

// Demote a chat administrator to be a regular participant
BBMParticipantDemoteMessage *demote = [[BBMParticipantDemoteMessage alloc] initWithChatId:chatId 
                                                                                  userUri:userUri];
[[BBMEnterpriseService sharedService] sendMessageToService:demote];

// Remove a participant from the chat
BBMParticipantRemoveMessage *remove = [[BBMParticipantRemoveMessage alloc] initWithChatId:chatId 
                                                                                  userUri:userUri];
[[BBMEnterpriseService sharedService] sendMessageToService:remove];
// Identify the participant and the existing chat
let userUri = "..."
let chatId = "..."

// Promote a participant to be a chat administrator
let promote = BBMParticipantPromoteMessage(chatId: chatId, userUri: userUri)
BBMEnterpriseService.shared().sendMessage(toService: promote)

// Demote a chat administrator to be a regular participant
let demote = BBMParticipantDemoteMessage(chatId: chatId, userUri: userUri)
BBMEnterpriseService.shared().sendMessage(toService: demote)

// Remove a participant from the chat
let remove = BBMParticipantRemoveMessage(chatId: chatId, userUri: userUri)
BBMEnterpriseService.shared().sendMessage(toService: remove) 
    

An application can give administrators additional capabilities by applying restrictions to what regular participants can do. For example, an application can restrict the ability to edit the chat subject or modifying the chat metadata to chat administrators.

See the participant APIs for Android, iOS, Linux, and JavaScript.

Chat Metadata

Chats have fields that allow your application to attach data to the chat. Your application can attach:

  • Data that is visible only to the local endpoint (localData).
  • Data that is visible only to endpoints belonging to the local user's identity (privateData).
  • Data that is visible only to participants of the chat (data).

Your application can use these opaque JSON data fields for application defined chat metadata. For example, an application can use the localData field for storing a draft of a message that the user has typed. This data is relevant only to the local endpoint.

Chats: Message Draft

Your application can utilize the privateData field for example to share per-chat notification settings that is only relevant to the local user's endpoints. The data field can be used to set a chat avatar URL, a chat description, or the identifiers of external resources associated with the chat. These shared data can be read or written to by multiple endpoints. Concurrent writes are resolved in a way that all endpoints will eventually see the same steady-state outcome.

Chats: Chat Metadata

In the code snippet below we show how your application can set a message draft, a chat avatar, and a per-chat notification setting:

// Obtain an SDK instance.
const bbmsdk = ...;

// Identify the chat
const chatId = "...";

// Since the JavaScript SDK does not store data persistently, localData is
// not supported, however similar behaviour can be implemented by the user with
// a Map.
const chatExtraDataMap = new Map();

// Set a chat draft
chatExtraDataMap.set(chatId, {draft: 'This is a message draft'});

// Set a chat avatar
bbmsdk.messenger.chatUpdate(chatId, {data: {'avatar', 'https://www.company.com/group/12345/avatar'}});

// Set a chat priority notification setting
bbmsdk.messenger.chatUpdate(chatId, {privateData: {'priority', true}});
// Identify the chat
Chat chat = ...;


// Use the existing chat localData or create a new JSONObject
final JSONObject draft = chat.localData != null ? chat.localData : new JSONObject();
// Set a chat draft
draft.put("draft", "This is a message draft");
Chat.AttributesBuilder builder = new Chat.AttributesBuilder().localData(draft);

BBMEnterprise.getInstance().getBbmdsProtocol().send(chat.requestListChange(builder));

// Use the existing chat data or create a new JSONObject
final JSONObject avatar = chat.data != null ? chat.data : new JSONObject();
// Set a chat avatar
avatar.put("avatar", "https://www.company.com/group/12345/avatar");
builder = new Chat.AttributesBuilder().data(avatar);

BBMEnterprise.getInstance().getBbmdsProtocol().send(chat.requestListChange(builder));

// Set a chat priority notification setting
final JSONObject priority = chat.privateData != null ? chat.privateDate : new JSONObject();
// Use the existing chat privateData or create a new JSONObject
priority.put("priority", true);
builder = new Chat.AttributesBuilder().privateData(priority);

BBMEnterprise.getInstance().getBbmdsProtocol().send(chat.requestListChange(builder));
// Identify the chat
BBMChat chat = ...

// Copy any existing localData or create a new JSON dictionary
NSMutableDictionary *localData = [chat.localData mutableCopy] ?: [NSMutableDictionary dictionary];
localData[@"draft"] = @"This is a message draft";

// Update the localData.  This will overwrite the existing localData property. 
NSArray *ldElements = @[@{@"chatId":chat.chatId, @"localData" : localData}];
BBMRequestListChangeMessage *localDataRequest = [[BBMRequestListChangeMessage alloc] initWithElements:ldElements 
                                                                                                 type:@"chat"];
[[BBMEnterpriseService sharedService] sendMessageToService:localDataRequest];

// Copy any existing privateData or create a new JSON dictionary
NSMutableDictionary *privateData = [chat.privateData mutableCopy] ?: [NSMutableDictionary dictionary];
privateData[@"avatar"] = @"https://www.company.com/group/12345/avatar";

// Update the privateData.  This will overwrite the existing privateData property. 
NSArray *pdElements = @[@{@"chatId":chat.chatId, @"privateData" : privateData}];
BBMRequestListChangeMessage *privateDataRequest = [[BBMRequestListChangeMessage alloc] initWithElements:pdElements 
                                                                                                    type:@"chat"];
[[BBMEnterpriseService sharedService] sendMessageToService:privateDataRequest];

// Copy any existing data or create a new JSON dictionary
NSMutableDictionary *data = [chat.data mutableCopy] ?: [NSMutableDictionary dictionary];
data[@"priority"] = @(YES);

// Update the data.  This will overwrite the existing data property. 
NSArray *dataElements = @[@{@"chatId":chat.chatId, @"data" : data}];
BBMRequestListChangeMessage *dataRequest = [[BBMRequestListChangeMessage alloc] initWithElements:dataElements  
                                                                                            type:@"chat"];
[[BBMEnterpriseService sharedService] sendMessageToService:dataRequest];
let chat : BBMChat = BBMChat()

// Copy any existing localData or create a new JSON dictionary
var localData = chat.localData ?? Dictionary()
localData["draft"] = "This is a message draft"

// Update the localData.  This will overwrite the existing localData property. 
let localDataElements = [["chatId" : chat.chatId, "localData" : localData]]
let localDataRequest = BBMRequestListChangeMessage(elements: localDataElements, type: "chat")
BBMEnterpriseService.shared().sendMessage(toService: localDataRequest)

// Copy any existing privateData or create a new JSON dictionary
var privateData = chat.privateData ?? Dictionary()
privateData["avatar"] = "https://www.company.com/group/12345/avatar";

// Update the privateData.  This will overwrite the existing privateData property. 
let privateDataElements = [["chatId" : chat.chatId, "privateData" : privateData]]
let privateDataRequest = BBMRequestListChangeMessage(elements: privateDataElements, type: "chat")
BBMEnterpriseService.shared().sendMessage(toService: privateDataRequest)

// Copy any existing chat data or create a new JSON dictionary
var data = chat.data ?? Dictionary()
data["priority"] = true

// Update the data.  This will overwrite the existing data property. 
let dataElements = [["chatId" : chat.chatId, "data" : data]]
let dataRequest = BBMRequestListChangeMessage(elements: dataElements, type: "chat")
BBMEnterpriseService.shared().sendMessage(toService: dataRequest) 
    

See the chat APIs for Android, iOS, Linux, and JavaScript.

Chat Events

Participants can post real-time chat events to a chat. Unlike normal chat messages, chat events are delivered only to endpoints that are currently connected to the BlackBerry Infrastructure. They are not stored for future delivery and they do not form part of the chat's message history.

Chat events are suitable for sharing information that is time-sensitive or frequently updated, but which isn't very useful after a few moments. Offline endpoints don't waste resources restoring stale chat events upon reconnecting to the BlackBerry Infrastructure because chat events are not stored for future delivery. For example, an application might want to send live location information from one or more participants to all other participants.

In the code snippet below we show how to send a chat event with location:

// Obtain an SDK instance.
const bbmsdk = ...;

// Identify the chat
const chatId = "...";

// Define location tag
const locationTag = 'Location';

// Define the location data
const location = {
  latitude: 23.5415,
  longitude: -55.6545
};

// Send chat event
bbmsdk.messenger.chatEventSend(chatId, locationTag, location);
// Identify the chat
final String chatId = "...";

// Define location tag
final String locationTag = "Location";

// Define the location data
final JSONObject location = new JSONObject();
location.put("longitude", 23.5415);
location.put("latitude", -55.6545);

// Send chat Event
BBMEnterprise.getInstance().getBbmdsProtocol().send(new ChatEventSend(chatId, locationTag).data(location));
// Identify the chat
NSString *chatId = @"...";

// Define the location tag
NSString *locationTag = @"Location";

// Define the location data as a JSON serializable dictionary
NSDictionary *locationData = @{@"latitude" : @(23.5415), @"longitude" : @(-55.6545)};

// Send chat Event
BBMChatEventSendMessage *chatEvent = [[BBMChatEventSendMessage alloc] initWithChatId:chatId 
                                                                                 tag:locationTag];
chatEvent.data = locationData;
[[BBMEnterpriseService sharedService] sendMessageToService:chatEvent]; 
// Identify the chat
let chatId = "..."

// Define the location tag
let locationTag = "Location"

// Define the location data as a JSON serializable dictionary
let locationData = ["latitude" : 23.5415, "longitude" : -55.6545]

// Send chat Event
let chatEvent = BBMChatEventSendMessage(chatId: chatId, tag: locationTag)!
chatEvent.data = locationData
BBMEnterpriseService.shared().sendMessage(toService: chatEvent) 
    

See the APIs for sending chat events for Android, iOS, Linux, and JavaScript.

Typing Notifications

Chats: Chat Typing

When your application considers the user to have started typing in a chat, it can send a typing notification. The event will be relayed in real-time to other participants of the chat. On the receiving endpoints, the typing state of a user automatically reverts back to "not typing" after a short delay or when a message from them is added to the chat's history. There is no need for your application to track when a user stops typing.

See the APIs for sending typing notifications for Android, iOS, Linux, and JavaScript.

Chats: Voice Recording

Your application can specify a tag that indicates what kind of typing action the local user is performing. For example, this can be used to indicate that the local user is taking a picture to send, recording a voice note, or editing an existing message.

In the code snippet below we show how your application can send a typing notification specifying that the user is recording a voice note:

// Obtain an SDK instance.
const bbmsdk = ...;

// Identify the chat
const chatId = "...";

// Define the voice recording tag
const voiceNoteTag = 'voiceNote';

// Send chat typing event
bbmsdk.messenger.chatTyping(chatId, voiceNoteTag);
// Identify the chat
final String chatId = "...";

// Define the voice recording tag
final String voiceNoteTag = "voiceNote";

// Send chat typing event
BBMEnterprise.getInstance().getBbmdsProtocol().send(new ChatTyping(chatId).tag(voiceNoteTag));
// Identify the chat
NSString *chatId = @"..."

// Define the voice recording tag
NSString *voiceNoteTag = @"voiceNote";

// Send chat typing event
BBMChatTypingMessage *typing = [[BBMChatTypingMessage alloc] initWithChatId:chatId];
typing.tag = voiceNoteTag;
[[BBMEnterpriseService service] sendMessageToService:typing]; 
// Identify the chat
let chatId = "..."

// Define the voice recording tag
let voiceNoteTag = "voiceNote"

// Send chat typing event
let typing = BBMChatTypingMessage(chatId: chatId)!
typing.tag = voiceNoteTag
BBMEnterpriseService.shared().sendMessage(toService: typing)