86 lines
2.8 KiB
TypeScript
86 lines
2.8 KiB
TypeScript
// Parse the URL params the bot widget host appends when loading
|
|
// experience.url. Source of truth on the host side:
|
|
// src/app/features/bots/BotWidgetEmbed.ts (getBotWidgetUrl).
|
|
// Keep this in sync if the host adds params.
|
|
|
|
export type WidgetBootstrap = {
|
|
widgetId: string;
|
|
parentUrl: string;
|
|
parentOrigin: string;
|
|
roomId: string;
|
|
userId: string;
|
|
botId: string;
|
|
botMxid: string;
|
|
/** Base URL of the bridge provisioning HTTP API (bridgev2
|
|
* `/_matrix/provision` mount behind the reverse proxy), e.g.
|
|
* `https://vojo.chat/_provision/telegram`. Empty string when the host
|
|
* config hasn't exposed it — the App renders a config-required notice
|
|
* instead of booting the transport. */
|
|
provisioningUrl: string;
|
|
theme: 'light' | 'dark';
|
|
clientLanguage: string;
|
|
};
|
|
|
|
export type BootstrapResult =
|
|
| { ok: true; bootstrap: WidgetBootstrap }
|
|
| { ok: false; missing: string[] };
|
|
|
|
const REQUIRED = ['widgetId', 'parentUrl', 'roomId', 'userId', 'botMxid'] as const;
|
|
|
|
export const readBootstrap = (search: string): BootstrapResult => {
|
|
const params = new URLSearchParams(search);
|
|
const get = (k: string) => params.get(k) ?? '';
|
|
|
|
const missing = REQUIRED.filter((k) => !params.get(k));
|
|
if (missing.length > 0) return { ok: false, missing: [...missing] };
|
|
|
|
// Origin is what the widget validates against on incoming postMessage —
|
|
// see widget-api.ts. Falling back to '*' would defeat the security
|
|
// boundary, so a malformed parentUrl bails out as a missing-param error.
|
|
let parentOrigin: string;
|
|
try {
|
|
parentOrigin = new URL(get('parentUrl')).origin;
|
|
} catch {
|
|
return { ok: false, missing: ['parentUrl'] };
|
|
}
|
|
|
|
// The host validator (catalog.ts normalizeProvisioningUrl) already
|
|
// enforces https + no embedded credentials; re-parse defensively anyway
|
|
// because this is the widget's fetch target. Malformed → '' → the App
|
|
// shows the config-required notice rather than fetching a garbage URL.
|
|
let provisioningUrl = '';
|
|
const rawProvisioning = get('provisioningUrl').trim();
|
|
if (rawProvisioning) {
|
|
try {
|
|
const parsed = new URL(rawProvisioning);
|
|
if (
|
|
!parsed.username &&
|
|
!parsed.password &&
|
|
(parsed.protocol === 'https:' || (import.meta.env.DEV && parsed.protocol === 'http:'))
|
|
) {
|
|
provisioningUrl = parsed.toString().replace(/\/+$/, '');
|
|
}
|
|
} catch {
|
|
/* keep '' */
|
|
}
|
|
}
|
|
|
|
const themeRaw = get('theme');
|
|
const theme: 'light' | 'dark' = themeRaw === 'dark' ? 'dark' : 'light';
|
|
|
|
return {
|
|
ok: true,
|
|
bootstrap: {
|
|
widgetId: get('widgetId'),
|
|
parentUrl: get('parentUrl'),
|
|
parentOrigin,
|
|
roomId: get('roomId'),
|
|
userId: get('userId'),
|
|
botId: get('botId'),
|
|
botMxid: get('botMxid'),
|
|
provisioningUrl,
|
|
theme,
|
|
clientLanguage: get('clientLanguage'),
|
|
},
|
|
};
|
|
};
|