launch-client.ts
1 import { createClientLink } from './scheme'; 2 import type { Platform } from '../platform'; 3 4 /** 5 * Navigator for older Microsoft (MS) browsers like Internet Explorer. 6 */ 7 type MSNavigator = Navigator & { 8 msLaunchUri: ( 9 href: string | URL, 10 successCallback: () => void, 11 failureCallback: () => void, 12 ) => void; 13 }; 14 15 /** 16 * Check if the given value is an MSNavigator. 17 */ 18 function isMSNavigator(value: Partial<MSNavigator>): value is MSNavigator { 19 return typeof value?.msLaunchUri === 'function'; 20 } 21 22 /** 23 * Callback for client launches. 24 */ 25 export type LaunchCallback = (result: { 26 link: URL; 27 success: boolean; 28 }) => void | Promise<void>; 29 30 /** 31 * Attempt to launch the native client for the given Web URL. 32 */ 33 export function launchClient( 34 url: string | URL, 35 platform: Platform, 36 callback: LaunchCallback = () => {}, 37 ): void { 38 const { window, browser, os } = platform; 39 40 /** URL for opening the native application */ 41 const link = createClientLink(url, { platform }); 42 43 // macOS Safari 44 if (os.isMacOS && browser.isSafari) { 45 launchOnMacOS(link, platform, callback); 46 } 47 // Proprietary msLaunchUri method (IE 10+ on Windows 8+) 48 else if (isMSNavigator(platform.navigator)) { 49 platform.navigator.msLaunchUri( 50 String(link), 51 () => callback({ link, success: true }), 52 () => callback({ link, success: false }), 53 ); 54 } 55 // Other platforms 56 else { 57 try { 58 // on iOS, Windows and Android simply opening the href works 59 window!.top!.window.location.href = String(link); 60 callback({ link, success: true }); 61 } catch (e) { 62 // we know this is NOT installed 63 callback({ link, success: false }); 64 } 65 } 66 } 67 68 function launchOnMacOS( 69 link: URL, 70 platform: Platform, 71 callback: LaunchCallback, 72 ): void { 73 const { window } = platform; 74 75 if (typeof window === 'undefined') { 76 callback({ link, success: false }); 77 return; 78 } 79 80 /** Timer for blur fallback */ 81 let timer: number; 82 83 /** IFrame reference for opening the client link */ 84 let iframe: HTMLIFrameElement | undefined; 85 86 /** Cleanup function run after the client launch has been initiated */ 87 function finalize() { 88 clearTimeout(timer); 89 window!.removeEventListener('blur', finalize); 90 if (iframe !== undefined) { 91 window!.document.body.removeChild(iframe); 92 } 93 94 callback({ link, success: true }); 95 } 96 97 // Add an iFrame window to the current document to open the URL 98 iframe = window.document.createElement('iframe'); 99 iframe.id = 'launch-client-opener'; 100 iframe.style.display = 'none'; 101 window.document.body.appendChild(iframe); 102 103 // Redirect the iFrame to the client link to trigger it to open 104 iframe.contentWindow!.location.href = String(link); 105 106 // Wait a tiny amount of time for the client launch to appear 107 window.addEventListener('blur', finalize); 108 timer = setTimeout(finalize, 50) as unknown as number; 109 }