import { Capability, MatrixCapabilities } from 'matrix-widget-api'; // Conservative v1 capability policy: approve only benign display capabilities. // Everything else (room-event send/receive, to-device, uploads, user-directory, // delayed events, TURN servers) is denied — a random widget must not be able to // act as the user or read room data without an explicit consent flow (follow-up). export const ALLOWED_WIDGET_CAPABILITIES: ReadonlySet = new Set([ MatrixCapabilities.AlwaysOnScreen, MatrixCapabilities.RequiresClient, MatrixCapabilities.Screenshots, ]); export const filterWidgetCapabilities = (requested: Set): Set => new Set([...requested].filter((cap) => ALLOWED_WIDGET_CAPABILITIES.has(cap))); export type WidgetUrlError = 'empty' | 'invalid' | 'not-https' | 'same-origin'; // A widget URL to ADD must be https and NOT our own origin: a same-origin frame // with allow-same-origin + allow-scripts can break out of the sandbox against us. export const validateWidgetUrl = (raw: string, appOrigin: string): WidgetUrlError | undefined => { const trimmed = raw.trim(); if (!trimmed) return 'empty'; let url: URL; try { url = new URL(trimmed); } catch { return 'invalid'; } if (url.protocol !== 'https:') return 'not-https'; if (url.origin === appOrigin) return 'same-origin'; return undefined; }; // Is an already-resolved (complete) widget URL safe to render in a sandboxed // iframe that carries allow-same-origin? Rejects same-origin URLs (breakout). export const isWidgetUrlSafe = (completeUrl: string, appOrigin: string): boolean => { try { return new URL(completeUrl).origin !== appOrigin; } catch { return false; } }; export const generateWidgetId = (): string => `lotus_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;