From bce557cc2dc767628bed6aac87301a1be7c5431b Mon Sep 17 00:00:00 2001 From: rxliuli Date: Tue, 4 Nov 2025 05:03:50 +0800 Subject: init commit --- shared/utils/src/launch/launch-client.ts | 109 +++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 shared/utils/src/launch/launch-client.ts (limited to 'shared/utils/src/launch/launch-client.ts') diff --git a/shared/utils/src/launch/launch-client.ts b/shared/utils/src/launch/launch-client.ts new file mode 100644 index 0000000..24378d6 --- /dev/null +++ b/shared/utils/src/launch/launch-client.ts @@ -0,0 +1,109 @@ +import { createClientLink } from './scheme'; +import type { Platform } from '../platform'; + +/** + * Navigator for older Microsoft (MS) browsers like Internet Explorer. + */ +type MSNavigator = Navigator & { + msLaunchUri: ( + href: string | URL, + successCallback: () => void, + failureCallback: () => void, + ) => void; +}; + +/** + * Check if the given value is an MSNavigator. + */ +function isMSNavigator(value: Partial): value is MSNavigator { + return typeof value?.msLaunchUri === 'function'; +} + +/** + * Callback for client launches. + */ +export type LaunchCallback = (result: { + link: URL; + success: boolean; +}) => void | Promise; + +/** + * Attempt to launch the native client for the given Web URL. + */ +export function launchClient( + url: string | URL, + platform: Platform, + callback: LaunchCallback = () => {}, +): void { + const { window, browser, os } = platform; + + /** URL for opening the native application */ + const link = createClientLink(url, { platform }); + + // macOS Safari + if (os.isMacOS && browser.isSafari) { + launchOnMacOS(link, platform, callback); + } + // Proprietary msLaunchUri method (IE 10+ on Windows 8+) + else if (isMSNavigator(platform.navigator)) { + platform.navigator.msLaunchUri( + String(link), + () => callback({ link, success: true }), + () => callback({ link, success: false }), + ); + } + // Other platforms + else { + try { + // on iOS, Windows and Android simply opening the href works + window!.top!.window.location.href = String(link); + callback({ link, success: true }); + } catch (e) { + // we know this is NOT installed + callback({ link, success: false }); + } + } +} + +function launchOnMacOS( + link: URL, + platform: Platform, + callback: LaunchCallback, +): void { + const { window } = platform; + + if (typeof window === 'undefined') { + callback({ link, success: false }); + return; + } + + /** Timer for blur fallback */ + let timer: number; + + /** IFrame reference for opening the client link */ + let iframe: HTMLIFrameElement | undefined; + + /** Cleanup function run after the client launch has been initiated */ + function finalize() { + clearTimeout(timer); + window!.removeEventListener('blur', finalize); + if (iframe !== undefined) { + window!.document.body.removeChild(iframe); + } + + callback({ link, success: true }); + } + + // Add an iFrame window to the current document to open the URL + iframe = window.document.createElement('iframe'); + iframe.id = 'launch-client-opener'; + iframe.style.display = 'none'; + window.document.body.appendChild(iframe); + + // Redirect the iFrame to the client link to trigger it to open + iframe.contentWindow!.location.href = String(link); + + // Wait a tiny amount of time for the client launch to appear + window.addEventListener('blur', finalize); + timer = setTimeout(finalize, 50) as unknown as number; +} -- cgit v1.2.3