import { AbstractEventRecorder, Delegates } from '@amp-metrics/mt-metricskit-delegates-core';
import { EventRecorder as EventRecorder$1, ImmediateEventRecorder, environment, network, logger } from '@amp-metrics/mt-event-queue';
import { reflect, backoff } from '@amp-metrics/mt-metricskit-utils-private';

/*
 *  src/environment.js
 *  mt-metricskit-delegates-web
 *
 *  Copyright © 2022 Apple Inc. All rights reserved.
 *
 */

/**
 * Provides a pre-built HTML delegate to use against the metrics.system.environment delegate via metrics.system.environment.setDelegate()
 * If you want to use *most* of these methods, but not *all* of them, you can set this delegate and then create your own with whichever few methods you need to
 * customize additionally, and then setDelegate() *that* delegate, in order to override those methods.
 * @constructor
 * @param {Config} config (optional) - An instance of Config, which contains the topic and the relevant configurations for the topic.
 */

function Environment(config) {
    this._config = config;
}

/**
 ************************************ PSEUDO-PRIVATE METHODS/IVARS ************************************
 * These functions need to be accessible for ease of testing, but should not be used by clients
 */
Environment.prototype._document = function _document() {
    if (typeof document != 'undefined') {
        return document;
    } else {
        throw "metricskit-delegates-html.environment HTML delegate 'document' object not found";
    }
};

Environment.prototype._window = function _window() {
    if (typeof window != 'undefined') {
        return window;
    } else {
        throw "metricskit-delegates-html.environment HTML delegate 'window' object not found";
    }
};

/**
 ************************************ PUBLIC METHODS/IVARS ************************************
 */

/**
 * The name of the browser that generated the event, it only return browser field if "browserMaps" config property exists in the topic config
 * Common userAgent format: "Mozilla/5.0 (<system-information>) <platform> (<platform-details>) <extensions>"
 * This implementation will read the browser identifer from the first item in the extensions part of the userAgent.
 * For those browsers which do not store their browser identifier in the first item (can be configured in "specifiedBrowsers" config, see the below example),
 * the function will search the defined string in the userAgent to verify if it is the case.
 * It will load the browser related configurations from "browserMaps" in the topic. The config looks like
 * {
 *      specifiedBrowsers: string[], // listing the browsers identifiers which does not located in the first item in "extensions" in userAgent.
 *      browserMap: Record<string, string> // map the browser identifiers to readable string. For example, Edg -> Edge, OPR -> Opera
 * }
 * @example Safari
 * @returns {Promise<string | null> | null}
 * @overridable
 */
Environment.prototype.browser = function browser() {
    var userAgent = this._window().navigator.userAgent;

    if (!reflect.isDefinedNonNull(this._config) || !reflect.isDefinedNonNullNonEmpty(userAgent)) {
        return null;
    }

    return this._config.value('browserMaps').then(function (browserConfig) {
        if (!reflect.isDefinedNonNull(browserConfig)) {
            return null;
        }
        var specifiedBrowsers = browserConfig.specifiedBrowsers || [];
        var browserMap = browserConfig.browserMap || {};
        var browserIdentifier = null;
        for (var i = 0; i < specifiedBrowsers.length; i++) {
            var specifiedBrowser = specifiedBrowsers[i];
            if (userAgent.indexOf(specifiedBrowser) > -1) {
                browserIdentifier = specifiedBrowser;
                break;
            }
        }
        if (!reflect.isDefinedNonNull(browserIdentifier)) {
            // The Named capturing group: (?<name>...) requires "ECMAScript 2018"
            var matches = userAgent.match(/Mozilla\/5.0 \((.*?)\)(\s|$)((.*?)\/(.*?)(\s)\(.*\))?(.*?)\/(.*?)(\s|$)/);
            if (reflect.isDefinedNonNullNonEmpty(matches) && matches.length >= 8) {
                browserIdentifier = matches[7];
            }
        }

        if (reflect.isDefinedNonNull(browserIdentifier)) {
            browserIdentifier = browserIdentifier.trim();
        } else {
            browserIdentifier = 'unknown';
        }

        var browser = browserMap[browserIdentifier] || browserIdentifier;
        return browser;
    });
};

