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 __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 _HeartbeatMonitor_declareDead, _HeartbeatMonitor_timeoutHandle, _HeartbeatMonitor_disconnectHandlers, _ChannelProxy_desktopAgent, _FinsembleDesktopAgent_connected, _FinsembleDesktopAgent_heartbeatMonitor, _FinsembleDesktopAgent_startHasBeenCalled, _FinsembleDesktopAgent_uuid, _FinsembleDesktopAgent_appId, _FinsembleDesktopAgent_clientChannel, _FinsembleDesktopAgent_intentHandlers, _FinsembleDesktopAgent_contextHandlers, _FinsembleDesktopAgent_privateChannelSubscription, _FinsembleDesktopAgent_intentResultPromiseMap, _FinsembleDesktopAgent_intentResultPromiseResolveMap, _FinsembleDesktopAgent_queue, _FinsembleDesktopAgent_routerClient, _FinsembleDesktopAgent_startup, _FinsembleDesktopAgent_windowName, _FinsembleDesktopAgent_existingChannelProxy, _FinsembleDesktopAgent_FSBLVersion, _FinsembleDesktopAgent_userChannels;
/**
 * Regarding 1.2 compatibility. FDC3 1.2 defined some functions as synchronous which are now asynchronous. To provide
 * backward compatibility for these functions, we return a Promise which has additional members attached (usually an
 * unsubscribe function). Apps using either 1.2 or 2.0 will receive an interface that is a superset of what they expect.
 *
 * The resulting code is complex. It should be aggressively removed when FDC3 1.2 is retired, back to simple async functions
 * that return Promise<Listener>.
 *
 * For Typescript support, 1.2 apps can simply install the 1.2 version of the "@finos/fdc3" npm module
 */
import uuidv4 from "uuid-random";
import { OpenError, ResolveError, ResultError, ChannelError, } from "@finos/fdc3";
import Logger from "../logger";
import ConfigClient from "../configClient";
import SystemManagerClient from "../../common/systemManagerClient";
import { Globals } from "../../common/Globals";
import { SERVER_CHANNEL, EMBEDDED_TITLEBAR_INDICATOR_STRING } from "./types";
import { mmAdd, mmRemove, errorAsString, } from "./types";
// Timeout value for logging warning when intent response is delayed.
const WaitForIntentResultTimeout = 5000;
const PrivateChannelIdPrefix = "Private-Channel-Id";
export const isPrivateChannelId = (channelId) => channelId.includes(PrivateChannelIdPrefix);
/**
 * Heartbeats are used by the InteropService to detect dead fdc3 client connections. A connection
 * can die if a window crashes or is terminated. Heartbeats are also currently the only way to detect
 * that freestanding apps have closed, even gracefully.
 */
class HeartbeatMonitor {
    constructor() {
        // If Finsemble sleeps (power saving mode) then heartbeats will slow to once per minute (this is a browser thing)
        // So we only declare an app dead if a heartbeat isn't received for two minutes, even though normally we should
        // expect to receive one every 10 seconds. Hopefully in the future we will have more reliable, router disconnection
        // capabilities based on underlying transports such as WebSocket.
        _HeartbeatMonitor_declareDead.set(this, 2 * 60 * 1000);
        _HeartbeatMonitor_timeoutHandle.set(this, null);
        // Contains onDisconnect()'s registered handlers
        _HeartbeatMonitor_disconnectHandlers.set(this, []);
    }
    start() {
        this.registerHeartbeat();
    }
    registerHeartbeat() {
        // Got a heartbeat, so clear our death timer
        clearTimeout(__classPrivateFieldGet(this, _HeartbeatMonitor_timeoutHandle, "f"));
        Logger.system.debug(`FinsembleDesktopAgent: heartbeat received`);
        // And start a new one
        __classPrivateFieldSet(this, _HeartbeatMonitor_timeoutHandle, setTimeout(() => {
            Logger.system.warn(`FinsembleDesktopAgent: No heartbeat received within ${__classPrivateFieldGet(this, _HeartbeatMonitor_declareDead, "f")}  milliseconds`);
            this.notifyDisconnect();
        }, __classPrivateFieldGet(this, _HeartbeatMonitor_declareDead, "f")), "f");
    }
    // Called when either app heartbeat fails or router transport fails.
    notifyDisconnect() {
        console.warn("fdc3 has lost connectivity to the interop service. You may need to restart this app.");
        __classPrivateFieldGet(this, _HeartbeatMonitor_disconnectHandlers, "f").forEach((handler) => {
            handler(`fdc3 has lost connectivity to the interop service`);
        });
    }
    onDisconnect(cb) {
        __classPrivateFieldSet(this, _HeartbeatMonitor_disconnectHandlers, __classPrivateFieldGet(this, _HeartbeatMonitor_disconnectHandlers, "f").concat(cb), "f");
    }
    stop() {
        clearTimeout(__classPrivateFieldGet(this, _HeartbeatMonitor_timeoutHandle, "f"));
    }
}
_HeartbeatMonitor_declareDead = new WeakMap(), _HeartbeatMonitor_timeoutHandle = new WeakMap(), _HeartbeatMonitor_disconnectHandlers = new WeakMap();
/**
 * Implements FDC3's Channel interface. All actual transmissions still occur through the desktop agent via private functions.
 *
 * First pass the ChannelProxy implements PrivateChannel, which is a superset of Channel -- may refine this in second pass.
 */
