diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts index 5c5708eb5..d3f450fe2 100644 --- a/src/client/graphics/layers/EventsDisplay.ts +++ b/src/client/graphics/layers/EventsDisplay.ts @@ -16,6 +16,7 @@ import { import { ClientID } from "../../../core/Schemas"; import { Layer } from "./Layer"; import { SendAllianceReplyIntentEvent } from "../../Transport"; +import { unsafeHTML } from 'lit/directives/unsafe-html.js'; export enum MessageType { SUCCESS, @@ -34,6 +35,7 @@ export class DisplayMessageEvent implements GameEvent { interface Event { description: string; + unsafeDescription?: boolean buttons?: { text: string; className: string; @@ -313,7 +315,8 @@ export class EventsDisplay extends LitElement implements Layer { }); } else if (event.message.sender === myPlayer && event.message.recipient !== AllPlayers) { this.addEvent({ - description: `Sent ${event.message.recipient.displayName()} ${event.message.emoji}`, + description: `Sent ${event.message.recipient.displayName()}: ${event.message.emoji}`, + unsafeDescription: true, type: MessageType.INFO, highlight: true, createdAt: this.game.ticks(), @@ -332,7 +335,7 @@ export class EventsDisplay extends LitElement implements Layer { ${this.events.map((event, index) => html` - ${event.description} + ${event.unsafeDescription ? unsafeHTML(event.description) : event.description} ${event.buttons ? html`
${event.buttons.map(btn => html` diff --git a/src/core/Util.ts b/src/core/Util.ts index c0f12d386..0f60c874e 100644 --- a/src/core/Util.ts +++ b/src/core/Util.ts @@ -149,16 +149,39 @@ export function sanitize(name: string): string { export function processName(name: string): string { // First sanitize the raw input - strip everything except text and emojis - const withEmojis = twemoji.parse(sanitize(name), { - base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/', // Use jsDelivr CDN - folder: 'svg', // or 'png' if you prefer - ext: '.svg' // or '.png' if you prefer - }); - return DOMPurify.sanitize(withEmojis, { - ALLOWED_TAGS: ['img'], - ALLOWED_ATTR: ['src', 'alt', 'class'], - // Only allow twemoji CDN URLs - ALLOWED_URI_REGEXP: /^https:\/\/cdn\.jsdelivr\.net\/gh\/twitter\/twemoji/ + const sanitizedName = sanitize(name); + + // Process emojis with twemoji + const withEmojis = twemoji.parse(sanitizedName, { + base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/', + folder: 'svg', + ext: '.svg' }); + // Add CSS styles inline to the wrapper span + const styledHTML = ` + + ${withEmojis} + + `; + + // Add CSS for the emoji images + const withEmojiStyles = styledHTML.replace( + /