Skip to content

Commit af05a10

Browse files
authored
Merge pull request #440 from xTEddie/doc-handle-message-ordering
Documentation for Custom Chat Control to Render Messages in Order
2 parents 2486f19 + f7441ca commit af05a10

File tree

1 file changed

+114
-1
lines changed

1 file changed

+114
-1
lines changed

docs/DEVELOPMENT_GUIDE.md

+114-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
**[Using Custom Chat Control](#using-custom-chat-control)**
1414
1. [Render Adaptive Cards](#render-adaptive-cards)
1515
1. [Upload File Validation](#upload-file-validation)
16+
1. [Render Messages in Order](#render-messages-in-order)
1617

1718
## Using Bot Framework Web Chat Control
1819

@@ -498,4 +499,116 @@ fileSelector.onchange = async (event) => {
498499
}
499500
});
500501
}
501-
```
502+
```
503+
504+
### Render Messages in Order
505+
> ❗Minimum version of [@microsoft/[email protected]](https://www.npmjs.com/package/@microsoft/omnichannel-chat-sdk/v/1.10.16) is required
506+
507+
```js
508+
class CustomWidgetMessageRenderer {
509+
constructor(chatSDK) {
510+
this.chatSDK = chatSDK;
511+
this.postedMessageIds = new Set();
512+
this.postedOriginalMessageIds = new Set();
513+
this.messages = new Map();
514+
this.subscribers = [];
515+
}
516+
517+
async initialize(options = {}) {
518+
if (options.rehydrate) {
519+
setTimeout(async () => {
520+
const messages = await this.chatSDK.getMessages(); // Retrieve whole conversation messages
521+
messages.forEach(message => {
522+
this.postMessage(message);
523+
});
524+
}, 500); // Prevent race conditions
525+
}
526+
527+
await this.chatSDK.onNewMessage((message) => {
528+
this.postMessage(message);
529+
});
530+
}
531+
532+
async sendMessage(content) {
533+
const chatMessage = await this.chatSDK.sendMessage({content});
534+
this.postMessage(chatMessage);
535+
}
536+
537+
getMessages() { // Retrieve ordered messages
538+
const messages = [...this.messages.values()];
539+
messages.sort((a, b) => a.id - b.id); // Reorder messages in ascending order
540+
return messages;
541+
}
542+
543+
notifyChatTranscriptUpdate() {
544+
const messages = this.getMessages();
545+
this.subscribers.forEach(subscriber => {
546+
subscriber(messages);
547+
});
548+
}
549+
550+
postMessage(newMessage) {
551+
const isPostedMessageId = this.postedMessageIds.has(newMessage.id);
552+
let isPostedOriginalMessageId = undefined;
553+
554+
if (newMessage && newMessage.metadata && newMessage.metadata.originalMessageId) { // Verify whether the message has originalMessageId
555+
isPostedOriginalMessageId = this.postedOriginalMessageIds.has(newMessage.metadata.originalMessageId);
556+
}
557+
558+
if (isPostedMessageId) {
559+
const message = this.messages.get(newMessage.id);
560+
561+
// Update the message content of queue position message
562+
if (newMessage && newMessage.tags && newMessage.tags.includes('queueposition')) {
563+
this.messages.set(message.id, {...message, content: newMessage.content});
564+
this.notifyChatTranscriptUpdate();
565+
}
566+
} else if (isPostedOriginalMessageId === false) { // Original message id takes precedence over message id
567+
this.messages.set(newMessage.metadata.originalMessageId, {...newMessage, id: newMessage.metadata.originalMessageId}); // Replaces message id with originalMessageId
568+
569+
// Update posted message ids
570+
this.postedMessageIds.add(newMessage.id);
571+
this.postedOriginalMessageIds.add(newMessage.metadata.originalMessageId);
572+
this.notifyChatTranscriptUpdate();
573+
} else if (!isPostedMessageId) {
574+
this.messages.set(newMessage.id, newMessage);
575+
this.postedMessageIds.add(newMessage.id);
576+
this.notifyChatTranscriptUpdate();
577+
}
578+
}
579+
580+
onChatTranscriptUpdate(subscriber) { // Subscribe to chat transcript update
581+
this.subscribers.push(subscriber);
582+
}
583+
}
584+
585+
const useCustomWidgetMessageRenderer = async (chatSDK, options = {}) => {
586+
const renderer = new CustomWidgetMessageRenderer(chatSDK);
587+
const initializeOptions = {rehydrate: false};
588+
if (options.rehydrate) {
589+
initializeOptions.rehydrate = true;
590+
}
591+
await renderer.initialize(initializeOptions);
592+
return renderer;
593+
};
594+
595+
596+
// ...
597+
598+
const optionalParams = {
599+
liveChatContext
600+
};
601+
602+
await chatSDK.startChat(optionalParams);
603+
604+
// Set rehydrate option to 'true' only if conversation is rehydrated from liveChatContext or any previously existing conversation (Persistent Chat, Chat Reconnect, etc)
605+
const renderer = await useCustomWidgetMessageRenderer(chatSDK, {
606+
rehydrate: optionalParams.liveChatContext? true: false
607+
});
608+
609+
// Event triggered on new message or when the array of messages have been reordered
610+
renderer.onChatTranscriptUpdate((messages) => {
611+
// TODO: Add custom implementation to update UI with ordered messages
612+
});
613+
614+
renderer.sendMessage("Sample message from customer");

0 commit comments

Comments
 (0)