/**
 * The cookie string, e.g. "iTunes.cookie" (iTunes desktop), "iTunes.cookieForDefaultURL" (HTML iOS), "itms.cookie" (itml app), "document.cookie" (browser)
 * NOTE: Callers should override this method if they want to supply a different cookie.
 * @overridable
 */
Environment.prototype.cookie = function cookie() {
    return this._window().document.cookie;
};

/**
 * The URL that represents this page.
 * Typically this is a "deep link" type URL.
 * If no URL is available, this field may be omitted.
 * @example "https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewGrouping?cc=us&mt=8"
 * @returns {String}
 * @overridable
 */
Environment.prototype.pageUrl = function pageUrl() {
    return this._window().location.href;
};

/**
 * The URL of the parent page, if the app is embedded in a parent context.
 * Typically this is a "deep link" type URL.
 * If no URL is available, or if the app is not embedded, this field may be omitted.
 * @example "https://www.apple.com/blog/top-tracks.html"
 * @returns {String}
 * @overridable
 * Note: due to iframe sandbox rules, the parent window's location may not be accessible.
 * In that case, we fall back to document.referrer, which should be reliable if the app
 * within the iframe is a single page app (document.referrer changes on every page turn).
 * If the app in the iframe is not a single page app, we will have to persist the
 * original referrer from the first page across page turns via e.g. localStorage.
 * However, this use case is not currently needed by any client.
 */
Environment.prototype.parentPageUrl = function parentPageUrl() {
    var windowObject = this._window();
    var parentWindow = windowObject.parent;
    var parentPageUrl;

    if (parentWindow !== windowObject) {
        try {
            parentPageUrl = parentWindow.location.href;
        } catch (e) {
            parentPageUrl = this._document().referrer;
        }
    }

    return parentPageUrl;
};

/**
 * Pixel multiplier factor
 * @example 2
 * @returns {number}
 * @overridable
 */
Environment.prototype.pixelRatio = function pixelRatio() {
    return this._window().devicePixelRatio;
};

/**
 * Client screen height in pixels
 * @example 568
 * @returns {number}
 * @overridable
 */
Environment.prototype.screenHeight = function screenHeight() {
    return this._window().screen.height;
};

/**
 * Client screen width in pixels
 * @example 320
 * @returns {number}
 * @overridable
 */
Environment.prototype.screenWidth = function screenWidth() {
    return this._window().screen.width;
};

/**
 * Client’s user agent string.  If the "app field is not provided, "userAgent may be used to derive the value of the "app field
 * @example AppStore/2.0 iOS/8.3 model/iPhone7,2 build/12F70 (6; dt:106)
 * @returns {String}
 * @overridable
 */
Environment.prototype.userAgent = function userAgent() {
    return this._window().navigator.userAgent;
};

/**
 * App viewport height in pixels.  Does not include window “chrome”, status bars, etc.
 * Typically only available on desktop windowing systems.
 * @example 1920
 * @returns {number/undefined}
 * @overridable
 */
Environment.prototype.windowInnerHeight = function windowInnerHeight() {
    return this._window().innerHeight;
};

/**
 * App viewport width in pixels.  Does not include window “chrome”, status bars, etc.
 * Typically only available on desktop windowing systems.
 * @example 1080
 * @returns {number/undefined}
 * @overridable
 */
Environment.prototype.windowInnerWidth = function windowInnerWidth() {
    return this._window().innerWidth;
};

/**
 * Height in pixels of containing window, encompassing app viewport as well as window chrome, status bars, etc.
 * Typically only available on desktop windowing systems.
 * @example 1080
 * @returns {number/undefined}
 * @overridable
 */
Environment.prototype.windowOuterHeight = function windowOuterHeight() {
    return this._window().outerHeight;
};

/**
 * Width in pixels of containing window, encompassing app viewport as well as window chrome, status bars, etc.
 * Typically only available on desktop windowing systems.
 * @example 1920
 * @returns {number/undefined}
 * @overridable
 */
