|
13 | 13 | **[Using Custom Chat Control](#using-custom-chat-control)**
|
14 | 14 | 1. [Render Adaptive Cards](#render-adaptive-cards)
|
15 | 15 | 1. [Upload File Validation](#upload-file-validation)
|
| 16 | +1. [Render Messages in Order](#render-messages-in-order) |
16 | 17 |
|
17 | 18 | ## Using Bot Framework Web Chat Control
|
18 | 19 |
|
@@ -498,4 +499,116 @@ fileSelector.onchange = async (event) => {
|
498 | 499 | }
|
499 | 500 | });
|
500 | 501 | }
|
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