summaryrefslogtreecommitdiff
path: root/shared/utils/src/launch/launch-client.ts
blob: 24378d6282436f6f64e6edcedb04507d7ad8cc5f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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;
}