// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { PromiseDelegate, Token } from '@lumino/coreutils';
/* tslint:disable */
/**
 * The default window resolver token.
 */
export const IWindowResolver = new Token('@jupyterlab/apputils:IWindowResolver');
/**
 * A concrete implementation of a window name resolver.
 */
export class WindowResolver {
    /**
     * The resolved window name.
     *
     * #### Notes
     * If the `resolve` promise has not resolved, the behavior is undefined.
     */
    get name() {
        return this._name;
    }
    /**
     * Resolve a window name to use as a handle among shared resources.
     *
     * @param candidate - The potential window name being resolved.
     *
     * #### Notes
     * Typically, the name candidate should be a JupyterLab workspace name or
     * an empty string if there is no workspace.
     *
     * If the returned promise rejects, a window name cannot be resolved without
     * user intervention, which typically means navigation to a new URL.
     */
    resolve(candidate) {
        return Private.resolve(candidate).then(name => {
            this._name = name;
        });
    }
}
/*
 * A namespace for private module data.
 */
var Private;
(function (Private) {
    /**
     * The internal prefix for private local storage keys.
     */
    const PREFIX = '@jupyterlab/statedb:StateDB';
    /**
     * The local storage beacon key.
     */
    const BEACON = `${PREFIX}:beacon`;
    /**
     * The timeout (in ms) to wait for beacon responders.
     *
     * #### Notes
     * This value is a whole number between 200 and 500 in order to prevent
     * perfect timeout collisions between multiple simultaneously opening windows
     * that have the same URL. This is an edge case because multiple windows
     * should not ordinarily share the same URL, but it can be contrived.
     */
    const TIMEOUT = Math.floor(200 + Math.random() * 300);
    /**
     * The local storage window key.
     */
    const WINDOW = `${PREFIX}:window`;
    /**
     * Current beacon request
     *
     * #### Notes
     * We keep track of the current request so that we can ignore our own beacon
     * requests. This is to work around a bug in Safari, where Safari sometimes
     * triggers local storage events for changes made by the current tab. See
     * https://github.com/jupyterlab/jupyterlab/issues/6921#issuecomment-540817283
     * for more details.
     */
    let currentBeaconRequest = null;
    /**
     * A potential preferred default window name.
     */
    let candidate = null;
    /**
     * The window name promise.
     */
    const delegate = new PromiseDelegate();
    /**
     * The known window names.
     */
    const known = {};
    /**
     * The window name.
     */
    let name = null;
    /**
     * Whether the name resolution has completed.
     */
    let resolved = false;
    /**
     * Start the storage event handler.
     */
    function initialize() {
        // Listen to all storage events for beacons and window names.
        window.addEventListener('storage', (event) => {
            const { key, newValue } = event;
            // All the keys we care about have values.
            if (newValue === null) {
                return;
            }
            // If the beacon was fired, respond with a ping.
            if (key === BEACON &&
                newValue !== currentBeaconRequest &&
                candidate !== null) {
                ping(resolved ? name : candidate);
                return;
            }
            // If the window name is resolved, bail.
            if (resolved || key !== WINDOW) {
                return;
            }
            const reported = newValue.replace(/\-\d+$/, '');
            // Store the reported window name.
            known[reported] = null;
            // If a reported window name and candidate collide, reject the candidate.
            if (!candidate || candidate in known) {
                reject();
            }
        });
    }
    /**
     * Ping peers with payload.
     */
    function ping(payload) {
        if (payload === null) {
            return;
        }
        const { localStorage } = window;
        localStorage.setItem(WINDOW, `${payload}-${new Date().getTime()}`);
    }
    /**
     * Reject the candidate.
     */
    function reject() {
        resolved = true;
        currentBeaconRequest = null;
        delegate.reject(`Window name candidate "${candidate}" already exists`);
    }
    /**
     * Returns a promise that resolves with the window name used for restoration.
     */
    function resolve(potential) {
        if (resolved) {
            return delegate.promise;
        }
        // Set the local candidate.
        candidate = potential;
        if (candidate in known) {
            reject();
            return delegate.promise;
        }
        const { localStorage, setTimeout } = window;
        // Wait until other windows have reported before claiming the candidate.
        setTimeout(() => {
            if (resolved) {
                return;
            }
            // If the window name has not already been resolved, check one last time
            // to confirm it is not a duplicate before resolving.
            if (!candidate || candidate in known) {
                return reject();
            }
            resolved = true;
            currentBeaconRequest = null;
            delegate.resolve((name = candidate));
            ping(name);
        }, TIMEOUT);
        // Fire the beacon to collect other windows' names.
        currentBeaconRequest = `${Math.random()}-${new Date().getTime()}`;
        localStorage.setItem(BEACON, currentBeaconRequest);
        return delegate.promise;
    }
    Private.resolve = resolve;
    // Initialize the storage listener at runtime.
    (() => {
        initialize();
    })();
})(Private || (Private = {}));
//# sourceMappingURL=windowresolver.js.map