/** * [Gitea #43] Where the Element Call page is loaded from. * * By default it's the copy bundled with this app (same origin). With * `elementCallUrl` in config.json (e.g. * "https://call.chat.lotusguild.org/public/element-call/index.html") the web * app loads it from that origin instead, so the call frame can no longer * reach this origin's storage (login token, crypto store) or service worker. * * Web only: the desktop app keeps its bundled copy (a network copy could * drift from the bundle); see resolveDesktopCallPageUrl for how it isolates * it. Anything that isn't an absolute https URL (http only on localhost, for * development) is ignored, so a bad value falls * back to the bundled page instead of breaking calls. */ export const resolveCallPageUrl = (value: unknown, desktop: boolean): string | undefined => { if (desktop || typeof value !== 'string' || value.trim() === '') return undefined; try { const url = new URL(value); // http only for local development (localhost is a secure context). const local = url.hostname === 'localhost' || url.hostname === '127.0.0.1'; if (url.protocol !== 'https:' && !(url.protocol === 'http:' && local)) return undefined; url.search = ''; url.hash = ''; return url.href; } catch { return undefined; } }; /** * [Gitea #43] Desktop: the bundled call page from a second origin. * * The desktop app is served by its local server at http://localhost:. * The same server answers on http://127.0.0.1:, which is a different * origin (and still a secure context), so loading the bundled call page from * there cuts the call frame off from the app's storage (login token, crypto * store) without a network copy that could drift from the bundle. * * Only used when cinny-desktop sets `desktopCallOrigin` (it ships the server * and CSP changes this needs in the same release), only for a loopback http * origin on the SAME port as the app, and only when the app itself runs on * http://localhost (release builds). Anything else keeps the same-origin page. */ export const resolveDesktopCallPageUrl = ( value: unknown, appOrigin: string, basePath: string, ): string | undefined => { if (typeof value !== 'string' || value.trim() === '') return undefined; try { const app = new URL(appOrigin); const call = new URL(value); if (app.protocol !== 'http:' || app.hostname !== 'localhost' || !app.port) return undefined; if (call.protocol !== 'http:' || call.hostname !== '127.0.0.1') return undefined; if (call.port !== app.port || call.username || call.password) return undefined; if (call.pathname !== '/' || call.search || call.hash) return undefined; const base = basePath.replace(/\/+$/, ''); return `${call.origin}${base}/public/element-call/index.html`; } catch { return undefined; } }; let callPageUrl: string | undefined; export const setCallPageUrl = (url: string | undefined): void => { callPageUrl = url; }; export const getCallPageUrl = (): string | undefined => callPageUrl;