Source: widget/Requirement.js

(function() {
    const WAIT_TIME = 100;

    /**
     * @typedef {Object} Help4.widget.Requirement.Requirements
     * @property {string[]} [namespaces] - all required namespaces
     * @property {string[]} [instances] - names of all required widget instances
     */

    /**
     * Requirement tests for Help4.widget
     */
    Help4.widget.Requirement = class {
        /**
         * @param {Help4.widget.Requirement.Requirements} requirements
         * @param {Help4.widget.Widget.Context} context
         * @returns {Promise<boolean>}
         */
        static awaitRequirements(requirements, context) {
            /** @type {Array<Promise<boolean>>} */ const promises = [];
            const {
                /** @type {string[]} */
                namespaces = [],
                /** @type {string[]} */
                instances = []
            } = requirements;

            for (const namespace of namespaces) {
                promises.push(awaitNamespaceExists.call(this, namespace));
            }
            for (const instance of instances) {
                promises.push(awaitWidgetStarted.call(this, instance, context));
            }

            /**
             * @param {boolean[]} results
             * @returns {boolean}
             */
            const success = results => results.every(result => result);

            return Help4.Promise
            .all(promises)
            .then(success);
        }

        /**
         * @param {string} name - widget instance name
         * @returns {boolean}
         */
        static isWidgetStarted(name) {
            return Help4.widget.getInstance(name)?.isStarted() || false;
        }

        /**
         * @param {Help4.widget.Requirement.Requirements} requirements
         * @returns {string[]}
         */
        static allRequiredWidgetsStarted(requirements) {
            /** @type {string[]} */ const missing = [];
            const {/** @type {string[]} */ instances = []} = requirements;

            for (const instance of instances) {
                // a required widget instance has been shut down
                if (!this.isWidgetStarted(instance)) missing.push(instance);
            }
            return missing;  // all fulfilled or no requirements
        }
    }

    /**
     * Waits until the namespace exists. Will never resolve in case namespace is not created.
     * @memberof Help4.widget.Requirement#
     * @private
     * @param {string} name - namespace name
     * @returns {Promise<true>}
     */
    function awaitNamespaceExists(name) {
        return new Help4.Promise(resolve => {
            const parts = name.split('.');
            let obj = window;

            const test = () => {
                while (parts.length && obj[parts[0]]) {
                    obj = obj[parts.shift()];
                }

                parts.length
                    ? setTimeout(test, WAIT_TIME)
                    : resolve(true);
            }

            test();
        });
    }

    /**
     * Waits until the widget instance is started.
     * Will resolve with false on system error.
     * Will never resolve in case widget is not started.
     * @memberof Help4.widget.Requirement#
     * @private
     * @param {string} name - widget instance name
     * @param {Help4.widget.Widget.Context} context
     * @returns {Promise<true>}
     */
    function awaitWidgetStarted(name, context) {
        return new Help4.Promise(resolve => {
            // already started
            if (this.isWidgetStarted(name)) return resolve(true);

            // observe all widget starts
            const {eventBus} = context;
            const type = Help4.EventBus.TYPES.widgetStart;
            const ebo = new Help4.observer.EventBusObserver(({engine}) => {
                if (engine.getName() === name) {  // required widget has been started
                    ebo.destroy();
                    resolve(true);
                }
            })
            .observe(eventBus, {type});
        });
    }
})();