From 63c72dceabd86dea28bd6bd1d07d528ebbf81cea Mon Sep 17 00:00:00 2001 From: Gavin Barron Date: Mon, 13 Nov 2023 15:54:45 -0800 Subject: [PATCH] fix: ensure that emoji content is correctly rewritten (#2852) --- .../statefulClient/StatefulGraphChatClient.ts | 34 ++----------------- .../src/utils/rewriteEmojiContent.tests.ts | 23 +++++++++++++ .../mgt-chat/src/utils/rewriteEmojiContent.ts | 32 +++++++++++++++++ .../updateMessageContentWithImage.tests.ts | 0 .../updateMessageContentWithImage.ts | 0 samples/react-chat/src/index.tsx | 3 -- 6 files changed, 58 insertions(+), 34 deletions(-) create mode 100644 packages/mgt-chat/src/utils/rewriteEmojiContent.tests.ts create mode 100644 packages/mgt-chat/src/utils/rewriteEmojiContent.ts rename packages/mgt-chat/src/{statefulClient => utils}/updateMessageContentWithImage.tests.ts (100%) rename packages/mgt-chat/src/{statefulClient => utils}/updateMessageContentWithImage.ts (100%) diff --git a/packages/mgt-chat/src/statefulClient/StatefulGraphChatClient.ts b/packages/mgt-chat/src/statefulClient/StatefulGraphChatClient.ts index 0a5b483028..1592c0c279 100644 --- a/packages/mgt-chat/src/statefulClient/StatefulGraphChatClient.ts +++ b/packages/mgt-chat/src/statefulClient/StatefulGraphChatClient.ts @@ -58,8 +58,9 @@ import { updateChatMessage, updateChatTopic } from './graph.chat'; -import { updateMessageContentWithImage } from './updateMessageContentWithImage'; +import { updateMessageContentWithImage } from '../utils/updateMessageContentWithImage'; import { isChatMessage } from '../utils/types'; +import { rewriteEmojiContent } from '../utils/rewriteEmojiContent'; // 1x1 grey pixel const placeholderImageContent = @@ -193,17 +194,6 @@ interface MessageConversion { */ const graphImageUrlRegex = /(]+)src=(["']https:\/\/graph\.microsoft\.com[^"']*["'])/; -/** - * Regex to detect and extract emoji alt text - * - * Pattern breakdown: - * (]+): Captures the opening emoji tag, including any attributes. - * alt=["'](\w*[^"']*)["']: Matches and captures the "alt" attribute value within single or double quotes. The value can contain word characters but not quotes. - * (.*[^>]): Captures any remaining text within the opening emoji tag, excluding the closing tag. - * : Matches the closing emoji tag. - */ -const emojiRegex = /(]+)alt=["'](\w*[^"']*)["'](.*[^>])<\/emoji>/; - class StatefulGraphChatClient implements StatefulClient { private readonly _notificationClient: GraphNotificationClient; private readonly _eventEmitter: ThreadEventEmitter; @@ -973,26 +963,10 @@ detail: ${JSON.stringify(eventDetail)}`); this.removeParticipantFromState(membershpId); }; - private emojiMatch(messageContent: string): RegExpMatchArray | null { - return messageContent.match(emojiRegex); - } - private graphImageMatch(messageContent: string): RegExpMatchArray | null { return messageContent.match(graphImageUrlRegex); } - // iterative repave the emoji custom element with the content of the alt attribute - // on the emoji element - private processEmojiContent(messageContent: string): string { - let result = messageContent; - let match = this.emojiMatch(result); - while (match) { - result = result.replace(emojiRegex, '$2'); - match = this.emojiMatch(result); - } - return result; - } - private processMessageContent(graphMessage: ChatMessage, currentUser: string): MessageConversion { const conversion: MessageConversion = {}; // using a record here lets us track which image in the content each request is for @@ -1043,9 +1017,7 @@ detail: ${JSON.stringify(eventDetail)}`); let content = graphMessage.body?.content ?? 'undefined'; let result: MessageConversion = {}; // do simple emoji replacement first - if (this.emojiMatch(content)) { - content = this.processEmojiContent(content); - } + content = rewriteEmojiContent(content); // Handle any mentions in the content content = this.updateMentionsContent(content); diff --git a/packages/mgt-chat/src/utils/rewriteEmojiContent.tests.ts b/packages/mgt-chat/src/utils/rewriteEmojiContent.tests.ts new file mode 100644 index 0000000000..4f6351fe55 --- /dev/null +++ b/packages/mgt-chat/src/utils/rewriteEmojiContent.tests.ts @@ -0,0 +1,23 @@ +import { expect } from '@open-wc/testing'; +import { rewriteEmojiContent } from './rewriteEmojiContent'; + +describe('emoji rewrite tests', () => { + it('rewrites an emoji correctly', async () => { + const result = rewriteEmojiContent(``); + await expect(result).to.be.equal('😎'); + }); + it('rewrites an emoji in a p tag correctly', async () => { + const result = rewriteEmojiContent(`

`); + await expect(result).to.be.equal('

😎

'); + }); + it('rewrites multiple emoji in a p correctly', async () => { + const result = rewriteEmojiContent( + `

` + ); + await expect(result).to.be.equal('

😎ðŸĪŠ

'); + }); + it('returns the original value if there is no emoji', async () => { + const result = rewriteEmojiContent('

Seb is cool

'); + await expect(result).to.be.equal('

Seb is cool

'); + }); +}); diff --git a/packages/mgt-chat/src/utils/rewriteEmojiContent.ts b/packages/mgt-chat/src/utils/rewriteEmojiContent.ts new file mode 100644 index 0000000000..114982d377 --- /dev/null +++ b/packages/mgt-chat/src/utils/rewriteEmojiContent.ts @@ -0,0 +1,32 @@ +/** + * Regex to detect and extract emoji alt text + * + * Pattern breakdown: + * (]+): Captures the opening emoji tag, including any attributes. + * alt=["'](\w*[^"']*)["']: Matches and captures the "alt" attribute value within single or double quotes. The value can contain word characters but not quotes. + * (.[^>]): Captures any remaining text within the opening emoji tag, excluding the closing angle bracket. + * ><\/emoji>: Matches the remaining part of the tag. + */ +const emojiRegex = /(]+)alt=["'](\w*[^"']*)["'](.[^>]+)><\/emoji>/; +const emojiMatch = (messageContent: string): RegExpMatchArray | null => { + return messageContent.match(emojiRegex); +}; +// iterative repave the emoji custom element with the content of the alt attribute +// on the emoji element +const processEmojiContent = (messageContent: string): string => { + let result = messageContent; + let match = emojiMatch(result); + while (match) { + result = result.replace(emojiRegex, '$2'); + match = emojiMatch(result); + } + return result; +}; + +/** + * if the content contains an tag with an alt attribute the content is replaced by replacing the emoji tags with the content of their alt attribute. + * @param {string} content + * @returns {string} the content with any emoji tags replaced by the content of their alt attribute. + */ +export const rewriteEmojiContent = (content: string): string => + emojiMatch(content) ? processEmojiContent(content) : content; diff --git a/packages/mgt-chat/src/statefulClient/updateMessageContentWithImage.tests.ts b/packages/mgt-chat/src/utils/updateMessageContentWithImage.tests.ts similarity index 100% rename from packages/mgt-chat/src/statefulClient/updateMessageContentWithImage.tests.ts rename to packages/mgt-chat/src/utils/updateMessageContentWithImage.tests.ts diff --git a/packages/mgt-chat/src/statefulClient/updateMessageContentWithImage.ts b/packages/mgt-chat/src/utils/updateMessageContentWithImage.ts similarity index 100% rename from packages/mgt-chat/src/statefulClient/updateMessageContentWithImage.ts rename to packages/mgt-chat/src/utils/updateMessageContentWithImage.ts diff --git a/samples/react-chat/src/index.tsx b/samples/react-chat/src/index.tsx index 4f9709862c..5ab21b552d 100644 --- a/samples/react-chat/src/index.tsx +++ b/samples/react-chat/src/index.tsx @@ -11,9 +11,6 @@ brokerSettings.defaultSubscriptionLifetimeInMinutes = 7; brokerSettings.renewalThreshold = 65; brokerSettings.renewalTimerInterval = 15; -// GraphConfig.useCanary = true; -GraphConfig.ackAsString = true; - Providers.globalProvider = new Msal2Provider({ baseURL: GraphConfig.graphEndpoint, clientId: 'ed072e38-e76e-45ae-ab76-073cb95495bb',