import { Room, MatrixEvent } from 'matrix-js-sdk'; import { useMemo } from 'react'; import { Widget } from 'matrix-widget-api'; import { StateEvent } from '../../../../types/matrix/room'; import { StateKeyToEvents, useRoomState } from '../../../hooks/useRoomState'; import { isWidgetUrlSafe } from './widgetUtils'; /** * Builds the `Widget` list from raw `im.vector.modular.widgets` state events. * * NOTE: we do NOT use `WidgetParser.parseWidgetsFromRoomState` here. In * matrix-widget-api 1.17.0 its `isValidUrl` compares `URL.protocol` (which is * always colon-suffixed, e.g. "https:") against the bare strings "http"/ * "https", so it rejects every URL and the parser always returns []. We build * the `Widget`s ourselves with a correct scheme check plus the existing * `isWidgetUrlSafe` origin check. */ export const widgetsFromStateEvents = ( widgetEvents: StateKeyToEvents | undefined, appOrigin: string, ): Widget[] => { if (!widgetEvents) return []; const widgets: Widget[] = []; Array.from(widgetEvents.values()).forEach((event: MatrixEvent) => { const content = event.getContent(); // Removed widgets are represented as an empty content state event. if (!content || Object.keys(content).length === 0) return; const id = event.getStateKey(); const { type, url, name, data, waitForIframeLoad } = content; const creatorUserId = content.creatorUserId || event.getSender(); if (!id || !type || !url || !creatorUserId) return; let scheme: string; try { scheme = new URL(url).protocol; } catch { return; } if (scheme !== 'https:') return; if (!isWidgetUrlSafe(url, appOrigin)) return; widgets.push( new Widget({ id, creatorUserId, type, url, name, data, waitForIframeLoad, }), ); }); return widgets; }; /** * All valid `im.vector.modular.widgets` room widgets, reactive on room state. */ export const useRoomWidgets = (room: Room): Widget[] => { const state = useRoomState(room); return useMemo( () => widgetsFromStateEvents(state.get(StateEvent.Widget), window.location.origin), [state], ); };