Skip to content

Commit

Permalink
fix: ensure that emoji content is correctly rewritten (#2852)
Browse files Browse the repository at this point in the history
  • Loading branch information
gavinbarron committed Nov 13, 2023
1 parent 9c84955 commit 63c72dc
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 34 deletions.
34 changes: 3 additions & 31 deletions packages/mgt-chat/src/statefulClient/StatefulGraphChatClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -193,17 +194,6 @@ interface MessageConversion {
*/
const graphImageUrlRegex = /(<img[^>]+)src=(["']https:\/\/graph\.microsoft\.com[^"']*["'])/;

/**
* Regex to detect and extract emoji alt text
*
* Pattern breakdown:
* (<emoji[^>]+): 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.
* </emoji>: Matches the closing emoji tag.
*/
const emojiRegex = /(<emoji[^>]+)alt=["'](\w*[^"']*)["'](.*[^>])<\/emoji>/;

class StatefulGraphChatClient implements StatefulClient<GraphChatClient> {
private readonly _notificationClient: GraphNotificationClient;
private readonly _eventEmitter: ThreadEventEmitter;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);

Expand Down
23 changes: 23 additions & 0 deletions packages/mgt-chat/src/utils/rewriteEmojiContent.tests.ts
Original file line number Diff line number Diff line change
@@ -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(`<emoji id="cool" alt="😎" title="Cool"></emoji>`);
await expect(result).to.be.equal('😎');
});
it('rewrites an emoji in a p tag correctly', async () => {
const result = rewriteEmojiContent(`<p><emoji id="cool" alt="😎" title="Cool"></emoji></p>`);
await expect(result).to.be.equal('<p>😎</p>');
});
it('rewrites multiple emoji in a p correctly', async () => {
const result = rewriteEmojiContent(
`<p><emoji id="cool" alt="😎" title="Cool"></emoji><emoji id="1f92a_zanyface" alt="🤪" title="Zany face"></emoji></p>`
);
await expect(result).to.be.equal('<p>😎🤪</p>');
});
it('returns the original value if there is no emoji', async () => {
const result = rewriteEmojiContent('<p><em>Seb is cool</em></p>');
await expect(result).to.be.equal('<p><em>Seb is cool</em></p>');
});
});
32 changes: 32 additions & 0 deletions packages/mgt-chat/src/utils/rewriteEmojiContent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Regex to detect and extract emoji alt text
*
* Pattern breakdown:
* (<emoji[^>]+): 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 = /(<emoji[^>]+)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 <emoji> 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;
3 changes: 0 additions & 3 deletions samples/react-chat/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down

0 comments on commit 63c72dc

Please sign in to comment.