"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RequireLoader = void 0;
var tslib_1 = require("tslib");
var sp_core_library_1 = require("@microsoft/sp-core-library");
var sp_telemetry_1 = require("@ms/sp-telemetry");
var DeferredSystemJsFallbackLoader_1 = require("../../requirejs/DeferredSystemJsFallbackLoader");
var ComponentStore_1 = tslib_1.__importDefault(require("../../stores/ComponentStore"));
var LoadComponentExecutor_1 = tslib_1.__importDefault(require("../../utilities/LoadComponentExecutor"));
var dependencyLoading_1 = require("../common/dependencyLoading");
var normalize_1 = require("../common/normalize");
var RequireConfigurator_1 = require("./RequireConfigurator");
var killSwitches_1 = require("../common/killSwitches");
/**
 * RequireJS Loader
 */
var RequireLoader = /** @class */ (function () {
    function RequireLoader(_serviceScope, _useSecondaryCdn) {
        this._serviceScope = _serviceScope;
        this._useSecondaryCdn = _useSecondaryCdn;
        var isRequireLoaded = isRequireJsLoaded();
        if (!isRequireLoaded) {
            // using raw-loader ensures the module is loaded with the correct global (window) context
            DEPRECATED_UNIT_TEST
                ? require('./test/RequireJsMock')
                : eval.call(window, require('!!raw-loader!../../../blobs/requirejs/2.1.20/require.min'));
        }
        this._requireJs = window.requirejs;
        this._define = window.define;
        this._configurator = new RequireConfigurator_1.RequireConfigurator(this._requireJs, this._useSecondaryCdn, isRequireLoaded);
        this._loadComponentExecutor = new LoadComponentExecutor_1.default(this._loadComponent.bind(this));
    }
    /**
     * {@inheritdoc IModuleLoader.delete}
     *
     * @remarks Calls RequireJS undef() and removes the script tag from the head.
     */
    RequireLoader.prototype.delete = function (manifest) {
        var _a;
        var name = normalize_1.normalizeComponentId(manifest.id, manifest.version);
        this._requireJs.undef(name);
        var tags = document.querySelectorAll("script[data-requiremodule='" + name + "']");
        for (var i = 0; i < tags.length; ++i) {
            var t = tags[i];
            (_a = t.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(t);
        }
    };
    /**
     * {@inheritdoc IModuleLoader.ensure}
     */
    RequireLoader.prototype.ensure = function (moduleName, module) {
        this._requireJs.undef(moduleName);
        this._define(moduleName, [], function () { return module; });
        this._requireJs([moduleName]);
    };
    /**
     * {@inheritdoc IModuleLoader.loadComponent}
     */
    RequireLoader.prototype.loadComponent = function (manifest) {
        var _this = this;
        return new Promise(function (resolve) {
            var id = manifest.id, version = manifest.version;
            var component = ComponentStore_1.default.instance.tryGetComponent(id, version);
            if (!component) {
                component = _this._loadComponentExecutor.loadComponent(manifest, 0);
                ComponentStore_1.default.instance.storeComponent(id, version, component);
            }
            resolve(component);
        });
    };
    /**
     * {@inheritdoc IModuleLoader.loadEntryPoint}
     */
    RequireLoader.prototype.loadEntryPoint = function (manifest, name, globalName) {
        var normalizedName = name
            ? normalize_1.normalizeName(manifest, name)
            : normalize_1.normalizeComponentId(manifest.id, manifest.version);
        return this.load(normalizedName, globalName).then(function (module) {
            if (!name && manifest.loaderConfig.exportName) {
                module = module[manifest.loaderConfig.exportName];
            }
            return module;
        });
    };
    /**
     * {@inheritdoc IModuleLoader.loadScript}
     */
    RequireLoader.prototype.loadScript = function (url, options) {
        if (options === null || options === void 0 ? void 0 : options.globalExportsName) {
            this._configurator.configLoadScript(url, options.globalExportsName);
        }
        return this.load(url, options === null || options === void 0 ? void 0 : options.globalExportsName);
    };
    /**
     * {@inheritdoc IModuleLoader.load}
     */
    RequireLoader.prototype.load = function (moduleName, globalName) {
        var _this = this;
        return new Promise(function (resolve) {
            moduleName = _this._configurator.getDuplicateModuleName(moduleName) || moduleName;
            resolve(_this._require(moduleName));
        }).then(function (module) {
            /*
             * Resolves with the correct loaded module, when applicable.
             *
             * If the module is defined by a global variable, it returns the object in the global variable.
             * If the module has been loaded correctly, it returns the loaded module.
             * If the module failed to load and it's an SPFx component, we try to load a different version
             * of the same component.
             *
             * This happens because if the version in the manifest and the version in the module name don't match,
             * RequireJS has a weird behavior where it loads the component but doesn't return it.
             */
            if (globalName) {
                if (window.hasOwnProperty(globalName)) {
                    module = window[globalName];
                    _this.ensure(moduleName, module);
                }
                else {
                    module = {};
                }
            }
            else if (!module && isSPFxComponent(moduleName)) {
                return _this._loadWithDifferentModuleId(moduleName);
            }
            return module;
        });
    };
    RequireLoader.prototype._loadComponent = function (manifest, retryCount) {
        var _this = this;
        if (retryCount === void 0) { retryCount = 0; }
        var requireMonitor;
        var fallbackMonitor;
        if (killSwitches_1.isSkipFirstPartyTinyLogKillswtichActivated() || !manifest.isInternal) {
            requireMonitor = new sp_telemetry_1._QosMonitor('TinyRequireLoader._loadComponent');
        }
        var extraData = {
            alias: manifest.alias,
            isDebug: manifest._isDebug,
            isInternal: manifest.isInternal,
            manifestId: manifest.id,
            version: manifest.version
        };
        var depNames = Object.keys(manifest.loaderConfig.scriptResources);
        // Remove the loadPromise variable and expand the promises inline when graduate experiment
        // WEXRecoverParallelLoading 90080
        var loadAllDeps = function () {
            return Promise.all(dependencyLoading_1.loadComponentDependencies(_this, manifest, depNames));
        };
        var loadPromise = this._configurator.isRecoverParallelLoadingOn
            ? this._configurator.configLoadComponent(manifest).then(function () { return loadAllDeps(); })
            : loadAllDeps();
        return loadPromise
            .then(function () {
            if (!_this._configurator.isRecoverParallelLoadingOn) {
                // Load component dependencies first to ensure the manifests exist before configuration
                void _this._configurator.configLoadComponent(manifest);
            }
            // Load path dependencies after configuration to ensure the correct paths are configured
            return Promise.all(dependencyLoading_1.loadPathDependencies(_this, manifest, depNames));
        })
            .then(function () {
            var component = Promise.resolve({});
            if (manifest.loaderConfig.entryModuleId) {
                component = _this.loadEntryPoint(manifest);
            }
            else {
                _this.ensure(normalize_1.normalizeComponentId(manifest.id, manifest.version), {});
            }
            return component;
        })
            .catch(function (e) {
            // Stop retrying after the second retry (3 total attempts)
            if (retryCount > 1) {
                throw e;
            }
            // If the attempt to load fails, the module loader deletes the module to try to load it again
            // Otherwise, the retry will get the module from the cache, thus failing again.
            _this.delete(manifest);
            return _this._loadComponent(manifest, retryCount + 1);
        })
            .catch(function (e) {
            extraData.requirejsError = e;
            if (!killSwitches_1.isSkipFirstPartyTinyLogKillswtichActivated() && manifest.isInternal) {
                requireMonitor = new sp_telemetry_1._QosMonitor('TinyRequireLoader._loadComponent');
            }
            if (requireMonitor) {
                requireMonitor.writeUnexpectedFailure(undefined, e, extraData);
            }
            ComponentStore_1.default.instance.deleteComponent(manifest.id, manifest.version);
            // Only use the fallback for third party components
            if (manifest.isInternal) {
                throw e;
            }
            fallbackMonitor = new sp_telemetry_1._QosMonitor('TinyRequireLoader._loadComponentWithFallback');
            return _this._ensureFallback();
        })
            .then(function (c) {
            if (!c) {
                return _this._systemJsFallbackLoader.loadComponent(manifest);
            }
            if (requireMonitor) {
                requireMonitor.writeSuccess();
            }
            return c;
        })
            .catch(function (e) {
            if (fallbackMonitor) {
                extraData.systemjsError = e;
                fallbackMonitor.writeUnexpectedFailure(undefined, e, extraData);
            }
            throw extraData;
        });
    };
    RequireLoader.prototype._ensureFallback = function () {
        var _this = this;
        if (this._systemJsFallbackLoader) {
            return Promise.resolve();
        }
        return DeferredSystemJsFallbackLoader_1.DeferredSystemJsFallbackLoader.getInstance()
            .load()
            .then(function () {
            _this._systemJsFallbackLoader = DeferredSystemJsFallbackLoader_1.DeferredSystemJsFallbackLoader.getInstance().create(_this._serviceScope);
            _this._loadComponentExecutor.setAlternativeExecutor(_this._systemJsFallbackLoader.executor);
            _this._systemJsFallbackLoader.executor.setAlternativeExecutor(_this._loadComponentExecutor);
        });
    };
    /**
     * Looks at the path to see if it is a provider hosted app.  If it is, it modifies the existing
     * module name to match the provider hosted app js module name and then loads the component.  If
     * its not that case it looks at the registry of RequireJS to find another version of the same
     * component as moduleName, if found, it configures RequireJS with the same dependency resolution
     * (that comes from the manifest) and ensuring that the path points only to the found version,
     * and it loads the component.
     */
    RequireLoader.prototype._loadWithDifferentModuleId = function (moduleName) {
        // tslint:disable-next-line:no-any
        var requireContext = this._requireJs.s.contexts._;
        var path = requireContext.config.paths[moduleName];
        var requestedComponentId = splitComponentIdFromModuleName(moduleName);
        var loadedModuleName = '';
        if (path.indexOf('/sp-provider-hosted-web-part') !== -1) {
            // The provider hosted web part has js files with a fixed guid (given below), but
            // each instance of a webpart gets it's own unique guid which doesn't match the JS.
            // Therefore when we see this we replace it automatically and retry.
            loadedModuleName = '4fca678e-55b6-46c8-b823-dd875dfdb951_1.0.0';
        }
        else {
            var registry = Object.keys(requireContext.registry);
            for (var _i = 0, registry_1 = registry; _i < registry_1.length; _i++) {
                var rName = registry_1[_i];
                var loadedComponentId = splitComponentIdFromModuleName(rName);
                if (requestedComponentId === loadedComponentId && moduleName !== rName) {
                    loadedModuleName = rName;
                    break;
                }
            }
        }
        if (!loadedModuleName) {
            return Promise.reject('');
        }
        this._configurator.replaceModuleInConfig(loadedModuleName, moduleName, path);
        return this._require(loadedModuleName);
    };
    RequireLoader.prototype._require = function (moduleName) {
        var _this = this;
        return new Promise(function (resolve, reject) {
            _this._requireJs([moduleName], function (module) { return resolve(module); }, function (error) { return reject(error); });
        });
    };
    return RequireLoader;
}());
exports.RequireLoader = RequireLoader;
function isRequireJsLoaded() {
    return window.requirejs !== undefined && window.require !== undefined && window.define !== undefined;
}
function isSPFxComponent(moduleName) {
    if (moduleName.split('/').length > 1) {
        return false; // This is a path dependency, not a component itself
    }
    var substrings = moduleName.split('_');
    return substrings.length === 2 && sp_core_library_1.Guid.isValid(substrings[0]) && sp_core_library_1.Version.isValid(substrings[1]);
}
function splitComponentIdFromModuleName(moduleName) {
    return moduleName.split('_')[0];
}
//# sourceMappingURL=RequireLoader.js.map