diff --git a/frontend/src/app/pages/altitude/chat/chat.component.html b/frontend/src/app/pages/altitude/chat/chat.component.html index 2ba6612..450c45f 100644 --- a/frontend/src/app/pages/altitude/chat/chat.component.html +++ b/frontend/src/app/pages/altitude/chat/chat.component.html @@ -10,10 +10,11 @@
@for (message of messages(); track message.timestamp) {

- @if (!message.notBlocked) { - [BLOCKED] + @if (message.notBlocked) { + [{{ message.server }}] + } @else { + [{{ message.server }}] } - [{{ message.server }}]

} diff --git a/frontend/src/app/pages/altitude/chat/mini-message/normalize.util.ts b/frontend/src/app/pages/altitude/chat/mini-message/normalize.util.ts new file mode 100644 index 0000000..3b653b4 --- /dev/null +++ b/frontend/src/app/pages/altitude/chat/mini-message/normalize.util.ts @@ -0,0 +1,64 @@ +import {MiniMessageClickEvent, MiniMessageComponent, MiniMessageHoverEvent} from './mini-message.types'; + +/** + * What actually arrives over the wire can be looser than the strict MiniMessageComponent + * type: some serializers emit bare strings inside `extra` instead of {text: "..."}, and + * some use snake_case event keys (hover_event/click_event) with different subfield names + * (e.g. click_event.command instead of clickEvent.value). This function tolerates both + * and always returns a clean, strictly-typed tree. + * + * Call this once, right after JSON.parse, before the data enters your app's state: + * + * messageJson: normalizeComponent(JSON.parse(m.messageJson)) + */ +export function normalizeComponent(input: unknown): MiniMessageComponent { + if (typeof input === 'string') { + return {text: input}; + } + if (input === null || typeof input !== 'object') { + return {text: ''}; + } + + const raw = input as Record; + const node: MiniMessageComponent = {...(raw as MiniMessageComponent)}; + + node.clickEvent = normalizeClickEvent(raw['clickEvent'] ?? raw['click_event']); + node.hoverEvent = normalizeHoverEvent(raw['hoverEvent'] ?? raw['hover_event']); + delete node['click_event' as keyof MiniMessageComponent]; + delete node['hover_event' as keyof MiniMessageComponent]; + + if (Array.isArray(raw['extra'])) { + node.extra = raw['extra'].map((child: unknown) => normalizeComponent(child)); + } + + // auditShape(node); + + return node; +} + +function normalizeClickEvent(raw: Record | undefined): MiniMessageClickEvent | undefined { + if (!raw) return undefined; + const value = raw['value'] ?? raw['command'] ?? raw['url'] ?? raw['page'] ?? ''; + return {action: raw['action'], value: String(value)}; +} + +function normalizeHoverEvent(raw: Record | undefined): MiniMessageHoverEvent | undefined { + if (!raw) return undefined; + const source = raw['value'] ?? raw['contents']; + return { + action: raw['action'], + value: typeof source === 'string' ? source : source ? normalizeComponent(source) : undefined, + }; +} + +export function auditShape(node: unknown, path = 'root'): void { + if (typeof node === 'string') { + console.warn(`Bare string in tree at ${path}:`, node); + return; + } + const n = node as Record; + if (n['hover_event'] || n['click_event']) { + console.warn(`snake_case event key at ${path}`, n); + } + (n['extra'] as unknown[] | undefined)?.forEach((c, i) => auditShape(c, `${path}.extra[${i}]`)); +} diff --git a/frontend/src/app/pages/altitude/chat/service/chat.service.ts b/frontend/src/app/pages/altitude/chat/service/chat.service.ts index c3fa31e..534813d 100644 --- a/frontend/src/app/pages/altitude/chat/service/chat.service.ts +++ b/frontend/src/app/pages/altitude/chat/service/chat.service.ts @@ -4,6 +4,7 @@ import {EventSourcePolyfill} from 'event-source-polyfill'; import {ChatMessage} from '@pages/altitude/chat/objects/chat-message.object'; import {MiniMessageComponent} from '@pages/altitude/chat/mini-message/mini-message.types'; import {RawChatMessage} from '@pages/altitude/chat/objects/raw-chat-message.object'; +import {normalizeComponent} from '@pages/altitude/chat/mini-message/normalize.util'; export interface ChatEvent { type: string; @@ -45,7 +46,7 @@ export class ChatService implements OnDestroy { const raw = JSON.parse(event.data) as RawChatMessage[]; const messages: ChatMessage[] = raw.map((m) => ({ ...m, - messageJson: JSON.parse(m.messageJson) as MiniMessageComponent, + messageJson: normalizeComponent(JSON.parse(m.messageJson)), })); this._messages.update((old) => [...old, ...messages]); });