Environment.prototype.windowOuterWidth = function windowOuterWidth() {
    return this._window().outerWidth;
};

/**
 * The offset between W3C timing entry timestamps (which are relative to the page lifecycle) and the epoch time
 * and the epoch time, in milliseconds
 * @return {Number}
 * @overridable
 * Note: this is only currently used by PerfKit
 * TODO: <rdar://problem/44976037> Refactor: Delegates: revisit HTML delegate packaging
 */
Environment.prototype.timeOriginOffset = function timeOriginOffset() {
    var returnValue = null;
    var performance = this._window().performance;

    if (performance && performance.timing) {
        returnValue = performance.timing.navigationStart;
    }

    return returnValue;
};

/**
 * THE FOLLOWING DATA ARE UNAVAILABLE IN A PURE WEB BROWSER CONTEXT,
 * BUT MAY BE IMPLEMENTED (VIA POTENTIALLY DIFFERENT APIS) IN VARIOUS HTML WEB VIEW CONTEXTS (iOS vs Desktop vs tvOS)
 * THEY ARE LEFT UNIMPLEMENTED FOR CONTEXT-SPECIFIC DELEGATES TO OVERWRITE IF APPLICABLE
 */

/**
 * The app identifier of the binary app
 * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
 * @example "com.apple.appstore" or "com.apple.gamecenter"
 * @returns {String}
 * @overridable
 */
Environment.prototype.app = function app() {};

/**
 * The version number of this application
 * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
 * @example "1.0", "5.43", etc.
 * @returns {String}
 * @overridable
 * @defaultimpl navigator.appVersion
 */
Environment.prototype.appVersion = function appVersion() {};

/**
 * The total data capacity of the system, without regard for what's already been used or not.
 * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
 * @returns {number}
 * @overridable
 */
Environment.prototype.capacityData = function capacityData() {};

/**
 * The total available data capacity of the system.
 * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
 * @returns {number}
 * @overridable
 */
Environment.prototype.capacityDataAvailable = function capacityDataAvailable() {};

/**
 * The total disk capacity of the system, without regard for what's already been used or not.
 * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
 * @returns {number}
 * @overridable
 */
Environment.prototype.capacityDisk = function capacityDisk() {};

/**
 * The total system capacity, without regard for what's already been used or not.
 * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
 * @returns {number}
 * @overridable
 */
Environment.prototype.capacitySystem = function capacitySystem() {};

/**
 * The total available system capacity of the system.
 * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
 * @api public
 * @overridable
 */
Environment.prototype.capacitySystemAvailable = function capacitySystemAvailable() {};

/**
 * Type of internet connection.
 * Only applicable to devices
 * Beware that users on WiFi may actually be receiving 3G speeds (i.e. if device is tethered to a portable hotspot.)
 * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
 * @example "WiFi, "3G, etc.
 * @returns {String}
 * @overridable
 */
Environment.prototype.connectionType = function connectionType() {};

/**
 * The id of this user ("directory service id").
 * This id will get anonymized on the server prior to being saved.
 * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
 * @example 659261189
 * @returns {String}
 * @overridable
 */
Environment.prototype.dsId = function dsId() {};

/**
 * The hardware brand of the device. Not required for Apple devices.
 * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
 * @example "Samsung", "LG", "Google"
 * @returns {String}
 */
Environment.prototype.hardwareBrand = function hardwareBrand() {};

/**
 * The hardware family of the device
 * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
 * @example "iPhone", "Macbook Pro"
 * @returns {String}
 */
Environment.prototype.hardwareFamily = function hardwareFamily() {};

/**
 * The model of the device
 * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
 * @example "iPhone10,2", "MacbookPro11,5"
 * @returns {String}
 */
Environment.prototype.hardwareModel = function hardwareModel() {};

/**
 * App that is hosting the storesheet or app
 * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
 * @example com.rovio.AngryBirds
 * @returns {String}
 * @overridable
 */
Environment.prototype.hostApp = function hostApp() {};

/**
 * Version of the app that is hosting the storesheet or app
 * NO DEFAULT IMPLEMENTATION... HOWEVER: this field is optional
 * @example "1.0.1"
 * @returns {String}
 * @overridable
 */
