How to Create Threads and Quote Messages with the PubNub Chat SDK
Introduction to the PubNub Chat SDK
The PubNub Chat SDK, available for TypeScript and JavaScript applications, exposes a set of APIs designed to make it easy to add powerful and flexible chat features to your app with minimum development. Chat options like quoting, mentioning users, channel references, threads, read receipts, and typing indicators are supported natively by the SDK, letting you build a full-fledged app quickly.
Please refer to our documentation and sample chat app to get started with the Chat SDK. Our tutorial will walk you through the basic functionality of the Chat SDK and touch on some more advanced features whilst our hosted demo will show the Chat SDK in action.
This how-to is part of a series of posts diving into some of the more powerful features of the PubNub Chat SDK. The series can be read in any order, but the list of related articles is below:
- How to Manage User Channel Membership with the Chat SDK
- How to Add Reactions and Emojis to Messages with the Chat SDK
- How to Create Threads and Quote Messages with the Chat SDK
- How to Mention Users and Channels with the Chat SDK
To use the Chat SDK, please ensure you enable App Context on your PubNub keyset.
Create Threads & Quote Messages
One of the reasons we introduced the PubNub Chat SDK was to address feedback we received from chat developers using our traditional SDKs. Although any of our PubNub SDKs make it trivial to exchange real-time messages between users, we saw many developers needed to implement the same chat features that users had come to expect, features such as organizing messages into threads, quoting a previous message, or tagging other users. With the PubNub Chat SDK, we have taken those standard chat features and exposed APIs to make it easy for you to implement them in your own app; this guide will walk you through how to implement the first two examples, creating a message thread and quoting a previous message.
Prerequisites
Ensure you have an instance of the Chat object instantiated in your app
const chat = await Chat.init({
publishKey: "YOUR_PUBLISH_KEY",
subscribeKey: "YOUR_SUBSCRIBE_KEY",
userId: "YOUR_USER_ID",
})// include the PubNub Unity Chat SDK package in your code before initializing the client.
using PubnubChatAPI.Entities;
using PubnubChatApi.Entities.Data;
// initialize your Unity Chat SDK client using your app keys from the Admin Portal and a unique user ID for your client
var chat = new Chat(new PubnubChatConfig(
"YOUR_PUBLISH_KEY",
"YOUR_SUBSCRIBE_KEY",
"YOUR_USER_ID")
);PubnubChatSubsystem* PubnubChatSubsystem = GameInstance->GetSubsystem<UPubnubChatSubsystem>();
UPubnubChat* Chat = PubnubChatSubsystem->InitChat(
"YOUR_PUBLISH_KEY",
"YOUR_SUBSCRIBE_KEY",
"YOUR_USER_ID"
);val chatConfig = ChatConfiguration()
val pnConfiguration = PNConfiguration.builder(userId = UserId("myUserId"),
subscribeKey = "mySubscribeKey").build()
Chat.init(chatConfig, pnConfiguration).async { result ->
result.onSuccess { chat: Chat ->
println("Chat successfully initialized")
}.onFailure { exception: PubNubException ->
println("Exception initialising chat: ${exception.message}")
}
}let pubNubConfiguration = PubNubConfiguration(
publishKey: "your-publish-key",
subscribeKey: "your-subscribe-key",
userId: "your-user-id"
)
let yourChat = ChatImpl(
chatConfiguration: ChatConfiguration(),
pubNubConfiguration: pubNubConfiguration
)
yourChat.initialize { result in
switch result {
case .success(_):
debugPrint("Initialized successfully!")
case .failure(let error):
debugPrint("Error during initialization: \(error)")
}
}There are a lot of possible parameters you can pass to the Chat SDK, but for threads and quotes, you do not need anything more than the standard publish key, subscribe key, and user ID. If all of this is new to you, and you’re not sure where to get started, please check out the initial configuration section in our documentation.
Message Threads
The documentation for message threads lives under the Message documentation and will explain some of the implementation details; essentially, each message thread requires its own PubNub channel to handle communication, but that implementation is hidden from you if you are using the Chat SDK.
Given an existing root Message, you can create a thread from that message using the createThread() API. Since you can only have a single thread for any message, it is best practice to check whether a thread already exists before creating a new one with the hasThread() API
if (!rootMessage.hasThread)
{
threadChannel = await rootMessage.createThread()
}// check if the message starts a thread
if (message.HasThread())
{
var threadChannel = message.CreateThread();
}bool* HasThread = RootMessage->HasThread();
if (HasThread)
{
UPubnubThreadChannel* ThreadChannel = RootMessage->CreateThread();
}// Assuming rootMessage is an existing instance of Message
if (!rootMessage?.hasThread) {
rootMessage?.createThread().async { threadResult ->
threadResult.onSuccess { threadChannel ->
// handle success
}.onFailure {
// handle failure
}
}
}// Assuming rootMessage is an existing instance of MessageImpl
if (!rootMessage.hasThread) {
rootMessage.createThread { result in
switch result {
case let .success(threadChannel):
debugPrint("Thread created successfully with ID: \(threadChannel.id)")
case let .failure(error):
debugPrint("Failed to create thread: \(error)")
}
}
}createThread() will return a ThreadChannel object with similar methods to Channel. For example, if you wanted to read the entire history of a conversation with threads, the pseudocode might look like this:
FOREACH message in Channel
message.getHistory()
IF (message.hasThread() THEN
threadChannel = message.getThread()
threadChannel.getHistory()
END IF
END FOREACHYou can send text messages on a thread channel in the same way you sent messages on a standard channel, with the sendText() API
const threadChannel = rootMessage.getThread()
threadChannel.sendText(“hello”)if (message.TryGetThread(out var threadChannel))
{
Console.WriteLine($"Thread channel successfully retrieved: {threadChannel.Name}");
threadChannel.sendText("hello")
}
else
{
Console.WriteLine("No thread channel associated with this message.");
}UPubnubThreadChannel* ThreadChannel = RootMessage->GetThread();
FString Message = "hello";
ThreadChannel->SendText(Message)// threadChannel is retrieved in the previous code example
// or use the Message getThread() API
threadChannel.sendText("hello").async { /*...*/ }// threadChannel is retrieved in the previous code example
threadChannel.sendText("hello")Quoting Messages with MessageDraft (TypeScript only)
Quoting a message is an example of one of our Chat SDK features that provides some context about the message being sent, which the recipient can interpret. Being able to sendText() like the previous example is all very good, but what if you want to include a file with your message? What if you want to tag users or channels in your message? Or, most pertinently for this guide, what if you wanted to quote a previous message within the current message? All of these scenarios are handled by our TypeScript Chat SDK using a MessageDraft. Please see the next section for detials about how to quote a message without using Message Draft.
A MessageDraft is a fundamental type in the Chat SDK and will be used by most chat implementations to deliver a feature-rich experience for your users. You will need to create a new MessageDraft object every time a new chat message is composed, then you can call the addQuote() API to reference a previous message.
The MessageDraft is discussed in more detail in the guide describing how to mention users and channels, but in summary, you would:
Declare a variable to hold the message draft
const [newMessageDraft, setNewMessageDraft] = useState<MessageDraft>()// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDK// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDK// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDK// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDKAs part of your chat initialization, and after any message is sent, initialize a new MessageDraft
setNewMessageDraft(threadChannel.createMessageDraft())// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDK// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDK// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDK// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDKWhenever your input text changes (i.e. the user types something), notify the message draft
// Note: Within the onChange() handler for the input field
const response = await newMessageDraft.onChange(event.currentTarget.value)// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDK// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDK// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDK// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDKAdd a previous message as a quote in response to a user action
newMessageDraft.addQuote(message)// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDK// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDK// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDK// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDKSend the message when required
newMessageDraft.send()// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDK// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDK// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDK// Quoting messages with MessageDraft is only currently supported by the TypeScript Chat SDKQuoting Messages without MessageDraft
Some of our Chat SDKs will allow you to add a quoted message directly within the call to sendText(), for example:
// Assuming the active Channel object is stored in supportChannel
// Assuming the Message object we wish to quote is stored in quotedMessage
supportChannel.sendText(
text: "This message contains a quoted message",
quotedMessage: quotedMessage
).async { sendResult ->
sendResult.onSuccess { publishResult ->
// handle success
println("Message sent successfully: ${publishResult.timetoken}")
}.onFailure { exception ->
// handle failure
println("Error sending message: ${exception.message}")
}
}
// When the recipient receives the sent message,
// the quoted message can be accessed as follows:
val quotedMessage = message?.quotedMessage// Simplified example from the documentation for message quotes.
// Assuming the active Channel object is stored in supportChannel
// Assuming the Message object we wish to quote is stored in quotedMessage
supportChannel.sendText(
text: "This message contains a quoted message",
quotedMessage: quotedMessage
) {
switch $0 {
case let .success(sentTimetoken):
print("Message sent successfully with timetoken: \(sentTimetoken)")
case let .failure(error):
print("Failed to send message: \(error)")
}
}
// When the recipient receives the sent message,
// the quoted message can be accessed as follows:
let quotedMessage = message.quotedMessagePutting it all together
You can see a very simple example of message threads and quoting messages in the short demo below:
This is a real, live, working demo, so feel free to launch the demo in multiple tabs to see real-time messages being received. Because this is a shared application, ‘Reset App State’ will give you a blank slate to play with.
Interactive Demo
The code that drives this demo is available on GitHub, but here are some excerpts from pertinent code in the app showing how it fits together:
// Code that powers embedded demo, above
// Input field onChange handler
async function handleInput(event: React.FormEvent<HTMLInputElement>)
{
// No special rendering for Message Draft with this app
if (!newMessageDraft) return
const response = await newMessageDraft.onChange(event.currentTarget.value)
}The logic to submit a message. This demo is hard-coded to always reply to the thread channel
// Code that powers embedded demo, above
async function handleSend(event: React.SyntheticEvent) {
newMessageDraft.send()
setNewMessageDraft(threadChannel.createMessageDraft())
setQuotedMessagePreview("")
setText("")
}The User presses the “Quote Message” button
// Code that powers embedded demo, above
async function handleQuoteMessage(message: Message)
{
if (!newMessageDraft) return;
setQuotedMessagePreview(message.text)
newMessageDraft.addQuote(message)
}Remember that you can’t quote a message from another channel, so the demo only allows you to quote messages from the same thread.
Demo: Create Threads and Quote Messages with our React Native demo on Mobile
You can play with these features using our Chat SDK Demo for Mobile, available as a hosted demo with complete source code on GitHub. You should also see the demo rendered in an iFrame at the bottom of this section.
Follow these steps to create a thread and quote a message in our demo:
- Log into the application, choosing a random user ID for each device.
- Start a conversation from the second device, selecting the user logged into the first device.
- Add some messages to the conversation
- Long press on one of the messages.
- Select ‘Reply in thread’ and send a reply
- Long press on one of the messages (it could also be the reply you just sent)
- Select ‘Quote message’
- Send a message containing this quoted text
- Notice that if you back out of the conversation or even log out of and back into the app, the reactions are retained and read from the message history.