UNPKG

@jupyterlab/apputils

Version:
182 lines 5.84 kB
// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { PromiseDelegate } from '@lumino/coreutils'; /** * 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