summaryrefslogtreecommitdiff
path: root/shared/utils/src/launch/launch-client.ts
diff options
context:
space:
mode:
Diffstat (limited to 'shared/utils/src/launch/launch-client.ts')
-rw-r--r--shared/utils/src/launch/launch-client.ts109
1 files changed, 109 insertions, 0 deletions
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<MSNavigator>): value is MSNavigator {
+ return typeof value?.msLaunchUri === 'function';
+}
+
+/**
+ * Callback for client launches.
+ */
+export type LaunchCallback = (result: {
+ link: URL;
+ success: boolean;
+}) => void | Promise<void>;
+
+/**
+ * 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;
+}