import { HOST_BRIDGE_NATIVE_APP_QUERY, HOST_BRIDGE_NATIVE_APP_QUERY_KEY, HOST_BRIDGE_PUBLIC_WEB_ORIGIN, HOST_BRIDGE_PUBLIC_WEB_URL, HOST_BRIDGE_VERSION, type HostBridgeCapability, type NativeHostPlatform, } from '../../../../packages/shared/src/contracts/hostBridge'; export type MobileShellUrlOptions = { platform: Extract; hostVersion: string; capabilities: readonly HostBridgeCapability[]; }; export type MobileShellBaseWebUrlOptions = { allowLocalDevelopment?: boolean; }; export const DEFAULT_MOBILE_SHELL_WEB_URL = HOST_BRIDGE_PUBLIC_WEB_URL; export const ALLOWED_PRODUCTION_WEB_ORIGIN = HOST_BRIDGE_PUBLIC_WEB_ORIGIN; const LOCAL_DEVELOPMENT_WEB_HOSTS = new Set([ '127.0.0.1', 'localhost', '[::1]', ]); function isAllowedMobileShellBaseUrl( url: URL, options: MobileShellBaseWebUrlOptions = {}, ) { if (url.origin === ALLOWED_PRODUCTION_WEB_ORIGIN) { return true; } return Boolean(options.allowLocalDevelopment) && url.protocol === 'http:' && LOCAL_DEVELOPMENT_WEB_HOSTS.has(url.hostname); } export function resolveMobileShellBaseWebUrl( rawUrl: unknown, options: MobileShellBaseWebUrlOptions = {}, ) { if (typeof rawUrl !== 'string') { return DEFAULT_MOBILE_SHELL_WEB_URL; } const value = rawUrl.trim(); if (!value) { return DEFAULT_MOBILE_SHELL_WEB_URL; } try { const url = new URL(value); if (!isAllowedMobileShellBaseUrl(url, options)) { return DEFAULT_MOBILE_SHELL_WEB_URL; } return url.toString(); } catch { return DEFAULT_MOBILE_SHELL_WEB_URL; } } export function buildMobileShellUrl( rawUrl: string, options: MobileShellUrlOptions, baseWebUrlOptions: MobileShellBaseWebUrlOptions = {}, ) { const url = new URL(resolveMobileShellBaseWebUrl(rawUrl, baseWebUrlOptions)); url.searchParams.set( HOST_BRIDGE_NATIVE_APP_QUERY_KEY.clientRuntime, HOST_BRIDGE_NATIVE_APP_QUERY.clientRuntime, ); url.searchParams.set( HOST_BRIDGE_NATIVE_APP_QUERY_KEY.clientType, HOST_BRIDGE_NATIVE_APP_QUERY.clientType, ); url.searchParams.set( HOST_BRIDGE_NATIVE_APP_QUERY_KEY.hostShell, HOST_BRIDGE_NATIVE_APP_QUERY.hostShellExpoMobile, ); url.searchParams.set( HOST_BRIDGE_NATIVE_APP_QUERY_KEY.hostPlatform, options.platform, ); url.searchParams.set( HOST_BRIDGE_NATIVE_APP_QUERY_KEY.hostVersion, options.hostVersion, ); url.searchParams.set( HOST_BRIDGE_NATIVE_APP_QUERY_KEY.bridgeVersion, HOST_BRIDGE_VERSION.toString(), ); url.searchParams.set( HOST_BRIDGE_NATIVE_APP_QUERY_KEY.hostCapabilities, options.capabilities.join(','), ); return url.toString(); }