Archipelago — open-source initial import
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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
|
||||
/** Richer launch (companion ≥0.5.26): catalog icon + display name drive the
|
||||
* native branded loader instead of the site favicon. */
|
||||
openInAppEx?: (url: string, iconUrl: string, name: string) => void
|
||||
}
|
||||
|
||||
/** Optional app identity for the native loading screen. */
|
||||
export interface InAppLaunchMeta {
|
||||
iconUrl?: string
|
||||
name?: string
|
||||
}
|
||||
|
||||
function nativeBridge(): ArchipelagoNativeBridge | undefined {
|
||||
return (window as unknown as { ArchipelagoNative?: ArchipelagoNativeBridge }).ArchipelagoNative
|
||||
}
|
||||
|
||||
/**
|
||||
* True when running inside the Android companion app (native WebView shell).
|
||||
* The shell injects `window.ArchipelagoNative`; a plain mobile browser / PWA
|
||||
* never has it.
|
||||
*/
|
||||
export function isCompanionApp(): boolean {
|
||||
const native = nativeBridge()
|
||||
return !!native && typeof native.openInApp === 'function'
|
||||
}
|
||||
|
||||
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, meta?: InAppLaunchMeta): void {
|
||||
if (!url) return
|
||||
const native = nativeBridge()
|
||||
if (native && typeof native.openInAppEx === 'function' && (meta?.iconUrl || meta?.name)) {
|
||||
// Absolutize the icon path so the native shell can fetch it directly.
|
||||
const icon = meta.iconUrl ? new URL(meta.iconUrl, window.location.origin).href : ''
|
||||
native.openInAppEx(url, icon, meta.name ?? '')
|
||||
return
|
||||
}
|
||||
if (native && typeof native.openInApp === 'function') {
|
||||
native.openInApp(url)
|
||||
return
|
||||
}
|
||||
window.open(url, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
Reference in New Issue
Block a user