Environment.prototype.hostAppVersion = function hostAppVersion() {
    // Optional field value
};

/**
 * The name of the OS
 * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
 * @example "ios", "macos", "windows"
 * @returns {String}
 */
Environment.prototype.os = function os() {};

/**
 * The build number of the OS
 * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
 * @example "15D60", "17E192"
 * @returns {String}
 */
Environment.prototype.osBuildNumber = function osBuildNumber() {};

/**
 * A string array of language IDs, ordered in descending preference
 * NO DEFAULT IMPLEMENTATION... THIS METHOD MUST BE REPLACED
 * @example ["en-US", "fr-CA"]
 * @returns {Array}
 */
Environment.prototype.osLanguages = function osLanguages() {};

/**
 * The full OS version number
 * In ITML, the value can be retrieved via Device.systemVersion
 * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
 * @example "8.2.1" (iOS) "10.10.3" (Desktop)
 * @returns {String}
 * @overridable
 */
Environment.prototype.osVersion = function osVersion() {};

/**
 * HTML resources revision number
 * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
 * @example 2C97 or 8.4.0.0.103
 * @returns {String}
 * @overridable
 */
Environment.prototype.resourceRevNum = function resourceRevNum() {};

/**
 * ISO 3166 Country Code. Apps that cannot provide a storeFrontHeader should provide a storeFrontCountryCode instead
 * NO DEFAULT IMPLEMENTATION... Either this method or storeFrontHeader must be replaced.
 * @example US
 * @returns {String}
 * @overridable
 */
Environment.prototype.storeFrontCountryCode = function storeFrontCountryCode() {};

/**
 * The value contained in the X-Apple-Store-Front header value at the time the event is being created.
 * NO DEFAULT IMPLEMENTATION... Either this method or storeFrontCountryCode must be replaced.
 * @example K143441-1,29 ab:rSwnYxS0
 * @returns {String}
 * @overridable
 */
Environment.prototype.storeFrontHeader = function storeFrontHeader() {};

/**
 * The best supported language for a storefront
 * NO DEFAULT IMPLEMENTATION... HOWEVER: this field is optional
 * @example en-US
 * @returns {String}
 * @overridable
 */
Environment.prototype.storeFrontLanguage = function storeFrontLanguage() {};

/**
 * The type of subscriber this user is.
 * NO DEFAULT IMPLEMENTATION... THIS METHOD SHOULD BE REPLACED
 * @example subscribed, notSubscribed, unknown, needsAuthentication
 * @returns {String}
 * @overridable
 */
Environment.prototype.userType = function userType() {};

/*
 *  src/event_recorder
 *  mt-metricskit-delegates-web
 *
 *  Copyright © 2022 Apple Inc. All rights reserved.
 *
 */

/**
 * A proxy EventRecorder to bridge the delegates layer to the mt-event-queue lib
 * @param eventRecorder - A eventRecorder implementation from the mt-event-queue lib
 * @constructor
 */
function EventRecorder(eventRecorder) {
    AbstractEventRecorder.call(this);
    this._proxyEventRecorder = eventRecorder;
    this.SEND_METHOD = eventRecorder.SEND_METHOD;
}

EventRecorder.prototype = Object.create(AbstractEventRecorder.prototype);
EventRecorder.prototype.constructor = EventRecorder;

/**
 * recordEvent implementation
 * This is an implementation for "AbstractEventRecorder._recordEvent"
 * @override
 * @param topic
 * @param eventFields
 * @private
 */
EventRecorder.prototype._recordEvent = function _recordEvent(topic, eventFields) {
    return this._proxyEventRecorder.recordEvent.apply(this._proxyEventRecorder, arguments);
};

/**
 * This overrides the same method in AbstractEventRecorder to ignore the pending recorded event when the appExitSendMethod === 'SEND_METHOD.BEACON_SYNCHRONOUS'.
 * @param {Boolean} appIsExiting - Pass true if events are being flushed due to your app exiting or page going away
 * (the send method will be different in order to attempt to post events prior to actual termination)
 * @param {String} appExitSendMethod (optional) the send method for how events will be flushed when the app is exiting.
 * Possible options are enumerated in the `eventRecorder.SEND_METHOD` object.
 * Note: This argument will be ignored if appIsExiting is false.
 * @returns {Promise}
 */
