"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RequireConfigurator = void 0;
var tslib_1 = require("tslib");
var loadScriptWithoutEval_1 = require("../../requirejs/loadScriptWithoutEval");
var AddressStore_1 = tslib_1.__importDefault(require("../../stores/AddressStore"));
var ManifestStore_1 = tslib_1.__importDefault(require("../../stores/ManifestStore"));
var isCorsEnabled_1 = tslib_1.__importDefault(require("../../utilities/isCorsEnabled"));
var resolveAddress_1 = tslib_1.__importDefault(require("../../utilities/resolveAddress"));
var normalize_1 = require("../common/normalize");
var RequireConfigurator = /** @class */ (function () {
    function RequireConfigurator(_requireJs, _useSecondaryCdn, skipBaseConfig) {
        this._requireJs = _requireJs;
        this._useSecondaryCdn = _useSecondaryCdn;
        this._configuredFailoverPaths = new Set();
        this._duplicateModuleNames = new Map();
        var config = {
            waitSeconds: 90
        };
        if (!skipBaseConfig) {
            config.baseUrl = 'https://relative-path.invalid';
        }
        config.onNodeCreated = function (node, c, n, url) {
            // TODO (1213908): Handle the subresource integrity
            if ((0, isCorsEnabled_1.default)(url)) {
                // <script crossorigin="anonymous"> enables scripts be transparent to service workers
                node.setAttribute('crossorigin', 'anonymous');
            }
        };
        this._requireJs.config(config);
    }
    RequireConfigurator.prototype.configLoadComponent = function (manifest) {
        var _this = this;
        var resources = manifest.loaderConfig.scriptResources;
        var config = { paths: {}, map: {}, shim: {} };
        var currentMapConfig = {};
        var resourceNames = Object.keys(resources);
        var deps = [];
        for (var _i = 0, resourceNames_1 = resourceNames; _i < resourceNames_1.length; _i++) {
            var name_1 = resourceNames_1[_i];
            var resource = resources[name_1];
            if (resource.type === 'component') {
                deps.push(this._configureComponentResource(name_1, resource, manifest, config, currentMapConfig));
            }
            else {
                this._configurePathResource(name_1, resource, manifest, config, currentMapConfig);
            }
        }
        var handler = function () {
            var normalizedName = (0, normalize_1.normalizeComponentId)(manifest.id, manifest.version);
            config.map[normalizedName] = currentMapConfig;
            _this._requireJs.config(config);
            // There's a global __spfxPreloadedModules that may host the define arguments for preloaded components.
            // This ensures that RequireJS will automatically load them if they are present.
            if (window.define &&
                window.__spfxPreloadedModules &&
                window.__spfxPreloadedModules.hasOwnProperty(normalizedName)) {
                var args = window.__spfxPreloadedModules[normalizedName];
                window.define(args.id, args.deps, args.f);
                delete window.__spfxPreloadedModules[normalizedName];
            }
        };
        //
        // We need to update the config right away to store the paths.
        // Example: we have 2 components A and B, both have a dependency on external c.js.
        // We start with loading A, adding c.js to the AddressStore as a known name.
        // But then we're not updating Require config right await, but waiting untill all component dependencies for A are loaded (see Promise.all below).
        // At that moment (while waiting for A dependencies) we start loading B.
        // c.js will be found in AddressStore as a known name, and we will try to require it.
        // But it will fail, because RequireJS doesn't have paths config for c.js yet.
        // The code below will update RequireJS config right away, so that it will be able to require/resolve c.js for B.
        //
        // See: https://github.com/SharePoint/sp-dev-docs/issues/8489
        //
        if (Object.keys(config.paths).length) {
            var pathsConfig = {
                paths: config.paths
            };
            this._requireJs.config(pathsConfig);
            config.paths = undefined;
        }
        return Promise.all(deps).then(function () { return handler(); });
    };
    RequireConfigurator.prototype.configLoadScript = function (url, globalExportsName) {
        var _a;
        this._requireJs.config({ shim: (_a = {}, _a[url.replace(/\.js$/, '')] = { exports: globalExportsName }, _a) });
    };
    RequireConfigurator.prototype.getDuplicateModuleName = function (moduleName) {
        return this._duplicateModuleNames.get(moduleName);
    };
    RequireConfigurator.prototype.replaceModuleInConfig = function (existingModuleName, newModuleName, modulePath) {
        var _a, _b;
        // tslint:disable-next-line:no-any
        var requireContext = this._requireJs.s.contexts._;
        this._requireJs.config({
            map: (_a = {}, _a[existingModuleName] = requireContext.config.map[newModuleName], _a),
            paths: (_b = {}, _b[existingModuleName] = modulePath, _b)
        });
        // Remove the definition (including the <script> tag) of any previously load of the same component
        // Otherwise RequireJS can't load the module again
        this._requireJs.undef(newModuleName);
        this._requireJs.undef(existingModuleName);
        // Avoid two path entries going to the same path
        requireContext.config.paths[newModuleName] = "SPFx: Use ".concat(existingModuleName, " instead");
    };
    RequireConfigurator.prototype._configurePathResource = function (name, resource, manifest, config, mapConfig) {
        // Step 1: Set map configuration with the normalized name.
        // The normalized name of the entry point is the name of the component (i.e. <id>_<version>)
        // Other path dependencies have the resource name appended (i.e. <id>_<version>/<name>)
        var normalizedName;
        if (name === manifest.loaderConfig.entryModuleId) {
            // the entry point of the module
            normalizedName = (0, normalize_1.normalizeComponentId)(manifest.id, manifest.version);
        }
        else {
            normalizedName = (0, normalize_1.normalizeName)(manifest, name);
            mapConfig[name] = normalizedName;
        }
        // Step 2: Set path configuration with the URL of the resource.
        // If the URL for the resource is already used by another manifest, we re-use that normalized name.
        var address = resolveAddressRequire(manifest, name, this._useSecondaryCdn);
        var existingNormalizedNameForAddress = AddressStore_1.default.getNormalizedName(address);
        if (existingNormalizedNameForAddress) {
            mapConfig[name] = existingNormalizedNameForAddress;
            this._duplicateModuleNames.set(normalizedName, existingNormalizedNameForAddress);
        }
        else {
            // Start downloading the script without evaluating it.
            // tslint:disable-next-line: no-floating-promises
            (0, loadScriptWithoutEval_1.loadScriptWithoutEval)(address);
            // TODO (1213908): Handle the subresource integrity
            config.paths[normalizedName] = address.path;
            AddressStore_1.default.set(normalizedName, address);
        }
        // Step 3: Set shim configuration with global names and dependencies, if applicable.
        // Only path configurations (not localized path configurations) support it.
        // There are some issues with JQuery and Yammer using RequireJS that we can self-recover.
        // This does nothing if JQuery or Yammer are not dependencies of the component.
        fixKnownIssues(name, manifest.loaderConfig.scriptResources, resource);
        if (resource.globalName) {
            config.shim[normalizedName] = {
                exports: resource.globalName,
                deps: resource.globalDependencies
            };
        }
    };
    RequireConfigurator.prototype._configureComponentResource = function (name, moduleConfiguration, manifest, config, mapConfig) {
        var _this = this;
        var handleManifest = function (resourceManifest) {
            if (resourceManifest) {
                mapConfig[name] = (0, normalize_1.normalizeComponentId)(resourceManifest.id, resourceManifest.version);
            }
            if (moduleConfiguration.failoverPath) {
                // Multiple components can have the same dependencies with failover paths. Configure only once.
                if (!_this._configuredFailoverPaths.has(name)) {
                    _this._configuredFailoverPaths.add(name);
                    // Store the address both in RequireJS and the AddressStore for the SPFx loader.
                    var address = resolveAddressRequire(manifest, name, _this._useSecondaryCdn);
                    // Start downloading the script without evaluating it.
                    // tslint:disable-next-line: no-floating-promises
                    (0, loadScriptWithoutEval_1.loadScriptWithoutEval)(address);
                    // TODO (1213908): Handle the subresource integrity
                    config.paths[name] = address.path;
                    AddressStore_1.default.set(name, address);
                }
            }
        };
        return ManifestStore_1.default.instance
            .requestManifest(moduleConfiguration.id, moduleConfiguration.version)
            .then(function (resourceManifest) { return handleManifest(resourceManifest); });
    };
    return RequireConfigurator;
}());
exports.RequireConfigurator = RequireConfigurator;
function resolveAddressRequire(manifest, resourceName, useSecondaryCdn) {
    var integrityPath = (0, resolveAddress_1.default)(manifest, resourceName, useSecondaryCdn);
    // Remove the .js extension
    integrityPath.path = integrityPath.path.replace(/.js$/, '');
    return integrityPath;
}
function fixKnownIssues(name, resources, resource) {
    // tslint:disable:no-string-literal
    // Fix-up: Some customers declared jQuery dependency without explicitly declaring a global name in the manifest
    // Fix-up: Some customers declared the global as 'jquery' instead of 'jQuery'
    // RequireJS doesn't work properly in either of those cases
    fixWrongGlobalName(name, 'jquery', 'jquery', 'jQuery', resource);
    // Fix-up: Some customers declare JQuery UI without the dependency on JQuery
    if (name.toLowerCase() === 'jqueryui' &&
        (!resource.globalDependencies || resource.globalDependencies.length === 0)) {
        if (resources['jquery']) {
            resource.globalDependencies = ['jquery'];
        }
        else if (resources['jQuery']) {
            resource.globalDependencies = ['jQuery'];
        }
    }
    // Fix-up: Some customers declared yammer dependency without explicitly declaring a global name in the manifest
    // Fix-up: Some customers declared the global as 'yammer' instead of 'yam'
    fixWrongGlobalName(name, 'yammer', 'yammer', 'yam', resource);
    // tslint:enable:no-string-literal
}
function fixWrongGlobalName(name, expectedName, knownBadName, expectedGlobalName, resource) {
    if ((name.toLowerCase() === expectedName && !resource.globalName) || resource.globalName === knownBadName) {
        resource.globalName = expectedGlobalName;
    }
}
//# sourceMappingURL=RequireConfigurator.js.map