import { requireNativeModule, type EventSubscription } from 'expo-modules-core'; export type LaunchMode = | 'normal' | 'cct-normal' // Custom-Tabs-shaped normal launch (toggle on home screen) | 'cct-private' // Custom-Tabs-shaped private launch (CCT toggle + Private button) // Sends multiple hints: ENABLE_EPHEMERAL_BROWSING (Chrome 137+), // EXTRA_OPEN_NEW_INCOGNITO_TAB (Brave/Edge/Vivaldi). // Browser honors whichever it understands; Firefox ignores both. | 'firefox-normal' // Firefox/IronFox — forced non-private via CCT shape (legacy, unused) | 'chromium-class' // Chrome — IncognitoTabLauncher class with URL via intent.data | 'chromium-extra' // Brave/Vivaldi/Edge — VIEW + EXTRA_OPEN_NEW_INCOGNITO_TAB cascade | 'firefox-private'; type DefaultBrowserModuleType = { isDefaultBrowser(): boolean; getDefaultBrowserPackage(): string | null; getInstalledBrowsers(): string[]; getBrowserIcon(pkg: string): string | null; getBrowserActivities(pkg: string): string[]; getInitialSharedText(): string | null; getInitialViewUrl(): string | null; markViewUrlConsumed(): void; markSharedTextConsumed(): void; openInBrowser( url: string, pkg: string, mode: LaunchMode | null, extras: { key: string; type: 'bool' | 'string' | 'int'; value: string | boolean | number }[] | null, ): boolean; launchPackage(pkg: string): boolean; getBoolPref(key: string, defaultValue: boolean): boolean; setBoolPref(key: string, value: boolean): void; getStringPref(key: string, defaultValue: string): string; setStringPref(key: string, value: string): void; addListener(event: 'onSharedTextChanged', listener: (e: { text: string }) => void): EventSubscription; addListener(event: 'onIncomingUrl', listener: (e: { url: string }) => void): EventSubscription; }; const DefaultBrowser = requireNativeModule('DefaultBrowser'); export function isDefaultBrowser(): boolean { return DefaultBrowser.isDefaultBrowser(); } export function getDefaultBrowserPackage(): string | null { return DefaultBrowser.getDefaultBrowserPackage(); } export function getInstalledBrowsers(): string[] { return DefaultBrowser.getInstalledBrowsers(); } export function getBrowserIcon(pkg: string): string | null { return DefaultBrowser.getBrowserIcon(pkg); } export function getBrowserActivities(pkg: string): string[] { return DefaultBrowser.getBrowserActivities(pkg); } export function getInitialSharedText(): string | null { return DefaultBrowser.getInitialSharedText(); } /** * Symmetric to getInitialSharedText, but reads the URL from an * ACTION_VIEW intent (clicking an http/https link in another app). * Returns the URL once then self-clears, so polling on every foreground * transition is safe. Used as a safety net for the case where * expo-linking's onNewIntent listener drops the first delivery after * the activity is brought to front. */ export function getInitialViewUrl(): string | null { return DefaultBrowser.getInitialViewUrl(); } /** * Mark the native VIEW / SEND intent slots consumed without reading them. * Call right after handling a URL delivered by the onIncomingUrl / * onSharedTextChanged event, so the AppState-resume safety drain * (getInitial*) doesn't later re-serve the same already-handled intent * and overwrite the URL field with the stale link. */ export function markViewUrlConsumed(): void { DefaultBrowser.markViewUrlConsumed(); } export function markSharedTextConsumed(): void { DefaultBrowser.markSharedTextConsumed(); } export function openInBrowser( url: string, pkg: string, mode: LaunchMode | null = 'normal', extras: { key: string; type: 'bool' | 'string' | 'int'; value: string | boolean | number }[] | null = null, ): boolean { return DefaultBrowser.openInBrowser(url, pkg, mode, extras); } /** * Launch a browser by its package's MAIN/LAUNCHER intent — no URL. * Used by the "Browser config" preset to open the browser to its home * screen so the user can paste a clipboarded internal-scheme URL * (chrome://flags, brave://flags, about:config) into the address bar. * Internal schemes can't reach the browser via VIEW intent, so this * + Clipboard is the only path that works. */ export function launchPackage(pkg: string): boolean { return DefaultBrowser.launchPackage(pkg); } export function getBoolPref(key: string, defaultValue: boolean): boolean { return DefaultBrowser.getBoolPref(key, defaultValue); } export function setBoolPref(key: string, value: boolean): void { DefaultBrowser.setBoolPref(key, value); } export function getStringPref(key: string, defaultValue: string): string { return DefaultBrowser.getStringPref(key, defaultValue); } export function setStringPref(key: string, value: string): void { DefaultBrowser.setStringPref(key, value); } /** * Subscribe to fresh SEND intents while the app is already running. * Fires when the system delivers a new intent (typical share-sheet flow * into an already-resumed activity) so the JS layer doesn't have to wait * for an AppState transition to poll getInitialSharedText. */ export function onSharedTextChanged(listener: (text: string) => void): EventSubscription { return DefaultBrowser.addListener('onSharedTextChanged', (e) => listener(e.text)); } /** * Subscribe to fresh ACTION_VIEW intents while the app is already * running. Fires alongside expo-linking's 'url' event — both pipes * funnel into the same `consumeIncoming` dedup so duplicates are * harmless. Belt-and-suspenders for the case where expo-linking misses * the first delivery after a cold-to-warm transition. */ export function onIncomingUrl(listener: (url: string) => void): EventSubscription { return DefaultBrowser.addListener('onIncomingUrl', (e) => listener(e.url)); }