EventRecorder.prototype.flushUnreportedEvents = function flushUnreportedEvents(appIsExiting, appExitSendMethod) {
    var self = this;
    var args = Array.prototype.slice.call(arguments);
    // if this._proxyEventRecorder is an instance of QueuedEventRecorder and the callers wanted to flush events synchronously, ignore the pending events
    if (
        reflect.isDefinedNonNull(this._proxyEventRecorder.SEND_METHOD) &&
        appExitSendMethod === this._proxyEventRecorder.SEND_METHOD.BEACON_SYNCHRONOUS
    ) {
        return this._proxyEventRecorder.flushUnreportedEvents.apply(this._proxyEventRecorder, arguments);
    } else {
        return this._operationPromiseChain.then(function () {
            // Reset the promise chain
            self._operationPromiseChain = Promise.resolve();
            return self._proxyEventRecorder.flushUnreportedEvents.apply(self._proxyEventRecorder, args);
        });
    }
};

/**
 * Sends any remaining events in the queue
 * This is an implementation for "AbstractEventRecorder._flushUnreportedEvents"
 * @override
 */
EventRecorder.prototype._flushUnreportedEvents = function _flushUnreportedEvents() {
    return this._proxyEventRecorder.flushUnreportedEvents.apply(this._proxyEventRecorder, arguments);
};

/**
 * The methodology being used to send batches of events to the server
 * This field should be hardcoded in the client based on what method it is using to encode and send its events to Figaro.
 * The three typical values are:
 *      "itms" - use this value when/if JavaScript code enqueues events for sending via the "itms.recordEvent()" method in ITML.
 *      "itunes" - use this value when/if JavaScript code enqueues events by calling the "iTunes.recordEvent()" method in Desktop Store apps.
 *      "javascript" - use this value when/if JavaScript code enqueues events for sending via the JavaScript eventQueue management. This is typically only used by older clients which don't have the built-in functionality of itms or iTunes available to them.
 * DEFAULT implementation: console.debug()
 * @example "itms", "itunes", "javascript"
 * @returns {String}
 * @overridable
 */
EventRecorder.prototype.sendMethod = function sendMethod() {
    return this._proxyEventRecorder.sendMethod.apply(this._proxyEventRecorder, arguments);
};

/**
 * Set event queue related properties for the giving topic
 * @param {String} topic defines the Figaro "topic" that this event should be stored under
 * @param {Object} properties the event queue properties for the topic
 * @param {Boolean} properties.anonymous true if sending all events for the topic with credentials omitted(no cookies, no PII fields)
 */
EventRecorder.prototype.setProperties = function setProperties(topic, properties) {
    return this._proxyEventRecorder.setProperties.apply(this._proxyEventRecorder, arguments);
};

/**
 * clean resources of event recorder
 * Subclasses implement this method to handle how to clean resources
 * @returns {Promise} returns a Promise if the cleanup will asynchronously execute or undefined for synchronously executing
 */
EventRecorder.prototype.cleanup = function cleanup() {
    return this._proxyEventRecorder.cleanup.apply(this._proxyEventRecorder, arguments);
};

/*
 *  src/web_delegate.js
 *  mt-metricskit-delegates-web
 *
 *  Copyright © 2022 Apple Inc. All rights reserved.
 *
 */

/**
 * Delegate for providing access to the "canned" web MetricsKit delegates.
 * If further modification of these delegates is required, clients may pass in delegate options to override any of the fields within any delegate.
 * @constructor
 * @param {String} topic - Defines the AMP Analytics "topic" for events to be stored under
 * @param {Object} delegateOptions (optional) - Options that can be passed to either add additional delegates or to extend/override existing ones.
 */