class ChannelProxy {
    constructor(desktopAgent, id, type = "app", displayMetadata) {
        _ChannelProxy_desktopAgent.set(this, void 0);
        __classPrivateFieldSet(this, _ChannelProxy_desktopAgent, desktopAgent, "f");
        this.id = id;
        this.type = type;
        this.displayMetadata = displayMetadata;
    }
    /**
     * Broadcasts the given context on this channel. This is equivalent to joining the channel and then calling the
     * top-level FDC3 `broadcast` function.
     *
     * Note that this function can be used without first joining the channel, allowing applications to broadcast on
     * channels that they aren't a member of.
     *
     * `Error` with a string from the `ChannelError` enumeration.
     */
    async broadcast(context) {
        if (!context || !context.type) {
            throw new Error("Invalid arguments. An FDC3 Context object (i.e. an Object with a type field) is required.");
        }
        await __classPrivateFieldGet(this, _ChannelProxy_desktopAgent, "f").channelBroadcast(this.id, context, this.type);
    }
    /**
     * Returns the last context that was broadcast on this channel. All channels initially have no context, until a
     * context is broadcast on the channel. If there is not yet any context on the channel, this method
     * will return `null`.
     *
     * The context of a channel will be captured regardless of how the context is broadcasted on this channel - whether
     * using the top-level FDC3 `broadcast` function, or using the channel-level {@link broadcast} function on this
     * object.
     *
     * Optionally a {@link contextType} can be provided, in which case the current context of the matching type will
     * be returned (if any). Desktop agent implementations may decide to record contexts by type, in which case it will
     * be possible to get the most recent context of the type specified, but this is not guaranteed.
     *
     * `Error` with a string from the `ChannelError` enumeration.
     */
    getCurrentContext(contextType) {
        return __classPrivateFieldGet(this, _ChannelProxy_desktopAgent, "f").channelGetCurrentContext(this.id, contextType);
    }
    /**
     * Adds a listener for incoming contexts whenever a broadcast happens on this channel.
     */
    // @ts-ignore <---------------------------------------------------------------------------------------------------------------------
    addContextListener(contextTypeOrHandler, handler) {
        if (contextTypeOrHandler === undefined) {
            throw new Error("Invalid arguments. The contextTypeOrHandler parameter shouldn't be undefined.");
        }
        if (typeof contextTypeOrHandler !== "function" && !handler) {
            const msg = "channel addContextListener Invalid arguments. The required handler parameter is missing.";
            console.error(msg, contextTypeOrHandler, handler);
            Logger.system.error(msg);
            throw new Error(msg);
        }
        // Dangerous code: See note at top of file about 1.2 compatibility
        const { exchangePromise, unsubscribe } = __classPrivateFieldGet(this, _ChannelProxy_desktopAgent, "f").channelAddContextListener(this.id, contextTypeOrHandler, handler);
        let promise = new Promise((resolve) => {
            exchangePromise.then(() => {
                resolve({
                    unsubscribe,
                });
            });
        });
        promise.unsubscribe = () => {
            Logger.system.deprecated("Use of the synchronous version of addContextListener from FDC3 1.2 is deprecated. Please switch to `await addContextListener`.");
            unsubscribe();
        };
        return promise;
    }
    /**
     * Adds a listener that will be called each time that the remote app invokes
     * addContextListener on this channel.
     *
     * Desktop Agents MUST call this for each invocation of addContextListener on this
     * channel, including those that occurred before this handler was registered
     * (to prevent race conditions).
     */
    onAddContextListener(handler) {
        return __classPrivateFieldGet(this, _ChannelProxy_desktopAgent, "f").channelOnAddContextListener(this.id, handler);
    }
    /**
     * Adds a listener that will be called whenever the remote app invokes
     * Listener.unsubscribe() on a context listener that it previously added.
     *
     * Desktop Agents MUST call this when disconnect() is called by the other party, for
     * each listener that they had added.
     */
    onUnsubscribe(handler) {
        return __classPrivateFieldGet(this, _ChannelProxy_desktopAgent, "f").channelOnUnsubscribe(this.id, handler);
    }
    /**
     * Adds a listener that will be called when the remote app terminates, for example
     * when its window is closed or because disconnect was called. This is in addition
     * to calls that will be made to onUnsubscribe listeners.
     */
    onDisconnect(handler) {
        return __classPrivateFieldGet(this, _ChannelProxy_desktopAgent, "f").channelOnDisconnect(this.id, handler);
    }
    /**
     * May be called to indicate that a participant will no longer interact with this channel.
     *
     * After this function has been called, Desktop Agents SHOULD prevent apps from broadcasting
     * on this channel and MUST automatically call Listener.unsubscribe() for each listener that
     * they've added (causing any onUnsubscribe handler added by the other party to be called)
     * before triggering any onDisconnect handler added by the other party.
     */
    disconnect() {
        return __classPrivateFieldGet(this, _ChannelProxy_desktopAgent, "f").channelDisconnect(this.id);
    }
}
_ChannelProxy_desktopAgent = new WeakMap();
/**
 * See https://stackoverflow.com/questions/45847399/friend-class-in-typescript for implementation of "friend" class
 */
