var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _FSBLDesktop_startup, _FSBLDesktop_fdc3;
/*!
 * Copyright 2017 - 2020 by ChartIQ, Inc.
 * All rights reserved.
 */
import { FSBLDependencyManagerSingleton as FSBLDependencyManager } from "../../common/dependencyManager";
import { System } from "../../common/system";
import { FinsembleWindow } from "../../common/FinsembleWindow";
import UserNotification from "../../common/toast";
import * as applicationConstants from "../../common/constants";
import { FEAGlobals } from "../../common/Globals";
import { ConfigUtilInstance as ConfigUtils } from "../../common/configUtil";
import SystemManagerClient from "../../common/systemManagerClient";
import TestPoint from "../../common/testPoint";
import Logger from "../logger";
import InteropAdmin from "../../clients/Interop/InteropAdmin";
import { RouterClient, RouterController } from "../../clients/routerClient";
import { Startup } from "../../clients/Startup/Startup";
import { start as startSDDListeners } from "./SDDListeners";
import { getRouterConfig, getSystemStartupData, getManifestFromStartupData } from "./RouterConfig";
import AuthenticationClient from "../authenticationClient";
import { WindowClient, getControllerInstance as getWindowClientController } from "../windowClient";
import DistributedStoreClient from "../distributedStoreClient";
import AppsClient from "../appsClient";
import LauncherClient from "../launcherClient";
import DragAndDropClient from "../../deprecated/dragAndDropClient";
import LinkerClient from "../../deprecated/linkerClient";
import StorageClient from "../storageClient";
import WorkspaceClient from "../workspaceClient";
import DialogManager from "../dialogManagerClient";
import ConfigClient from "../configClient";
import HotkeyClient from "../hotkeyClient";
import SearchClient from "../searchClient";
import NotificationClient from "../notificationClient";
import { SmartDesktopClient } from "../../deprecated/SmartDesktopClient";
import { createKeys } from "../../platform/services/Interop/AuthSignVerify";
import { shutdownApplication as shutdownApplicationFromDesktop, restartApplication as restartApplicationFromDesktop, } from "../../common/Desktop";
import { FinsembleDesktopAgent } from "../Interop/FinsembleDesktopAgent";
import { windowStartup } from "./windowStartup";
import { getFSBLInfo } from "../../common/Desktop";
const { APPLICATION_STATE_CHANNEL } = applicationConstants;
let startAppAlreadyCalled = false;
/**
 * This will be set to the system manifest, retrieved from System.getOptions().
 */
let systemManifest = {};
/**
 * Extracts startup data from System.getOptions() and initializes the internal variables that
 * this module depends upon such as windowName and componentEntry. This is also where
 * StartupData is initialized. Other Finsemble code, such as FinsembleDesktopAgent, depend on
 * this StartupData.
 */
