/** * Open a URL in the device's real browser. * * In a normal mobile/desktop browser this is just `window.open(_blank)`. Inside * the Android companion app the page runs in a WebView where `window.open` is * unreliable (noopener/noreferrer can suppress onCreateWindow), so the native * shell injects a `window.ArchipelagoNative.openExternal(url)` bridge that hands * the URL to an ACTION_VIEW intent. We prefer the bridge when present and fall * back to `window.open` otherwise — so the working mobile-browser path is * untouched. */ interface ArchipelagoNativeBridge { openExternal?: (url: string) => void openInApp?: (url: string) => void } function nativeBridge(): ArchipelagoNativeBridge | undefined { return (window as unknown as { ArchipelagoNative?: ArchipelagoNativeBridge }).ArchipelagoNative } export function openExternalUrl(url: string): void { if (!url) return const native = nativeBridge() if (native && typeof native.openExternal === 'function') { native.openExternal(url) return } window.open(url, '_blank', 'noopener,noreferrer') } /** * Launch an app that can't be embedded in an iframe (X-Frame-Options) from a * mobile surface — with NO "this app opens in a tab" interstitial. * * - Android companion: hand it to the in-app WebView (`openInApp`) so it stays * inside Archipelago with the native back/forward/reload/close controls. * - Plain mobile browser (PWA): open directly in a new browser tab. */ export function openInAppOrNewTab(url: string): void { if (!url) return const native = nativeBridge() if (native && typeof native.openInApp === 'function') { native.openInApp(url) return } window.open(url, '_blank', 'noopener,noreferrer') }