export class FinsembleDesktopAgent {
    constructor({ routerClient, startup, windowName, }) {
        _FinsembleDesktopAgent_connected.set(this, false);
        _FinsembleDesktopAgent_heartbeatMonitor.set(this, new HeartbeatMonitor());
        // A lock that prevents start() from running multiple times
        _FinsembleDesktopAgent_startHasBeenCalled.set(this, false);
        _FinsembleDesktopAgent_uuid.set(this, "undefined");
        _FinsembleDesktopAgent_appId.set(this, "undefined");
        _FinsembleDesktopAgent_clientChannel.set(this, `FSBL.FDC3.Client.${uuidv4()}`);
        _FinsembleDesktopAgent_intentHandlers.set(this, {});
        _FinsembleDesktopAgent_contextHandlers.set(this, {});
        _FinsembleDesktopAgent_privateChannelSubscription.set(this, {});
        _FinsembleDesktopAgent_intentResultPromiseMap.set(this, {});
        _FinsembleDesktopAgent_intentResultPromiseResolveMap.set(this, {});
        // Stores fdc3 calls that are received before a connection is established with the interop service
        // Once a connection is established then the queue is released.
        _FinsembleDesktopAgent_queue.set(this, []);
        _FinsembleDesktopAgent_routerClient.set(this, void 0);
        _FinsembleDesktopAgent_startup.set(this, void 0);
        // Can be used to override the default windowName, such as for testing purposes
        _FinsembleDesktopAgent_windowName.set(this, void 0);
        _FinsembleDesktopAgent_existingChannelProxy.set(this, {});
        _FinsembleDesktopAgent_FSBLVersion.set(this, "8.6");
        _FinsembleDesktopAgent_userChannels.set(this, null);
        __classPrivateFieldSet(this, _FinsembleDesktopAgent_routerClient, routerClient, "f");
        __classPrivateFieldSet(this, _FinsembleDesktopAgent_startup, startup, "f");
        __classPrivateFieldSet(this, _FinsembleDesktopAgent_windowName, windowName, "f");
        __classPrivateFieldGet(this, _FinsembleDesktopAgent_routerClient, "f").onFailure(() => {
            Logger.console.error("Router failure: transport timeout error");
            __classPrivateFieldGet(this, _FinsembleDesktopAgent_heartbeatMonitor, "f").notifyDisconnect();
        });
        // Per spec, dispatch this event as soon as the global fdc3 object exists and is ready to accept API calls.
        // We should not wait until start() because in some circumstances, start() is not called *until* an API call is made.
        // Skip this if the global Event class is undefined (if running in Node.js such as a unit test). Note: also needed to confirm window.dispatchEvent was available, perhaps because of latest Node version.
        if (Globals.Event && Globals.dispatchEvent) {
            const event = new Globals.Event("fdc3Ready");
            Globals.dispatchEvent(event);
        }
    }
    privateChannelId() {
        return `${PrivateChannelIdPrefix}-${uuidv4()}`;
    }
    async isUserChannel(channelIdValue) {
        // the first time this function is called, cache the user channels for future use
        if (__classPrivateFieldGet(this, _FinsembleDesktopAgent_userChannels, "f") === null) {
            __classPrivateFieldSet(this, _FinsembleDesktopAgent_userChannels, await this.getUserChannels(), "f");
        }
        return __classPrivateFieldGet(this, _FinsembleDesktopAgent_userChannels, "f").find((channelObject) => channelObject.id === channelIdValue) !== undefined;
    }
    // This wrapper around startup data is necessary to synchronize access to it since it might not have the correct data until startup.onReady() fires.
    async fetchStartupData() {
        await __classPrivateFieldGet(this, _FinsembleDesktopAgent_startup, "f").onReady();
        const startupData = __classPrivateFieldGet(this, _FinsembleDesktopAgent_startup, "f").getStartupData();
        const { authenticationToken, windowName } = startupData;
        const maybeEmbeddedTitlebarIndicator = startupData.webContentsName.includes(EMBEDDED_TITLEBAR_INDICATOR_STRING)
            ? EMBEDDED_TITLEBAR_INDICATOR_STRING
            : "";
        // The uuid has the appId encoded for diagnostics, but appId is not available until startup data is ready; therefore must set uuid here the first time this function is called
        if (__classPrivateFieldGet(this, _FinsembleDesktopAgent_uuid, "f") === "undefined") {
            __classPrivateFieldSet(this, _FinsembleDesktopAgent_uuid, `fdc3-instanceId-${maybeEmbeddedTitlebarIndicator}${authenticationToken.meta.appId}=${uuidv4()}`, "f");
            __classPrivateFieldSet(this, _FinsembleDesktopAgent_appId, authenticationToken.meta.appId, "f");
        }
        const appMetadata = { ...authenticationToken.meta, instanceId: __classPrivateFieldGet(this, _FinsembleDesktopAgent_uuid, "f") };
        Logger.system.debug(`fetchStartupData`, startupData, windowName, authenticationToken, appMetadata);
        return { windowName, authenticationToken, appMetadata };
    }
    meta() {
        return {
            appId: __classPrivateFieldGet(this, _FinsembleDesktopAgent_appId, "f"),
            uuid: __classPrivateFieldGet(this, _FinsembleDesktopAgent_uuid, "f"),
            href: window.document ? document.location.href : "",
        };
    }
    handleHeartbeat() {
        __classPrivateFieldGet(this, _FinsembleDesktopAgent_heartbeatMonitor, "f").registerHeartbeat();
    }
    triggerContextListeners(message) {
        var _a, _b;
        const { context } = message.payload;
        let contextMetadata = undefined;
        let handlers;
        // if the originator was an app, then fill in the contextMetatdata to be passed to context listener
        if (message.meta.originatorAppId && message.meta.originatorUuid) {
            contextMetadata = { source: { appId: message.meta.originatorAppId, instanceId: message.meta.originatorUuid } };
        }
        // The targetSubscriberId field is set only when contexts are from an autoBroadcast. During autoBroadcast, one context is received for a specific
        // subscriber (for addContextListener), or multiple contexts may be received in rapid fire (for joinChannel with existing subscribers).
        // For all cases only the handler identified by targetSubscriberId is triggered.
        //
        // The need for targetSubscriberId is due to a channel having multiple listeners (based on context), with each of these context listeners potentially
        // having multiple handlers. Without targetSubscriberId, autoBroadcast would go to all listeners, which isn't correct (ambiguity causes duplicate triggering).
        //
        // For a specific example, if the app adds a second global listener for any context (i.e. "*") then only that one new listener should trigger
        // when the corresponding autoBroadcast message arrives with the latest context; targetSubscriberId ensures this by uniquely identifying
        // the listener and it corresponding handler.
        if (message.meta.targetSubscriberId) {
            let targetHandlerSet;
            // Find the subscriber's handler and save in targetHandlerSet. (Alternatively, could have created a direct mapping, but reusing the existing data structure.)
            // This code can be converted to flatMap now that we have ES2020 support.
            Object.values(__classPrivateFieldGet(this, _FinsembleDesktopAgent_contextHandlers, "f")).every((handlersByType) => {
                targetHandlerSet = handlersByType.find((handlerSet) => handlerSet.targetSubscriberId === message.meta.targetSubscriberId);
                return targetHandlerSet === undefined;
            });
            // there is only one handler for the autoBroadcast path, but next section of code expects an array for the general case.
            handlers = targetHandlerSet ? [targetHandlerSet] : [];
        }
        else {
            // get all registered handlers, for this context type or global handlers
            handlers = ((_a = __classPrivateFieldGet(this, _FinsembleDesktopAgent_contextHandlers, "f")[context === null || context === void 0 ? void 0 : context.type]) !== null && _a !== void 0 ? _a : []).concat((_b = __classPrivateFieldGet(this, _FinsembleDesktopAgent_contextHandlers, "f")["*"]) !== null && _b !== void 0 ? _b : []);
        }
        handlers.forEach(async ({ handler, channel }) => {
            var _a, _b;
            // Note 1: if meta.channel set then broadcast addressed to a app channel; otherwise addressed to a user channel.
            // Note 2: if this handler's "channel" variable is set, then the handler is for an app channel; otherwise it is for a user channel
            // If one of the following cases matches then invoke the handler
            //   1) if broadcast addressed to a specified app channel and the handler is for that channel, then invoke it, or
            //   2) if broadcast addressed to a user channel and the handler is for a user channel, then invoke it, or
            //   3) if broadcast addressed to a user channel and the handler is an "app channel proxy" for a user channel, then invoke it
            //   4) if broadcast addressed to a specified app channel that is a user channel and handler is for joined user channel,
            //		then invoke handler
            if ((message.meta.channel && message.meta.channel === channel) ||
                (!message.meta.channel && !channel) ||
                (!message.meta.channel &&
                    (await this.isUserChannel(channel)) &&
                    ((_a = (await this.getCurrentChannels())) === null || _a === void 0 ? void 0 : _a.find((element) => element.id === channel))) ||
                (message.meta.channel &&
                    !channel &&
                    (await this.isUserChannel(message.meta.channel)) &&
                    ((_b = (await this.getCurrentChannels())) === null || _b === void 0 ? void 0 : _b.find((element) => element.id === message.meta.channel)))) {
                Logger.system.debug(`FDC3 Client: triggerContextListeners for all on ${channel !== null && channel !== void 0 ? channel : "default"} channel`, message.type, message);
                // invoke the addContextListener handler
                handler(context, contextMetadata);
            }
            else {
                Logger.system.debug(`FDC3 Client: triggerContextListeners skipped for all on ${channel !== null && channel !== void 0 ? channel : "default"} channel`, message.type, message);
            }
        });
    }
    // See https://fdc3.finos.org/docs/api/ref/DesktopAgent#examples-1 for supported examples of intents with various return characteristics
    async triggerIntentListener(message) {
        const { intent, context, requestId } = message.payload;
        let contextMetadata = undefined;
        const handler = __classPrivateFieldGet(this, _FinsembleDesktopAgent_intentHandlers, "f")[intent];
        let intentResultError;
        let result = null;
        let localIntentResult = undefined;
        // if the originator was an app, then fill in the contextMetatdata to be passed to intent listener
        if (message.meta.originatorAppId && message.meta.originatorUuid) {
            contextMetadata = { source: { appId: message.meta.originatorAppId, instanceId: message.meta.originatorUuid } };
        }
        if (typeof handler === "function") {
            // Set timer to issue warning if handler doesn't return in expect time period (but don't fail since handler could be interacting with user and therefore be very slow)
            let intentResultTimer = setInterval(() => {
                Logger.system.warn(`tag:FDC3:raiseIntent No intent result has been returned yet for intent ${intent} -- will continue to wait`, message.payload);
            }, WaitForIntentResultTimeout);
            Logger.system.log(`tag:FDC3:raiseIntent triggering intent listener for intent ${intent}`, message.payload);
            try {
                // Wait for the intent handler to return a result
                localIntentResult = await handler(context, contextMetadata);
            }
            catch (err) {
                Logger.system.error(`tag:FDC3:raiseIntent -- error thrown in handler for intent ${intent}`, err, context, message.payload);
                intentResultError = ResultError.IntentHandlerRejected;
                result = {
                    type: "intentResult",
                    meta: this.meta(),
                    payload: {
                        intentResultError,
                        intent,
                        requestId,
                    },
                };
                this.exchange(result);
            }
            clearInterval(intentResultTimer);
            // If no error was thrown above when the intent handler was invoked, then return the handler result
            if (!intentResultError) {
                // convert IntentResult to InteropIntentResult, a form than can be transmitted to InteropService
                // `?addContextListener` is how we dynamically tell whether the user returned a Channel
                const intentResult = (localIntentResult === null || localIntentResult === void 0 ? void 0 : localIntentResult.addContextListener)
                    ? { channelId: localIntentResult === null || localIntentResult === void 0 ? void 0 : localIntentResult.id, type: localIntentResult.type }
                    : localIntentResult;
                // If intentResult isn't void, then ensure it appears to be either a channel or context
                if (intentResult && (typeof intentResult !== "object" || !intentResult.type)) {
                    Logger.system.error(`IntentResult returned by FDC3 client app for intent ${intent} is rejected since it doesn't match expected channel or context definition`, intentResult);
                    intentResultError = ResultError.NoResultReturned;
                }
                result = {
                    type: "intentResult",
                    meta: this.meta(),
                    payload: {
                        intentResult,
                        intentResultError,
                        intent,
                        requestId,
                    },
                };
                this.exchange(result);
            }
        }
        else {
            Logger.system.error(`Legal listener not found for intent ${intent}`);
            result = {
                type: "intentResult",
                meta: this.meta(),
                payload: {
                    intentResultError: ResultError.IntentHandlerRejected,
                    intent,
                    requestId,
                },
            };
            this.exchange(result);
        }
        Logger.system.log(`tag:FDC3:raiseIntent handler response for intent ${intent}`, result);
    }
    async handlePrivateChannelEvent(message) {
        const { channel, context, targetSubscriberId } = message.payload;
        const channelSubscriptions = __classPrivateFieldGet(this, _FinsembleDesktopAgent_privateChannelSubscription, "f")[channel];
        const targetSubscription = channelSubscriptions.find((s) => s.targetSubscriberId === targetSubscriberId);
        if (targetSubscription) {
            switch (targetSubscription.subscribeType) {
                case "onAddContextListener":
                    targetSubscription.handler(context);
                    break;
                case "onUnsubscribe":
                    targetSubscription.handler(context);
                    break;
                case "onDisconnect":
                    targetSubscription.handler();
                    break;
                default: {
                    Logger.system.error("FDC3 Client: unknown subscribeType in matching targetSubscription", targetSubscription);
                }
            }
        }
        else {
            Logger.system.error("FDC3 Client: could not find target subscription", targetSubscriberId, channelSubscriptions);
        }
    }
    async handleIntentResult(message) {
        const { intent: intentName, intentResult, requestId, intentResultError } = message.payload;
        // If there is an outstanding resolve function associated with the request id, then invoke it
        if (typeof __classPrivateFieldGet(this, _FinsembleDesktopAgent_intentResultPromiseResolveMap, "f")[requestId] === "function") {
            Logger.system.log(`tag:FDC3:raiseIntent result message received for intent ${intentName}`, message);
            // Resolve the outstanding promise, waited on in the corresponding getResult function, returning the intent result or an error
            __classPrivateFieldGet(this, _FinsembleDesktopAgent_intentResultPromiseResolveMap, "f")[requestId]({ errorResult: intentResultError, intentResult });
        }
        else {
            Logger.system.error(`Legal internal handler for intent result not found`, message, __classPrivateFieldGet(this, _FinsembleDesktopAgent_intentResultPromiseResolveMap, "f"));
        }
    }
    handleIncoming(message) {
        if (message.type === "heartbeat") {
            Logger.system.debug("Client heartbeat", message);
        }
        else {
            Logger.system.debug("FDC3 Client: incoming", message.type, message);
        }
        switch (message.type) {
            case "broadcast":
                this.triggerContextListeners(message);
                break;
            case "intent":
                this.triggerIntentListener(message);
                break;
            case "privateChannelEvent":
                this.handlePrivateChannelEvent(message);
                break;
            case "intentResult":
                this.handleIntentResult(message);
                break;
            case "heartbeat":
                this.handleHeartbeat();
                break;
            default:
                break;
        }
    }
    /**
     * Releases the queue, sending API calls to the interop service that had been deferred until a connection
     * was established.
     */
    releaseQueue() {
        /**
         * When messages were queued, the uuid would not have been available (uuid is based on windowName
         * which is only available after startup.onReady). Now that we're releasing the queue, update
         * the uuid in any queued messages before sending them out.
         */
        const updateUUID = (message) => {
            var _a;
            if (((_a = message === null || message === void 0 ? void 0 : message.meta) === null || _a === void 0 ? void 0 : _a.uuid) !== undefined) {
                message.meta.uuid = __classPrivateFieldGet(this, _FinsembleDesktopAgent_uuid, "f");
            }
            return message;
        };
        __classPrivateFieldGet(this, _FinsembleDesktopAgent_queue, "f").forEach(async (entry) => {
            Logger.system.log(`fdc3.releaseQueue`, entry);
            const { message, handler, resolve } = entry;
            const { err, receiveMessage } = await this.exchange(updateUUID(message), handler);
            resolve({ err, receiveMessage });
        });
        __classPrivateFieldSet(this, _FinsembleDesktopAgent_queue, [], "f");
    }
    /**
     * This is the central point for exchanging data with the interop service. Even though some FDC3
     * calls such as broadcast() are one way, we always perform a query/response with the server, at
     * least getting an empty response to know that it was handled.
     */
    exchange(message, handler = () => { }) {
        return new Promise(async (resolve) => {
            if (__classPrivateFieldGet(this, _FinsembleDesktopAgent_connected, "f")) {
                Logger.system.debug("FDC3 Client: outgoing", message.type, message);
                const { err, response } = await __classPrivateFieldGet(this, _FinsembleDesktopAgent_routerClient, "f").query(SERVER_CHANNEL, message, handler);
                const receiveMessage = response === null || response === void 0 ? void 0 : response.data;
                if (receiveMessage)
                    Logger.system.log(`FDC3 Client: incoming`, receiveMessage);
                resolve({ err, receiveMessage });
            }
            else {
                __classPrivateFieldGet(this, _FinsembleDesktopAgent_queue, "f").push({ message, handler, resolve });
                this.start();
            }
        });
    }
    /**
     * Performs the handshake to the interop service and starts heartbeats.
     * @internal
     */
    async connect() {
        /**
         * StartupData contains the AuthenticationToken necessary to connect to the interop service. When an app
         * is spawned by Finsemble, the AuthenticationToken is automatically created by the launcher. When an app
         * is freestanding, the AuthenticationToken is generated when a developer calls FSBLJSAdapter.startApp with a
         * digital signing key. If an app can't be authenticated then Startup ensures that an anonymous
         * AuthenticationToken exists (type "unauthenticated").
         */
        var _a;
        const { windowName, authenticationToken } = await this.fetchStartupData();
        Logger.system.log(`fdc3.connect StartupData for ${windowName}`);
        const registerClient = {
            type: "register",
            meta: this.meta(),
            payload: {
                // It would be better if the server assigned the client channel name. We could then protect against clashes.
                clientChannel: __classPrivateFieldGet(this, _FinsembleDesktopAgent_clientChannel, "f"),
                // Ideally the server gets/verifies the windowName from the router
                windowName: (_a = __classPrivateFieldGet(this, _FinsembleDesktopAgent_windowName, "f")) !== null && _a !== void 0 ? _a : windowName,
                // Required by InteropService to authenticate this client (preventing spoofing)
                authenticationToken,
            },
        };
        /**
         * We need to ensure that the SERVER_CHANNEL responder has been added to the router. This happens
         * at the kernel stage when the InteropService is starting up. Therefore we need to wait until the
         * kernel stage is complete (i.e. InteropService is up and running) before we can query the SERVER_CHANNEL.
         *
         * Freestanding apps will periodically try to register with Interop if they are started prior to Finsemble
         * coming up which would cause interop registration request being fired before the responder was added to the router.
         *
         * This waitForBootStage works, even though Finsemble might not be up, for two reasons:
         * 	1) the underlying router subscribe in waitForBootStage will be queued by the routerClient until the router is up
         * 	2) the underlying pubsub topic relies on a default pubSub responder in the router service, so it will immediately
         * 		be available when the router is available.
         */
        SystemManagerClient.waitForBootStage("kernel", "stageCompleted", (error) => {
            if (error) {
                Logger.system.error("connect error waitingForBootStage", error);
            }
            else {
                __classPrivateFieldGet(this, _FinsembleDesktopAgent_routerClient, "f").query(SERVER_CHANNEL, registerClient, (err) => {
                    if (err) {
                        Logger.system.error("connect error", err);
                    }
                    else {
                        __classPrivateFieldGet(this, _FinsembleDesktopAgent_routerClient, "f").addResponder(__classPrivateFieldGet(this, _FinsembleDesktopAgent_clientChannel, "f"), (err2, request) => {
                            if (err2 || !request) {
                                const errorString = errorAsString(err2 !== null && err2 !== void 0 ? err2 : "No request received on responder");
                                Logger.system.error(`FinsembleDesktopAgent can't listen on channel ${__classPrivateFieldGet(this, _FinsembleDesktopAgent_clientChannel, "f")} : ${errorString}`);
                                // channel isn't available, uh oh
                            }
                            else {
                                if (request.data)
                                    this.handleIncoming(request.data);
                                request.sendQueryResponse(null);
                            }
                        });
                        ConfigClient.get(["finsemble", "system", "FSBLVersion"], (_err, data) => {
                            __classPrivateFieldSet(this, _FinsembleDesktopAgent_FSBLVersion, data, "f");
                        });
                        __classPrivateFieldSet(this, _FinsembleDesktopAgent_connected, true, "f");
                        Logger.system.log(`FDC3 client connected for window ${windowName}`);
                        this.releaseQueue();
                        // The heartbeat monitor shouldn't start until now that we've successfully connected to the interop service
                        __classPrivateFieldGet(this, _FinsembleDesktopAgent_heartbeatMonitor, "f").start();
                    }
                });
            }
        });
    }
    /**
     * Connect to the interop service. This method can be safely called multiple times. It will only
     * establish a single connection.
     * This function is called by FSBLDesktop and FSBLFreestanding.
     * @internal
     */
    async start() {
        // We only need to register with the FSBL event handlers once. Prevents connecting multiple times.
        if (__classPrivateFieldGet(this, _FinsembleDesktopAgent_startHasBeenCalled, "f"))
            return;
        __classPrivateFieldSet(this, _FinsembleDesktopAgent_startHasBeenCalled, true, "f");
        await __classPrivateFieldGet(this, _FinsembleDesktopAgent_startup, "f").onReady();
        // The connect method performs the handshake and starts the heartbeats.
        // **We do not want to await** it since any calling function would stall, until a connection is established. In this
        // instance this is a "fire and forget" action and any connections issues will be highlighted by other API calls.
        this.connect();
    }
    /**
     * Stops the client and puts it back into queuing state. The client
     * can be restarted by calling connect().
     * @internal
     */
    async stop() {
        __classPrivateFieldGet(this, _FinsembleDesktopAgent_heartbeatMonitor, "f").stop();
        const unregisterClient = {
            type: "unregister",
            meta: this.meta(),
        };
        await this.exchange(unregisterClient);
        __classPrivateFieldSet(this, _FinsembleDesktopAgent_startHasBeenCalled, false, "f");
        __classPrivateFieldGet(this, _FinsembleDesktopAgent_routerClient, "f").removeResponder(__classPrivateFieldGet(this, _FinsembleDesktopAgent_clientChannel, "f"));
        __classPrivateFieldSet(this, _FinsembleDesktopAgent_connected, false, "f");
    }
    /**
     * Registers handlers which will be called if the client is disconnected from Finsemble. This is only useful when
     * running in freestanding apps that might continue running after Finsemble has been terminated. Users should be
     * prompted to restart their freestanding app when a disconnect is received.
     *
     * @param cb Handler function.
     * @experimental This method is not part of the FDC3 standard and may be replaced in the future with alternative mechanisms.
     */
    onDisconnect(cb) {
        __classPrivateFieldGet(this, _FinsembleDesktopAgent_heartbeatMonitor, "f").onDisconnect(cb);
    }
    /** @internal */
    async channelGetCurrentContext(channel, contextType) {
        const message = {
            type: "currentContextRequest",
            meta: {
                ...this.meta(),
                channel,
            },
            payload: {
                contextType,
            },
        };
        const { err, receiveMessage } = await this.exchange(message);
        if (err)
            return null;
        return (receiveMessage === null || receiveMessage === void 0 ? void 0 : receiveMessage.type) === "currentContext" ? receiveMessage.payload.context : null;
    }
    /**
     * Publishes context to other apps on the desktop.
     * Calling broadcast at the DesktopAgent scope will push the context to whatever Channel the app is joined to.
     * If the app is not currently joined to a channel, calling fdc3.broadcast will have no effect.
     * Apps can still directly broadcast and listen to context on any channel via the methods on the Channel class.
     * @param context
     */
    async broadcast(context) {
        if (!context || !context.type) {
            throw new Error("Invalid arguments. An FDC3 Context object (i.e. an Object with a type field) is required.");
        }
        const message = {
            type: "broadcast",
            meta: this.meta(),
            payload: {
                context,
                channelType: "user",
            },
        };
        await this.exchange(message);
    }
    /** @internal */
    async channelBroadcast(channel, context, channelType) {
        const message = {
            type: "broadcast",
            meta: {
                ...this.meta(),
                channel,
            },
            payload: {
                context,
                channelType,
            },
        };
        await this.exchange(message);
    }
    /**
     * Retrieves a list of the System channels available for the app to join.
     * This should include the 'global' channel.
     */
    async getUserChannels() {
        var _a;
        const message = {
            type: "systemChannelRequest",
            meta: this.meta(),
        };
        const { err, receiveMessage } = await this.exchange(message);
        if (err)
            return [];
        const channelNames = (receiveMessage === null || receiveMessage === void 0 ? void 0 : receiveMessage.type) === "systemChannels" ? (_a = receiveMessage === null || receiveMessage === void 0 ? void 0 : receiveMessage.payload) === null || _a === void 0 ? void 0 : _a.channels : [];
        return channelNames.map((name) => {
            var _a, _b, _c;
            let metaData;
            if ((receiveMessage === null || receiveMessage === void 0 ? void 0 : receiveMessage.type) === "systemChannels" && ((_a = receiveMessage === null || receiveMessage === void 0 ? void 0 : receiveMessage.payload) === null || _a === void 0 ? void 0 : _a.systemChannelMeta)) {
                metaData = (_c = (_b = receiveMessage === null || receiveMessage === void 0 ? void 0 : receiveMessage.payload) === null || _b === void 0 ? void 0 : _b.systemChannelMeta[name]) === null || _c === void 0 ? void 0 : _c.displayMetaData;
            }
            return new ChannelProxy(this, name, "user", metaData);
        });
    }
    /**
     * For backwards compatibility.  Retrieves a list of the System channels available for the app to join.
     */
    async getSystemChannels() {
        return this.getUserChannels();
    }
    async open(app, context) {
        var _a;
        if (!app) {
            throw new Error("Invalid arguments. The required 'app' parameter is missing.");
        }
        const { appMetadata } = await this.fetchStartupData();
        const message = {
            type: "open",
            meta: this.meta(),
            payload: {
                requestingAppId: appMetadata.appId,
                app,
                context,
            },
        };
        const { err, receiveMessage } = await this.exchange(message);
        const interopOpenResponse = receiveMessage;
        if (err) {
            throw new Error(OpenError.ResolverUnavailable);
        }
        else if (interopOpenResponse.payload.error) {
            throw new Error(interopOpenResponse.payload.error);
        }
        return (_a = interopOpenResponse.payload.appIdentifier) !== null && _a !== void 0 ? _a : { appId: "unknown-open-appIdentifier" };
    }
    /**
     * Find out more information about a particular intent by passing its name, and optionally its context.
     *
     * findIntent is effectively granting programmatic access to the Desktop Agent's resolver.
     * A promise resolving to the intent, its metadata and metadata about the apps that registered it is returned.
     * This can be used to raise the intent against a specific app.
     *
     * If the resolution fails, the promise will return an `Error` with a string from the `ResolveError` enumeration.
     *
     * @param intent
     * @param context
     */
    async findIntent(intent, context, resultType) {
        if (!intent) {
            throw new Error("Invalid arguments. The required intent parameter is missing.");
        }
        if (context && !context.type) {
            throw new Error("Invalid arguments. Second parameter must be a valid context.");
        }
        if (resultType && typeof resultType !== "string") {
            throw new Error("Invalid arguments. Third parameter must be a string.");
        }
        const message = {
            type: "findIntent",
            meta: this.meta(),
            payload: {
                intent,
                context,
                resultType,
            },
        };
        const { err, receiveMessage } = await this.exchange(message);
        const interopFindIntentResponse = receiveMessage;
        if (err) {
            throw new Error(ResolveError.ResolverUnavailable);
        }
        else if (interopFindIntentResponse.payload.error) {
            throw new Error(interopFindIntentResponse.payload.error);
        }
        else if (!interopFindIntentResponse.payload.appIntent) {
            throw new Error("Invalid response returned from InteropService for findIntent. No appIntent and no error.");
        }
        return interopFindIntentResponse.payload.appIntent;
    }
    /**
     * Find all the available intents for a particular context.
     *
     * findIntents is effectively granting programmatic access to the Desktop Agent's resolver.
     * A promise resolving to all the intents, their metadata and metadata about the apps that registered it is returned,
     * based on the context types the intents have registered.
     *
     * If the resolution fails, the promise will return an `Error` with a string from the `ResolveError` enumeration.
     * @param context
     */
    async findIntentsByContext(context, resultType) {
        if (!context) {
            throw new Error("Invalid arguments. The required context parameter is missing.");
        }
        if (!context.type) {
            throw new Error("Invalid arguments. Second parameter must be a valid context.");
        }
        if (resultType && typeof resultType !== "string") {
            throw new Error("Invalid arguments. Second parameter must be a string to identify result type.");
        }
        // The context parameter was made optional to support a query for all intents. This is consistent with findIntent, however it is not consistent with FDC3 spec plus.
        // a mechanism is still needed to query for contexts. A better solution is to add two new InteropAdmin methods; e.g. getIntents() and getContexts().
        const message = {
            type: "findIntentsByContext",
            meta: this.meta(),
            payload: {
                context,
                resultType,
            },
        };
        const { receiveMessage } = await this.exchange(message);
        const interopIntentsList = receiveMessage;
        if (interopIntentsList.payload.error) {
            throw new Error(interopIntentsList.payload.error);
        }
        return interopIntentsList.payload.intents || [];
    }
    // @ts-ignore <----------------------------------------------------------------------------------------------------------------------------------------------
    async createPrivateChannel() {
        return new ChannelProxy(this, this.privateChannelId(), "private");
    }
    /**
     * Common implementation for raiseIntent() and raiseIntentForContext().
     *
     * @param intent
     * @param context
     * @param target
     * @param forContext If true then invoked by raiseIntentForContext which gives a different meaning to context parameter
     */
    async raiseIntentInternal(intent, context, target, forContext = false) {
        if (context && !context.type) {
            throw new Error("Invalid arguments. Second parameter must be a valid context.");
        }
        const requestId = `raise-intent-request-Id-${uuidv4()}`;
        Logger.system.log(`tag:FDC3:raiseIntent initiated for intent ${intent} with ${context ? " context type " : "no context"}${context === null || context === void 0 ? void 0 : context.type}`, context, target, requestId, forContext);
        const message = {
            type: "raiseIntent",
            meta: this.meta(),
            payload: {
                intent,
                context,
                target,
                requestId,
                forContext,
            },
        };
        Logger.system.log(`tag:FDC3:raiseIntent request sent to InteropService for intent ${intent}`, message);
        // The message received back from the InteropService for the below exchange(message) corresponds to the IntentResolution (signaling the raiseIntent has been successfully sent to the target app, or an error).
        // Asynchronously, an IntentResult will come in later.
        const { err, receiveMessage } = await this.exchange(message);
        const interopIntentResolution = receiveMessage;
        Logger.system.log(`tag:FDC3:raiseIntent intent resolution received for intent ${intent}`, err, receiveMessage);
        if (err) {
            throw new Error(ResolveError.ResolverUnavailable);
        }
        else if (interopIntentResolution.payload.error) {
            throw new Error(interopIntentResolution.payload.error);
        }
        else if (!interopIntentResolution.payload.data) {
            throw new Error("Invalid response returned from InteropService for raiseIntent. No data.");
        }
        /* Allocate a promise for this raiseIntent and map it to the request id to handle the getResult() synchronization.

           This promise is waited on below in the getResult function returned in the raiseIntent response,
           then resolved in handleIntentResult when the result arrives for the requestId
         */
        __classPrivateFieldGet(this, _FinsembleDesktopAgent_intentResultPromiseMap, "f")[requestId] = new Promise((intentResultResolve) => {
            // Map the resolve function to the requestID to be later invoked to complete this promise when the intent result arrives.
            __classPrivateFieldGet(this, _FinsembleDesktopAgent_intentResultPromiseResolveMap, "f")[requestId] = intentResultResolve;
        });
        const intentResolution = {
            source: interopIntentResolution.payload.data.source,
            intent: interopIntentResolution.payload.data.intent,
            version: interopIntentResolution.payload.data.version,
            getResult: async () => {
                let errorResult;
                let intentResult;
                Logger.system.log(`tag:FDC3:raiseIntent getResult initiated for intent ${intent}`, message);
                if (typeof __classPrivateFieldGet(this, _FinsembleDesktopAgent_intentResultPromiseResolveMap, "f")[requestId] === "function") {
                    // Wait for the corresponding intent result to be returned.
                    // The promise waited on here is resolved in handleIntentResult when the result with the correct requestId arrives from InteropService.
                    ({ errorResult, intentResult } = await __classPrivateFieldGet(this, _FinsembleDesktopAgent_intentResultPromiseMap, "f")[requestId]);
                }
                else {
                    errorResult = "could not fulfill intent resolution due to no corresponding result promise";
                }
                Logger.system.log(`tag:FDC3:raiseIntent result for intent ${intent}`, { errorResult, intentResult });
                // Any error has to be thrown here in this original thread of control for the app to receive it -- it cannot be thrown when the result is processed in handleIntentResult
                if (errorResult) {
                    throw new Error(errorResult);
                }
                // The result might be a channel type, or it might be a context type, or it might be undefined.
                const resultChannelId = intentResult === null || intentResult === void 0 ? void 0 : intentResult.channelId;
                const resultType = intentResult === null || intentResult === void 0 ? void 0 : intentResult.type;
                const fdc3IntentResult = resultChannelId
                    ? new ChannelProxy(this, resultChannelId, resultType)
                    : (intentResult !== null && intentResult !== void 0 ? intentResult : undefined);
                Logger.system.log(`tag:FDC3:raiseIntent getResult() returned for intent ${intent}`, fdc3IntentResult);
                return fdc3IntentResult;
            },
        };
        return intentResolution;
    }
    async raiseIntent(intent, context, target) {
        if (!intent) {
            throw new Error("Invalid arguments. The required intent parameter is missing.");
        }
        if (!context) {
            throw new Error("Invalid arguments. The required context parameter is missing.");
        }
        if (!context.type) {
            throw new Error("Invalid arguments. Second parameter must be a valid context.");
        }
        return this.raiseIntentInternal(intent, context, target);
    }
    async raiseIntentForContext(context, target) {
        if (!context) {
            throw new Error("Invalid arguments. The required context parameter is missing.");
        }
        return this.raiseIntentInternal(null, context, target, true);
    }
    /**
     * Adds a listener for incoming Intents from the Agent.  Unlike addContextListener, there can only be one listener for an intent.
     * @param intent
     * @param handler
     */
    addIntentListener(intent, handler) {
        if (!intent) {
            throw new Error("Invalid arguments. The required intent parameter is missing.");
        }
        if (!handler) {
            throw new Error("addIntentListener Invalid arguments. The required handler parameter is missing.");
        }
        if (__classPrivateFieldGet(this, _FinsembleDesktopAgent_intentHandlers, "f")[intent]) {
            throw new Error(`Intent listener already defined for intent ${intent}`);
        }
        else {
            __classPrivateFieldGet(this, _FinsembleDesktopAgent_intentHandlers, "f")[intent] = handler;
        }
        const message = {
            type: "subscribeIntent",
            meta: this.meta(),
            payload: {
                intent,
            },
        };
        // Dangerous code: See note at top of file about 1.2 compatibility
        const exchangePromise = this.exchange(message); // don't wait for response
        const listener = {
            unsubscribe: () => {
                const unsubscribeMessage = {
                    type: "unsubscribeIntent",
                    meta: this.meta(),
                    payload: {
                        intent,
                    },
                };
                this.exchange(unsubscribeMessage); // don't wait for response
                delete __classPrivateFieldGet(this, _FinsembleDesktopAgent_intentHandlers, "f")[intent];
            },
        };
        // Dangerous code: See note at top of file about 1.2 compatibility
        let promise = new Promise((resolve) => {
            exchangePromise.then(() => {
                resolve(listener);
            });
        });
        promise.unsubscribe = () => {
            Logger.system.deprecated("Use of the synchronous version of addIntentListener from FDC3 1.2 is deprecated. Please switch to `await addIntentListener`.");
            listener.unsubscribe();
        };
        return promise;
    }
    // Common functions to handler incoming Private Channel events to support onAddListener, onSubscribe, and oDisconnect
    listenerForPrivateChannelEvent(subscribeType, channel, handler) {
        // A unique targetSubscriberId must be passed in InteropSubscribeChannelOnAddContext; the InteropService can direct messages to this specific listener by returning the targetSubscriberId in a broadcast message,
        const targetSubscriberId = `subscriberId.${channel}.${subscribeType}.${Date.now()}_${Math.random()}`;
        // Bind targetSubscriberId to the specific handler and save in map
        const handlerSet = { subscribeType, handler, channel, targetSubscriberId };
        __classPrivateFieldGet(this, _FinsembleDesktopAgent_privateChannelSubscription, "f")[channel] = mmAdd(__classPrivateFieldGet(this, _FinsembleDesktopAgent_privateChannelSubscription, "f"), channel, handlerSet);
        let message = {
            type: "subscribePrivateChannelEvent",
            payload: {
                subscribeType,
                channel,
                targetSubscriberId,
            },
            meta: this.meta(),
        };
        this.exchange(message);
        return {
            unsubscribe: () => {
                let unsubscribeMessage = {
                    type: "unsubscribePrivateChannelEvent",
                    payload: {
                        subscribeType,
                        channel,
                        targetSubscriberId,
                    },
                    meta: this.meta(),
                };
                __classPrivateFieldGet(this, _FinsembleDesktopAgent_privateChannelSubscription, "f")[channel] = mmRemove(__classPrivateFieldGet(this, _FinsembleDesktopAgent_privateChannelSubscription, "f"), channel, handlerSet);
                this.exchange(unsubscribeMessage); // Don't wait for response
            },
        };
    }
    /** @internal */
    channelOnAddContextListener(channel, handler) {
        return this.listenerForPrivateChannelEvent("onAddContextListener", channel, handler);
    }
    /** @internal */
    channelOnUnsubscribe(channel, handler) {
        return this.listenerForPrivateChannelEvent("onUnsubscribe", channel, handler);
    }
    /** @internal */
    channelOnDisconnect(channel, handler) {
        return this.listenerForPrivateChannelEvent("onDisconnect", channel, handler);
    }
    /** @internal */
    channelDisconnect(channel) {
        let message = {
            type: "channelDisconnect",
            meta: { ...this.meta(), channel },
        };
        this.exchange(message);
    }
    /** @internal */
    channelAddContextListener(channel, contextTypeOrHandler, handler) {
        // Derives a definitive contextType and handler from the varargs
        // Possibilities: addContextListener(handler) - deprecated 1.1, addContextListener(null, handler), addContextListener(contextType, handler)
        const contextTypeOrWildCard = contextTypeOrHandler ? contextTypeOrHandler : "*";
        const contextType = handler ? contextTypeOrWildCard : "*";
        if (!handler)
            handler = contextTypeOrHandler;
        // A unique targetSubscriberId must be passed in InteropSubscribeContext; the InteropService can direct messages to this specific listener by returning the targetSubscriberId in a broadcast message,
        const targetSubscriberId = `subscriberId.${Date.now()}_${Math.random()}`;
        // Bind targetSubscriberId to the specific handler and save in map
        const handlerSet = { handler, channel, targetSubscriberId };
        __classPrivateFieldGet(this, _FinsembleDesktopAgent_contextHandlers, "f")[contextType] = mmAdd(__classPrivateFieldGet(this, _FinsembleDesktopAgent_contextHandlers, "f"), contextType, handlerSet);
        let message = {
            type: "subscribeContext",
            meta: { ...this.meta(), targetSubscriberId },
            payload: {
                contextType,
            },
        };
        if (channel)
            message.meta.channel = channel;
        const exchangePromise = this.exchange(message);
        return {
            exchangePromise,
            unsubscribe: () => {
                let unsubscribeMessage = {
                    type: "unsubscribeContext",
                    meta: this.meta(),
                    payload: {
                        contextType,
                    },
                };
                if (channel)
                    unsubscribeMessage.meta.channel = channel;
                __classPrivateFieldGet(this, _FinsembleDesktopAgent_contextHandlers, "f")[contextType] = mmRemove(__classPrivateFieldGet(this, _FinsembleDesktopAgent_contextHandlers, "f"), contextType, handlerSet);
                this.exchange(unsubscribeMessage); // Don't wait for response
            },
        };
    }
    /**
     * Adds a listener for incoming context broadcast from the Desktop Agent.
     * If the consumer is only interested in a context of a particular type, they can use the relevant overload that allows the type to be specified.
     * @param contextTypeOrHandler
     * @param handler
     */
    addContextListener(contextTypeOrHandler, handler) {
        if (contextTypeOrHandler === undefined) {
            throw new Error("addContextListener Invalid arguments. The contextTypeOrHandler parameter shouldn't be undefined.");
        }
        if (typeof contextTypeOrHandler !== "function" && !handler) {
            const msg = "addContextListener Invalid arguments. The required handler parameter is missing.";
            console.error(msg, contextTypeOrHandler, handler);
            Logger.system.error(msg);
            throw new Error(msg);
        }
        // Dangerous code: See note at top of file about 1.2 compatibility
        const { exchangePromise, unsubscribe } = this.channelAddContextListener(null, contextTypeOrHandler, handler);
        let promise = new Promise((resolve) => {
            exchangePromise.then(() => {
                resolve({
                    unsubscribe,
                });
            });
        });
        promise.unsubscribe = () => {
            Logger.system.deprecated("Use of the synchronous version of addContextListener from FDC3 1.2 is deprecated. Please switch to `await addContextListener`.");
            unsubscribe();
        };
        return promise;
    }
    /**
     * Joins the app to the specified channel.
     * If an app is joined to a channel, all fdc3.broadcast calls will go to the channel, and all listeners assigned via fdc3.addContextListener will listen on the channel.
     * An app can only be joined to one channel at a time.
     * Rejects with error if the channel is unavailable or the join request is denied.
     * Error with a string from the ChannelError enumeration.
     * @param channelId
     */
    async joinUserChannel(channelId) {
        if (!channelId) {
            throw new Error("Invalid arguments. The required channelId parameter is missing.");
        }
        if (!(await this.isUserChannel(channelId))) {
            Logger.system.error("Invalid arguments. The channel being joined must be a user channel.");
            throw new Error(ChannelError.AccessDenied);
        }
        // Move this test to the InteropService so no delay is added, which might cause a backwards compatibility issue
        // const userChannels = await this.getUserChannels();
        // if (userChannels.find((app) => channelId === app.id) === undefined) {
        // 	throw new Error(ChannelError.NoChannelFound);
        // }
        const message = {
            type: "joinChannels",
            meta: this.meta(),
            payload: {
                channels: [channelId],
            },
        };
        const { err, receiveMessage } = await this.exchange(message);
        const interopJoinChannelsResponse = receiveMessage;
        if (err) {
            throw new Error(err);
        }
        else if (interopJoinChannelsResponse.payload.error) {
            throw new Error(interopJoinChannelsResponse.payload.error);
        }
    }
    async joinChannel(channelId) {
        return this.joinUserChannel(channelId);
    }
    /**
     * Returns a Channel object for the specified channel, creating it (as an App channel) - if it does not exist.
     * Error with a string from the ChannelError enumeration if channel could not be created or access was denied.
     * @param channelId
     */
    async getOrCreateChannel(channelId) {
        if (!channelId) {
            Logger.system.debug("getOrCreateChannel: The required channelId parameter is missing.");
            throw new Error(ChannelError.CreationFailed);
        }
        else if (isPrivateChannelId(channelId)) {
            throw new Error(ChannelError.AccessDenied);
        }
        // if channel proxy has already been allocated for the channelId, then return it; otherwise allocate a new one
        let channelInstance = __classPrivateFieldGet(this, _FinsembleDesktopAgent_existingChannelProxy, "f")[channelId];
        if (!channelInstance) {
            channelInstance = new ChannelProxy(this, channelId);
            __classPrivateFieldGet(this, _FinsembleDesktopAgent_existingChannelProxy, "f")[channelId] = channelInstance;
        }
        return channelInstance;
    }
    /**
     * Get all joined channels
     *
     * Known race condition: getCurrentChannels() can return an empty string if it is called before the InteropService's linker module
     * has completed initializing the app. This can occur if fdc3.getCurrentChannels() is the very first call that an app makes. To work around
     * this issue, use the interop.autoConnect config flag. A future version of the InteropService will hold off on completed registrations
     * until submodules have finished their work.
     */
    async getCurrentChannels() {
        const message = {
            type: "getCurrentChannels",
            meta: this.meta(),
        };
        const { err, receiveMessage } = await this.exchange(message);
        const interopGetCurrentChannelsResponse = receiveMessage;
        if (err) {
            throw new Error(err);
        }
        else if (interopGetCurrentChannelsResponse.payload.error) {
            throw new Error(interopGetCurrentChannelsResponse.payload.error);
        }
        else if (!interopGetCurrentChannelsResponse.payload.data) {
            throw new Error("Invalid response returned from InteropService for getCurrentChannels. No data.");
        }
        return interopGetCurrentChannelsResponse.payload.data.map((entry) => {
            var _a, _b, _c;
            let metaData;
            if ((receiveMessage === null || receiveMessage === void 0 ? void 0 : receiveMessage.type) === "interopGetCurrentChannelsResponse" && ((_a = receiveMessage === null || receiveMessage === void 0 ? void 0 : receiveMessage.payload) === null || _a === void 0 ? void 0 : _a.systemChannelMeta)) {
                metaData = (_c = (_b = receiveMessage === null || receiveMessage === void 0 ? void 0 : receiveMessage.payload) === null || _b === void 0 ? void 0 : _b.systemChannelMeta[entry.channel]) === null || _c === void 0 ? void 0 : _c.displayMetaData;
            }
            return new ChannelProxy(this, entry.channel, "user", metaData);
        });
    }
    /**
     * Returns the Channel object for the current channel membership.
     * Returns null if the app is not joined to a channel.
     */
    async getCurrentChannel() {
        const channels = await this.getCurrentChannels();
        return channels && channels.length ? channels[0] : null;
    }
    /**
     * Removes the app from any channel membership.
     * Context broadcast and listening through the top-level fdc3.broadcast and fdc3.addContextListener will be in a no-op when the app is not on a channel.
     */
    async leaveCurrentChannel() {
        const message = {
            type: "leaveChannels",
            meta: this.meta(),
            payload: {},
        };
        await this.exchange(message);
    }
    /**
     * Removes the app from the channel.
     * Context broadcast and listening through the top-level fdc3.broadcast and fdc3.addContextListener will be in a no-op when the app is not on the channel.
     */
    async leaveChannel(channelId) {
        const message = {
            type: "leaveChannels",
            meta: this.meta(),
            payload: { channels: [channelId] },
        };
        await this.exchange(message);
    }
    /**
     * Return specified app's meta data.
     */
    async getAppMetadata(app) {
        var _a;
        if (!app || !(app === null || app === void 0 ? void 0 : app.appId)) {
            throw new Error("getAppMetadata: The required app parameter is missing or not an AppIdentifier.");
        }
        const message = {
            type: "getAppMetadata",
            app,
            meta: this.meta(),
        };
        const { err, receiveMessage } = await this.exchange(message);
        const interopGetCurrentChannelsResponse = receiveMessage;
        if (err) {
            throw new Error(err);
        }
        else if (interopGetCurrentChannelsResponse.payload.error) {
            throw new Error(interopGetCurrentChannelsResponse.payload.error);
        }
        else if (!interopGetCurrentChannelsResponse.payload.appMetadata) {
            throw new Error("Invalid response returned from InteropService for getAppMetadata. No data.");
        }
        return (_a = interopGetCurrentChannelsResponse.payload) === null || _a === void 0 ? void 0 : _a.appMetadata;
    }
    /**
     * Return all the open instances of given appID.
     */
    async findInstances(app) {
        let instances = [];
        const message = {
            type: "findInstances",
            app,
            meta: this.meta(),
        };
        if (app === null || app === void 0 ? void 0 : app.appId) {
            const { err, receiveMessage } = await this.exchange(message);
            const response = receiveMessage;
            if (err) {
                throw new Error(err);
            }
            else if (response.payload.error) {
                throw new Error(response.payload.error);
            }
            else if (!response.payload.instances) {
                throw new Error("Invalid response returned from InteropService for findInstances. No data.");
            }
            ({ instances } = response.payload);
        }
        return instances;
    }
    /**
     * Retrieves information about the FDC3 Desktop Agent implementation, such as
     * the implemented version of the FDC3 specification and the name of the implementation
     * provider.
     */
    getInfo() {
        // Dangerous code: See note at top of file about 1.2 compatibility
        let p = new Promise(async (resolve) => {
            const { data: FSBLVersion } = await ConfigClient.get(["finsemble", "system", "FSBLVersion"]);
            const { appMetadata: startupMetadata } = await this.fetchStartupData();
            // The interopService has more appMetaData than startup, plus it should be the single source of true for an app's metaData
            const fullAppMetadata = await this.getAppMetadata({ appId: startupMetadata.appId, instanceId: __classPrivateFieldGet(this, _FinsembleDesktopAgent_uuid, "f") });
            resolve({
                fdc3Version: "2.1",
                provider: "Finsemble",
                providerVersion: FSBLVersion,
                appMetadata: fullAppMetadata,
                optionalFeatures: {
                    OriginatingAppMetadata: true,
                    UserChannelMembershipAPIs: true,
                },
            });
        });
        // Backward compatibility for 1.2 which expects this to be synchronous
        p.fdc3Version = "1.2";
        p.provider = "Finsemble";
        p.providerVersion = __classPrivateFieldGet(this, _FinsembleDesktopAgent_FSBLVersion, "f");
        return p;
    }
}
_FinsembleDesktopAgent_connected = new WeakMap(), _FinsembleDesktopAgent_heartbeatMonitor = new WeakMap(), _FinsembleDesktopAgent_startHasBeenCalled = new WeakMap(), _FinsembleDesktopAgent_uuid = new WeakMap(), _FinsembleDesktopAgent_appId = new WeakMap(), _FinsembleDesktopAgent_clientChannel = new WeakMap(), _FinsembleDesktopAgent_intentHandlers = new WeakMap(), _FinsembleDesktopAgent_contextHandlers = new WeakMap(), _FinsembleDesktopAgent_privateChannelSubscription = new WeakMap(), _FinsembleDesktopAgent_intentResultPromiseMap = new WeakMap(), _FinsembleDesktopAgent_intentResultPromiseResolveMap = new WeakMap(), _FinsembleDesktopAgent_queue = new WeakMap(), _FinsembleDesktopAgent_routerClient = new WeakMap(), _FinsembleDesktopAgent_startup = new WeakMap(), _FinsembleDesktopAgent_windowName = new WeakMap(), _FinsembleDesktopAgent_existingChannelProxy = new WeakMap(), _FinsembleDesktopAgent_FSBLVersion = new WeakMap(), _FinsembleDesktopAgent_userChannels = new WeakMap();
//# sourceMappingURL=FinsembleDesktopAgent.js.map