const initializeStartupData = async (startup) => {
    var _a, _b, _c;
    const systemStartupData = await getSystemStartupData();
    const authenticationToken = (_a = systemStartupData === null || systemStartupData === void 0 ? void 0 : systemStartupData.customData) === null || _a === void 0 ? void 0 : _a.authenticationToken;
    const finWindow = System.Window.getCurrent();
    const windowName = finWindow.name;
    const webContentsName = (_b = finWindow.webContentsName) !== null && _b !== void 0 ? _b : windowName;
    systemManifest = (_c = getManifestFromStartupData(systemStartupData)) !== null && _c !== void 0 ? _c : {};
    const componentEntry = systemStartupData === null || systemStartupData === void 0 ? void 0 : systemStartupData.customData;
    startup.setStartupData({ windowName, webContentsName, authenticationToken });
    return {
        windowName,
        componentEntry,
        systemStartupData,
    };
};
// Attaches a click handler to the DOM when a window is loaded.
// When something on the DOM is clicked, it checks to see if its a mailto link.
// If it is a mailto link, it sets the appropriate FSBL variable (clickedMailTo).
// This allows the beforeunload event to ignore the window close
const clickedMailToWorkaround = (fsbl) => {
    // Add an event handler to check if a mailto link was clicked on unload.
    // Essentially, if a mailto link is clicked the browser tells the OS to open the
    // default mail client, for some reason this also tells the window
    // to unload the document and its resources.
    // By setting a boolean and checking said boolean in the beforeunload/unload event
    // we can allow the action to proceed and _not_ close the window. If this event is
    // fired any other time and a mailto link _wasn't_ clicked, it will continue to close
    // NOTE: https://stackoverflow.com/questions/9740510/mailto-link-in-chrome-is-triggering-window-onbeforeunload-can-i-prevent-this
    window.addEventListener("beforeunload", (e) => {
        // Don't close the window if this was a click on a mailto link. Otherwise, reset the flag.
        if (!fsbl.clickedMailTo) {
            fsbl.windowClose();
        }
        else {
            fsbl.clickedMailTo = false;
            e.preventDefault();
        }
    });
    const body = document.querySelector("body");
    body === null || body === void 0 ? void 0 : body.addEventListener("click", (e) => {
        var _a, _b;
        // Get the target of the click event
        const target = e.target;
        // If the target of the click is an anchor tag and if the hyperlink reference
        // includes "mailto" then set clickedMailTo to true
        if (((_a = target === null || target === void 0 ? void 0 : target.tagName) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === "a" && ((_b = target === null || target === void 0 ? void 0 : target.href) === null || _b === void 0 ? void 0 : _b.includes("mailto"))) {
            // Open with the system browser to delegate to the default email client
            fsbl.System.openUrlWithBrowser(target.href);
            fsbl.clickedMailTo = true;
        }
    });
};
const maybeShouldListenForSDDUpdates = (componentEntry) => {
    var _a;
    const isBrowserView = System.isBrowserView();
    const isTitlebarWindow = System.isTitlebarWindow();
    const isTitleBar = !isBrowserView && isTitlebarWindow;
    // Customer app content should never listen for SDD updates or have theme.css injected
    // However, if it is an app with an injected titlebar then we need to inject the themes
    // so that injected titlebar windows are themable - which can leak styles into the app contents
    // If it's not an app or it's not a titlebar then go ahead and listen
    // In the future we might decide to support a config based opt-in for app content
    const shouldListenForSDDUpdates = !componentEntry.isAnApp || isTitleBar || !isBrowserView;
    if (shouldListenForSDDUpdates)
        startSDDListeners(systemManifest, (_a = componentEntry.component) === null || _a === void 0 ? void 0 : _a.type);
};
/**
 * Run any functionality that must wait until after the DOM is loaded
 */
const onDOMContentLoaded = (fsbl, componentEntry) => {
    maybeShouldListenForSDDUpdates(componentEntry);
    clickedMailToWorkaround(fsbl);
};
/**
 * Resolves when Finsemble has transitioned to the right state for the app to start. Finsemble
 * will send a "ready" state change to any apps that run in the authentication boot stage. Apps
 * that run in later stages may receive either a "ready" or "authenticated" state change notice.
 *
 * Will resolve to true if the state is authentication (which should only occur if the client
 * is running in an authentication app), otherwise false.
 */
const waitForApplicationState = async () => new Promise((resolve) => {
    const authorizationSubscriber = RouterClient.subscribe(APPLICATION_STATE_CHANNEL, (subscribeErr, response) => {
        // Immediately remove the state change subscription. We only want the first valid state change.
        RouterClient.unsubscribe(authorizationSubscriber);
        // Inside authentication apps, the "authenticated" state will never occur.
        const isAnAuthenticationComponent = response.data.state !== "authenticated" && response.data.state !== "ready";
        resolve(isAnAuthenticationComponent);
    });
});
/**
 * In desktop apps, we auto-connect to the interop service if this window/view is an app.
 * This forces the linker to immediately display.
 */
const maybeConnectFDC3 = async ({ fdc3, componentEntry, }) => {
    var _a;
    // isAnApp is set to true by launcher when launching from appd.json (rather than any other config file
    const { isAnApp, interop } = componentEntry;
    // We're a content view if we're an app and we don't have a browser view. If we had a browser view then we would be the titlebar view.
    // Note that some apps don't have WindowTitleBars, or have injected WindowTitleBars. As long as they don't have a browser view then we
    // know we're in the content view. Toolbar has browser views (dashbar) but isAnApp will be false because it is a UIComponent.
    const isContentView = isAnApp && !((_a = FEAGlobals.fin) === null || _a === void 0 ? void 0 : _a.isTitlebarWindow());
    const implicitUseLinker = isContentView;
    // The useLinker setting overrides our implicit logic for whether we have a linker
    const useLinker = interop === null || interop === void 0 ? void 0 : interop.useLinker;
    const implicitAutoConnect = (implicitUseLinker && useLinker !== false) || useLinker === true;
    // The autoConnect setting further overrides our implicit logic for whether to auto connect
    const autoConnect = interop === null || interop === void 0 ? void 0 : interop.autoConnect;
    const definitiveAutoConnect = (implicitAutoConnect && autoConnect !== false) || autoConnect === true;
    if (definitiveAutoConnect)
        await fdc3.start();
};
/**
 * Starts all client APIs other than Router and Logger. Currently this is WindowClient, DialogManagerClient
 * and the deprecated clients LinkerClient and DragAndDropClient.
 *
 * If the current component is an authentication component then linker and DragAndDrop are
 * not started because they are incompatible with that stage.
 */
const maybeStartClients = async () => {
    Logger.system.log(/*'bW9udGVyZXlzIGNvYXN0', */ `maybeStartClients.waitForApplicationState`);
    const isAuthentication = await waitForApplicationState();
    Logger.system.log(/*'bW9udGVyZXlzIGNvYXN0', */ `maybeStartClients.waitForApplicationState got`, isAuthentication);
    // Start the window client
    getWindowClientController().start();
    if (!isAuthentication) {
        // Initialize the deprecated LinkerClient and DragAndDropClients.
        await LinkerClient.start();
        DragAndDropClient.addListeners();
    }
};
export class FSBLDesktop {
    constructor({ fdc3 }) {
        this.debug = false;
        this.FinsembleWindow = FinsembleWindow;
        this.ConfigUtils = ConfigUtils;
        this.createKeys = createKeys;
        this.UserNotification = UserNotification;
        this.SystemManagerClient = SystemManagerClient;
        this.TestHooks = {
            TestPoint,
            FinsembleDesktopAgent,
            Startup,
            InteropAdmin: null,
        };
        this.clickedMailTo = false;
        this.isInitialized = false;
        this.displayStartupTimeoutError = true;
        this.dependencyManager = FSBLDependencyManager;
        this.System = System;
        _FSBLDesktop_startup.set(this, Startup.getInstance());
        /** Namespace for internal APIs */
        this.internal = {
            interopAdmin: new InteropAdmin(),
        };
        this.componentType = "undefined";
        /**
         * Apps and UI components depend on FSBL.Clients to exist during the import stage. We therefore need for these entries
         * to exist statically in the FSBL object that is exported. The clients are still initialized dynamically when FSBL.startApp()
         * is called.
         */
        // This order is _roughly_ as follows: Get everything up that is a dependency of something else, then get those up. The linker and DnD depend on the Store. Workspace depends on Storage and so on. It's not exact, but this was the combination that yielded the best results for me.
        /** @namespace Clients  */
        this.Clients = {
            RouterClient,
            AuthenticationClient,
            WindowClient,
            DistributedStoreClient,
            AppsClient,
            LauncherClient,
            DragAndDropClient,
            LinkerClient,
            StorageClient,
            WorkspaceClient,
            DialogManager,
            ConfigClient,
            HotkeyClient,
            SearchClient,
            NotificationClient,
            // This is for LTS compatibility. It provides no actual functionality.
            SmartDesktopClient,
            Logger,
        };
        _FSBLDesktop_fdc3.set(this, void 0);
        this.getFSBLInfo = getFSBLInfo;
        __classPrivateFieldSet(this, _FSBLDesktop_fdc3, fdc3, "f");
        // For security reasons, only expose InteropAdmin in TestHooks when in dev mode
        System.getFEAInfo((info) => {
            if (info.isDevMode) {
                this.TestHooks.InteropAdmin = new InteropAdmin();
            }
        });
    }
    publishReady() {
        // componentType is undefined until startApp executes. Waiting for onReady ensures that componentType is defined
        __classPrivateFieldGet(this, _FSBLDesktop_startup, "f").onReady(() => SystemManagerClient.publishBootStatus(this.componentType, "components", "completed"));
    }
    /**
     * Register to receive a callback when FSBL is ready. This is part of the pattern
     * that is recommended to app builders using the FSBL API.
     * ```
     * if (window.FSBL && FSBL.addEventListener) {
     * 	FSBL.addEventListener("onReady", init);
     * } else {
     * 	window.addEventListener("FSBLReady", init);
     * }
     * ```
     */
    addEventListener(listenerType, callback) {
        __classPrivateFieldGet(this, _FSBLDesktop_startup, "f").onReady(callback);
    }
    /**
     * Shutdown the SmartDesktop (Finsemble).
     */
    shutdownApplication(params) {
        shutdownApplicationFromDesktop();
    }
    /**
     * Restart the SmartDesktop (Finsemble).
     * @param [params]
     * @param [params.forceRestart]=false Whether to give components a chance to shutdown. ForceRestart is used during the reset sequence.
     */
    restartApplication(params) {
        restartApplicationFromDesktop(params);
    }
    /**
     * Deprecated. This is supposed to provide an optional callback during system shutdown
     * but it hasn't been functional in many versions.
     * @deprecated
     */
    onShutdown() {
        Logger.system.deprecated(`FSBL.onShutdown(). This has been non-functional for many versions.`);
    }
    /**
     * Invokes the onClose method of each API client when the window is closed.
     *
     * The three clients that currently require an onClose event are:
     * - hotkeyClient (to deregister global hotkeys that were added by this app) - better handled by the hotkeys service by listening for closed windows
     * - linkerClient (to remove distributed store listeners) - linkerClient is deprecated
     * - notificationClient (to remove notification subscriptions) - better handled by the notification service by listening for closed windows
     *
     * FinsembleWindow.closeAllWraps() - Removes listeners in the window service. - better handled by the window service itself.
     *
     * RouterClient.disconnectAll() - Removes all registered responders, listeners and subscribers. - This can probably be handled by the router service, e.g. by detecting transport disconnect or by handling window close events.
     */
    windowClose() {
        for (const clientKey in this.Clients) {
            const client = this.Clients[clientKey];
            if (client.onClose) {
                client.onClose();
            }
        }
        FinsembleWindow.closeAllWraps();
    }
    /**
     * Depending on config, signals the window service and boot system that the component/window has been successfully
     * started. This is never done inside a BrowserView (because the parent BrowserWindow will have already
     * send these signals).
     *
     * @param {string} name - The name of this instance (windowName)
     * @param {string} type - The component type of this instance
     */
    maybeSignalSuccessfulStart(name, type, waitForInitialization) {
        if (!System.isBrowserView()) {
            // Always publish this part, which is independent of startup.
            RouterClient.publish(`Finsemble.${name}.componentReady`, {
                name,
            });
            // If appD config indicates the app will not do the publish, then do it here
            if (!waitForInitialization) {
                SystemManagerClient.publishBootStatus(type, "components", "completed");
                Logger.system.log("publishBootStatus", type, "components", "completed", systemManifest.customData);
            }
            else {
                Logger.system.log(`waitForInitialization for ${name}`);
            }
        }
    }
    /**
     * Starts the client API (running in this app). This is called automatically by the preload (FSBL.js aka finsemble-desktop-api-preload.ts)
     * which is injected by the launcher service.
     *
     * Any usage of client APIs prior to this function completing is indeterminate.
     *
     * Within this function we perform a number of steps including:
     * - Extraction of startup data (from options.customData)
     * - Starting of the router
     * - Creation of the global finsembleWindow
     * - initialization of the FSBL client APIs
     * - Registering of error handlers and other miscellany
     */
    startApp() {
        return new Promise(async (resolve) => {
            var _a, _b, _c;
            const { windowName, componentEntry, systemStartupData } = await initializeStartupData(__classPrivateFieldGet(this, _FSBLDesktop_startup, "f"));
            FEAGlobals.fin.log("Preload: Completed initializeStartupData");
            Logger.console.log(`WINDOW LIFECYCLE:STARTUP: startApp invoked in ${windowName}`);
            Logger.system.log(/*'bW9udGVyZXlzIGNvYXN0', */ `WINDOW LIFECYCLE:STARTUP: startApp invoked in ${windowName}`);
            if (startAppAlreadyCalled) {
                console.warn(`FSBL.startApp called twice.`);
                return resolve(null);
            }
            startAppAlreadyCalled = true;
            // imesner: 20220405: Capture securitypolicyviolation events and log as errors.
            // This must be added before the dom is loaded in order to capture them
            // These are checked in e2e here:
            // packages\e2e-testing\features\tests\Finsemble\Functional\0_System\Content Security Policies.feature
            document.addEventListener("securitypolicyviolation", (e) => {
                Logger.system.error("securitypolicyviolation:", e);
            });
            const routerConfig = getRouterConfig({
                windowName,
                systemManifest,
            });
            await RouterController.start(routerConfig);
            FEAGlobals.fin.log("Preload: Completed RouterController.start");
            Logger.console.debug(`WINDOW LIFECYCLE:STARTUP: Router is ready in ${windowName}`);
            Logger.system.log(`WINDOW LIFECYCLE:STARTUP: Router is ready in ${windowName}, transmitting onSpawned`);
            RouterClient.transmit(`${windowName}.onSpawned`, { name: windowName }, { suppressWarnings: true });
            // Now that router is running , enable API usage tracking for System when used by FSBLDesktop
            System.enableAPIUsageTracking();
            try {
                Logger.system.log(/*'bW9udGVyZXlzIGNvYXN0', */ `getting instance for `, windowName);
                const { wrap: win } = await FinsembleWindow.getInstance({
                    name: windowName,
                });
                // Race Condition Alert: For windows opened with window.open rather than spawn (like the Toolbar menus), the fin.options
                // are only accurate right here at this moment, after having awaited for getInstance(). There's kind of
                // a chicken and egg problem as a result because we can't initialize the router client without those options
                // and we don't have the final options until they arrive from the router. Practically, this isn't a problem
                // because the options which are available at the time we initialize the router client are those of the
                // parent (opener). Ultimately, the way we use window.open needs
                // refactoring (e.g. options should be set in FEA *before* this preload is running).
                Logger.system.log(/*'bW9udGVyZXlzIGNvYXN0', */ `assigning finsembleWindow instance for `, windowName);
                FEAGlobals.finsembleWindow = win;
            }
            catch (err) {
                // We cannot get an instance of a window in a service.
                if (err !== "Cannot Wrap Service Manager or Services") {
                    Logger.console.error(`Unable to get FinsembleWindow instance for ${windowName}`, err);
                }
            }
            Logger.system.log(/*'bW9udGVyZXlzIGNvYXN0', */ `START:maybeStartClients for`, windowName);
            await maybeStartClients();
            Logger.system.log(/*'bW9udGVyZXlzIGNvYXN0', */ `START:windowStartup for`, windowName);
            await windowStartup({
                windowName,
                componentType: (_a = componentEntry.component) === null || _a === void 0 ? void 0 : _a.type,
                finsembleWindow: FEAGlobals.finsembleWindow,
                systemStartupData,
            });
            // Add the CSS class "finsemble" to the app's HTML. Apps can use this for responsive styling.
            document.getElementsByTagName("html")[0].classList.add("finsemble");
            // save type in cased needed for publishReady(), needs to happen before setReady()
            this.componentType = (_b = componentEntry.component) === null || _b === void 0 ? void 0 : _b.type;
            Logger.system.log(/*'bW9udGVyZXlzIGNvYXN0', */ `START:setReady for`, windowName);
            __classPrivateFieldGet(this, _FSBLDesktop_startup, "f").setReady();
            Logger.start();
            Logger.system.log(/*'bW9udGVyZXlzIGNvYXN0', */ `START:maybeConnectFDC3 for`, windowName);
            await maybeConnectFDC3({ fdc3: __classPrivateFieldGet(this, _FSBLDesktop_fdc3, "f"), componentEntry });
            Logger.system.log(/*'bW9udGVyZXlzIGNvYXN0', */ `WINDOW LIFECYCLE:STARTUP: FSBL Online in ${windowName}`);
            Logger.system.log(/*'bW9udGVyZXlzIGNvYXN0', */ `START:maybeSignalSuccessfulStart for`, windowName);
            this.maybeSignalSuccessfulStart(windowName, (_c = componentEntry.component) === null || _c === void 0 ? void 0 : _c.type, componentEntry.waitForInitialization);
            // Run any logic that must wait until the DOM is loaded
            if (document.readyState === "loading") {
                document.addEventListener("DOMContentLoaded", () => {
                    onDOMContentLoaded(this, componentEntry);
                });
            }
            else {
                onDOMContentLoaded(this, componentEntry);
            }
            resolve(null);
        });
    }
    /**
     * Stops the app. This primarily closes the router and terminates each of the FSBL clients.
     */
    stopApp() {
        startAppAlreadyCalled = false;
        this.windowClose();
        RouterController.stop();
    }
}
_FSBLDesktop_startup = new WeakMap(), _FSBLDesktop_fdc3 = new WeakMap();
//# sourceMappingURL=FSBLDesktop.js.map