var WebDelegates = function WebDelegates(topic, delegateOptions) {
    var config = this.getOrCreateConfig(topic);
    Delegates.call(this, topic, {
        environment: new Environment(config),
        eventRecorder: new EventRecorder(new EventRecorder$1(config))
    });

    this.immediateEventRecorder = new EventRecorder(new ImmediateEventRecorder(config));
    this.network = null;
    this.logger = null;

    if (delegateOptions) {
        this.mergeDelegates(delegateOptions);

        if (delegateOptions.environment) {
            this.setEnvironment(delegateOptions.environment);
        }

        if (delegateOptions.eventRecorder) {
            this.setEventRecorder(delegateOptions.eventRecorder);
        }

        if (delegateOptions.network) {
            this.setNetwork(delegateOptions.network);
        }

        if (delegateOptions.logger) {
            this.setLogger(delegateOptions.logger);
        }

        /**
         * TODO: We are temporarily setting this configUrl() delegate on the config to avoid breaking changes.
         * In the next major release, we will remove this and just fetch the config from
         * the delegateOptions.config.url directly instead of reading it from
         * this config method (defaulting to https://xp.apple.com/config/1/report/<topic>).
         */
        if (delegateOptions.config) {
            if (delegateOptions.config.url) {
                this.config.setDelegate({
                    configUrl: delegateOptions.config.url
                });
            }
            this.setConfig(delegateOptions.config);
        }
    }
};

/**
 * Inherit from the base Delegate class
 */
WebDelegates.prototype = Object.create(Delegates.prototype);
WebDelegates.prototype.constructor = WebDelegates;

/**
 * Sets the environment for the web-specific event queue as well as setting it on the delegate itself.
 * @param {Object} environment
 * @returns {WebDelegates}
 */
WebDelegates.prototype.setEnvironment = function (environment$1) {
    environment.setDelegate(environment$1);
    return Delegates.prototype.setEnvironment.call(this, environment$1);
};

/**
 * Sets the network for the web-specific event queue as well as setting it on the delegate itself.
 * @param {Object} network
 * @returns {WebDelegates}
 */
WebDelegates.prototype.setNetwork = function (network$1) {
    if (network$1) {
        this.network = network$1;
        network.setDelegate(network$1);
    }
    return this;
};

/**
 * Sets the logger for the web-specific event queue as well as setting it on the delegate itself.
 * @param {Object} logger
 * @returns {WebDelegates}
 */
WebDelegates.prototype.setLogger = function (logger$1) {
    if (logger$1) {
        this.logger = logger$1;
        logger.setDelegate(logger$1);
    }
    return this;
};

/**
 * Sets the immediate event recorder for the delegate.
 * Note: Immediate event recorders are specific to web delegates.
 * @param {Object} immediateEventRecorder
 * @returns {WebDelegates}
 */
WebDelegates.prototype.setImmediateEventRecorder = function (immediateEventRecorder) {
    if (immediateEventRecorder) {
        var newImmediateEventRecorder = Object.create(this.immediateEventRecorder);
        Object.assign(newImmediateEventRecorder, immediateEventRecorder);
        this.immediateEventRecorder = newImmediateEventRecorder;
    }
    return this;
};

/**
 * @returns {Object} The config sources.
 */
WebDelegates.prototype.configSources = function configSources() {
    var self = this;

    return new Promise(function (resolve, reject) {
        var onFetchSuccess = function onFetchSuccess(response) {
            try {
                var configObject = JSON.parse(response);
                self.config.setCachedSource(configObject);
                self.config.setServiceSource(configObject); // TODO: Deprecated
                resolve(configObject);
            } catch (error) {
                onFetchFailure(error);
            }
        };

        var onFetchFailure = function onFetchFailure(error) {
            self.config.setCachedSource(self.config.cachedSource());
            reject(error);
        };

        var configUrlPromise = Promise.resolve(self.config.configUrl());

        configUrlPromise
            .then(function (configUrl) {
                backoff.exponentialBackoff(
                    self.config.network.makeAjaxRequest.bind(self.config.network, configUrl, 'GET', null),
                    onFetchSuccess,
                    onFetchFailure
                );
            })
            .catch(onFetchFailure);
    });
};

export { Environment, WebDelegates };
