newton.js

/*!
 * Buongiorno Newton JS Library
 *
 * Copyright 2016, Buongiorno, S.P.A.
 * All Rights Reserved
 * https://static.newton.pm/js/latest/reference
 * Released under the Modified BSD License
 */
;
(function(win) {
'use strict';

// Avoid override newton instance
if (win.Newton) return;

var AUTH_END_SANDBOX_DOMAIN = 'https://auth-api-sandbox.newton.pm';
var AUTH_END_DOMAIN = 'https://auth-api.newton.pm';
var CLIENT_END_SANDBOX_DOMAIN = 'https://client-api-sandbox.newton.pm';
var CLIENT_END_DOMAIN = 'https://client-api.newton.pm';

/** Generated by autorevision - do not hand-hack! */

var autorevision = {
	VCS_TYPE: "git",
	VCS_BASENAME: "_library",
	VCS_UUID: "a2751d8e1deb39c894b46bf88920c581f41b2c3a",
	VCS_NUM: 792,
	VCS_DATE: "2022-03-17T09:32:09+0100",
	VCS_BRANCH: "remotes/origin/master",
	VCS_TAG: "v2.30.1",
	VCS_TICK: 0,
	VCS_EXTRA: "",

	VCS_FULL_HASH: "db8879ee4172c52ea5deb93bbf9063d19d12d661",
	VCS_SHORT_HASH: "db8879e",

	VCS_WC_MODIFIED: false
};

/** Node.js compatibility */
if (typeof module !== 'undefined') {
	module.exports = autorevision;
}

/** end */

/* exported assertionInitializator */

var assertionInitializator = {
  deps: ['NewtonError'],
  init: function(NewtonError) {
    var urlPattern = /^https?:\/\/[\.\w\d]+(:\d+)?/;
    var labelRegexp = /^[\w-]{1,32}$/;

    function stringifyValue(val) {
      if (assertion.isUndefined(val)) {
        return '<undefined>';
      }
      if (assertion.isNull(val)) {
        return '<null>';
      }
      return val.toString();
    }

    var assertion = {
      assertIsALabel: function assertIsALabel(label) {
        if (!labelRegexp.test(label) || typeof label !== 'string') {
          throw new NewtonError('label should be match ' + labelRegexp.toString());
        }
      },
      assertStringIsAValidUrl: function assertStringIsAValidUrl(str) {
        if (!urlPattern.test(str)) {
          throw new NewtonError('string should be a valid url');
        }
      },
      assertIfIsString: function assertIfIsString(val) {
        if (!this.isString(val)) {
          throw new NewtonError('String expected. Found ' + stringifyValue(val));
        }
      },
      assertIfIsNumber: function assertIfIsNumber(val) {
        if (!this.isNumber(val)) {
          throw new NewtonError('Number expected. Found ' + stringifyValue(val));
        }
      },
      assertIfIsObject: function assertIfIsObject(val) {
        if (!this.isObject(val)) {
          throw new NewtonError('Object expected. Found ' + stringifyValue(val));
        }
      },
      assertIfIsBoolean: function assertIfIsBoolean(val) {
        if (!this.isBoolean(val)) {
          throw new NewtonError('Boolean expected. Found ' + stringifyValue(val));
        }
      },
      isBoolean: function(val) {
        return val === true || val === false;
      },
      isNumber: function(val) {
        return typeof val === 'number' && isFinite(val);
      },
      isString: function(val) {
        return typeof val === 'string';
      },
      isUndefined: function(val) {
        return val === undefined;
      },
      isNull: function(val) {
        return val === null;
      },
      isFunction: function(val) {
        return !!(val && val.constructor && val.call && val.apply);
      },
      isObject: function(val) {
        return !this.isNull(val) && !this.isUndefined(val) && (val instanceof Object);
      }
    };

    return assertion;
  }
};

/* exported consoleInitializator */

var consoleInitializator = {
  deps: ['window', 'utils'],
  init: function (parent, utils) {

    function isDebugging() {
      return utils.cookie.getItem('newton-debug') || false;
    }

    parent.console = parent.console || {};
    var parentConsoleLog = parent.console.log || function() { };

    var __console__ = {
      log: parent.console.log || parentConsoleLog,
      debug: parent.console.debug || parentConsoleLog,
      info: parent.console.info || parentConsoleLog,
      warn: parent.console.warn || parentConsoleLog,
      error: parent.console.error || parentConsoleLog
    };

    return {
      log: function() {
        if (isDebugging()) {
          __console__.log.apply(parent.console, arguments);
        }
      },
      debug: function() {
        if (isDebugging()) {
          __console__.debug.apply(parent.console, arguments);
        }
      },
      info: function() {
        if (isDebugging()) {
          __console__.info.apply(parent.console, arguments);
        }
      },
      warn: function() {
        __console__.warn.apply(parent.console, arguments);
      },
      error: function() {
        __console__.error.apply(parent.console, arguments);
      }
    };
  }
};

/* exported errorInitializator */

var errorInitializator = {
  deps: [],
  init: function () {

    function NewtonError(message) {
      this.name = 'NewtonError';
      this.message = message;
      this.stack = (new Error()).stack;
      this.extra = {};
      this.code = 'SHOULD_NEVER_HAPPEN';
      this.statusCode = 0;
    }

    NewtonError.prototype = Error.prototype;
    NewtonError.prototype.constructor = NewtonError;
    NewtonError.prototype.stringifyErr = function() {
      var  NewtonErrorJsonStringify={};
      for(var prop in this) {
        NewtonErrorJsonStringify[prop]=this[prop];
      }
      return  JSON.stringify(NewtonErrorJsonStringify);
    
    }

    return NewtonError;
  }
}

/* exported EventListenerInitializator */

var EventListenerInitializator = {
  deps: [],
  init: function() {
    var EventListener  = function() { this._events = {}; };
    EventListener.prototype  = {
      on: function(event, fct) {
        this._events[event] = this._events[event] || [];
        this._events[event].push([fct, -1]);
      },
      onOnce: function(event, fct) {
        this._events[event] = this._events[event] || [];
        this._events[event].push([fct, 1]);
      },
      removeListener: function(event, fct) {
        if(event in this._events === false  )  return;
        if(fct === undefined) {
          delete this._events[event];
          return;
        }
        for(var index = 0; index < this._events[event].length; index++) {
          if (this._events[event][index][0] === fct) break;
        }
        if (index >= this._events[event].length) return;
        this._events[event].splice(index, 1);
      },
      emit : function(event /* , args... */) {
        if( event in this._events === false  )  return;
        for(var i = 0; i < this._events[event].length; i++){
          if (this._events[event][i][1] === 0) continue;
          this._events[event][i][0].apply({}, Array.prototype.slice.call(arguments, 1));
          this._events[event][i][1]--;
        }
      }
    };
    return EventListener;
  }
};
/* exported eventModuleInitializator */

var eventModuleInitializator = {
  deps: ['window', 'console', 'assertion', 'httpSingleton', 'statusSingleton', 'publicInterface', 'utils', 'platformSpecific', 'staticInterface', 'simpleObject', 'NewtonError'],
  init: function(parent, _console, assertion, httpSingleton, statusSingleton, publicInterface, utils, platformSpecific, staticInterface, SimpleObject, NewtonError) {
    var timers = {};
    var queueLocalStorageKey = 'newton-queued-events';
    var onFlushingEventListener = function() {};
    var flushingPeriodInSeconds = 2;
    var flowLocalStorageKey = 'newton-flow';
    var flowLocalStorageLength = 12;
    var heartBeatPeriodInSeconds = 10;
    var environmentCustomDataLocalStorageKey = 'newton-env-custom-data';

    function getHashFromSimpleObjectOrUndefined(customData) {
      return customData ? customData.toJSONObject() : undefined;
    }

    function continueSession() {
      _console.debug('continueSession');
      var environmentCustomData = utils.localStorage.getItem(environmentCustomDataLocalStorageKey) || null;
      if (environmentCustomData) {
        try {
          environmentCustomData = SimpleObject.fromJSONObject(environmentCustomData);
        } catch(e) {
          environmentCustomData = null;
        }
      }
      addContinueSessionEvent(environmentCustomData);
    }

    function startNewSession() {
      _console.debug('startNewSession');
      var environmentCustomData = utils.localStorage.getItem(environmentCustomDataLocalStorageKey) || null;
      if (environmentCustomData) {
        try {
          environmentCustomData = SimpleObject.fromJSONObject(environmentCustomData);
        } catch(e) {
          environmentCustomData = null;
        }
      }
      statusSingleton.setSessionId(utils.getRandomString2());
      addStartSessionEvent(environmentCustomData);
    }

    function getNEEnrichment() {
      var key = 'ne_enrichment';
      // key = encodeURIComponent(key);
      var cookies = document.cookie.split(";");
      for (var i = 0; i < cookies.length; i++) {
        var splitted = cookies[i].split("=");
        var name = (splitted.shift() || '').trim();
        if (name === key) {
          var value = splitted.shift();
          return JSON.parse(decodeURIComponent(value));
        }
      }
      return null;
    }

    // Inplace enrichment
    function mergeObject(obj1, obj2) {
      for (var k in obj2) {
        if (obj1[k]) continue
        obj1[k] = obj2[k]
      }
    }

    function isEmptyObject(obj) {
      for(var prop in obj) {
        if(obj.hasOwnProperty(prop))
          return false;
      }
      return JSON.stringify(obj) === JSON.stringify({});
    }

    function inflateEvent(ev) {
      _console.debug('Inflating event');
      var sessionId = statusSingleton.getSessionId();
      if (!sessionId) {
        startNewSession();
        return inflateEvent(ev);
      }
      ev.session_id = sessionId;
      ev.creation_date = (new Date()).toISOString();
      ev.user_id = statusSingleton.getUserToken();

      ev.local_context = {
        phase: localAnalyticContext
      };

      var eventTypesToEnrich = ['start session', 'attached_session', 'identify'];
      if (eventTypesToEnrich.indexOf(ev.event_type) > -1) {
        try {
          var neEnrichment = getNEEnrichment();
          if (neEnrichment && !isEmptyObject(neEnrichment)) {
            if (!ev.custom_data) ev.custom_data = {};
            mergeObject(ev.custom_data, neEnrichment);
          }
        } catch (e) { }
      }
    }

    function queueEvent(ev) {
      inflateEvent(ev);
      try {
        assertion.assertIfIsString(ev.session_id);
        assertion.assertIfIsString(ev.creation_date);
        assertion.assertIfIsString(ev.user_id);
        assertion.assertIfIsString(ev.event_type);
      } catch(e) {
        _console.warn('Invalid event queued. Skipped', ev);
        _console.error(e);
        return;
      }
      var events = utils.localStorage.getItem(queueLocalStorageKey);
      events = events ? events : '';
      events += ',' + JSON.stringify(ev);

      _console.debug('Event is stored');
      utils.localStorage.setItem(queueLocalStorageKey, events);
    }

    function removeEventsAndReturn() {
      var events = utils.localStorage.getItem(queueLocalStorageKey);
      if (!events) return;
      utils.localStorage.removeItem(queueLocalStorageKey);

      return '[' + events.substring(1) + ']';
    }

    function addContinueSessionEvent(environmentCustomData) {
      try {
        SimpleObject._assertIsInstanceOfOrNull(environmentCustomData);
        environmentCustomData = environmentCustomData.toJSONObject();
      } catch(e) {
        environmentCustomData = undefined;
      }
      var eventObject = {
        event_type: 'attached_session',
        attach_session_type: 'continue',
        custom_data: environmentCustomData
      }

      var logViewInfoObject = httpSingleton.getLogViewInfo();
      if (logViewInfoObject) {
        eventObject.logview_info = logViewInfoObject;
      }
      queueEvent(eventObject);
    }

    function addStartSessionEvent(environmentCustomData) {
      try {
        SimpleObject._assertIsInstanceOfOrNull(environmentCustomData);
        environmentCustomData = environmentCustomData.toJSONObject();
      } catch(e) {
        environmentCustomData = undefined;
      }
      var eventObject = {
        event_type: 'start session',
        browser: platformSpecific.browser,
        os: platformSpecific.os,
        device: platformSpecific.device,
        screen_width: platformSpecific.screen_width,
        screen_height: platformSpecific.screen_height,
        user_agent: platformSpecific.userAgent,
        with_ls: platformSpecific.isLocalStorageSupported,
        with_cookie: platformSpecific.isCookieSupported,
        with_hidden: platformSpecific.isHiddenSupported,
        language: platformSpecific.language,
        device_id: statusSingleton.getDeviceId(),
        sdk_version: staticInterface.getVersionString(),
        custom_data: environmentCustomData
      }

      var logViewInfoObject = httpSingleton.getLogViewInfo();
      if (logViewInfoObject) {
        eventObject.logview_info = logViewInfoObject;
      }
      var oldSessionId = statusSingleton.getOldSessionId();
      if (oldSessionId) {
        eventObject.refreshed_session = oldSessionId;
      }
      queueEvent(eventObject);
    }

    function addPerformanceMetricsEvent() {
      try {
        if (platformSpecific.isMetricsSupported){
            queueEvent({
              event_type: 'resource_timing',
              metrics: parent.performance.timing
            });
        }
      } catch (e){
        _console.warn('Unable to access resource timing', e);
      }
    }

    function flushFunction() {
      var events = removeEventsAndReturn();
      if (!events) return;
      _console.debug('Flushing', events, 'to', httpSingleton.CLIENT_END_DOMAIN + '/events/track_bulk');
      isFlushingEvents = true;
      httpSingleton.makeSignedRequest(httpSingleton.CLIENT_END_DOMAIN + '/events/track_bulk', events, function(err, data, status) {
        isFlushingEvents = false;
        if (typeof onInvalidateInstanceCallback === 'function') {
          onInvalidateInstanceCallback()
        } else {
          onFlushingEventListener(err, data, status);
        }
      });
    }

    function heartBeatFunction() {
      if (platformSpecific.isHiddenSupported && parent.document[platformSpecific.hiddenKey]) {
        return;
      }
      var names = Object.keys(timers);
      if (names.length < 1) { return; }

      _console.debug('Generate heartbeat event');
      queueEvent({
        name: 'heartbeat',
        event_type: 'timer heartbeat',
        timers: names,
        period: heartBeatPeriodInSeconds
      });
    }

    var flushIntervalId;
    var heartBeatIntervalId;
    var localAnalyticContext;
    var isFlushingEvents = false;
    var onInvalidateInstanceCallback;
    var waitForFlushOnInvalidate = false;
    function setupInstance(_onFlushingEventListener, environmentCustomData, analyticContext, waitForFlush) {
      onFlushingEventListener = _onFlushingEventListener
      localAnalyticContext = analyticContext;
      waitForFlushOnInvalidate = !!waitForFlush;

      utils.localStorage.setItem(environmentCustomDataLocalStorageKey, environmentCustomData ? environmentCustomData.toJSONObject() : null);
      if (!statusSingleton.getSessionId()) {
        startNewSession();
      } else {
        continueSession();
      }

      addPerformanceMetricsEvent();

      /*
       * setInterval waits flushingPeriodInSeconds * 1000 msec to call the function first time.
       * This force to flush all events on the startup
       */
      flushFunction();

      // Force acquisition flush time to avoid send and navigate corner cases.
      // TODO: implement a proper destructor to force flushing and icrease delay to 0.5
      var flushPeriod = (localAnalyticContext == 'acquisition') ? 0.2 : flushingPeriodInSeconds;

      flushIntervalId = setInterval(flushFunction, flushPeriod * 1000);
      heartBeatIntervalId = setInterval(heartBeatFunction, heartBeatPeriodInSeconds * 1000);

      _console.debug('event singleton is set up');
    }

    /**
     * Stops the events and heartbeat timers
     * @method clearTimersAndFlush
     * @memberOf Newton
     * @instance
     */
    function clearTimersAndFlush(callback) {
      clearInterval(heartBeatIntervalId);
      clearInterval(flushIntervalId);

      var _fireAndForget = function() {
        onInvalidateInstanceCallback = function() {};
        flushFunction();
        callback();
      }

      var _fireAndWait = function() {
        onInvalidateInstanceCallback = callback;
        flushFunction();
      }
      var finalAction = waitForFlushOnInvalidate ? _fireAndWait : _fireAndForget;

      if (isFlushingEvents) {
        onInvalidateInstanceCallback = finalAction;
      } else {
        finalAction();
      }
    }

    /**
     * Queue a custom event
     * @method sendEvent
     * @memberOf Newton
     * @instance
     * @throws Will throw error if label is not valid or customData is a wrong type
     * @param {String} name - The name of this event
     * @param {Newton.SimpleObject} [customData] - Some data attached to this event
     */
    function sendEvent(name, customData) {
      _console.debug('add event', name, customData);
      assertion.assertIsALabel(name);
      SimpleObject._assertIsInstanceOfOrNull(customData);
      queueEvent({
        event_type: 'custom event',
        name: name + '',
        custom_data: getHashFromSimpleObjectOrUndefined(customData)
      });
    }
    /**
     * Start a timed event
     * @method timedEventStart
     * @memberOf Newton
     * @instance
     * @param {String} name - The name of the timed event
     * @param {Newton.SimpleObject} [customData] - Some data attached to this event
     */
    function timedEventStart(name, customData) {
      _console.debug('start timed event', name, customData);
      assertion.assertIsALabel(name);
      SimpleObject._assertIsInstanceOfOrNull(customData);

      if (timers[name]) {
        throw new NewtonError('Timer is already started');
      }

      timers[name] = {
        name: name,
        startDate: Math.ceil((new Date()).getTime() / 1000)
      };

      queueEvent({
        name: name,
        event_type: 'timer start',
        custom_data: getHashFromSimpleObjectOrUndefined(customData)
      });
    }
    /**
     * Stop a timed event
     * @method timedEventStop
     * @memberOf Newton
     * @instance
     * @throws Throw if there's no started event the given name
     * @param {String} name - The name of this event
     * @param {Newton.SimpleObject} [customData] - Some data attached to this event
     */
    function timedEventStop(name, customData) {
      _console.debug('stop timed event', name, customData);
      assertion.assertIsALabel(name);
      SimpleObject._assertIsInstanceOfOrNull(customData);

      var timedEvent = timers[name];
      if (!timedEvent) {
        throw new NewtonError('Timer is not yet started');
      }
      delete timers[name];

      timedEvent.end = (new Date()).toISOString();
      timedEvent.delta = Math.abs(Math.ceil((new Date()).getTime() / 1000) - timedEvent.startDate);
      timedEvent.startDate = (new Date(timedEvent.startDate * 1000)).toISOString();

      timedEvent.custom_data = getHashFromSimpleObjectOrUndefined(customData);

      timedEvent.event_type = 'timer stop';
      queueEvent(timedEvent);
    }
    /**
     * Return true if there is a running timed event with that name
     * @method isTimedEventRunning
     * @memberOf Newton
     * @instance
     *
     * @param {String} name - The name of the running timed event
     * @return {Boolean} - true if there is a running timed event with that name
     */
    function isTimedEventRunning(name) {
      return !!timers[name];
    }
    /**
     * Begin a flow. Close a previous flow with a fail if need.
     * @method flowBegin
     * @memberOf Newton
     * @instance
     *
     * @param {String} name - The name of the flow
     * @param {Newton.SimpleObject} [customData] - Some data attached to this event
     */
    function flowBegin(name, customData) {
      _console.debug('flow begin', name, customData);
      assertion.assertIsALabel(name);
      SimpleObject._assertIsInstanceOfOrNull(customData);

      var flowId = utils.getRandomString(flowLocalStorageLength);
      if (utils.localStorage.getItem(flowLocalStorageKey)) {
        flowFail('flow-start', SimpleObject.fromJSONObject({'started-flow-id': flowId}));
      }
      utils.localStorage.setItem(flowLocalStorageKey, flowId);

      queueEvent({
        event_type: 'flow_start',
        name: name,
        flow_id: flowId,
        custom_data: getHashFromSimpleObjectOrUndefined(customData)
      });
    }
    /**
     * Make a step for a flow
     * @method flowStep
     * @memberOf Newton
     * @instance
     * @throws Throw if there's not a begun flow
     *
     * @param {String} name - The name of the step
     * @param {Newton.SimpleObject} [customData] - Some data attached to this event
     */
    function flowStep(name, customData) {
      _console.debug('flow step', name, customData);
      assertion.assertIsALabel(name);
      SimpleObject._assertIsInstanceOfOrNull(customData);

      var flowId = utils.localStorage.getItem(flowLocalStorageKey);
      if (!flowId) {
        throw new NewtonError('No flow found');
      }

      queueEvent({
        event_type: 'flow_step',
        flow_id: flowId,
        name: name,
        custom_data: getHashFromSimpleObjectOrUndefined(customData)
      });
    }
    /**
     * End a flow with a success
     * @method flowSucceed
     * @memberOf Newton
     * @instance
     * @throws Throw if there's not a begun flow
     *
     * @param {String} [name="ok"] - A name that identify the end
     * @param {Newton.SimpleObject} [customData] - Some data
     */
    function flowSucceed(name, customData) {
      _console.debug('flow succeeds', arguments);
      if (name && name.constructor === SimpleObject) {
        throw new NewtonError('Cannot create event without the label');
      }
      if (assertion.isUndefined(name) || typeof name === 'object') {
        customData = name;
        name = 'ok';
      }
      assertion.assertIsALabel(name);
      SimpleObject._assertIsInstanceOfOrNull(customData);

      var flowId = utils.localStorage.getItem(flowLocalStorageKey);
      if (!flowId) {
        throw new NewtonError('No flow found');
      }
      utils.localStorage.removeItem(flowLocalStorageKey);

      queueEvent({
        event_type: 'flow_succeeded',
        flow_id: flowId,
        name: name || 'ok',
        custom_data: getHashFromSimpleObjectOrUndefined(customData)
      });
    }
    /**
     * End a flow with a failure
     * @method flowFail
     * @memberOf Newton
     * @instance
     * @throws Throw if there's not a begun flow
     *
     * @param {String} name - The reason of the failure
     * @param {Newton.SimpleObject} [customData] - Some data
     */
    function flowFail(name, customData) {
      _console.debug('flow fails', arguments);
      assertion.assertIsALabel(name);
      SimpleObject._assertIsInstanceOfOrNull(customData);

      var flowId = utils.localStorage.getItem(flowLocalStorageKey);
      if (!flowId) {
        throw new NewtonError('No flow found');
      }
      utils.localStorage.removeItem(flowLocalStorageKey);

      queueEvent({
        event_type: 'flow_failed',
        flow_id: flowId,
        name: name,
        custom_data: getHashFromSimpleObjectOrUndefined(customData)
      });
    }
    /**
     * End a flow with a cancellation
     * @method flowCancel
     * @memberOf Newton
     * @instance
     * @throws Throw if there's not a begun flow
     *
     * @param {String} name - The reason of the cancellation
     * @param {Newton.SimpleObject} [customData] - Some data
     */
    function flowCancel(name, customData) {
      _console.debug('flow cancel', arguments);
      assertion.assertIsALabel(name);
      SimpleObject._assertIsInstanceOfOrNull(customData);

      var flowId = utils.localStorage.getItem(flowLocalStorageKey);
      if (!flowId) {
        throw new NewtonError('No flow found');
      }
      utils.localStorage.removeItem(flowLocalStorageKey);
      queueEvent({
        event_type: 'flow_canceled',
        flow_id: flowId,
        name: name,
        custom_data: getHashFromSimpleObjectOrUndefined(customData)
      });
    }
    /**
     * Rank a content in a scope
     * @method rankContent
     * @memberOf Newton
     * @instance
     * @throws Throw if the params are not correct
     *
     * @param {String} contentId - The contentId (at least 4 chars)
     * @param {Newton.RankingScope} scope - The scope for this rank
     * @param {Number} [multiplier] - The multiplier for this rank
     */
    function rankContent(contentId, scope, multiplier) {
      multiplier = multiplier || 1;
      assertion.assertIfIsString(contentId);
      assertion.assertIfIsString(scope);
      assertion.assertIfIsNumber(multiplier);
      if (contentId.length < 3) {
        throw new NewtonError('Invalid content id');
      }

      var ok = false;
      for (var k in staticInterface.RankingScope) {
        if (staticInterface.RankingScope[k] === scope) {
          ok = true;
          break;
        }
      }
      if (!ok) throw new NewtonError('Unknown scope');

      queueEvent({
        event_type: 'rank content',
        content_id: contentId,
        scope: scope,
        multiplier: multiplier
      });
    }

    /**
     * RankingScope
     * @readonly
     * @property {string} consumption - Then consumption scope
     * @property {string} social - Then social scope
     * @property {string} editorial - Then editorial scope
     * @namespace
     * @memberOf Newton
     */
    var RankingScope = {
      consumption: 'consumption',
      social: 'social',
      editorial: 'editorial'
    };
    staticInterface.RankingScope = RankingScope;

    publicInterface.sendEvent = sendEvent;
    publicInterface.timedEventStart = timedEventStart;
    publicInterface.timedEventStop = timedEventStop;
    publicInterface.isTimedEventRunning = isTimedEventRunning;
    publicInterface.flowBegin = flowBegin;
    publicInterface.flowStep = flowStep;
    publicInterface.flowSucceed = flowSucceed;
    publicInterface.flowFail = flowFail;
    publicInterface.flowCancel = flowCancel;
    publicInterface.rankContent = rankContent;

    /**
     * Return true if a flow is begun (analytics)
     * @method isAnalyticFlowBegun
     * @memberOf Newton
     * @instance
     *
     * @return {Boolean} - true if there is a begun flow
     */
    publicInterface.isAnalyticFlowBegun = function() {
      return !!utils.localStorage.getItem(flowLocalStorageKey);
    };
    /**
     * Force to flush all events - dummy method in javascript
     * @method flushEvents
     * @memberOf Newton
     * @instance
     * @param {callback} - invoked asynchronously
     */
    publicInterface.flushEvents = function(callback) {
      setTimeout(callback, 0);
    };

    return {
      addOtherEvent: queueEvent,
      setupInstance: setupInstance,
      // only for testing
      destroyInstance: function() {
        clearInterval(flushIntervalId);
        clearInterval(heartBeatIntervalId);
      },
      clearTimersAndFlush: clearTimersAndFlush,
      heartBeatFunction: heartBeatFunction
    };
  }
};

/* exported exceptionInitializator */

var exceptionInitializator = {
  deps: ['window', 'console', 'publicInterface', 'NewtonError'],
  init: function(parent, _console, publicInterface, NewtonError) {
    var raven;

    function setRavenInstance(ravenInstance) {
      if (typeof ravenInstance.captureException !== 'function') {
        throw new NewtonError('Invalid Raven instance: missing method "captureException"');
      }
      raven = ravenInstance;
    }

    publicInterface.logException = function(error) {
      if (raven) {
        raven.captureException(error);
      } else {
        _console.error(error);
      }
    };

    return {
      setupInstance: function(_ravenInstance) {
        setRavenInstance(_ravenInstance);
      }
    };
  }
};

/* exported httpSingletonInitializator*/

var httpSingletonInitializator = {
  deps: ['window', 'console', 'platformSpecific', 'assertion', 'utils', 'staticInterface', 'NewtonError', 'publicInterface'],
  init: function(parent, _console, platformSpecific, assertion, utils, staticInterface, NewtonError, publicInterface) {
    var disabled = false;

    // Application credential
    var applicationSecret = null;
    var clientIdentifier = null;
    var whiteLabel;
    var isInternational;

    var jsonpCallbacks = {};
    var connectionTimeout = 10000;

    var logViewInfoObject = null;

    /**
     * Sets the logView info object to be used when Auth API is called
     * @method setLogViewInfo
     * @memberOf httpSingleton
     * @param {Object} logViewInfo
     */
    function setLogViewInfo(logViewInfo) {
      if (!logViewInfo || typeof logViewInfo !== 'object'){
        throw new NewtonError('invalid logViewInfo object: unable to stringify as JSON')
      }

      logViewInfoObject = logViewInfo;
    }

    /**
     * Returns the logView info object
     * @method getLogViewInfo
     * @memberOf httpSingleton
     * @private
     */
    function getLogViewInfo() {
      return logViewInfoObject;
    }



    if (publicInterface) {
      publicInterface.setLogViewInfo = setLogViewInfo;
    }

    /**
     * Returns a hash message authentication code for the HmacSHA1 algorithm.
     * @method getIpawnHash
     * @memberOf httpSingleton
     * @param {String} url
     * @param {String} method
     * @param {String} bodyString
     * @param {String} queryString
     * @param {Number} timestamp
     * @private
     *
     * @return {String}
     */
    function getIpawnHash(url, method, bodyString, queryString, timestamp) {
      url = url.replace(/^https?:\/\//,'');
      var msg = [url, method, bodyString, queryString, timestamp].join('|');
      _console.debug('hmac sha1:', msg);
      return utils.getHmacSha1(msg, applicationSecret);
    }

    /**
     * Returns an Authorization Header for iPawn identifier
     * @method getAuthHeader
     * @memberOf httpSingleton
     * @param {String} method
     * @param {String} url
     * @param {String} bodyString
     * @param {String} queryString
     * @param {Number} timestamp
     * @private
     *
     * @return {String}
     */
    function getAuthHeader(method, url, bodyString, queryString, timestamp) {
      var authHeaderSignature = getIpawnHash(url, method, bodyString, queryString, timestamp);
      return [
        'iPawn application_id=' + JSON.stringify(clientIdentifier),
        'platform=' + JSON.stringify('JS'),
        (isInternational ? 'group=' + JSON.stringify(whiteLabel) : ''),
        'signature=' + JSON.stringify(authHeaderSignature),
        'version=' + JSON.stringify("2.1"),
        'timestamp=' + JSON.stringify('' + timestamp)
      ].join('');
    }
    /**
     * Returns the appropriate NewtonError based on the status code of a response
     * @method _getErrorFromResponse
     * @memberOf httpSingleton
     * @param {Object} response
     *
     * @return {undefined}
     */
    function _getErrorFromResponse(response) {
      var err;

      if (response.status < 299 && response.status > 199){
        return undefined;
      }

      if (response.status === 400) {
        err = new NewtonError('Invalid parameter');
      }
      if (response.status === 404) {
        err = new NewtonError('Not found');
      }
      if (response.status > 499) {
        err = new NewtonError('Internal Newton Error');
      }

      // handling two kind of response
      // {error: { code: '', message: ''}}
      // {errors: ''}
      var body = httpSingleton._getBodyOrUndefined(response);
      if (!err) {
        var errorString = 'Unknown error';
        if (body && body.error && body.error.message && assertion.isString(body.error.message)) {
          errorString = body.error.message;
        }
        if (body && body.errors && assertion.isString(body.errors)) {
          errorString = body.errors;
        }
        err = new NewtonError(errorString);
      }
      if (body && body.error && body.error.code && assertion.isString(body.error.code)) {
        err.code = body.error.code;
        err.error_code = body.error.code;
      }
      if (body && body.errors && assertion.isString(body.errors)) {
        err.code = body.errors;
      }

      err.statusCode = response.status;

      return err;
    }

    /**
     * Returns the body of the response if not missing, otherwise undefined
     * @method _getBodyOrUndefined
     * @memberOf httpSingleton
     * @param {Object} response
     *
     * @return {Object}
     */
    function _getBodyOrUndefined(response) {
      if (response.status === 204) return undefined;
      if (response.status === 205) return undefined;

      var data;
      try {
        data = JSON.parse(response.responseText);
      } catch(e) {
        _console.error('Invalid json', e, response.responseText);
      }
      return data;
    }

    /**
     * Returns an URI encoded querystring from an object
     * @method getQueryString
     * @memberOf httpSingleton
     * @param {Object} obj
     * @private
     *
     * @return {String}
     */
    function getQueryString(obj) {
      var str = [];
      for(var i in obj) {
        if (obj.hasOwnProperty(i)) {
          str.push(encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]));
        }
      }
      return str.join('&');
    }

    /**
     * Sends an XHR POST request with given parameters
     * @method makePostWithXHR
     * @memberOf httpSingleton
     * @param {String} url
     * @param {Object} body
     * @param {Array} headers
     * @param {HttpRequestCallback} callback
     * @private
     *
     * @return {undefined}
     */
    function makePostWithXHR(url, body, headers, callback) {
      var local_callback = callback;

      var request = new parent.XMLHttpRequest();
      _console.debug('Make POST to', url, 'with body', body, 'with headers', headers);

      request.onreadystatechange = function () {
        if (request.readyState == 4 || request.isMockedFromNewton) {
          _console.debug('Request ended', 'status:', request.status, 'body:', request.responseText);
          setTimeout( function () {
            if (local_callback) {
              local_callback(_getErrorFromResponse(request), _getBodyOrUndefined(request), request.status);
              local_callback = null;
            }
          }, 10)
        }
      };

      request.ontimeout = function () {
        _console.debug('Request timeout', 'status:', request.status, 'body:', request.responseText);
        if (local_callback) {
          var err = new NewtonError("Request timeout!");
          err.code = "REQUEST_TIMEOUT";
          err.extra['url'] = request.responseURL;
          local_callback(err, undefined, request.status);
          local_callback = null;
        }
      };

      request.open('POST', url, true);
      request.timeout = connectionTimeout;
      for (var k in headers) {
        request.setRequestHeader(k, headers[k]);
      }
      request.setRequestHeader('Content-type', 'application/json; charset=UTF-8');
      request.send(body);
    }

    /**
     * Sends a JSONP request with given querystring
     * @method makeJSONP
     * @memberOf httpSingleton
     * @param {String} url
     * @param {String} queryString
     * @private
     *
     * @return {undefined}
     */
    function makeJSONP(url, queryString) {
      var scriptTag = parent.document.createElement('script');
      scriptTag.src = url + '?' + queryString;
      _console.debug('Make JSON to',scriptTag.src);
      parent.document.body.appendChild(scriptTag);
    }

    staticInterface.__jsonpCallback = function __jsonpCallback(param) {
      if (!param) { _console.debug('JSON is returned', 'Invalid param', param); return; }
      if (!param.callbackId) { _console.debug('JSON is returned', 'Invalid param', param); return; }
      if (!jsonpCallbacks[param.callbackId]) { _console.debug('JSON is returned', 'No callback found', param); return; }

      var callback = jsonpCallbacks[param.callbackId];
      delete jsonpCallbacks[param.callbackId];

      _console.debug('JSON is returned', param);
      param.status = param.statusCode;

      callback(_getErrorFromResponse(param), param.content, param.status);

    };

    function addLogViewInfoAsObject(content){
      if (content && typeof content == 'object'){
        content.logview = logViewInfoObject;
      } else {
        try {
          var parsed = JSON.parse(content);
          if (parsed && typeof parsed == 'object'){
            parsed.logview = logViewInfoObject;
            content = parsed;
          } else {
            _console.log('Unable to add logview info, content is not an object');
          }
        } catch (e){
          _console.log('Unable to parse request content and add logview info');
        }
      }
      return content;
    }

    function addLogViewInfoAsQueryString(queryString){
      if (queryString.indexOf('&') >= 0){
        queryString += '&';
      }
      return queryString + 'logview=' + JSON.stringify(logViewInfoObject);
    }

    /**
     * The httpSingleton object
     * @namespace httpSingleton
     * @name httpSingleton
     * @private
     */

     /**
      * HttpRequestCallback
      * @name HttpRequestCallback
      * @private
      * @callback
      * @param {Error} [error] - Not null if something went wrong
      * @param {Object} [response] - Response body (converted from json) or undefined if no valid body
      * @param {Number} status - HTTP status code
      */

    var httpSingleton = {
      /**
       * Sends a signed request with given content
       * @method makeSignedRequest
       * @memberOf httpSingleton
       * @param {String} url
       * @param {Object} content
       * @param {HttpRequestCallback} callback
       *
       * @return {undefined}
       */
      makeSignedRequest: function(url, content, callback) {
        if (disabled) {
          var e = new NewtonError('Disabled Newton instance');
          e.code = 'INVALIDATED_INSTANCE';
          throw e;
        }
        assertion.assertStringIsAValidUrl(url);
        _console.info('Making signed request', url, content);

        var timestamp = utils.getCurrentTimestamp();

        if (platformSpecific.hasXHRWithCors) {
          if (logViewInfoObject && url.indexOf(httpSingleton.AUTH_END_DOMAIN) >= 0){
            content = addLogViewInfoAsObject(content);
          }

          if (!assertion.isString(content)) {
            content = JSON.stringify(content);
          }
          var headers = {
            Authorization: getAuthHeader('POST', url, content, '', timestamp)
          };

          makePostWithXHR(url, content, headers, callback);
        } else {
          var callbackId = utils.getRandomString(5);
          jsonpCallbacks[callbackId] = callback;

          var query = content;
          query.callback = 'Newton.__jsonpCallback';
          query.callbackId = callbackId;

          var hash = getIpawnHash(url, 'GET', '', getQueryString(query), timestamp);
          query.identifier = clientIdentifier + '|JS';
          query.signature = hash;
          query.timestamp = timestamp;
          query.version = '2.0';
          var queryString = getQueryString(query);

          if (logViewInfoObject && url.indexOf(httpSingleton.AUTH_END_DOMAIN) >= 0){
            queryString = addLogViewInfoAsQueryString(queryString);
          }
          makeJSONP(url, queryString, callback);
        }
      },
      /**
       * Sends an unsigned signed request with given content
       * @method makeUnsignedRequest
       * @memberOf httpSingleton
       * @param {String} url
       * @param {Object} content
       * @param {HttpRequestCallback} callback
       *
       * @return {undefined}
       */
      makeUnsignedRequest: function(url, content, callback) {
        if (disabled) {
          var e = new NewtonError('Disabled Newton instance');
          e.code = 'INVALIDATED_INSTANCE';
          throw e;
        }
        _console.info('Making unsigned request', url, content);
        assertion.assertStringIsAValidUrl(url);

        if (platformSpecific.hasXHRWithCors) {
          if (logViewInfoObject && url.indexOf(httpSingleton.AUTH_END_DOMAIN) >= 0){
            content = addLogViewInfoAsObject(content);
          }
          content = JSON.stringify(content);
          makePostWithXHR(url, content, {}, callback);
        } else {

          var callbackId = utils.getRandomString(5);
          jsonpCallbacks[callbackId] = callback;

          var query = content;
          query.callback = 'Newton.__jsonpCallback';
          query.callbackId = callbackId;

          var queryString = getQueryString(query);

          if (logViewInfoObject && url.indexOf(httpSingleton.AUTH_END_DOMAIN) >= 0){
            queryString = addLogViewInfoAsQueryString(queryString);
          }

          makeJSONP(url, queryString, callback);
        }
      },
      /**
       * Sets secret, identifier and client/auth end domains
       * @method setupInstance
       * @memberOf httpSingleton
       * @param {String} identifier
       * @param {String} secret
       * @param {Object} urls
       * @param {Object} iPawnConfig - contains whiteLabel {String} and isInternational {Boolean} (both or none of them)
       *
       * @return {undefined}
       */
      setupInstance: function setupInstance(identifier, secret, urls, iPawnConfig) {
        applicationSecret = secret;
        clientIdentifier = identifier;
        httpSingleton.CLIENT_END_DOMAIN = urls.client;
        httpSingleton.AUTH_END_DOMAIN = urls.auth;

        if (iPawnConfig) {
          if (iPawnConfig.whiteLabel !== undefined && iPawnConfig.isInternational === undefined) {
            throw new NewtonError('missing isInternational');
          } else if (iPawnConfig.whiteLabel === undefined && iPawnConfig.isInternational !== undefined) {
            throw new NewtonError('missing whiteLabel');
          } else {
            if (iPawnConfig.whiteLabel !== undefined) {
              assertion.assertIfIsString(iPawnConfig.whiteLabel);
              whiteLabel = iPawnConfig.whiteLabel;
            }
            if (iPawnConfig.isInternational !== undefined) {
              assertion.assertIfIsBoolean(iPawnConfig.isInternational);
              isInternational = iPawnConfig.isInternational;
            }
          }
        }
      },
      /**
       * Returns whether a request returned an OK status
       * @method is2xx
       * @memberOf httpSingleton
       * @param {Object} response
       *
       * @return {Boolean}
       */
      _is2xx: function _is2xx(response) {
        return response.status < 299 && response.status > 199;
      },
      _getErrorFromResponse: _getErrorFromResponse,
      _getBodyOrUndefined: _getBodyOrUndefined,
      /**
       * Returns whether a response was returned with a given status code
       * @method _hasErrorCode
       * @memberOf httpSingleton
       * @param {Object} response
       * @param {Number} errorCode
       *
       * @return {Boolean}
       */
      _hasErrorCode: function _hasErrorCode(response, errorCode) {
        var parsed = httpSingleton._getBodyOrUndefined(response);
        return parsed && parsed.error && (parsed.error.code === errorCode);
      },

      getApplicationId: function() {
        return clientIdentifier;
      },

      getDistributionGroup: function() {
        return (isInternational ? whiteLabel : '__DEFAULT__');
      },

      // only for test
      getIpawnHash: getIpawnHash,
      setLogViewInfo: setLogViewInfo,

      getLogViewInfo: getLogViewInfo,

      disable: function() {
        disabled = true;
      }
    };

    return httpSingleton;
  }
};

/* exported identityInitializator*/

var identityInitializator = {
  deps: ['console', 'httpSingleton', 'statusSingleton', 'NewtonError'],
  init: function(_console, httpSingleton, statusSingleton, NewtonError) {

    /**
     * Identity class
     * @class Identity
     * @name Identity
     * @private
     */
    function Identity(obj) {
      this._id = obj.identity_id;
      this._isValid = true;
      this._type = obj.type;
      this._identifier = obj.identifier;
      this._secret = obj.secret;
      this._operator = obj.operator;
      this._confirmed = obj.confirmed;
    }
    /**
     * Return if this identity is changed (change pwd email etc...)
     * @method isValid
     * @memberOf Identity
     * @instance
     *
     * @return {Boolean}
     */
    Identity.prototype.isValid = function isValid() {
      return this._isValid;
    };
    /**
     * Return the type of the identity (facebook, google, msisdn, email)
     * @method getType
     * @memberOf Identity
     * @instance
     *
     * @return {String}
     */
    Identity.prototype.getType = function getType() {
      return this._type;
    };
    /**
     * Return the identifier for that provider in the current real
     * @method getIdentifier
     * @memberOf Identity
     * @instance
     *
     * @return {String} This could partially be obfuscated
     */
    Identity.prototype.getIdentifier = function getIdentifier() {
      return this._identifier;
    };
    /**
     * Return the secret
     * @method getSecret
     * @memberOf Identity
     * @instance
     *
     * @return {String} This is partially or entirely obfuscated
     */
    Identity.prototype.getSecret = function getSecret() {
      return this._secret;
    };
    /**
     * Return the operator (available only for msisdn) or null
     * @method getOperator
     * @memberOf Identity
     * @instance
     *
     * @return {String} The operator or null
     */
    Identity.prototype.getOperator = function getOperator() {
      return this._operator;
    };
    /**
     * Valid only for email identities, return if the email is confirmed (triangulate) or not
     * @method isConfirmed
     * @memberOf Identity
     * @instance
     *
     * @return {Boolean}
     */
    Identity.prototype.isConfirmed = function isConfirmed() {
      return this._confirmed;
    };
    /**
     * Update the identifier. This operation invalids the instance of this identity
     * @method updateCredential
     * @memberOf Identity
     * @instance
     * @param {String} newCredential - the new identifier
     * @param {String} password - the secret
     * @param {UpdateCredentialCallback} callback - the callback called after the operation is done
     */
    Identity.prototype.updateCredential = function updateCredential(newCredential, password, callback) {
      if (this.getType() !== 'email') {
        var tmpError = new NewtonError('Cannot update credential for this identity type');
        tmpError.code = 'WRONG_IDENTITY_TYPE';
        return callback(tmpError);
      }
      _console.info('identity: Updating credential');

      var body = {
        user_id: statusSingleton.getUserToken(),
        identity_id: this._id,
        new_email: newCredential,
        password: password
      };
      var self = this;
      httpSingleton.makeSignedRequest(httpSingleton.AUTH_END_DOMAIN + '/handling/direct/identity/update/email', body, function(err) {
        if (err) {
          return callback(err);
        }
        self._isValid = false;

        callback(null);
      });
    };
    /**
     * Update the secret. This operation invalids the instance of this identity
     * @method updateSecret
     * @memberOf Identity
     * @instance
     * @param {String} oldSecret - the old password (required for email identity types)
     * @param {String} newSecret - the new password
     * @param {UpdateSecretCallback} callback - the callback called after the operation is done
     */
    Identity.prototype.updateSecret = function updateSecret(oldSecret, newSecret, callback) {
      if (this._type !== 'email' && this._type !== 'msisdn') {
        var tmpError = new NewtonError('Cannot update secret for this identity type');
        tmpError.code = 'WRONG_IDENTITY_TYPE';
        return callback(tmpError);
      }
      _console.info('identity: Updating secret');

      var body = {
        user_id: statusSingleton.getUserToken(),
        identity_id: this._id,
        new_password: newSecret
      };

      if (oldSecret) body.password = oldSecret

      var self = this;
      httpSingleton.makeSignedRequest(httpSingleton.AUTH_END_DOMAIN + '/handling/direct/identity/update/password', body, function(err) {

        if (err) {
          return callback(err);
        }
        self._isValid = false;

        callback(null);
      });
    };
    /**
     * Delete this identity. This operation invalids the instance of this identity
     * @method delete
     * @memberOf Identity
     * @instance
     * @param {DeleteIdenityCallback} callback - the callback called after the operation is done
     */
    Identity.prototype.delete = function deleteIdentity(callback) {
      _console.info('identity: delete');
      var body = {
        user_id: statusSingleton.getUserToken(),
        identity_id: this._id
      };
      var self = this;
      httpSingleton.makeSignedRequest(httpSingleton.AUTH_END_DOMAIN + '/handling/direct/identity/remove', body, function(err) {

        if (err) {
          return callback(err);
        }
        self._isValid = false;

        callback(null);
      });
    };

    /**
     * This callback is used during a identity deletion
     * @callback DeleteIdenityCallback
     * @param {Error} error is something goes wrong
     */
    /**
     * This callback is used during a identity secret update
     * @callback UpdateSecretCallback
     * @param {Error} error - when something goes wrong
     */
    /**
     * This callback is used during a identity identifier update
     * @callback UpdateCredentialCallback
     * @param {Error} error - when something goes wrong
     */

    return {
      Identity: Identity
    };
  }
};

/* exported identityManagerInizializator*/

var identityManagerInizializator = {
  deps: ['console', 'httpSingleton', 'statusSingleton', 'publicInterface', 'identityModule', 'NewtonError', 'simpleObject'],
  init: function(_console, httpSingleton, statusSingleton, publicInterface, IdentityModule, NewtonError, SimpleObject) {
    var Identity = IdentityModule.Identity;

    function deleteUser(newtonToken, callback) {
      httpSingleton.makeSignedRequest(
        httpSingleton.AUTH_END_DOMAIN + '/handling/direct/userdelete',
        {user_id: newtonToken},
        function(err) {
          if (err) {
            return callback(err);
          }

          publicInterface.userLogout('delete');

          callback();
        }
      );
    }

    /**
     * @class AddEmailIdentityFlow
     * @name AddEmailIdentityFlow
     * @private
     */
    function AddEmailIdentityFlow(params) {
      this.user_id = params.user_id;
      this.email = params.email;
      this.password = params.password;
      this.confirmToken = params.confirmToken;
      this.smsTemplate = params.smsTemplate;
      this.productEmailParams = params.productEmailParams;
      this.onFlowCompletecallback = params.onFlowCompletecallback;
    }

    /**
     * Start the add identity flow. Call onFlowCompleteCallback with the result
     * @method startAddIdentityFlow
     * @memberOf AddEmailIdentityFlow
     */
    AddEmailIdentityFlow.prototype.startAddIdentityFlow = function startAddIdentityFlow() {
      _console.info('AddEmailIdentityFlow: startAddIdentityFlow');

      if (!this.user_id || !this.email || !this.password) {
        _console.warn('Some parameters are missing', this.user_id, this.email, this.password);
        return this.onFlowCompletecallback(new NewtonError('Some parameters are missing'));
      }

      var body = {
        user_id: this.user_id,
        email: this.email,
        password: this.password,
        sms_template: this.smsTemplate,
        productmail_parameters: this.productEmailParams ? this.productEmailParams.toJSONObject() : undefined
      };
      var self = this;
      httpSingleton.makeSignedRequest(httpSingleton.AUTH_END_DOMAIN + '/handling/direct/identity/add/email/identify', body, function(err) {
        if (err) {
          return self.onFlowCompletecallback(err);
        }

        self.onFlowCompletecallback();
      });
    };
    /**
     * @class AddOAuthIdentityFlow
     * @name AddOAuthIdentityFlow
     * @private
     */
    function AddOAuthIdentityFlow(params) {
      this.user_id = params.user_id;
      this.oauther = params.oauther,
      this.accessToken = params.accessToken;
      this.onFlowCompletecallback = params.onFlowCompletecallback;
    }

    /**
     * Start the add identity flow. Call onFlowCompleteCallback with the result
     * @method startAddIdentityFlow
     * @memberOf AddOAuthIdentityFlow
     */
    AddOAuthIdentityFlow.prototype.startAddIdentityFlow = function startAddIdentityFlow() {
      _console.info('AddOAuthIdentityFlow: startAddIdentityFlow');

      if (!this.user_id || !this.accessToken || !this.oauther) {
        _console.warn('Some parameters are missing', this.user_id, this.accessToken, this.oauther);
        return this.onFlowCompletecallback(new NewtonError('Some parameters are missing'));
      }

      var body = {
        user_id: this.user_id,
        oauther: this.oauther,
        access_token: this.accessToken
      };
      var self = this;
      httpSingleton.makeSignedRequest(httpSingleton.AUTH_END_DOMAIN + '/handling/direct/identity/add/oauth', body, function(err) {

        if (err) {
          return self.onFlowCompletecallback(err);
        }

        self.onFlowCompletecallback(null);
      });
    };

    /**
     * @class AddGenericIdentityFlow
     * @name AddGenericIdentityFlow
     * @private
     */
    function AddGenericIdentityFlow(params) {
      this.user_id = params.user_id;
      this.smsTemplate = params.smsTemplate;
      this.onFlowCompletecallback = params.onFlowCompletecallback;
    }
    /**
     * Start the add identity flow. Call onFlowCompleteCallback with the result
     * @method startAddIdentityFlow
     * @memberOf AddGenericIdentityFlow
     */
    AddGenericIdentityFlow.prototype.startAddIdentityFlow = function startAddIdentityFlow() {
      _console.info('AddGenericIdentityFlow: startAddIdentityFlow');

      var body = {
        user_id: this.user_id,
        sms_template: this.smsTemplate || undefined
      };
      var self = this;
      httpSingleton.makeSignedRequest(httpSingleton.AUTH_END_DOMAIN + '/handling/direct/identity/add/generic/identify', body, function(err) {
        if (err) {
          return self.onFlowCompletecallback(err);
        }

        self.onFlowCompletecallback();
      });
    };

    /**
     * @class IdentityBuilder
     * @name IdentityBuilder
     * @private
     */
    function IdentityBuilder() { this.params = {}; }
    /**
     * Set the email
     * @method setEmail
     * @memberOf IdentityBuilder
     * @param {string} email - The email
     * @return {IdentityBuilder} the same instance
     */
    IdentityBuilder.prototype.setEmail = function setEmail(email) {
      this.params.email = email;
      return this;
    };
    /**
     * Set the sms template
     * @method setSMSTemplate
     * @memberOf IdentityBuilder
     * @param {string} smsTemplate - The SMS template
     * @return {IdentityBuilder} the same instance
     */
    IdentityBuilder.prototype.setSMSTemplate = function setSMSTemplate(smsTemplate) {
      this.params.smsTemplate = smsTemplate;
      return this;
    };
    /**
     * Set the callback called after the flow is completed
     * @method setOnFlowCompleteCallback
     * @memberOf IdentityBuilder
     * @param {FlowCompleteCallback} onFlowCompletecallback - The callback
     * @return {IdentityBuilder} the same instance
     */
    IdentityBuilder.prototype.setOnFlowCompleteCallback = function setOnFlowCompleteCallback(onFlowCompletecallback) {
      this.params.onFlowCompletecallback = onFlowCompletecallback;
      return this;
    };
    /**
     * Set the password
     * @method setPassword
     * @memberOf IdentityBuilder
     * @param {string} password - The password
     * @return {IdentityBuilder} the same instance
     */
    IdentityBuilder.prototype.setPassword = function setPassword(password) {
      this.params.password = password;
      return this;
    };
    /**
     * Set the access token
     * @method setAccessToken
     * @memberOf IdentityBuilder
     * @param {string} accessToken - The access token
     * @return {IdentityBuilder} the same instance
     */
    IdentityBuilder.prototype.setAccessToken = function setAccessToken(accessToken) {
      this.params.accessToken = accessToken;
      return this;
    };
    /**
     * Set the oauth provider
     * @method setOAuthProvider
     * @memberOf IdentityBuilder
     * @param {string} oauthProvideer - The oauth provider
     * @return {IdentityBuilder} the same instance
     */
    IdentityBuilder.prototype.setOAuthProvider = function setOAuthProvider(oauthProvider) {
      this.params.oauthProvider = oauthProvider;
      return this;
    };
    /**
     * Set Product Email template parameter
     * @method setProductEmailParams
     * @memberOf IdentityBuilder
     * @param {SimpleObject} params - The parameters used to interpolare the template
     * @return {IdentityBuilder} the same instance
     */
    IdentityBuilder.prototype.setProductEmailParams = function setProductEmailParams(params) {
      this.params.productEmailParams = params;
      return this;
    };

    /**
     * Return a AddEmailIdentityFlow
     * @method getAddEmailIdentityFlow
     * @memberOf IdentityBuilder
     * @return {AddEmailIdentityFlow}
     */
    IdentityBuilder.prototype.getAddEmailIdentityFlow = function() {
      SimpleObject._assertIsInstanceOfOrNull(this.params.productEmailParams);

      return new AddEmailIdentityFlow({
        user_id: statusSingleton.getUserToken(),
        smsTemplate: this.params.smsTemplate,
        productEmailParams: this.params.productEmailParams,
        email: this.params.email,
        password: this.params.password,
        confirmToken: this.params.confirmToken,
        onFlowCompletecallback: this.params.onFlowCompletecallback || function() {}
      });
    };

    /**
     * Return a AddOAuthIdentityFlow
     * @method getAddOAuthIdentityFlow
     * @memberOf IdentityBuilder
     * @return {AddOAuthIdentityFlow}
     */
    IdentityBuilder.prototype.getAddOAuthIdentityFlow = function() {
      return new AddOAuthIdentityFlow({
        user_id: statusSingleton.getUserToken(),
        oauther: this.params.oauthProvider,
        accessToken: this.params.accessToken,
        onFlowCompletecallback: this.params.onFlowCompletecallback || function() {}
      });
    };

    /**
     * Return a AddGenericIdentityFlow
     * @method getAddGenericIdentityFlow
     * @memberOf IdentityBuilder
     * @return {AddGenericIdentityFlow}
     */
    IdentityBuilder.prototype.getAddGenericIdentityFlow = function() {
      return new AddGenericIdentityFlow({
        user_id: statusSingleton.getUserToken(),
        smsTemplate: this.params.smsTemplate,
        onFlowCompletecallback: this.params.onFlowCompletecallback || function() {}
      });
    };

    function getIdentities(callback) {
      if (statusSingleton.isDisabled()) {
        var e = new NewtonError('Disabled Newton instance');
        e.code = 'INVALIDATED_INSTANCE';
        throw e;
      }
      _console.info('identity: get');

      var body = {
        user_id: statusSingleton.getUserToken()
      };
      httpSingleton.makeSignedRequest(httpSingleton.AUTH_END_DOMAIN + '/handling/direct/identity/get', body, function(err, responseData) {
        if (err) {
          return callback(err);
        }
        var parsed = responseData;
        if(!parsed || !parsed.identities) {
          return callback(new NewtonError('Unknown error'));
        }

        var identities = [];
        for (var i=0; i < parsed.identities.length; i++) {
          identities.push(new Identity(parsed.identities[i]));
        }

        callback(undefined, identities);
      });
    }

    function getIdentityBuilder() {
      if (statusSingleton.isDisabled()) {
        var e = new NewtonError('Disabled Newton instance');
        e.code = 'INVALIDATED_INSTANCE';
        throw e;
      }
      return new IdentityBuilder();
    }

    function getIdentityManager() {
      if (!publicInterface.isUserLogged()) {
        throw new NewtonError('User is not logged');
      }
      if (!statusSingleton.isTheTokenType(statusSingleton.getUserObjectToken(), ['U'])) {
        throw new NewtonError('Cannot use identity builder for this user type');
      }

      /**
       * Add Identity Manager
       * @class IdentityManager
       * @name IdentityManager
       * @param {GetIdentityCallback} callback - Invoked when the identities are available
       * @private
       */
      return {
        /**
         * Return the identities
         * @method getIdentities
         * @memberOf IdentityManager
         */
        getIdentities: getIdentities,
        /**
         * Return an identiti Builder
         * @method getIdentityBuilder
         * @memberOf IdentityManager
         */
        getIdentityBuilder: getIdentityBuilder,
        /**
         * Delete the current user.
         * @method userDelete
         * @memberOf IdentityManager
         * @param {UserDeleteCallback} userDeleteCallback - The callback invoked when the request is done. It has an error if the request couldn't be completed
         */
        userDelete: function userDelete(userDeleteCallback) {
          if (statusSingleton.isDisabled()) {
            var e = new NewtonError('Disabled Newton instance');
            e.code = 'INVALIDATED_INSTANCE';
            throw e;
          }
          var userToken = statusSingleton.getUserToken();
          var type = userToken.charAt(0);
          switch(type) {
            case 'A':
              setTimeout(function() {
                userDeleteCallback(new NewtonError('User is unlogged'));
              });
              break;
            case 'C':
              setTimeout(function() {
                userDeleteCallback(new NewtonError('Cannot delete the current user'));
              });
              break;
            case 'U':
            case 'N':
            case 'E':
              return deleteUser(userToken, userDeleteCallback);
            default:
              return setTimeout(function() { userDeleteCallback(new NewtonError('Unknown user type')); });
          }
        }
      };
    }

    /**
     * This callback is displayed as part of the Requester class.
     * @callback GetIdentityCallback
     * @param {Error} error - when something goes wrong
     * @param {Identity[]} identities - an array of Identity
     */
    /**
     * This callback is used to notify that the flow is completed
     * @callback FlowCompleteCallback
     * @param {Error} error is something goes wrong
     */


    /**
     * Return a login builder instance
     * @method getIdentityManager
     * @memberOf Newton
     * @instance
     *
     * @return {IdentityManager}
     */
    publicInterface.getIdentityManager = getIdentityManager;

    return {}
  }
};

/* exported initInitializator */
/* global AUTH_END_DOMAIN, CLIENT_END_SANDBOX_DOMAIN, CLIENT_END_DOMAIN, AUTH_END_SANDBOX_DOMAIN */

var initInitializator = {
  deps: ['console', 'staticInterface', 'autorevision', 'httpSingleton', 'eventSingleton', 'simpleObject', 'utils', 'publicInterface', 'loginBuilder', 'statusSingleton', 'exception', 'userModule', 'paymentManager', 'NewtonError', 'pushManager', 'assertion'],
  init: function(_console, staticInterface, autorevision, httpSingleton, eventSingleton, SimpleObject, utils, publicInterface, loginBuilder, statusSingleton, exception, userModule, paymentManager, NewtonError, pushManager, assertion) {
    var initialized = false;

    var isDev = /api(1|2).newton.pm/.test(AUTH_END_DOMAIN);
    var isSandbox;
    /**
     * Get the shared instance. Create one if no sharedInstance found
     * @method getSharedInstanceWithConfig
     * @memberOf Newton
     * @static
     * @param {String} secret - application secret
     * @param {Object} environmentCustomData - custom data object
     * @param {Object} config - contains various configurations
     * @param {String} config.whiteLabel
     * @param {Object} config.context - the analytic context that every event will be inflated with. Can be "application" or "acquisition" (different values will default to "application")
     * @param {Boolean} config.isInternational
     * @param {Object} config.ravenInstance - note: Raven must be initialized by the application
     * @param {Boolean} config.waitForFlushOnInvalidate - Alter behaviour of invalidateInstance (default false)
     * @param {Function} config.replaceStateDelegate - An optional function with the same signature as window.history.replaceState to be used to replace browser history
     * @throws {Error} - throws if no secret is given, throws if only one of [whiteLabel, isInternational] is passed (both of them must be passed, or none of them)
     *
     * @return {Newton}
     */
    staticInterface.getSharedInstanceWithConfig = function(secret, environmentCustomData, config) {
      _console.debug('configuring Newton sdk');
      if (initialized) {
        return publicInterface;
      }

      if (config) {
        assertion.assertIfIsObject(config);
      } else {
        config = {};
      }

      var localAnalyticContext = typeof config.context === 'string' && config.context.toLowerCase() === 'acquisition' ? 'acquisition' : 'application';

      if (config.ravenInstance !== undefined) {
        assertion.assertIfIsObject(config.ravenInstance);
        exception.setupInstance(config.ravenInstance);
      }

      // normalization for improper use of older versions
      secret = secret || {};
      if (secret.constructor === Object ) {
        secret = secret.secret;
      }

      if (!assertion.isString(secret) || !secret) {
        throw new NewtonError('secret should be given');
      }

      isSandbox = /_/.test(secret);
      var applicationIdentifier = utils.getHostname();

      var clientEndDomain = isSandbox ? CLIENT_END_SANDBOX_DOMAIN : CLIENT_END_DOMAIN;
      var authEndDomain = isSandbox ? AUTH_END_SANDBOX_DOMAIN : AUTH_END_DOMAIN;

      httpSingleton.setupInstance(applicationIdentifier, secret, {
        client: clientEndDomain,
        auth: authEndDomain
      }, {
        whiteLabel: config.whiteLabel,
        isInternational: config.isInternational
      });

      // WARNING!!! this is a HttpRequestCallback of http_singleton makeRequest/makeSignedRequest
      var onFlushingEventListener = function(error, response, status) {
        _console.debug('on events flushed', response);
        if (loginBuilder && status === 205 || /^U_/.test(statusSingleton.getUserToken()) || /^E_/.test(statusSingleton.getUserToken())) {
          loginBuilder.queueCallbackOnAutoLoginFlowQueue(function(err) {
            if (err) { _console.error(err); }
          });
        }
      };
      eventSingleton.setupInstance(onFlushingEventListener, environmentCustomData, localAnalyticContext, !!config.waitForFlushOnInvalidate);

      if (loginBuilder && userModule && paymentManager) {
        loginBuilder.setupInstance(applicationIdentifier, config.replaceStateDelegate);
        userModule.setupInstance();
      }
      pushManager.setupInstance();

      initialized = true;

      _console.warn('Newton runs in', publicInterface.getEnvironmentString());

      return publicInterface;
    };

    /**
     * Get the shared instance
     * @method getSharedInstance
     * @memberOf Newton
     * @static
     * @throws {Error} - Throw if getSharedInstanceWithConfig hasn't been called
     *
     * @return {Newton}
     */
    staticInterface.getSharedInstance = function() {
      if (!initialized) {
        throw new NewtonError('Call getSharedInstanceWithConfig before');
      }
      return publicInterface;
    };

    /**
     * Invalidates the public interface and flushes the event subsystem
     * @method invalidateInstance
     * @memberOf Newton
     * @static
     * @throws {Error} - Throw if getSharedInstanceWithConfig hasn't been called or if already invalidated
     * @param {Function} callback - If waitForFlushOnInvalidate called after server response otherwise called when last XHR is started
     */
    staticInterface.invalidateInstance = function(callback) {
      if (!initialized) {
        throw new NewtonError('Call getSharedInstanceWithConfig before');
      }
      if (statusSingleton.isDisabled()) {
        var e = new NewtonError('Disabled Newton instance');
        e.code = 'INVALIDATED_INSTANCE';
        throw e;
      }
      statusSingleton.disable();
      Object.keys(publicInterface).forEach(function(key) {
        if (!publicInterface.hasOwnProperty(key)) return;
        if (typeof publicInterface[key] !== 'function') return;
        publicInterface[key] = function() {
          var e = new NewtonError('Disabled Newton instance');
          e.code = 'INVALIDATED_INSTANCE';
          throw e;
        }
      })
      var onClearTimersCallback = function() {
        httpSingleton.disable();
        if (typeof callback === 'function') callback()
      }
      eventSingleton.clearTimersAndFlush(onClearTimersCallback)
    }

    staticInterface.SimpleObject = SimpleObject;
    /**
     * Get current Newton version
     * @method getVersionString
     * @memberOf Newton
     * @static
     *
     * @return {String}
     */
    staticInterface.getVersionString = function() {
      return [
        autorevision.VCS_TAG,
        autorevision.VCS_SHORT_HASH,
        autorevision.VCS_DATE
      ].join(' ');
    };

    /**
     * Get the environment string representation
     * @method getEnvironmentString
     * @memberOf Newton
     * @instance
     * @return {String}
     */
    publicInterface.getEnvironmentString = function() {
      return (isDev ? 'DEV' : 'PROD') + '-' + (isSandbox ? 'SANDBOX' : 'RELEASE');
    };

    /**
     * Get a Snowplow User Context for the current logged user
     * @method getSnowplowUser
     * @memberOf Newton
     * @instance
     * @return {Object} - Snowplow user context OR null if user is not logged
     */
    publicInterface.getSnowplowUser = function() {
      if (!publicInterface.isUserLogged()) {
        return null;
      } else {
        return ({
          user_token: statusSingleton.getUserToken(),
          application_id: httpSingleton.getApplicationId(),
          distribution_group: httpSingleton.getDistributionGroup(),
          signature: (isDev ? 'PP' : 'PR') + (isSandbox ? 'SB' : 'RL')
        });
      }
    }

    /**
     * Fix history for get parameters immediatly
     * @method immediateFixHistory
     * @memberOf Newton
     * @static
     * @param {Function} replaceStateDelegate - An optional function with the same signature as window.history.replaceState to be used to replace browser history
     */
    staticInterface.immediateFixHistory = function(_replaceStateDelegate) {
      loginBuilder.sniffURLParams();
      loginBuilder.cleanURL(_replaceStateDelegate);
    };

    /**
     * SyncStateCallback
     * @name SyncStateCallback
     * @callback
     * @param {Error} [error] - Not null if something went wrong
     */
    /**
     * FlushCallback
     * @name FlushCallback
     * @callback
     * @param {Error} [error] - always null
     */
    /**
     * MetaInfoCallback
     * @name MetaInfoCallback
     * @callback
     * @param {Error} error - not null if something went wrong
     * @param {Object} data - hash for all user meta info
     */
    /**
     * FlowCompleteCallback
     * @name FlowCompleteCallback
     * @callback
     * @param {Error} error - not null if something went wrong
     */
  }
};

/* exported loginBuilderInitializator */

var loginBuilderInitializator = {
  deps: ['console', 'assertion', 'statusSingleton', 'eventSingleton', 'publicInterface', 'utils', 'window', 'httpSingleton', 'simpleObject', 'EventListener', 'NewtonError'],
  init: function(_console, assertion, statusSingleton, eventSingleton, publicInterface, utils, parent, httpSingleton, SimpleObject, EventListener, NewtonError) {
    var oauthProviders = [ 'facebook', 'google' ];
    var oauthLoginProviderCookieKey = 'newton-login-oauth-provider';
    var oauthLoginDataLocalStorageKey = 'newton-login-oauth-data';
    var applicationIdentifier;
    var errorFromLoginFlow;

    var globalLoginFlowEventListener = new EventListener();
    var stateChangeListener = {
      onLoginStateChange: function(err) {
        _console.warn('Please call setUserStateChangeListener!', err);
      }
    };

    function isUserTokenGood(userToken) {
      return !/undefined/.test(userToken) && !/null/.test(userToken);
    }

    function isUserLogged() {
      return !/^A_/.test(statusSingleton.getUserToken());
    }

    function deleteUser(newtonToken, callback) {
      httpSingleton.makeSignedRequest(
        httpSingleton.AUTH_END_DOMAIN + '/user/delete',
        {user_id: newtonToken},
        function(responseErr, data) {
          if (responseErr) {
            _console.warn('Api /user/delete returns', data);
            var e = new NewtonError('Invalid status code');
            e.type = 'user delete  invalid status code';
            callback(e);
            return;
          }

          userLogout('delete');

          callback();
        }
      );
    }

    /**
     * Delete the current user.
     * @method __temporaryUserDelete
     * @instance
     * @memberOf Newton
     * @param {UserDeleteCallback} userDeleteCallback - The callback invoked when the request is done. It has an error if the request couldn't be completed
     */
    publicInterface.__temporaryUserDelete = function(userDeleteCallback) {
      var userToken = statusSingleton.getUserToken();
      var type = userToken.charAt(0);
      switch(type) {
        case 'A':
          setTimeout(function() {
            userDeleteCallback(new NewtonError('User is unlogged'));
          });
          break;
        case 'C':
          setTimeout(function() {
            userDeleteCallback(new NewtonError('Cannot delete the current user'));
          });
          break;
        case 'U':
        case 'N':
        case 'E':
          return deleteUser(userToken, userDeleteCallback);
        default:
          return setTimeout(function() { userDeleteCallback(new NewtonError('Unknown user type')); }, 10);
      }
    };

    function makeCall(urlPath, body, callback) {
      httpSingleton.makeSignedRequest(
        httpSingleton.AUTH_END_DOMAIN + urlPath,
        body,
        function(responseErr, data) {
          if (responseErr) return callback(responseErr);
          callback(undefined, data);
      });
    }

    var instance = null;
    function AutoLoginFlow(transient_token) {
      var el = new EventListener();
      var INVALID_TOKEN_CODE= 400;
      var EXPIRED_TOKEN_CODE = 401;

      this.addCallback = function(callback) {
        el.onOnce('end', callback);
      };

      function callAllCallbacks(err) {
        el.emit('end', err);
        instance = null;
      }
      /******
       TODO add an internal callback if window. is set to notify autologin to native part
       Native app expose:
        - window.nativeLoginSuccess(String <JSON for MFP login>)
        - window.nativeLoginFailed(String <JSON NewtonError>)
        Handle proper call in try/catch
      *******/

      this.toString = function() {
        return 'AutoLoginFlow({})';
      };
      this.startLoginFlow = function() {

        var onNativeWebView = false;
        var userObjectDataRefreshtoken = {user_id: statusSingleton.getUserObjectToken()};
        if (transient_token) {
          // Override token if transient is set
          userObjectDataRefreshtoken.user_id = transient_token;
        }

        if (parent.NewtonNative && (parent.NewtonNative.loginSuccess&& typeof parent.NewtonNative.loginSuccess==="function") &&
            (parent.NewtonNative.loginFailed&& typeof parent.NewtonNative.loginFailed==="function")) {
              onNativeWebView = true;
              userObjectDataRefreshtoken.generate_transient = true;
          }

        if (statusSingleton.isAutoLoginFlowRunning() ||
            statusSingleton.isLoginFlowRunning()) {
          return callAllCallbacks(new NewtonError('Another flow is running'));
        }
        if (!transient_token && (!publicInterface.isUserLogged() ||
            /^C_/.test(statusSingleton.getUserToken()))) {
          return callAllCallbacks(new NewtonError('User is not logged using Newton'));
        }

        statusSingleton.setAutoLoginFlowRunning(true);
        httpSingleton.makeSignedRequest(httpSingleton.AUTH_END_DOMAIN + '/handling/direct/refreshtoken', userObjectDataRefreshtoken , function(responseErr, body) {
          statusSingleton.setAutoLoginFlowRunning(false);

          if (!responseErr && body && body.session_token) {
            if (body.autologin_token) {
              statusSingleton.setUserObjectToken(body.autologin_token);
            }
            statusSingleton.setNewtonToken(body.session_token);
            eventSingleton.addOtherEvent({
              event_type: 'identify',
              login_type: 'autologin'
            });

            if(onNativeWebView){
              try{
                parent.NewtonNative.loginSuccess(JSON.stringify({"session_id":statusSingleton.getSessionId(),"user_id":body.transient_token}));
              } catch (err) {
                _console.log('Error on native layer:', err);
              }
            }

            return callAllCallbacks();
          }
          if (responseErr && responseErr.statusCode) {
            if(onNativeWebView){
              try{
                parent.NewtonNative.loginFailed(responseErr.stringifyErr());
              } catch (err) {
                _console.log('Error on native layer:', err);
              }
            }
            return responseErrorLogoutReason(responseErr);
          }

          if (!responseErr && !body) {
            responseErr = new NewtonError('Unable to parse JSON string');
          }
          stateChangeListener.onLoginStateChange(responseErr);
          callAllCallbacks(responseErr);
        });
      };

      function responseErrorLogoutReason (responseErr) {

        var statusCode = responseErr.statusCode
        var reason;
        switch (statusCode) {
          case INVALID_TOKEN_CODE:
            reason = 'invalid';
            break;
          case EXPIRED_TOKEN_CODE:
            reason = 'refresh';
            break;
          default:
            reason = 'error'
            break;
        }

        userLogout(reason);
        stateChangeListener.onLoginStateChange();

        return callAllCallbacks(responseErr);

      }

    }


    function queueCallbackOnAutoLoginFlowQueue(callback, transient_token) {
      var started = !!instance;
      if (!started) {
        instance = new AutoLoginFlow(transient_token);
      }
      instance.addCallback(callback);
      if (!started) {
        instance.startLoginFlow();
      }
    }

    /**
     * Custom Login Flow
     * @class CustomLoginFlow
     * @name CustomLoginFlow
     * @private
     */
    function CustomLoginFlow(options) {
      /**
       * Return a string representation
       * @method toString
       * @memberOf CustomLoginFlow
       * @instance
       *
       * @return {String}
       */
      this.toString = function() {
        return 'CustomLoginFlow(' + JSON.stringify(options) + ')';
      };
      /**
       * Start the custom login flow
       * @method startLoginFlow
       * @memberOf CustomLoginFlow
       * @instance
       */
      this.startLoginFlow = function() {
        if (isUserLogged()) {
          return setTimeout(function() {
            var err = new Error('User is already logged');
            options.callback(err);
            globalLoginFlowEventListener.emit('end', err);
          }, 1);
        }
        statusSingleton.setCustomLoginFlowRunning(true);

        // make really async
        setTimeout(function() {
          statusSingleton.setCustomUserToken(options.custom_id, options.allow_custom_login);

          _console.info('User is logged with', options.custom_id, 'allow csutom login', options.allow_custom_login);

          eventSingleton.addOtherEvent({
            event_type: 'identify',
            login_type: 'CUSTOM',
            custom_data: options.custom_data ? options.custom_data.toJSONObject() : undefined
          });

          stateChangeListener.onLoginStateChange();

          statusSingleton.setCustomLoginFlowRunning(false);

          options.callback();

          globalLoginFlowEventListener.emit('end');
        }, 1);
      };
    }
    /**
     * OAuth Login Flow
     * @class OAuthLoginFlow
     * @name OAuthLoginFlow
     * @private
     */
    function OAuthLoginFlow(options) {
      /**
       * Return a string representation
       * @method toString
       * @memberOf OAuthLoginFlow
       * @instance
       *
       * @return {String}
       */
      this.toString = function() {
        return 'OAuthLoginFlow(' + JSON.stringify(options) + ')';
      };
      /**
       * Start the oauth login flow
       * @method startLoginFlow
       * @memberOf OAuthLoginFlow
       * @instance
       */
      this.startLoginFlow = function() {
        if (isUserLogged()) {
          var err = new NewtonError('User is already logged');
          options.callback(err);
          return globalLoginFlowEventListener.emit('end', err);
        }

        if (options.access_token) {
          statusSingleton.setDirectLoginFlowRunning(true);

          makeCall('/login/direct/oauth', {
            access_token: options.access_token,
            oauther: options.provider
          }, function(err, body) {
            if (err) {
              statusSingleton.setDirectLoginFlowRunning(false);
              options.callback(err);
              globalLoginFlowEventListener.emit('end', err);
              return;
            }

            if (!body) {
              statusSingleton.setDirectLoginFlowRunning(false);
              err = new NewtonError('Cannot read json response');
              options.callback(err);
              globalLoginFlowEventListener.emit('end', err);
              return;
            }

            statusSingleton.setUserObjectToken(body.autologin_token);
            statusSingleton.setNewtonToken(body.session_token);

            eventSingleton.addOtherEvent({
              event_type: 'identify',
              login_type: options.provider,
              custom_data: options.custom_data ? options.custom_data.toJSONObject() : undefined
            });

            stateChangeListener.onLoginStateChange();
            statusSingleton.setDirectLoginFlowRunning(false);

            options.callback();
            globalLoginFlowEventListener.emit('end');
          })
          return; // ignore all other parameters
        }

        if (options.custom_data) {
          utils.localStorage.setItem(oauthLoginDataLocalStorageKey, options.custom_data.toJSONObject());
        }
        utils.cookie.setItem(oauthLoginProviderCookieKey, options.provider);

        var url = httpSingleton.AUTH_END_DOMAIN + '/login/redirect/oauth/start'; // '/' + options.provider + '/start';

        url = url + '?';
        if (options.waitingUrl) {
          url = url + 'waitingUrl=' + encodeURIComponent(options.waitingUrl) + '&';
        } else {
          url = url + 'returnUrl=' + encodeURIComponent(options.returnUrl) + '&';
        }

        url = url + 'errorUrl=' + encodeURIComponent(options.errorUrl) +
          '&oauther=' + options.provider +
          '&application=' + applicationIdentifier;

        _console.info('Redirecting to', url, 'to login with', options.provider);

        // used to set cookie or localStorage
        setTimeout(function() {
          utils.redirectTo(url);
        }, 0);
      };
    }

    /**
     * External Login Flow
     * @class ExternalLoginFlow
     * @class ExternalLoginFlow
     * @private
     */
    function ExternalLoginFlow(options) {
      /**
       * Return a string representation
       * @method toString
       * @memberOf ExternalLoginFlow
       * @instance
       *
       * @return {String}
       */
      this.toString = function() {
        return 'ExternalLoginFlow(' + JSON.stringify(options) + ')';
      };

      /**
       * Start the external login flow
       * @method startLoginFlow
       * @memberOf ExternalLoginFlow
       * @instance
       */
      this.startLoginFlow = function() {
        if (isUserLogged()) {
          return setTimeout(function() {
            options.callback(new NewtonError('User is already logged'));
          }, 1);
        }
        statusSingleton.setExternalLoginFlowRunning(true);

        makeCall('/login/direct/external', {external_token: options.external_id}, function(err, body) {
          if (err) {
            statusSingleton.setExternalLoginFlowRunning(false);
            options.callback(err);
            globalLoginFlowEventListener.emit('end', err);
            return;
          }
          if (!body) {
            statusSingleton.setExternalLoginFlowRunning(false);
            err = new NewtonError('Cannot read json response');
            options.callback(err);
            globalLoginFlowEventListener.emit('end', err);
            return;
          }

          statusSingleton.setUserObjectToken(body.autologin_token); // the external token
          statusSingleton.setNewtonToken(body.session_token);

          eventSingleton.addOtherEvent({
            event_type: 'identify',
            login_type: 'external',
            custom_data: options.custom_data ? options.custom_data.toJSONObject() : undefined
          });

          stateChangeListener.onLoginStateChange();
          statusSingleton.setExternalLoginFlowRunning(false);

          options.callback();
          globalLoginFlowEventListener.emit('end');
        })
      };
    }

    /**
     * UR MSISDN Login Flow
     * @class MSISDNURLoginFlow
     * @private
     */
    function MSISDNURLoginFlow(options) {
      /**
       * Return a string representation
       * @method toString
       * @memberOf MSISDNURLoginFlow
       * @instance
       *
       * @return {String}
       */
      this.toString = function() {
        return 'MSISDNURLoginFlow(' + JSON.stringify(options) + ')';
      };

      /**
       * Start the msisdn login flow
       * @method startLoginFlow
       * @memberOf MSISDNURLoginFlow
       * @instance
       */
      this.startLoginFlow = function() {
        if (isUserLogged()) {
          return setTimeout(function() {
            options.callback(new NewtonError('User is already logged'));
          }, 1);
        }
        if (options.custom_data) {
          utils.localStorage.setItem(oauthLoginDataLocalStorageKey, options.custom_data.toJSONObject());
        }
        utils.cookie.setItem(oauthLoginProviderCookieKey, 'msisdn');

        var url = httpSingleton.AUTH_END_DOMAIN + '/login/redirect/msisdn/start?' + 'application=' + encodeURIComponent(options.domain);

        if (options.waitingUrl) {
          url += '&waitingUrl=' + encodeURIComponent(options.waitingUrl);
        }
        if (options.returnUrl) {
          url += '&returnUrl=' + encodeURIComponent(options.returnUrl);
        }
        if (options.subscribeUrl) {
          url += '&subscribe_url=' + encodeURIComponent(options.subscribeUrl);
        }
        setTimeout(function() {
          utils.redirectTo(url);
        }, 0);
      };
    }

    /**
     * PIN MSISDN Login Flow
     * @class MSISDNPINLoginFlow
     * @private
     */
    function MSISDNPINLoginFlow(options) {
      /**
       * Return a string representation
       * @method toString
       * @memberOf MSISDNPINLoginFlow
       * @instance
       *
       * @return {String}
       */
      this.toString = function() {
        return 'MSISDNPINLoginFlow(' + JSON.stringify(options) + ')';
      };

      /**
       * Start the pin msisdn login flow
       * @method startLoginFlow
       * @memberOf MSISDNPINLoginFlow
       * @instance
       */
      this.startLoginFlow = function() {
        if (isUserLogged()) {
          return setTimeout(function() {
            options.callback(new NewtonError('User is already logged'));
          }, 1);
        }
        statusSingleton.setDirectLoginFlowRunning(true);

        var requestBody = {
          pin: options.pin
        };

        if (options.msisdn) {
          requestBody.msisdn = options.msisdn;
        }

        if (options.alias) {
          requestBody.alias = options.alias;
        }

        if (options.operator) {
          requestBody.operator = options.operator;
        }

        makeCall('/login/direct/msisdn/PINidentify', requestBody, function(err, body) {
          if (err) {
            statusSingleton.setDirectLoginFlowRunning(false);
            options.callback(err);
            globalLoginFlowEventListener.emit('end', err);
            return;
          }

          if (!body) {
            statusSingleton.setDirectLoginFlowRunning(false);
            err = new NewtonError('Cannot read json response');
            options.callback(err);
            globalLoginFlowEventListener.emit('end', err);
            return;
          }

          statusSingleton.setUserObjectToken(body.autologin_token);
          statusSingleton.setNewtonToken(body.session_token);

          eventSingleton.addOtherEvent({
            event_type: 'identify',
            login_type: 'msisdn-pin',
            custom_data: options.custom_data ? options.custom_data.toJSONObject() : undefined
          });

          stateChangeListener.onLoginStateChange();
          statusSingleton.setDirectLoginFlowRunning(false);

          options.callback();
          globalLoginFlowEventListener.emit('end');
        })
      };
    }

    /**
     * PIN MSISDN Forgot Flow
     * @class MSISDNPINForgotFlow
     * @private
     */
    function MSISDNPINForgotFlow(options) {
      /**
       * Return a string representation
       * @method toString
       * @memberOf MSISDNPINForgotFlow
       * @instance
       *
       * @return {String}
       */
      this.toString = function() {
        return 'MSISDNPINForgotFlow(' + JSON.stringify(options) + ')';
      };

      /**
       * Start the pin msisdn login flow
       * @method startForgotFlow
       * @memberOf MSISDNPINForgotFlow
       * @instance
       */
      this.startForgotFlow = function() {
        if (isUserLogged()) {
          return setTimeout(function() {
            options.callback(new NewtonError('User is already logged'));
          }, 1);
        }
        if (statusSingleton.isLoginFlowRunning()) {
          return setTimeout(function() {
            options.callback(new NewtonError('A login flow is already running'));
          }, 1);
        }
        statusSingleton.setForgotFlowRunning(true);

        var requestBody = {
        };

        if (options.msisdn) {
          requestBody.msisdn = options.msisdn;
        }

        if (options.alias) {
          requestBody.alias = options.alias;
        }

        if(options.smsTemplate) {
          requestBody.sms_template = options.smsTemplate;
        }

        makeCall('/login/direct/msisdn/PINforgot', requestBody, function(err) {
          if (err) {
            statusSingleton.setForgotFlowRunning(false);
            return options.callback(err);
          }
          statusSingleton.setForgotFlowRunning(false);

          options.callback();
        })
      };
    }

    /**
     * Email Login Flow
     * @class EmailLoginFlow
     * @private
     */
    function EmailLoginFlow(options) {
      /**
       * Return a string representation
       * @method toString
       * @memberOf EmailLoginFlow
       * @instance
       *
       * @return {String}
       */
      this.toString = function() {
        return 'EmailLoginFlow(' + JSON.stringify(options) + ')';
      };

      /**
       * Start the email login flow
       * @method startLoginFlow
       * @memberOf EmailLoginFlow
       * @instance
       */
      this.startLoginFlow = function() {
        if (isUserLogged()) {
          return setTimeout(function() {
            options.callback(new NewtonError('User is already logged'));
          }, 1);
        }
        statusSingleton.setDirectLoginFlowRunning(true);

        makeCall('/login/direct/email/login', {email: options.email, password: options.password}, function(err, body) {
          if (err) {
            statusSingleton.setDirectLoginFlowRunning(false);
            options.callback(err);
            globalLoginFlowEventListener.emit('end', err);
            return;
          }
          if (!body) {
            statusSingleton.setDirectLoginFlowRunning(false);
            err = new NewtonError('Cannot read json response');
            options.callback(err);
            globalLoginFlowEventListener.emit('end', err);
            return;
          }

          statusSingleton.setUserObjectToken(body.autologin_token);
          statusSingleton.setNewtonToken(body.session_token);

          eventSingleton.addOtherEvent({
            event_type: 'identify',
            login_type: 'email',
            custom_data: options.custom_data ? options.custom_data.toJSONObject() : undefined
          });

          stateChangeListener.onLoginStateChange();
          statusSingleton.setDirectLoginFlowRunning(false);

          options.callback();
          globalLoginFlowEventListener.emit('end');
        })
      };
    }

    /**
     * Email Signup Flow
     * @class EmailSignupFlow
     * @private
     */
    function EmailSignupFlow(options) {
      /**
       * Return a string representation
       * @method toString
       * @memberOf EmailSignupFlow
       * @instance
       *
       * @return {String}
       */
      this.toString = function() {
        return 'EmailSignupFlow(' + JSON.stringify(options) + ')';
      };

      /**
       * Start the email login flow
       * @method startLoginFlow
       * @memberOf EmailSignupFlow
       * @instance
       */
      this.startLoginFlow = function() {
        if (isUserLogged()) {
          return setTimeout(function() {
            options.callback(new NewtonError('User is already logged'));
          }, 1);
        }
        statusSingleton.setDirectLoginFlowRunning(true);

        var body = {
          email: options.email,
          password: options.password,
          user_properties: options.user_properties ? options.user_properties.toJSONObject() : undefined,
          productmail_parameters: options.productEmailParams
        };
        makeCall('/login/direct/email/signup', body, function(err, body) {
          if (err) {
            statusSingleton.setDirectLoginFlowRunning(false);
            options.callback(err);
            globalLoginFlowEventListener.emit('end', err);
            return;
          }
          if (!body) {
            statusSingleton.setDirectLoginFlowRunning(false);
            err = new NewtonError('Cannot read json response');
            options.callback(err);
            globalLoginFlowEventListener.emit('end', err);
            return;
          }

          statusSingleton.setUserObjectToken(body.autologin_token);
          statusSingleton.setNewtonToken(body.session_token);

          eventSingleton.addOtherEvent({
            event_type: 'identify',
            login_type: 'email-signup',
            custom_data: options.custom_data ? options.custom_data.toJSONObject() : undefined
          });

          stateChangeListener.onLoginStateChange();
          statusSingleton.setDirectLoginFlowRunning(false);

          options.callback();
          globalLoginFlowEventListener.emit('end');
        })
      };
    }

    /**
     * Email confirm Flow
     * @class EmailConfirmFlow
     * @private
     */
    function EmailConfirmFlow(options) {
      /**
       * Return a string representation
       * @method toString
       * @memberOf EmailConfirmFlow
       * @instance
       *
       * @return {String}
       */
      this.toString = function() {
        return 'EmailConfirmFlow(' + JSON.stringify(options) + ')';
      };

      /**
       * Start the email confirm flow
       * @method startLoginFlow
       * @memberOf EmailConfirmFlow
       * @instance
       */
      this.startLoginFlow = function() {
        if (isUserLogged()) {
          return setTimeout(function() {
            options.callback(new NewtonError('User is already logged'));
          }, 1);
        }
        statusSingleton.setDirectLoginFlowRunning(true);

        makeCall('/login/direct/email/confirm_login', { email_token: options.email_token }, function(err, body) {
          if (err) {
            statusSingleton.setDirectLoginFlowRunning(false);
            options.callback(err);
            globalLoginFlowEventListener.emit('end', err);
            return;
          }

          if (!body) {
            statusSingleton.setDirectLoginFlowRunning(false);
            err = new NewtonError('Cannot read json response');
            options.callback(err);
            globalLoginFlowEventListener.emit('end', err);
            return;
          }

          statusSingleton.setUserObjectToken(body.autologin_token);
          statusSingleton.setNewtonToken(body.session_token);

          eventSingleton.addOtherEvent({
            event_type: 'identify',
            login_type: 'email-confirm',
            custom_data: options.custom_data ? options.custom_data.toJSONObject() : undefined
          });

          stateChangeListener.onLoginStateChange();
          statusSingleton.setDirectLoginFlowRunning(false);

          options.callback();
          globalLoginFlowEventListener.emit('end');
        })
      };
    }

    /**
     * Email forgot Flow
     * @class EmailForgotFlow
     * @private
     */
    function EmailForgotFlow(options) {
      /**
       * Return a string representation
       * @method toString
       * @memberOf EmailForgotFlow
       * @instance
       *
       * @return {String}
       */
      this.toString = function() {
        return 'EmailForgotFlow(' + JSON.stringify(options) + ')';
      };

      /**
       * Start the email forgot flow
       * @method startForgotFlow
       * @memberOf EmailForgotFlow
       * @instance
       */
      this.startForgotFlow = function() {
        if (isUserLogged()) {
          return setTimeout(function() {
            options.callback(new Error('User is already logged'));
          }, 1);
        }
        makeCall('/login/direct/email/forgot', { email: options.email }, options.callback)
      };
    }

    /**
     * Email resend Flow
     * @class EmailResendFlow
     * @private
     */
    function EmailResendFlow(options) {
      /**
       * Return a string representation
       * @method toString
       * @memberOf EmailResendFlow
       * @instance
       *
       * @return {String}
       */
      this.toString = function() {
        return 'EmailResendFlow(' + JSON.stringify(options) + ')';
      };

      /**
       * Start the email resend flow
       * @method startLoginFlow
       * @memberOf EmailResendFlow
       * @instance
       */
      this.startLoginFlow = function() {
        if (isUserLogged()) {
          return setTimeout(function() {
            options.callback(new Error('User is already logged'));
          }, 1);
        }

        makeCall('/login/direct/email/resend', { email: options.email }, options.callback)
      };
    }

    /**
     * Generic Login Flow
     * @class GenericLoginFlow
     * @private
     */
    function GenericLoginFlow(options) {
      /**
       * Return a string representation
       * @method toString
       * @memberOf GenericLoginFlow
       * @instance
       *
       * @return {String}
       */
      this.toString = function() {
        return 'GenericLoginFlow(' + JSON.stringify(options) + ')';
      };

      /**
       * Start the generic login flow
       * @method startLoginFlow
       * @memberOf GenericLoginFlow
       * @instance
       */
      this.startLoginFlow = function() {
        if (isUserLogged()) {
          return setTimeout(function() {
            options.callback(new NewtonError('User is already logged'));
          }, 1);
        }
        statusSingleton.setDirectLoginFlowRunning(true);

        var body = {username: options.username, password: options.password}
        makeCall('/login/direct/generic/login', body, function(err, body) {
          if (err) {
            statusSingleton.setDirectLoginFlowRunning(false);
            options.callback(err);
            globalLoginFlowEventListener.emit('end', err);
            return;
          }
          if (!body) {
            statusSingleton.setDirectLoginFlowRunning(false);
            err = new NewtonError('Cannot read json response');
            options.callback(err);
            globalLoginFlowEventListener.emit('end', err);
            return;
          }

          statusSingleton.setUserObjectToken(body.autologin_token);
          statusSingleton.setNewtonToken(body.session_token);

          eventSingleton.addOtherEvent({
            event_type: 'identify',
            login_type: 'generic',
            custom_data: options.custom_data ? options.custom_data.toJSONObject() : undefined
          });

          stateChangeListener.onLoginStateChange();
          statusSingleton.setDirectLoginFlowRunning(false);

          options.callback();
          globalLoginFlowEventListener.emit('end');
        })
      };
    }

    /**
     * Payment Receipt Login Flow
     * @class PaymentReceiptLoginFlow
     * @private
     */
    function PaymentReceiptLoginFlow(options) {
      /**
       * Return a string representation
       * @method toString
       * @memberOf PaymentReceiptLoginFlow
       * @instance
       *
       * @return {String}
       */
      this.toString = function() {
        return 'PaymentReceiptLoginFlow(' + JSON.stringify(options) + ')';
      };

      /**
       * Start the payment receipt login flow
       * @method startLoginFlow
       * @memberOf PaymentReceiptLoginFlow
       * @instance
       */
      this.startLoginFlow = function() {
        if (isUserLogged()) {
          return setTimeout(function() {
            options.callback(new NewtonError('User is already logged'));
          }, 1);
        }
        statusSingleton.setDirectLoginFlowRunning(true);

        var body = {
          user_id: statusSingleton.getUserToken(),
          serialized_receipt: options.payment
        }

        makeCall('/login/direct/payment_receipt', body, function(err, body) {
          if (err) {
            statusSingleton.setDirectLoginFlowRunning(false);
            options.callback(err);
            globalLoginFlowEventListener.emit('end', err);
            return;
          }
          if (!body) {
            statusSingleton.setDirectLoginFlowRunning(false);
            err = new NewtonError('Cannot read json response');
            options.callback(err);
            globalLoginFlowEventListener.emit('end', err);
            return;
          }

          statusSingleton.setUserObjectToken(body.autologin_token);
          statusSingleton.setNewtonToken(body.session_token);

          eventSingleton.addOtherEvent({
            event_type: 'identify',
            login_type: 'payment-receipt',
            custom_data: options.custom_data ? options.custom_data.toJSONObject() : undefined
          });

          stateChangeListener.onLoginStateChange();
          statusSingleton.setDirectLoginFlowRunning(false);

          options.callback();
          globalLoginFlowEventListener.emit('end');
        })
      };
    }

    /**
     * CookieFlow Login Flow
     * @class CookieLoginFlow
     * @class CookieLoginFlow
     * @private
     */
     function CookieLoginFlow(options) {
      /**
       * Return a string representation
       * @method toString
       * @memberOf CookieLoginFlow
       * @instance
       *
       * @return {String}
       */
      this.toString = function() {
        return 'CookieLoginFlow(' + JSON.stringify(options) + ')';
      };

      /**
       * Start the cookieflow login flow
       * @method startLoginFlow
       * @memberOf CookieLoginFlow
       * @instance
       */
      this.startLoginFlow = function() {
        if (isUserLogged()) {
          return setTimeout(function() {
            options.callback(new NewtonError('User is already logged'));
          }, 1);
        }
        statusSingleton.setDirectLoginFlowRunning(true);

        makeCall('/login/direct/cookieflow', {cookieflow_token: options.cookieflow_id}, function(err, body) {
          if (err) {
            statusSingleton.setDirectLoginFlowRunning(false);
            options.callback(err);
            globalLoginFlowEventListener.emit('end', err);
            return;
          }
          if (!body) {
            statusSingleton.setDirectLoginFlowRunning(false);
            err = new NewtonError('Cannot read json response');
            options.callback(err);
            globalLoginFlowEventListener.emit('end', err);
            return;
          }

          statusSingleton.setUserObjectToken(body.autologin_token); // the cookieflow token
          statusSingleton.setNewtonToken(body.session_token);

          eventSingleton.addOtherEvent({
            event_type: 'identify',
            login_type: 'cookieflow',
            custom_data: options.custom_data ? options.custom_data.toJSONObject() : undefined
          });

          stateChangeListener.onLoginStateChange();
          statusSingleton.setDirectLoginFlowRunning(false);

          options.callback();
          globalLoginFlowEventListener.emit('end');
        })
      };
    }

    /**
     * Login utilities
     * @class LoginBuilder
     * @name LoginBuilder
     * @private
     */
    function LoginBuilder() {
      this.options = {};
    }

    // login builder utility
    function setInnerField(name) {
      return function(value) {
        this.options[name] = value;
        return this;
      };
    }

    /**
     * Set callback invoked when a login flow is ended
     * @method setOnFlowCompleteCallback
     * @memberOf LoginBuilder
     * @instance
     * @param {FlowCompleteCallback} callback - Invoked at the end of the login flow
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setOnFlowCompleteCallback = setInnerField('callback');
    /**
     * Set callback invoked when a forgot flow is ended
     * @method setOnForgotFlowCallback
     * @memberOf LoginBuilder
     * @instance
     * @param {FlowCompleteCallback} callback - Invoked at the end of the login flow
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setOnForgotFlowCallback = setInnerField('on_forgot_callback');
    /**
     * Set login data. Useful to track campaign parameters
     * @method setCustomData
     * @memberOf LoginBuilder
     * @instance
     * @param {Newton.SimpleObject} loginData - the custom data for the login event
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setCustomData = setInnerField('custom_data');
    /**
     * Set custom id. Used in custom flow only.
     * @method setCustomID
     * @memberOf LoginBuilder
     * @instance
     * @param {String} customID - custom user id
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setCustomID = setInnerField('custom_id');
    /**
     * Set external id. Used in external flow only.
     * @method setExternalID
     * @memberOf LoginBuilder
     * @instance
     * @param {String} externalID - external user id
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setExternalID = setInnerField('external_id');
    /**
     * Set cookieflow id. Used in cookieflow flow only.
     * @method setCookieFlowID
     * @memberOf LoginBuilder
     * @instance
     * @param {String} cookieflowID - cookieflow user id
     *
     * @return {LoginBuilder} the same instance
     */
     LoginBuilder.prototype.setCookieFlowID = setInnerField('cookieflow_id');
    /**
     * Set subscribe url. Used in MSISDN flow only.
     * @method setSubscribeUrl
     * @memberOf LoginBuilder
     * @instance
     * @param {String} subscribeUrl - the login url
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setSubscribeUrl = setInnerField('subscribe_url');
    /**
     * Set domain application. Used in MSISDN flow only. TEMPORARY AND ONLY FOR TEST!
     * @method __setDomain
     * @memberOf LoginBuilder
     * @instance
     * @param {String} domain - the application domain
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.__setDomain = setInnerField('domain');
    /**
     * Persist the custom login among the page refreshing. A session cookie is set.
     * @method setAllowCustomLogin
     * @memberOf LoginBuilder
     * @instance
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setAllowCustomLogin = function() {
      this.options.allow_custom_login = true;
      return this;
    };

    /**
     * Set serialized payment
     * @method setSerializedPayment
     * @memberOf LoginBuilder
     * @instance
     * @param {String} blob - the serialized payment
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setSerializedPayment = setInnerField('blob');

    /**
     * Set email. Used in Email flows.
     * @method setEmail
     * @memberOf LoginBuilder
     * @instance
     * @param {String} email - the email
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setEmail = setInnerField('email');
    /**
     * Set the password. Used in Email flows.
     * @method setPassword
     * @memberOf LoginBuilder
     * @instance
     * @param {String} password - the password
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setPassword = setInnerField('password');
    /**
     * Set the user properties if the EmailSignupFlow succeeded
     * @method setUserProperties
     * @memberOf LoginBuilder
     * @instance
     * @param {SimpleObject} user_properties - the user_properties
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setUserProperties = setInnerField('user_properties');
    /**
     * Set the email token. Used in EmailConfirmFlow
     * @method setEmailToken
     * @memberOf LoginBuilder
     * @instance
     * @param {Object} email_token - the email_token
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setEmailToken = setInnerField('email_token');

    /**
     * Set the email forgot token. Used in EmailConfirmForgotFlow
     * @method setForgot
     * @memberOf LoginBuilder
     * @instance
     * @param {Object} forgot_token - the forgot_token
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setForgotToken = setInnerField('forgot_token');

    /**
     * Set access token from oauth.
     * @method setAccessToken
     * @memberOf LoginBuilder
     * @instance
     * @param {String} access_token - The access token given
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setAccessToken = setInnerField('access_token');
    /**
     * Set oauth provider needed for OAuthLoginFlow
     * @method setOAuthProvider
     * @memberOf LoginBuilder
     * @instance
     * @param {String} oauth provider
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setOAuthProvider = setInnerField('oauth_provider');
    /**
     * Set return url.
     * @method setReturnUrl
     * @memberOf LoginBuilder
     * @instance
     * @param {String} returnUrl
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setReturnUrl = setInnerField('return_url');
    /**
     * Set error url.
     * @method setErrorUrl
     * @memberOf LoginBuilder
     * @instance
     * @param {String} errorUrl
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setErrorUrl = setInnerField('error_url');
    /**
     * Set waiting url. Used in OAuthLoginFlow
     * @method setWaitingUrl
     * @memberOf LoginBuilder
     * @instance
     * @param {String} waitingUrl
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setWaitingUrl = setInnerField('waiting_url');
    /**
     * Set PIN. Used in MSISDNPINLoginFlow
     * @method setPIN
     * @memberOf LoginBuilder
     * @instance
     * @param {String} pin
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setPIN = setInnerField('pin');
    /**
     * Set a fake PIN in order to perform the login without PIN. Used in MSISDNPINLoginFlow. This method overwrite the previous "setPIN" setting
     * @method setNoPIN
     * @memberOf LoginBuilder
     * @instance
     * @param {String} pin
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setNoPIN = function() {
      this.options.pin = '__THE_PINKY_SWEAR__';
      return this;
    };
    /**
     * Set MSISDN. Used in MSISDNPINLoginFlow
     * @method setMSISDN
     * @memberOf LoginBuilder
     * @instance
     * @param {String} msisdn
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setMSISDN = setInnerField('msisdn');
    /**
     * Set Alias. Used in MSISDNPINLoginFlow
     * @method setAlias
     * @memberOf LoginBuilder
     * @instance
     * @param {String} alias
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setAlias = setInnerField('alias');
    /**
     * Set Operator. Used in MSISDNPINLoginFlow
     * @method setOperator
     * @memberOf LoginBuilder
     * @instance
     * @param {String} operator
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setOperator = setInnerField('operator');
    /**
     * Set username
     * @method setUsername
     * @memberOf LoginBuilder
     * @instance
     * @param {String} username
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setUsername = setInnerField('username');
    /**
     * Set Product Email template parameter
     * @method setProductEmailParams
     * @memberOf LoginBuilder
     * @param {SimpleObject} params - The parameters used to interpolare the template
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setProductEmailParams = setInnerField('productEmailParams');
    /**
     * Set the MSISDN pin forgot sms template
     * @method setSMSTemplate
     * @memberOf LoginBuilder
     * @param {string} smsTemplate - The SMS template
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setSMSTemplate = setInnerField('smsTemplate');

    /**
     * Create a custom login flow.
     * @method getCustomLoginFlow
     * @memberOf LoginBuilder
     * @instance
     *
     * @return {CustomLoginFlow}
     */
    LoginBuilder.prototype.getCustomLoginFlow = function() {
      if (!this.options.custom_id) {
        throw new NewtonError('Custom_id must be provided');
      }
      if (!isUserTokenGood(this.options.custom_id)) {
        throw new NewtonError('invalid custom_id');
      }
      if (isUserLogged()) {
        throw new NewtonError('User is already logged');
      }
      SimpleObject._assertIsInstanceOfOrNull(this.options.custom_data);

      return new CustomLoginFlow({
        callback: this.options.callback || function() {},
        custom_data: this.options.custom_data,
        custom_id: 'C_' + this.options.custom_id,
        allow_custom_login: this.options.allow_custom_login
      });
    };

    /**
     * Create a custom login flow.
     * @method getOAuthLoginFlow
     * @memberOf LoginBuilder
     * @instance
     *
     * @return {OAuthLoginFlow}
     */
    LoginBuilder.prototype.getOAuthLoginFlow = function() {
      SimpleObject._assertIsInstanceOfOrNull(this.options.custom_data);

      var oauthProvider = this.options.oauth_provider;
      if (oauthProviders.indexOf(oauthProvider) < 0) {
        throw new NewtonError('Unkown oauth provider');
      }
      if (isUserLogged()) {
        throw new NewtonError('User is already logged');
      }

      var returnUrl;
      var errorUrl;
      var waitingUrl;
      if (!this.options.access_token) {
        returnUrl = this.options.return_url || utils.getCurrentUrl();
        assertion.assertStringIsAValidUrl(returnUrl);

        errorUrl = this.options.error_url || utils.getCurrentUrl();
        assertion.assertStringIsAValidUrl(errorUrl);

        waitingUrl = this.options.waiting_url;
        if (waitingUrl) {
          assertion.assertStringIsAValidUrl(waitingUrl);
        }
      }

      return new OAuthLoginFlow({
        provider: oauthProvider,
        access_token: this.options.access_token,
        custom_data: this.options.custom_data,
        returnUrl: returnUrl,
        errorUrl: errorUrl,
        waitingUrl: waitingUrl,
        callback: this.options.callback || function() {}
      });
    };

    /**
     * Create an external login flow.
     * @method getExternalLoginFlow
     * @memberOf LoginBuilder
     * @instance
     *
     * @return {ExternalLoginFlow}
     */
    LoginBuilder.prototype.getExternalLoginFlow = function() {
      SimpleObject._assertIsInstanceOfOrNull(this.options.custom_data);

      if (!this.options.external_id) {
        throw new NewtonError('External_id must be provided');
      }
      if (!isUserTokenGood(this.options.external_id)) {
        throw new NewtonError('invalid externalID');
      }
      if (isUserLogged()) {
        throw new NewtonError('User is already logged');
      }

      return new ExternalLoginFlow({
        external_id: this.options.external_id,
        custom_data: this.options.custom_data,
        callback: this.options.callback || function() {}
      });
    };


    /**
     * Create an cookieflow login flow.
     * @method getCookieLoginFlow
     * @memberOf LoginBuilder
     * @instance
     *
     * @return {CookieLoginFlow}
     */
     LoginBuilder.prototype.getCookieLoginFlow = function() {
      SimpleObject._assertIsInstanceOfOrNull(this.options.custom_data);

      if (!this.options.cookieflow_id) {
        throw new NewtonError('CookieFlow_id must be provided');
      }
      if (!isUserTokenGood(this.options.cookieflow_id)) {
        throw new NewtonError('invalid cookieflowID');
      }
      if (isUserLogged()) {
        throw new NewtonError('User is already logged');
      }

      return new CookieLoginFlow({
        cookieflow_id: this.options.cookieflow_id,
        custom_data: this.options.custom_data,
        callback: this.options.callback || function() {}
      });
    };

    /**
     * Create a payment receipt login flow.
     * @method getPaymentReceiptLoginFlow
     * @memberOf LoginBuilder
     * @instance
     *
     * @return {PaymentReceiptLoginFlow}
     */
    LoginBuilder.prototype.getPaymentReceiptLoginFlow = function() {
      SimpleObject._assertIsInstanceOfOrNull(this.options.custom_data);

      if (!this.options.blob) {
        throw new NewtonError('blob must be provided');
      }

      try {
        var serializedPaymentObject = JSON.parse(atob(this.options.blob));
      } catch (e){
        throw new NewtonError('cannot convert blob from base64 to a valid JSON object');
      }
      if (typeof serializedPaymentObject !== typeof {}){
        throw new NewtonError('cannot convert blob from base64 to a valid JSON object');
      }

      if (isUserLogged()) {
        throw new NewtonError('User is already logged');
      }

      return new PaymentReceiptLoginFlow({
        payment: serializedPaymentObject,
        custom_data: this.options.custom_data,
        callback: this.options.callback || function() {}
      });
    };

    /**
     * Create a msisdn login flow.
     * @method getMSISDNURLoginFlow
     * @memberOf LoginBuilder
     * @instance
     *
     * @return {MSISDNURLoginFlow}
     */
    LoginBuilder.prototype.getMSISDNURLoginFlow = function() {
      SimpleObject._assertIsInstanceOfOrNull(this.options.custom_data);

      if (!((!!this.options.waiting_url) ^ (!!this.options.return_url))) {
        throw new NewtonError('waitingUrl or returnUrl are mutually exclusive');
      }
      if (this.options.waiting_url) {
        assertion.assertStringIsAValidUrl(this.options.waiting_url);
      }
      if (this.options.return_url) {
        assertion.assertStringIsAValidUrl(this.options.return_url);
      }
      if (isUserLogged()) {
        throw new NewtonError('User is already logged');
      }

      return new MSISDNURLoginFlow({
        waitingUrl: this.options.waiting_url,
        subscribeUrl: this.options.subscribe_url,
        returnUrl: this.options.return_url,
        domain: this.options.domain || window.location.hostname,
        custom_data: this.options.custom_data,
        callback: this.options.callback || function() {}
      });
    };

    /**
     * Create a msisdn pin login flow.
     * @method getMSISDNPINLoginFlow
     * @memberOf LoginBuilder
     * @instance
     *
     * @return {MSISDNPINLoginFlow}
     */
    LoginBuilder.prototype.getMSISDNPINLoginFlow = function() {
      SimpleObject._assertIsInstanceOfOrNull(this.options.custom_data);

      assertion.assertIfIsString(this.options.pin);

      if (this.options.msisdn) {
        assertion.assertIfIsString(this.options.msisdn);
      }

      if (this.options.alias) {
        assertion.assertIfIsString(this.options.alias);
      }

      if (this.options.msisdn && this.options.alias) {
        throw new NewtonError('You set MSISDN and Alias');
      }

      if (assertion.isUndefined(this.options.msisdn) && assertion.isUndefined(this.options.alias)) {
        throw new NewtonError('Missing MSISDN or Alias');
      }

      if (this.options.operator) {
        assertion.assertIfIsString(this.options.operator);
      }

      if (isUserLogged()) {
        throw new NewtonError('User is already logged');
      }
      return new MSISDNPINLoginFlow({
        msisdn: this.options.msisdn,
        alias: this.options.alias,
        pin: this.options.pin,
        operator: this.options.operator,
        custom_data: this.options.custom_data,
        callback: this.options.callback || function() {}
      });
    };

    /**
     * Create a msisdn pin forgot flow.
     * @method getMSISDNPINForgotFlow
     * @memberOf LoginBuilder
     * @instance
     *
     * @return {MSISDNPINForgotFlow}
     */
    LoginBuilder.prototype.getMSISDNPINForgotFlow = function() {
      if (this.options.msisdn) {
        assertion.assertIfIsString(this.options.msisdn);
      }

      if (this.options.alias) {
        assertion.assertIfIsString(this.options.alias);
      }

      if (this.options.msisdn && this.options.alias) {
        throw new NewtonError('You set MSISDN and Alias');
      }

      if (assertion.isUndefined(this.options.msisdn) && assertion.isUndefined(this.options.alias)) {
        throw new NewtonError('Missing MSISDN or Alias');
      }

      if (isUserLogged()) {
        throw new NewtonError('User is already logged');
      }

      return new MSISDNPINForgotFlow({
        msisdn: this.options.msisdn,
        alias: this.options.alias,
        smsTemplate: this.options.smsTemplate,
        custom_data: this.options.custom_data,
        callback: this.options.on_forgot_callback || function() {}
      });
    };

    /**
     * Create an email login flow.
     * @method getEmailLoginFlow
     * @memberOf LoginBuilder
     * @instance
     *
     * @return {EmailLoginFlow}
     */
    LoginBuilder.prototype.getEmailLoginFlow = function() {
      SimpleObject._assertIsInstanceOfOrNull(this.options.custom_data);

      if (!this.options.password) {
        throw new NewtonError('password must be provided');
      }
      if (!this.options.email) {
        throw new NewtonError('email must be provided');
      }
      if (isUserLogged()) {
        throw new NewtonError('User is already logged');
      }

      return new EmailLoginFlow({
        email: this.options.email,
        password: this.options.password,
        custom_data: this.options.custom_data,
        callback: this.options.callback || function() {}
      });
    };

    /**
     * Create a generic login flow.
     * @method getGenericLoginFlow
     * @memberOf LoginBuilder
     * @instance
     *
     * @return {GenericLoginFlow}
     */
    LoginBuilder.prototype.getGenericLoginFlow = function() {
      SimpleObject._assertIsInstanceOfOrNull(this.options.custom_data);

      if (!this.options.password) {
        throw new NewtonError('password must be provided');
      }
      if (!this.options.username) {
        throw new NewtonError('username must be provided');
      }
      if (isUserLogged()) {
        throw new NewtonError('User is already logged');
      }

      return new GenericLoginFlow({
        username: this.options.username,
        password: this.options.password,
        custom_data: this.options.custom_data,
        callback: this.options.callback || function() {}
      });
    };

    /**
     * Create an email signup flow.
     * @method getEmailSignupFlow
     * @memberOf LoginBuilder
     * @instance
     *
     * @return {EmailSignupFlow}
     */
    LoginBuilder.prototype.getEmailSignupFlow = function() {
      SimpleObject._assertIsInstanceOfOrNull(this.options.custom_data);
      SimpleObject._assertIsInstanceOfOrNull(this.options.user_properties);

      if (!this.options.password) {
        throw new NewtonError('password must be provided');
      }
      if (!this.options.email) {
        throw new NewtonError('email must be provided');
      }
      if (isUserLogged()) {
        throw new NewtonError('User is already logged');
      }

      return new EmailSignupFlow({
        email: this.options.email,
        password: this.options.password,
        custom_data: this.options.custom_data,
        user_properties: this.options.user_properties,
        productEmailParams: this.options.productEmailParams,
        callback: this.options.callback || function() {}
      });
    };

    /**
     * Create an email login flow.
     * @method getEmailConfirmFlow
     * @memberOf LoginBuilder
     * @instance
     *
     * @return {EmailConfirmFlow}
     */
    LoginBuilder.prototype.getEmailConfirmFlow = function() {
      if (!this.options.email_token) {
        throw new NewtonError('email_token must be provided');
      }
      if (isUserLogged()) {
        throw new NewtonError('User is already logged');
      }

      return new EmailConfirmFlow({
        email_token: this.options.email_token,
        custom_data: this.options.custom_data,
        callback: this.options.callback || function() {}
      });
    };

    /**
     * Create an email login flow.
     * @method getEmailResendFlow
     * @memberOf LoginBuilder
     * @instance
     *
     * @return {EmailResendFlow}
     */
    LoginBuilder.prototype.getEmailResendFlow = function() {
      if (!this.options.email) {
        throw new NewtonError('email must be provided');
      }
      if (isUserLogged()) {
        throw new NewtonError('User is already logged');
      }

      return new EmailResendFlow({
        email: this.options.email,
        callback: this.options.callback || function() {}
      });
    };

    /**
     * Create an email forgot flow.
     * @method getEmailForgotFlow
     * @memberOf LoginBuilder
     * @instance
     *
     * @return {EmailForgotFlow}
     */
    LoginBuilder.prototype.getEmailForgotFlow = function() {
      if (!this.options.email) {
        throw new NewtonError('email must be provided');
      }
      if (isUserLogged()) {
        throw new NewtonError('User is already logged');
      }

      return new EmailForgotFlow({
        email: this.options.email,
        callback: this.options.on_forgot_callback || function() {}
      });
    };

    /**
     * Return a list of available and supported oauth provider
     * @method getOAuthProviders
     * @memberOf Newton
     * @instance
     *
     * @return {Array} - the list
     */
    publicInterface.getOAuthProviders = function() {
      // return a copy
      return oauthProviders.slice();
    };

    /**
     * Return a login builder instance
     * @method getLoginBuilder
     * @memberOf Newton
     * @instance
     *
     * @return {LoginBuilder}
     */
    publicInterface.getLoginBuilder = function() {
      return new LoginBuilder();
    };

    /**
     * Notify the changing of the user status.
     * @method setUserStateChangeListener
     * @memberOf Newton
     * @instance
     * @param {UserStateChangeListener} listener
     */
    /**
      * This listener is used on state changes.
      * @typedef {Object} UserStateChangeListener
      * @property {USC_LoginStateCallback} onLoginStateChange - callback for login changes
      */
    /**
      * This callback for login state changes.
      * @callback USC_LoginStateCallback
      * @param {Error} error - when something goes wrong
      */
    publicInterface.setUserStateChangeListener = function(listener) {
      if (!assertion.isObject(listener) || !assertion.isFunction(listener.onLoginStateChange)) {
        throw new NewtonError('Invalid listener');
      }

      stateChangeListener = listener;

      if (statusSingleton.isReturningFromLoginFlow()) {
        statusSingleton.setReturningFromLoginFlow(false);

        var err = errorFromLoginFlow ? new Error(errorFromLoginFlow) : undefined;
        errorFromLoginFlow = undefined;

        stateChangeListener.onLoginStateChange(err);
        globalLoginFlowEventListener.emit('end', err);
      }
    };

    function userLogout(reason) {
      var userWasLogged = isUserLogged()
      statusSingleton.userCleanup();
      if (userWasLogged) {
        eventSingleton.addOtherEvent({
          event_type: 'logout',
          logout_type: reason
        });
      }
    }

    /**
     * Log the user out
     * @method userLogout
     * @memberOf Newton
     * @instance
     */
    publicInterface.userLogout = function() {
      var oldstate = isUserLogged();
      userLogout('explicit');
      if (isUserLogged() != oldstate) {
        stateChangeListener.onLoginStateChange();
      }
    };

    /**
     * Invoke a logout that force token invalidation
     * @method userLogoutAsync
     * @memberOf Newton
     * @instance
     * @param {Function} callback - called at the end of async process (AFTER loginStateChange) with null OR error as first argument
     */
    publicInterface.userLogoutAsync = function(callback) {
      callback = callback || function() { }

      if (!isUserLogged()) {
        callback();
        return;
      }

      httpSingleton.makeSignedRequest(
        httpSingleton.AUTH_END_DOMAIN + '/handling/direct/invalidatetoken',
        { user_id: statusSingleton.getUserToken() },
        function(responseErr) {
          if (responseErr) {
            return callback(responseErr);
          }

          userLogout('explicit_async');
          stateChangeListener.onLoginStateChange();
          callback();
      });
    };

    /**
     * Return true if Newton User is logged
     * @method isUserLogged
     * @instance
     * @memberOf Newton
     *
     * @return {Boolean} - true if Newton User is logged
     */
    publicInterface.isUserLogged = isUserLogged;

    /**
     * Finalize login flow.
     * Used only for customized waiting page
     * @method finalizeLoginFlow
     * @memberOf Newton
     * @instance
     * @param {Function} callback - invoked at the end of the flow. An error is given to discern the error case
     */
    publicInterface.finalizeLoginFlow = function(callback) {
      _console.info('finalizeLoginFlow');

      var state = utils.getParameterByName('state');
      var currentHostname = utils.getHostname();

      var oauthProvider = (state || '').split('_')[0];
      if (!state || !oauthProvider) {
        utils.cookie.removeItem(oauthLoginProviderCookieKey);
        _console.error('no state or oauthProvider found');
        if (/^auth-api(-sandbox)?2?\.newton\.pm$/.test(currentHostname)) {
          return utils.redirectTo('https://' + currentHostname + '/error_page.html?step=2');
        }
        return callback(new Error('No login flow found'), false);
      }

      var oauthProviderToProvider = {
        facebook: 'oauth',
        google: 'oauth',
        msisdn: 'msisdn'
      };
      var oauthProviderToIndentifyType = {
        facebook: 'facebook',
        google: 'google',
        msisdn: 'msisdn-ur'
      };
      var url = httpSingleton.AUTH_END_DOMAIN + '/login/redirect/' + oauthProviderToProvider[oauthProvider] + '/finalize';

      var queryString = utils.getQueryString();

      var splitted = queryString.split('&');
      var body = {};
      for(var i=0; i < splitted.length; i++) {
          var s = splitted[i].split('=', 2);
          body[s[0]] = decodeURIComponent(s[1]);
      }

      httpSingleton.makeUnsignedRequest(url, body, function(responseErr, data) {
        var parsed, err;
        try {
          parsed = JSON.parse(data);
        } catch(e) {
          _console.debug('Response is not a json', data);
          _console.error(e);
          utils.cookie.removeItem(oauthLoginProviderCookieKey);
          if (/^auth-api(-sandbox)?2?\.newton\.pm$/.test(currentHostname)) {
            return utils.redirectTo('https://' + currentHostname + '/error_page.html?step=2');
          }
          err = new NewtonError('Unknown error');
          callback(err);
          return globalLoginFlowEventListener.emit('end', err);
        }

        // newton default loading page
        if (/^auth-api(-sandbox)?2?\.newton\.pm$/.test(currentHostname)) {
          return utils.redirectTo(parsed.url);
        }

        if (parsed.error) {
          _console.info('Error during the identify', parsed);
          utils.cookie.removeItem(oauthLoginProviderCookieKey);
          var errorString = 'Unknown identify error';
          if(assertion.isString(parsed.error)) {
            errorString = parsed.error;
          } else if(parsed.error.message && assertion.isString(parsed.error.message)) {
            errorString = parsed.error.message;
          }
          err = new NewtonError(errorString);
          if (parsed.error.code && assertion.isString(parsed.error.code)) {
            err.code = parsed.error.code;
          }
          callback(err);
          return globalLoginFlowEventListener.emit('end', err);
        }

        if (!parsed.autologin_token || !parsed.session_token) {
          _console.info('No token is returned from server', parsed);
          utils.cookie.removeItem(oauthLoginProviderCookieKey);
          err = new NewtonError('Unknown error');
          callback(err);
          return globalLoginFlowEventListener.emit('end', err);
        }

        if (!isUserTokenGood(parsed.autologin_token)) {
          _console.info('Invalid user token', parsed);
          utils.cookie.removeItem(oauthLoginProviderCookieKey);
          err = new NewtonError('Unknown error');
          callback(err);
          return globalLoginFlowEventListener.emit('end', err);
        }
        statusSingleton.setUserObjectToken(parsed.autologin_token);
        statusSingleton.setNewtonToken(parsed.session_token);

        var customData = utils.localStorage.getItem(oauthLoginDataLocalStorageKey) || undefined;
        if (customData) {
          try {
            customData = SimpleObject.fromJSONObject(customData).toJSONObject();
          } catch(e) {
            _console.warn('Custom data are not a valid SimpleObject');
            customData = undefined;
          }
        }

        stateChangeListener.onLoginStateChange();
        eventSingleton.addOtherEvent({
          event_type: 'identify',
          login_type: oauthProviderToIndentifyType[oauthProvider],
          custom_data: customData
        });

        setTimeout(function() {
          utils.cookie.removeItem(oauthLoginProviderCookieKey);

          callback();
          globalLoginFlowEventListener.emit('end');
        }, 0);
      });
    };

    var urlParams = null;
    var urlCleaned = false;

    function sniffURLParams() {
      if (urlParams) return;
      urlParams = {
        _nt: utils.getParameterByName('_nt'),
        _uot: utils.getParameterByName('_uot'),
        _bet: utils.getParameterByName('_bet'),
        _ne: utils.getParameterByName('_ne')
      }
    }

    function cleanURL(_replaceStateDelegate) {
      if (urlCleaned) return;
      if (parent.history) {
        var cleanedUrl = utils.removeDomain(utils.removeParameters(utils.getCurrentUrl(), ['_nmc', '_ne', '_nt', '_uot', '_bet']));

        // WARNING parent.history.replaceState must be properly binded or throws. Don't optimize the following call
        if(assertion.isFunction(_replaceStateDelegate)) {
          _replaceStateDelegate(parent.history.state, document.title, cleanedUrl);
        } else {
          parent.history.replaceState(parent.history.state, document.title, cleanedUrl);
        }
      }
      urlCleaned = true;
    }

    function setupInstance(_applicationIdentifier, _replaceStateDelegate) {
      applicationIdentifier = _applicationIdentifier;

      sniffURLParams();
      var newtonUserTokenFromLoginFlow = urlParams['_nt'];
      var userObjectUserTokenFromLoginFlow = urlParams['_uot'];
      var externalUserObjectToken = urlParams['_bet'];
      errorFromLoginFlow = urlParams['_ne'];
      statusSingleton.setReturningFromLoginFlow(!!((newtonUserTokenFromLoginFlow || userObjectUserTokenFromLoginFlow) || errorFromLoginFlow) || !!externalUserObjectToken);

      if (!statusSingleton.isReturningFromLoginFlow()) {
        return;
      }
      _console.info('returning from a login flow');

      statusSingleton.setReturningFromLoginFlow(false);

      cleanURL(_replaceStateDelegate);

      if (externalUserObjectToken) {
        // TODO need to uniform this case with the redirect flows and reverse fingerprint (callback deferred)
        statusSingleton.userCleanup(); // Avoid mixed users
        statusSingleton.setUserObjectToken('U_' + externalUserObjectToken);
        eventSingleton.addOtherEvent({
          event_type: 'identify',
          login_type: 'implicit'
        });

        setTimeout(function() {
          stateChangeListener.onLoginStateChange();
        }, 10);
        return;
      }

      if (errorFromLoginFlow) {
        // Error is saved for the callback
        statusSingleton.setReturningFromLoginFlow(true);
        _console.error('Error in login', errorFromLoginFlow);
        return;
      }
      if (userObjectUserTokenFromLoginFlow && !isUserTokenGood(userObjectUserTokenFromLoginFlow)) {
        errorFromLoginFlow = 'Invalid user token';
        _console.error('User token is very bad', newtonUserTokenFromLoginFlow);
        return;
      }

      statusSingleton.userCleanup(); // Avoid mixed users

      // check for transient token in _uot
      if (/^T_/.test(userObjectUserTokenFromLoginFlow)) {
        queueCallbackOnAutoLoginFlowQueue(function(err) {
          if (err) {
            _console.error(err);
          } else {
            stateChangeListener.onLoginStateChange();
          }
        }, userObjectUserTokenFromLoginFlow);
        return;
      }

      if (newtonUserTokenFromLoginFlow) {
        statusSingleton.setNewtonToken(newtonUserTokenFromLoginFlow);
      }
      if (userObjectUserTokenFromLoginFlow) {
        statusSingleton.setUserObjectToken(userObjectUserTokenFromLoginFlow);
      }

      var customData = utils.localStorage.getItem(oauthLoginDataLocalStorageKey) || undefined;
      if (customData) {
        try {
          customData = SimpleObject.fromJSONObject(customData).toJSONObject();
        } catch(e) {
          _console.warn('Custom data are not a valid SimpleObject');
          customData = undefined;
        }
        utils.localStorage.removeItem(oauthLoginDataLocalStorageKey);
      }

      var oauthProvider = utils.cookie.getItem(oauthLoginProviderCookieKey) || 'implicit';
      utils.cookie.removeItem(oauthLoginProviderCookieKey);

      eventSingleton.addOtherEvent({
        event_type: 'identify',
        login_type: oauthProvider,
        custom_data: customData
      });

      statusSingleton.setReturningFromLoginFlow(true);
    }

    /**
     * Refresh the current local user state asking to the server the real user state
     * @method syncUserState
     * @instance
     * @memberOf Newton
     * @param {SyncStateCallback} syncStateCallback - The callback invoked when the state is really refreshed
     */
    publicInterface.syncUserState = function(syncStateCallback) {
      switch(statusSingleton.getUserToken()[0]) {
        case 'C':
          return setTimeout(syncStateCallback, 0);
        case 'N':
        case 'U':
        case 'E':
          return queueCallbackOnAutoLoginFlowQueue(function(err) {
            if (!err || err.message === 'Token expired') {
              syncStateCallback();
            } else {
              syncStateCallback(err);
            }
          });
        case 'A':
          break;
        default:
          return setTimeout(function() {
            syncStateCallback(new Error('Unknown state'));
          }, 0);
      }
      // A
      if (!statusSingleton.isLoginFlowRunning()) {
        setTimeout(syncStateCallback, 0);
      } else {
        globalLoginFlowEventListener.onOnce('end', function() {
          syncStateCallback();
        });
      }
    };

    /**
     * Start the confirm identity flow. Call the callback with the result
     * @method confirmEmail
     * @memberOf Newton
     * @instance
     * @param confirmToken - The token from the email
     * @param callback - The callback invoked at the end with the status (undefined or error)
     */
    publicInterface.confirmEmail = function confirmEmail(confirmToken, callback) {
      _console.info('confirmEmail', confirmToken);
      callback = callback || function() { }

      if (!confirmToken) {
        _console.warn('Some parameters are missing', confirmToken);
        return callback(new Error('Some parameters are missing'));
      }

      httpSingleton.makeSignedRequest(
        httpSingleton.AUTH_END_DOMAIN + '/login/direct/email/confirm',
        { email_token: confirmToken },
        function(responseErr) {
          if (responseErr) {
            return callback(responseErr);
          }
          callback(null);
      });
    };

    publicInterface.isLoginFlowRunning = statusSingleton.isLoginFlowRunning;

    /**
     * Start the reset password flow. Call the callback with the result
     * @method resetPassword
     * @memberOf Newton
     * @instance
     * @param forgotToken - The token from the email
     * @param password - The new password
     * @param callback - The callback invoked at the end with the status (undefined or error)
     */
    publicInterface.resetPassword = function(forgotToken, password, callback) {
      makeCall(
        '/login/direct/email/change_pwd',
        { forgot_token: forgotToken, new_password: password },
        callback
      )
    }

    return {
      LoginBuilder: LoginBuilder,
      setupInstance: setupInstance,
      queueCallbackOnAutoLoginFlowQueue: queueCallbackOnAutoLoginFlowQueue,
      sniffURLParams: sniffURLParams,
      cleanURL: cleanURL
    };
  }
};

/* exported paymentManagerInitializator */

var paymentManagerInitializator = {
  deps: ['publicInterface', 'console', 'httpSingleton', 'statusSingleton', 'NewtonError'],
  init: function(publicInterface, _console, httpSingleton, statusSingleton, NewtonError) {
    /**
     * Returns whether the user is paying for default subscription
     * @method isUserPayingForDefault
     * @memberOf paymentManager
     * @param {Function} callback
     */
    var isUserPayingForDefault = function(callback) {
      if (statusSingleton.isDisabled()) {
        var e = new NewtonError('Disabled Newton instance');
        e.code = 'INVALIDATED_INSTANCE';
        throw e;
      }
      var userId = statusSingleton.getUserToken();
      _console.log('isUserPayingForDefault with userId', userId);

      httpSingleton.makeSignedRequest(
        httpSingleton.CLIENT_END_DOMAIN + '/payment/is_paying_for',
        {user_id: userId},
        function(err, data) {
          if(err) {
            err = new NewtonError('Invalid http response');
            _console.log('isUserPayingForDefault invalid response', data, err);
            return callback(err);
          }

          _console.log('isUserPayingForDefault returns', data);
          var body = data;
          if (!body) {
            return callback(new NewtonError('Invalid JSON'));
          }
          callback(err, body);
        }
      );
    };

    // var IsUserPayingFor = function(item, callback){
    //   var userId = statusSingleton.getUserToken();
    //   _console.log('_temporaryIsUserPayingFor with userId', userId);

    //   httpSingleton.makeSignedRequest(
    //     httpSingleton.CLIENT_END_DOMAIN + '/payment/is_paying_for',
    //     {
    //       user_id: userId,
    //       item: item
    //     },
    //     function(err, data) {
    //       if(err) {
    //         err = new NewtonError('Invalid http response');
    //         _console.log('_temporaryIsUserPayingFor invalid response', data, err);
    //         return callback(err);
    //       }

    //       _console.log('_temporaryIsUserPayingFor returns', data);
    //       var body = data;
    //       if (!body) {
    //         return callback(new NewtonError('Invalid JSON'));
    //       }
    //       callback(err, body);
    //     }
    //   );
    // }

    /**
     * Gets the offer object for the specified native item and marketplace
     * @method isUserPayingForDefault
     * @memberOf paymentManager
     * @param {String} marketplace - the marketplace where the purchase is done ['appleStore', 'googlePlay']
     * @param {String} nativeItemId - the id of the native item
     * @param {Function} callback
     */
    var getOfferFor = function(nativeItemId, marketplace, callback){
      if (statusSingleton.isDisabled()) {
        var e = new NewtonError('Disabled Newton instance');
        e.code = 'INVALIDATED_INSTANCE';
        throw e;
      }
      var allowedMarketplaces = ['appleStore', 'googlePlay'];
      if (allowedMarketplaces.indexOf(marketplace) < 0){
        return callback(new NewtonError('invalid marketplace'));
      }

      var userId = statusSingleton.getUserToken();
      httpSingleton.makeSignedRequest(
        httpSingleton.CLIENT_END_DOMAIN + '/payment/offer/get',
        {
          user_id: userId,
          native_item_id: nativeItemId,
          marketplace: marketplace
        },
        function(err, data) {
          if (err) {
            if (err.code === 'ITEM_ALREADY_PAID'){
              err.message = 'item already paid';
              _console.log('getOfferFor item already paid', err);
              return callback(err);
            }
            else {
              _console.log('getOfferFor error', err);
              return callback(err);
            }
          }

          callback(undefined, data.offer.offer_id);
        }
      );
    }

    /**
     * Adds a serialized payment
     * @method addSerializedPayment
     * @memberOf paymentManager
     * @param {String} blob - the base64 serialized representation of the payment
     * @param {Function} callback
     */
    var addSerializedPayment = function(blob, callback){
      if (statusSingleton.isDisabled()) {
        var e = new NewtonError('Disabled Newton instance');
        e.code = 'INVALIDATED_INSTANCE';
        throw e;
      }
      try {
        var serializedPaymentObject = JSON.parse(atob(blob));
      } catch (e){
        return callback(new NewtonError('cannot convert blob from base64 to a valid JSON object'));
      }
      if (typeof serializedPaymentObject !== typeof {}){
        return callback(new NewtonError('cannot convert blob from base64 to a valid JSON object'));
      }

      var userId = statusSingleton.getUserToken();
      _console.log('addSerializedPayment with userId', userId);

      httpSingleton.makeSignedRequest(
        httpSingleton.CLIENT_END_DOMAIN + '/payment/receipt/add',
        {
          user_id: userId,
          serialized_receipt: serializedPaymentObject
        },
        function(err, data) {
          if (err) {
            err = new NewtonError('Invalid http response');
            _console.log('addSerializedPayment invalid response', data, err);
            return callback(err);
          }

          callback();
        }
      );
    }

    /**
     * Returns the payment manager, this method is exposed in public interface
     * @method getPaymentManager
     * @memberOf Newton
     * @instance
     */
    var getPaymentManager = function(){
      /**
       * payment manager object
       * @namespace paymentManager
       * @name paymentManager
       * @private
       */
      return {
        addSerializedPayment: addSerializedPayment,
        isUserPayingForDefault: isUserPayingForDefault,
        getOfferFor: getOfferFor
      }
    }
    publicInterface.getPaymentManager = getPaymentManager;

    return getPaymentManager();
  }
};

/* exported platformSpecificInitializator */

var platformSpecificInitializator = {
  deps: ['window'],
  init: function(parent) {

    function _includes(str, needle) {
      return str.indexOf(needle) !== -1;
    }

    var screen = parent.screen || {};
    var userAgent = parent.navigator.userAgent || '';

    var browser = (function() {
      var vendor = parent.navigator.vendor || ''; // vendor is undefined for at least IE9
      if (parent.opera) {
        if (_includes(userAgent, "Mini")) { return "Opera Mini"; }
        return "Opera";
      } else if (/(BlackBerry|PlayBook|BB10)/i.test(userAgent)) {
        return 'BlackBerry';
      } else if (_includes(userAgent, "FBIOS")) {
        return "Facebook Mobile";
      } else if (_includes(userAgent, "Chrome")) {
        return "Chrome";
      } else if (_includes(userAgent, "CriOS")) {
        return "Chrome iOS";
      } else if (_includes(vendor, "Apple")) {
        if (_includes(userAgent, "Mobile")) { return "Mobile Safari"; }
        return "Safari";
      } else if (_includes(userAgent, "Android")) {
        return "Android Mobile";
      } else if (_includes(userAgent, "Konqueror")) {
        return "Konqueror";
      } else if (_includes(userAgent, "Firefox")) {
        return "Firefox";
      } else if (_includes(userAgent, "MSIE") || _includes(userAgent, "Trident/")) {
        return "Internet Explorer";
      } else if (_includes(userAgent, "Gecko")) {
        return "Mozilla";
      } else {
        return 'N/A';
      }
    })();

    var os = (function() {
      if (/Windows/i.test(userAgent)) {
        if (/Phone/.test(userAgent)) { return 'Windows Mobile'; }
        return 'Windows';
      } else if (/(iPhone|iPad|iPod)/.test(userAgent)) {
        return 'iOS';
      } else if (/Android/.test(userAgent)) {
        return 'Android';
      } else if (/(BlackBerry|PlayBook|BB10)/i.test(userAgent)) {
        return 'BlackBerry';
      } else if (/Mac/i.test(userAgent)) {
        return 'Mac OS X';
      } else if (/Linux/.test(userAgent)) {
        return 'Linux';
      } else {
        return 'N/A';
      }
    })();

    var device = (function() {
      if (/iPad/.test(userAgent)) {
        return 'iPad';
      } else if (/iPod/.test(userAgent)) {
        return 'iPod Touch';
      } else if (/iPhone/.test(userAgent)) {
        return 'iPhone';
      } else if (/(BlackBerry|PlayBook|BB10)/i.test(userAgent)) {
        return 'BlackBerry';
      } else if (/Windows Phone/i.test(userAgent)) {
        return 'Windows Phone';
      } else if (/Android/.test(userAgent)) {
        return 'Android';
      } else {
        return 'N/A';
      }
    })();

    var hasXHRWithCors = (function() {
      // cast to false
      return parent.XMLHttpRequest && ('withCredentials' in (new parent.XMLHttpRequest())) || false;
    })();

    var isLocalStorageSupported = false;
    try {
      parent.localStorage.setItem('newton-test', 'pippo');
      isLocalStorageSupported = parent.localStorage.getItem('newton-test') === 'pippo';
    } catch (e) { }

    var isCookieSupported = false;
    try {
      parent.document.cookie = 'newton-test=pippo;';
      isCookieSupported = /newton-test=pippo/.test(parent.document.cookie);
    } catch (e) { }

    var hiddenKey;
    if (typeof parent.document.hidden !== 'undefined') { // Opera 12.10 and Firefox 18 and later support
      hiddenKey = 'hidden';
    } else if (typeof parent.document.mozHidden !== 'undefined') {
      hiddenKey = 'mozHidden';
    } else if (typeof parent.document.msHidden !== 'undefined') {
      hiddenKey = 'msHidden';
    } else if (typeof parent.document.webkitHidden !== 'undefined') {
      hiddenKey = 'webkitHidden';
    }

    var language;
    if (typeof parent.navigator.language !== 'undefined') {
      language = parent.navigator.language;
    } else if (typeof parent.navigator.userLanguage !== 'undefined') {
      language = parent.navigator.userLanguage;
    }
    if (language) {
      language = language.split(/[-_]/)[0];
    }

    var isMetricsSupported;
    try {
        isMetricsSupported = !!parent.performance && !!parent.performance.timing;
    } catch (e){
        isMetricsSupported = false;
    }

    return {
      userAgent: userAgent,
      browser: browser,
      os: os,
      device: device,
      hasXHRWithCors: hasXHRWithCors,
      screen_width: screen.width,
      screen_height: screen.height,
      isLocalStorageSupported: isLocalStorageSupported,
      isCookieSupported: isCookieSupported,
      isHiddenSupported: hiddenKey !== undefined,
      hiddenKey: hiddenKey,
      isLanguageSupported: language !== undefined,
      language: language,
      isMetricsSupported: isMetricsSupported
    };
  }
};

if (!Date.prototype.toISOString) {
  (function() {

    function pad(number) {
      if (number < 10) {
        return '0' + number;
      }
      return number;
    }

    Date.prototype.toISOString = function() {
      return this.getUTCFullYear() +
        '-' + pad(this.getUTCMonth() + 1) +
        '-' + pad(this.getUTCDate()) +
        'T' + pad(this.getUTCHours()) +
        ':' + pad(this.getUTCMinutes()) +
        ':' + pad(this.getUTCSeconds()) +
        '.' + (this.getUTCMilliseconds() / 1000).toFixed(3).slice(2, 5) +
        'Z';
    };

  }());
}

if (!window.JSON) {
  window.JSON = {
    parse: function(sJSON) {
      /* jshint -W061 */
      return eval('(' + sJSON + ')');
    },
    stringify: (function () {
      var toString = Object.prototype.toString;
      var isArray = Array.isArray || function (a) { return toString.call(a) === '[object Array]'; };
      var escMap = {'"': '\\"', '\\': '\\\\', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t'};
      var escFunc = function (m) { return escMap[m] || '\\u' + (m.charCodeAt(0) + 0x10000).toString(16).substr(1); };
      var escRE = /[\\"\u0000-\u001F\u2028\u2029]/g;
      return function stringify(value) {
        if (value === null || value === undefined) {
          return 'null';
        } else if (typeof value === 'number') {
          return isFinite(value) ? value.toString() : 'null';
        } else if (typeof value === 'boolean') {
          return value.toString();
        } else if (typeof value === 'object') {
          if (typeof value.toJSON === 'function') {
            return stringify(value.toJSON());
          } else if (isArray(value)) {
            var res = '[';
            for (var i = 0; i < value.length; i++)
              res += (i ? ',' : '') + stringify(value[i]);
            return res + ']';
          } else if (toString.call(value) === '[object Object]') {
            var tmp = [];
            for (var k in value) {
              if (value.hasOwnProperty(k))
                tmp.push(stringify(k) + ':' + stringify(value[k]));
            }
            return '{' + tmp.join(',') + '}';
          }
        }
        return '"' + value.toString().replace(escRE, escFunc) + '"';
      };
    })()
  };
}

if (!Function.prototype.bind) {
  Function.prototype.bind = function bind(obj) {
    var slice = [].slice;
    var args = slice.call(arguments, 1),
      self = this,
      FNOP = function() {
      },
      bound = function() {
        return self.apply(this instanceof FNOP ? this : (obj || {}), args.concat(slice.call(arguments)));
      };
    FNOP.prototype = this.prototype || {}; // Firefox cries sometimes if prototype is undefined
    bound.prototype = new FNOP();
    return bound;
  };
}

// Da https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
if (!Object.keys) {
  Object.keys = (function () {
    var hasOwnProperty = Object.prototype.hasOwnProperty,
        hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
        dontEnums = [
          'toString',
          'toLocaleString',
          'valueOf',
          'hasOwnProperty',
          'isPrototypeOf',
          'propertyIsEnumerable',
          'constructor'
        ],
        dontEnumsLength = dontEnums.length;

    return function (obj) {
      if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
        throw new TypeError('Object.keys called on non-object');
      }

      var result = [], prop, i;

      for (prop in obj) {
        if (hasOwnProperty.call(obj, prop)) {
          result.push(prop);
        }
      }

      if (hasDontEnumBug) {
        for (i = 0; i < dontEnumsLength; i++) {
          if (hasOwnProperty.call(obj, dontEnums[i])) {
            result.push(dontEnums[i]);
          }
        }
      }
      return result;
    };
  }());
}

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill
if (!String.prototype.trim) {
  (function() {
    // Make sure we trim BOM and NBSP
    var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
    String.prototype.trim = function() {
      return this.replace(rtrim, '');
    };
  })();
}

// Production steps of ECMA-262, Edition 5, 15.4.4.14
// Reference: http://es5.github.io/#x15.4.4.14
if (!Array.prototype.indexOf) {
  Array.prototype.indexOf = function(searchElement, fromIndex) {

    var k;

    // 1. Let O be the result of calling ToObject passing
    //    the this value as the argument.
    /*jshint eqnull:true */
    if (this == null) {
      throw new TypeError('"this" is null or not defined');
    }

    var O = Object(this);

    // 2. Let lenValue be the result of calling the Get
    //    internal method of O with the argument "length".
    // 3. Let len be ToUint32(lenValue).
    var len = O.length >>> 0;

    // 4. If len is 0, return -1.
    if (len === 0) {
      return -1;
    }

    // 5. If argument fromIndex was passed let n be
    //    ToInteger(fromIndex); else let n be 0.
    var n = +fromIndex || 0;

    if (Math.abs(n) === Infinity) {
      n = 0;
    }

    // 6. If n >= len, return -1.
    if (n >= len) {
      return -1;
    }

    // 7. If n >= 0, then Let k be n.
    // 8. Else, n<0, Let k be len - abs(n).
    //    If k is less than 0, then let k be 0.
    k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);

    // 9. Repeat, while k < len
    while (k < len) {
      // a. Let Pk be ToString(k).
      //   This is implicit for LHS operands of the in operator
      // b. Let kPresent be the result of calling the
      //    HasProperty internal method of O with argument Pk.
      //   This step can be combined with c
      // c. If kPresent is true, then
      //    i.  Let elementK be the result of calling the Get
      //        internal method of O with the argument ToString(k).
      //   ii.  Let same be the result of applying the
      //        Strict Equality Comparison Algorithm to
      //        searchElement and elementK.
      //  iii.  If same is true, return k.
      if (k in O && O[k] === searchElement) {
        return k;
      }
      k++;
    }
    return -1;
  };
}

if (!Date.now) {
  Date.now = function now() {
    return new Date().getTime();
  };
}

/* exported pushManagerInitializator */

var pushManagerInitializator = {
  deps: ['publicInterface', 'console', 'statusSingleton', 'NewtonError', 'window'],
  init: function(publicInterface, _console, statusSingleton, NewtonError, parent) {
    /**
     * Sets the callback that must be called everytime a push notification is received
     * @method setCallback
     * @memberOf pushManager
     * @param {Function} callback
     */
    var setCallback = function(callback) {
      if (statusSingleton.isDisabled()) {
        var e = new NewtonError('Disabled Newton instance');
        e.code = 'INVALIDATED_INSTANCE';
        throw e;
      }
      if (typeof callback !== 'function'){
        throw new NewtonError('callback must be a function');
      }
      if (parent.cordova){
        if (parent.NativeNewton){
          parent.NativeNewton.setPushCallback(callback);
        } else {
          _console.log('NativeNewton not found, unable to set push callback');
        }
      } else {
        _console.log('cordova not found, unable to set push callback');
      }
    };

    /**
     * Registers the device to push notifications
     * @method registerDevice
     * @memberOf pushManager
     * @param {Function} callbackSuccess
     * @param {Function} callbackError
     */
    var registerDevice = function(callbackSuccess, callbackError){
      if (statusSingleton.isDisabled()) {
        var e = new NewtonError('Disabled Newton instance');
        e.code = 'INVALIDATED_INSTANCE';
        throw e;
      }
      if (callbackSuccess === undefined){
        callbackSuccess = function(){
          _console.log('registerDevice success');
        };
      }
      if (callbackError === undefined){
        callbackError = function(){
          _console.log('registerDevice error');
        };
      }
      if (parent.cordova){
        if (parent.NativeNewton){
          parent.NativeNewton.registerDevice(callbackSuccess, callbackError);
        } else {
          _console.log('NativeNewton not found, unable to register device');
        }
      } else {
        _console.log('cordova not found, unable to register device');
      }
    }

    /**
     * Returns the push manager, this method is exposed in public interface
     * @method getPushManager
     * @memberOf Newton
     * @instance
     */
    var getPushManager = function(){

      /**
       * push manager object
       * @namespace pushManager
       * @name pushManager
       * @private
       */
      return {
        setCallback: setCallback,
        registerDevice: registerDevice,
        setupInstance: function(){
          _console.log("setup pushManager")
        }
      }
    }
    publicInterface.getPushManager = getPushManager;

    return getPushManager();
  }
};

/* exported simpleObjectInitializator */

var simpleObjectInitializator = {
  deps: ['assertion', 'NewtonError'],
  init: function(assertion, NewtonError) {
    /**
     * SimpleObject
     * @class
     * @memberOf Newton
     */
    function SimpleObject() {
      this.prop = {};
    }
    /**
     * Never use directly. Use the alias instead
     * @method _set
     * @memberOf Newton.SimpleObject
     * @instance
     * @private
     *
     * @param {String} key - The key
     * @param {String|null|Number|Boolean} value - The value
     */
    SimpleObject.prototype._set = function (key, value) {
      if (assertion.isUndefined(value)) {
        throw new NewtonError('cannot set ' + key + ' to undefined');
      }
      if (!assertion.isNull(value) &&
          !assertion.isNumber(value) &&
          !assertion.isString(value) &&
          !assertion.isBoolean(value))
      {
        var e = new NewtonError('cannot set ' + key + ' to ' + value + ' ' + value.constructor.name);
        throw e;
      }
      this.prop[key] = value;
    };
    /**
     * Set a string value
     * @method setString
     * @memberOf Newton.SimpleObject
     * @instance
     *
     * @param {String} key - The key
     * @param {String} value - The value
     */
    SimpleObject.prototype.setString = SimpleObject.prototype._set;
    /**
     * Set an integer value
     * @method setInt
     * @memberOf Newton.SimpleObject
     * @instance
     *
     * @param {String} key - The key
     * @param {Integer} value - The value
     */
    SimpleObject.prototype.setInt = SimpleObject.prototype._set;
    /**
     * Set a boolan value
     * @method setBool
     * @memberOf Newton.SimpleObject
     * @instance
     *
     * @param {String} key - The key
     * @param {Integer} value - The value
     */
    SimpleObject.prototype.setBool = SimpleObject.prototype._set;
    /**
     * Set a float value
     * @method setFloat
     * @memberOf Newton.SimpleObject
     * @instance
     *
     * @param {String} key - The key
     * @param {Integer} value - The value
     */
    SimpleObject.prototype.setFloat = SimpleObject.prototype._set;
    /**
     * Set the value to null
     * @method setNull
     * @memberOf Newton.SimpleObject
     * @instance
     *
     * @param {String} key - The key
     * @param {Integer} value - The value
     */
    SimpleObject.prototype.setNull = function(key) {
      this._set(key, null);
    };
    /**
     * Return an object with all set values
     * @method toJSONObject
     * @memberOf Newton.SimpleObject
     * @instance
     *
     * @return {Object} The object stored in this SimpleObject
     */
    SimpleObject.prototype.toJSONObject = function() {
      var a = {};
      for (var k in this.prop) {
        a[k] = this.prop[k];
      }
      return a;
    };
    /**
     * Return an json representation
     * @method toJSONString
     * @memberOf Newton.SimpleObject
     * @instance
     *
     * @return {String}
     *
     */
    SimpleObject.prototype.toJSONString = function() {
      return JSON.stringify(this.prop);
    };
    /**
     * Create a SimpleObject instance from a plain object
     * @method fromJSONObject
     * @memberOf Newton.SimpleObject
     * @static
     * @throws Will throw if the conversion is not possible
     *
     * @param {Object} obj - The object you would convert
     * @return {SimpleObject}
     */
    SimpleObject.fromJSONObject = function(obj) {
      if (!assertion.isObject(obj)) {
        var e = new NewtonError('obj is not a simple object compatible');
        e.obj = obj;
        throw e;
      }

      var so = new SimpleObject();
      for (var key in obj) {
        so._set(key, obj[key]);
      }
      return so;
    };

    /**
     * Create a SimpleObject instance from a string
     * @method fromJSONString
     * @memberOf Newton.SimpleObject
     * @static
     * @throws Will throw if the conversion is not possible
     *
     * @param {String} str - The object you would convert
     * @return {SimpleObject}
     */
    SimpleObject.fromJSONString = function(str) {
      return SimpleObject.fromJSONObject(JSON.parse(str));
    };

    SimpleObject._assertIsInstanceOfOrNull = function(customData) {
      if (customData !== null && customData !== undefined && customData.constructor !== SimpleObject) {
        throw new NewtonError('Custom data should be an instance of SimpleObject');
      }
    };

    return SimpleObject;
  }
};

/* exported statusSingletonInitializator */

var statusSingletonInitializator = {
  deps: ['console', 'utils', 'publicInterface', 'NewtonError', 'window', 'httpSingleton'],
  init: function(_console, utils, publicInterface, NewtonError, parent, httpSingleton) {
    var sessionCookieKey = 'newton-session';
    var deviceCookieKey = 'newton-device-id';
    var deviceIdLength = 20;
    var newtonTokenCookieKey = 'newton-newton-login';
    var newtonTokenExpireInMinutes = 30;
    var userObjectTokenCookieKey = 'newton-user-login';
    var customTokenCookieKey = 'newton-custom-login';
    var newtonTokenRegExp = /^N_/;
    var anonymousTokenRegExp = /^A_/;
    var customTokenRegExp = /^C_/;
    var userObjectTokenRegExp = /^U_/;
    var externalTokenRegExp = /^E_/;
    var expirationMinutesForInactivity = 30;
    var oldSessionId;
    var disabled = false;

    /*
     * deviceId can be created
     */
    var deviceId = utils.localStorage.getItem(deviceCookieKey) || utils.cookie.getItem(deviceCookieKey) || null;
    if (!deviceId) {
      _console.debug('No DeviceId found');
      deviceId = utils.getRandomString(deviceIdLength);
      var expirationDate = new Date();
      expirationDate.setFullYear(2999);
      utils.cookie.setItem(deviceCookieKey, deviceId, expirationDate, '/');
      utils.localStorage.setItem(deviceCookieKey, deviceId, expirationDate, '/');
    }
    _console.debug('DeviceId loaded', deviceId);

    function getDeviceId() {
      return deviceId;
    }

    /**
    * Must be called after deviceready
    */
    var attachMasterSession = function(sessionId, callbackSuccess, callbackError){
      if (callbackSuccess === undefined){
        callbackSuccess = function(){
          _console.log('attachMasterSession success');
        };
      }
      if (callbackError === undefined){
        callbackError = function(){
          _console.log('attachMasterSession error');
        };
      }
      if (parent.cordova){
        if (parent.NativeNewton){
          parent.NativeNewton.attachMasterSession(sessionId, getUserToken(), callbackSuccess, callbackError);
        } else {
          _console.log('NativeNewton not found, unable to attach master session');
        }
      } else {
        _console.log('cordova not found, unable to attach master session');
      }
    }

    /*
     * Load saved customToken if any
     */
    var customToken = utils.cookie.getItem(customTokenCookieKey) || null;

    function setSessionId(sessionId) {
      _console.debug('set session id', sessionId);
      var expireDate = new Date();
      expireDate.setUTCMinutes(expireDate.getUTCMinutes() + expirationMinutesForInactivity);
      var savedSession = utils.cookie.getItem(sessionCookieKey);
      if (savedSession) {
        oldSessionId = savedSession;
      }
      utils.cookie.setItem(sessionCookieKey, sessionId, expireDate, '/');
    }
    function getSessionId() {
      var sessionId = utils.cookie.getItem(sessionCookieKey);
      if (sessionId) {
        setSessionId(sessionId);
        attachMasterSession(sessionId);
      }
      return sessionId;
    }

    function getOldSessionId() {
      return oldSessionId;
    }

    function getAnonymousUserToken() {
      return 'A_' + deviceId;
    }
    function getCustomUserToken() {
      return customToken;
    }
    function setCustomUserToken(_customToken, allowCustomLoginSession) {
      _console.debug('set custom user token', _customToken);
      if (!customTokenRegExp.test(_customToken)) {
        throw new NewtonError('Invalid custom token');
      }
      customToken = _customToken;
      if (allowCustomLoginSession) {
        utils.cookie.setItem(customTokenCookieKey, customToken, null, '/');
      }
    }
    function getNewtonToken() {
      return utils.cookie.getItem(newtonTokenCookieKey);
    }
    function setNewtonToken(newtonToken) {
      _console.debug('set newton token', newtonToken);
      if (!newtonTokenRegExp.test(newtonToken)) {
        throw new NewtonError('Invalid newton token');
      }
      var expirationDate = new Date();
      expirationDate.setMinutes(expirationDate.getMinutes() + newtonTokenExpireInMinutes);
      utils.cookie.setItem(newtonTokenCookieKey, newtonToken, expirationDate, '/');
    }

    function generateTransientToken(callback) {
      var currentUserToken = getUserToken();
      var isAnonymousToken = anonymousTokenRegExp.test(currentUserToken);

      if (isAnonymousToken) {
        return callback(null, currentUserToken);
      } else {
        var generateTransientTokenUrl = httpSingleton.AUTH_END_DOMAIN + '/handling/direct/generatetransienttoken';
        var content = {
          user_token: currentUserToken
        }
        return httpSingleton.makeSignedRequest(generateTransientTokenUrl, content, function(err, data) {
          if (err || !data.transient) {
            callback(err)
          } else {
            callback(null, data.transient)
          }
        });
      }
    }

    function getUserObjectToken() {
      return utils.cookie.getItem(userObjectTokenCookieKey);
    }
    function setUserObjectToken(userObjectToken) {
      _console.debug('set user object token', userObjectToken);
      if (externalTokenRegExp.test(userObjectToken)) {
        utils.cookie.setItem(userObjectTokenCookieKey, userObjectToken, null, '/');
        return;
      }
      if (!userObjectTokenRegExp.test(userObjectToken)) {
        throw new NewtonError('Invalid user object token');
      }
      var year = parseInt(userObjectToken.substr(2, 4), 10);
      var month = parseInt(userObjectToken.substr(6, 2), 10) - 1;
      var day = parseInt(userObjectToken.substr(8, 2), 10);

      var hours = parseInt(userObjectToken.substr(10, 2), 10);
      var minutes = parseInt(userObjectToken.substr(12, 2), 10);
      var seconds = parseInt(userObjectToken.substr(14, 2), 10);
      if (isNaN(hours) || isNaN(minutes) || isNaN(seconds)) {
        hours = 23;
        minutes = 59;
        seconds = 59;
      } else {
        // hours = hours - 1;
      }

      var expirationDate = new Date(Date.UTC(year, month, day, hours, minutes, seconds));
      _console.debug('Using', expirationDate, 'as expiration date');
      utils.cookie.setItem(userObjectTokenCookieKey, userObjectToken, expirationDate, '/');
    }
    function getUserToken() {
      var customToken = getCustomUserToken();
      if (customToken) {
        _console.debug('custom token', customToken);
        return customToken;
      }
      var newtonToken = getNewtonToken();
      if (newtonToken) {
        _console.debug('newton token', newtonToken);
        return newtonToken;
      }
      var userObjectToken = getUserObjectToken();
      if (userObjectToken) {
        _console.debug('user object token', userObjectToken);
        return userObjectToken;
      }
      return getAnonymousUserToken();
    }

    function userCleanup() {
      _console.debug('User credentials cleanup');
      utils.cookie.removeItem(customTokenCookieKey);
      utils.cookie.removeItem(newtonTokenCookieKey);
      utils.cookie.removeItem(userObjectTokenCookieKey);

      customToken = null;
    }

    function isTheTokenType(token, allowedTokens) {
      if (!token) return false;
      var type = token.charAt(0);
      return allowedTokens.indexOf(type) > -1;
    }

    var returningFromLoginFlow = false;
    var customLoginFlowRunning = false;
    var externalLoginFlowRunning = false;
    var directLoginFlowRunning = false;
    var autoLoginFlowRunning = false;
    var forgotFlowRunning = false;

    var setDirectLoginFlowRunning = function(flag){
      directLoginFlowRunning = flag;
    }

    var setCustomLoginFlowRunning = function(flag){
      customLoginFlowRunning = flag;
    }

    var setExternalLoginFlowRunning = function(flag){
      externalLoginFlowRunning = flag;
    }

    var setAutoLoginFlowRunning = function(flag){
      autoLoginFlowRunning = flag;
    }

    var setForgotFlowRunning = function(flag){
      forgotFlowRunning = flag;
    }

    var setReturningFromLoginFlow = function(flag){
      returningFromLoginFlow = flag;
    }

    var isReturningFromLoginFlow = function(){
      return returningFromLoginFlow;
    }

    var isCustomLoginFlowRunning = function(){
      return customLoginFlowRunning;
    }

    var isExternalLoginFlowRunning = function(){
      return externalLoginFlowRunning;
    }

    var isDirectLoginFlowRunning = function(){
      return directLoginFlowRunning;
    }

    var isAutoLoginFlowRunning = function(){
      return autoLoginFlowRunning;
    }

    var isForgotFlowRunning = function(){
      return forgotFlowRunning;
    }

    /**
      * Callback for transient token generation.
      * @callback TransientTokenCallback
      * @param {Error} error - when something goes wrong
      * @param {String} transientToken - a one shot transient token to pass around
      */
    /**
     * Return a transient user token.
     * @method getTransientToken
     * @memberOf Newton
     * @instance
     * @param {TransientTokenCallback} callback - invoked asynchronously
     */
    publicInterface.getTransientToken = generateTransientToken;

    /**
     * Return the user token.
     * @method getUserToken
     * @memberOf Newton
     * @instance
     *
     * @return {String} - The user token
     */
    publicInterface.getUserToken = getUserToken;

    /**
     * Return the current session id.
     * @method getSessionId
     * @memberOf Newton
     * @instance
     *
     * @return {String} - The current session id
     */
    publicInterface.getSessionId = getSessionId;

    /**
     * Return true is a login flow is running
     * @method isLoginFlowRunning
     * @memberOf Newton
     * @instance
     *
     * @return {Boolean} - true if a login flow is running
     */
    var isLoginFlowRunning = function() {
      var oauthLoginProviderCookieKey = 'newton-login-oauth-provider';

      return customLoginFlowRunning ||
        externalLoginFlowRunning ||
        !!utils.cookie.getItem(oauthLoginProviderCookieKey) ||
        returningFromLoginFlow ||
        directLoginFlowRunning ||
        forgotFlowRunning ||
        false;
    };

    return {
      setSessionId: setSessionId,
      getSessionId: getSessionId,
      getOldSessionId: getOldSessionId,
      getDeviceId: getDeviceId,
      getAnonymousUserToken: getAnonymousUserToken,
      getCustomUserToken: getCustomUserToken,
      setCustomUserToken: setCustomUserToken,
      getNewtonToken: getNewtonToken,
      setNewtonToken: setNewtonToken,
      getUserObjectToken: getUserObjectToken,
      setUserObjectToken: setUserObjectToken,
      getUserToken: getUserToken,
      userCleanup: userCleanup,
      isTheTokenType: isTheTokenType,
      generateTransientToken: generateTransientToken,

      attachMasterSession: attachMasterSession,

      setDirectLoginFlowRunning: setDirectLoginFlowRunning,
      setCustomLoginFlowRunning: setCustomLoginFlowRunning,
      setExternalLoginFlowRunning: setExternalLoginFlowRunning,
      setAutoLoginFlowRunning: setAutoLoginFlowRunning,
      setForgotFlowRunning: setForgotFlowRunning,
      setReturningFromLoginFlow: setReturningFromLoginFlow,

      isDirectLoginFlowRunning: isDirectLoginFlowRunning,
      isCustomLoginFlowRunning: isCustomLoginFlowRunning,
      isExternalLoginFlowRunning: isExternalLoginFlowRunning,
      isAutoLoginFlowRunning: isAutoLoginFlowRunning,
      isForgotFlowRunning: isForgotFlowRunning,
      isReturningFromLoginFlow: isReturningFromLoginFlow,

      isLoginFlowRunning: isLoginFlowRunning,

      disable: function() {
        disabled = true;
      },
      isDisabled: function() {
        return disabled;
      }
    };
  }
};

/* exported userModuleInitializator */

var userModuleInitializator = {
  deps: ['console', 'publicInterface', 'statusSingleton', 'loginBuilder', 'httpSingleton', 'NewtonError'],
  init: function(_console, publicInterface, statusSingleton, LoginBuilder, httpSingleton, NewtonError) {

    function _getUserMetaInfo(newtonToken, callback) {
      httpSingleton.makeSignedRequest(
        httpSingleton.AUTH_END_DOMAIN + '/handling/direct/userinfo',
        {user_id: newtonToken},
        function(responseErr, data) {
          if(responseErr && responseErr.statusCode > 299) {
            return callback(new NewtonError('Invalid http response'));
          }
          callback(null, data);
        }
      );
    }

    /**
     * Retreive the user metainfo
     * @method getUserMetaInfo
     * @memberOf Newton
     * @instance
     * @param {MetaInfoCallback} callback
     *
     */
    publicInterface.getUserMetaInfo = function(callback) {
      var userToken = statusSingleton.getUserToken();
      var type = userToken.charAt(0);
      switch(type) {
        case 'A':
          return callback(new NewtonError('User is unlogged'));
        case 'C':
          return callback(null, {login: {created: false, flow: 'custom'}});
        case 'U':
        case 'E':
          LoginBuilder.queueCallbackOnAutoLoginFlowQueue(function(err) {
            if (err) return callback(err);
            userToken = statusSingleton.getUserToken();
            _getUserMetaInfo(userToken, callback);
          });
          break;
        case 'N':
            _getUserMetaInfo(userToken, callback);
          break;
        default:
          callback(new NewtonError('Unknown user type'));
      }
    };

    return {
      setupInstance: function() {
      }
    };
  }
};

/* exported utilsInitializator */

var utilsInitializator = {
  deps: ['window', 'platformSpecific'],
  init: function(parent, platformSpecific) {
    // https://gist.github.com/banksean/300494
    // MersenneTwister
    var MersenneTwister = function(seed) {
      if (!seed) {
        seed = new Date().getTime();
      }
      /* Period parameters */
      this.N = 624;
      this.M = 397;
      this.MATRIX_A = 0x9908b0df;   /* constant vector a */
      this.UPPER_MASK = 0x80000000; /* most significant w-r bits */
      this.LOWER_MASK = 0x7fffffff; /* least significant r bits */

      this.mt = new Array(this.N); /* the array for the state vector */
      this.mti=this.N+1; /* mti==N+1 means mt[N] is not initialized */

      this.init_genrand(seed);
    };

    MersenneTwister.prototype.init_genrand = function(s) {
      this.mt[0] = s >>> 0;
      for (this.mti=1; this.mti<this.N; this.mti++) {
          var a = this.mt[this.mti-1] ^ (this.mt[this.mti-1] >>> 30);
       this.mt[this.mti] = (((((a & 0xffff0000) >>> 16) * 1812433253) << 16) + (a & 0x0000ffff) * 1812433253) + this.mti;
          /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
          /* In the previous versions, MSBs of the seed affect   */
          /* only MSBs of the array mt[].                        */
          /* 2002/01/09 modified by Makoto Matsumoto             */
          this.mt[this.mti] >>>= 0;
          /* for >32 bit machines */
      }
    };

    MersenneTwister.prototype.genrand_int32 = function() {
      var y;
      var mag01 = new Array(0x0, this.MATRIX_A);
      /* mag01[x] = x * MATRIX_A  for x=0,1 */

      if (this.mti >= this.N) { /* generate N words at one time */
        var kk;

        if (this.mti == this.N+1)   /* if init_genrand() has not been called, */
          this.init_genrand(5489); /* a default initial seed is used */

        for (kk=0;kk<this.N-this.M;kk++) {
          y = (this.mt[kk]&this.UPPER_MASK)|(this.mt[kk+1]&this.LOWER_MASK);
          this.mt[kk] = this.mt[kk+this.M] ^ (y >>> 1) ^ mag01[y & 0x1];
        }
        for (;kk<this.N-1;kk++) {
          y = (this.mt[kk]&this.UPPER_MASK)|(this.mt[kk+1]&this.LOWER_MASK);
          this.mt[kk] = this.mt[kk+(this.M-this.N)] ^ (y >>> 1) ^ mag01[y & 0x1];
        }
        y = (this.mt[this.N-1]&this.UPPER_MASK)|(this.mt[0]&this.LOWER_MASK);
        this.mt[this.N-1] = this.mt[this.M-1] ^ (y >>> 1) ^ mag01[y & 0x1];

        this.mti = 0;
      }

      y = this.mt[this.mti++];

      /* Tempering */
      y ^= (y >>> 11);
      y ^= (y << 7) & 0x9d2c5680;
      y ^= (y << 15) & 0xefc60000;
      y ^= (y >>> 18);

      return y >>> 0;
    };

    MersenneTwister.prototype.random = function() {
      return this.genrand_int32()*(1.0/4294967296.0); // divided by 2^32
    };

    /* eslint-disable */
    /*
    CryptoJS v3.1.2
    code.google.com/p/crypto-js
    (c) 2009-2013 by Jeff Mott. All rights reserved.
    code.google.com/p/crypto-js/wiki/License
    */
    var CryptoJS = CryptoJS || function(g, l) {
        var e = {},
            d = e.lib = {},
            m = function() {},
            k = d.Base = {
                extend: function(a) {
                    m.prototype = this;
                    var c = new m;
                    a && c.mixIn(a);
                    c.hasOwnProperty("init") || (c.init = function() {
                        c.$super.init.apply(this, arguments)
                    });
                    c.init.prototype = c;
                    c.$super = this;
                    return c
                },
                create: function() {
                    var a = this.extend();
                    a.init.apply(a, arguments);
                    return a
                },
                init: function() {},
                mixIn: function(a) {
                    for (var c in a) a.hasOwnProperty(c) && (this[c] = a[c]);
                    a.hasOwnProperty("toString") && (this.toString = a.toString)
                },
                clone: function() {
                    return this.init.prototype.extend(this)
                }
            },
            p = d.WordArray = k.extend({
                init: function(a, c) {
                    a = this.words = a || [];
                    this.sigBytes = c != l ? c : 4 * a.length
                },
                toString: function(a) {
                    return (a || n).stringify(this)
                },
                concat: function(a) {
                    var c = this.words,
                        q = a.words,
                        f = this.sigBytes;
                    a = a.sigBytes;
                    this.clamp();
                    if (f % 4)
                        for (var b = 0; b < a; b++) c[f + b >>> 2] |= (q[b >>> 2] >>> 24 - 8 * (b % 4) & 255) << 24 - 8 * ((f + b) % 4);
                    else if (65535 < q.length)
                        for (b = 0; b < a; b += 4) c[f + b >>> 2] = q[b >>> 2];
                    else c.push.apply(c, q);
                    this.sigBytes += a;
                    return this
                },
                clamp: function() {
                    var a = this.words,
                        c = this.sigBytes;
                    a[c >>> 2] &= 4294967295 <<
                        32 - 8 * (c % 4);
                    a.length = g.ceil(c / 4)
                },
                clone: function() {
                    var a = k.clone.call(this);
                    a.words = this.words.slice(0);
                    return a
                },
                random: function(a) {
                    for (var c = [], b = 0; b < a; b += 4) c.push(4294967296 * g.random() | 0);
                    return new p.init(c, a)
                }
            }),
            b = e.enc = {},
            n = b.Hex = {
                stringify: function(a) {
                    var c = a.words;
                    a = a.sigBytes;
                    for (var b = [], f = 0; f < a; f++) {
                        var d = c[f >>> 2] >>> 24 - 8 * (f % 4) & 255;
                        b.push((d >>> 4).toString(16));
                        b.push((d & 15).toString(16))
                    }
                    return b.join("")
                },
                parse: function(a) {
                    for (var c = a.length, b = [], f = 0; f < c; f += 2) b[f >>> 3] |= parseInt(a.substr(f,
                        2), 16) << 24 - 4 * (f % 8);
                    return new p.init(b, c / 2)
                }
            },
            j = b.Latin1 = {
                stringify: function(a) {
                    var c = a.words;
                    a = a.sigBytes;
                    for (var b = [], f = 0; f < a; f++) b.push(String.fromCharCode(c[f >>> 2] >>> 24 - 8 * (f % 4) & 255));
                    return b.join("")
                },
                parse: function(a) {
                    for (var c = a.length, b = [], f = 0; f < c; f++) b[f >>> 2] |= (a.charCodeAt(f) & 255) << 24 - 8 * (f % 4);
                    return new p.init(b, c)
                }
            },
            h = b.Utf8 = {
                stringify: function(a) {
                    try {
                        return decodeURIComponent(escape(j.stringify(a)))
                    } catch (c) {
                        throw Error("Malformed UTF-8 data");
                    }
                },
                parse: function(a) {
                    return j.parse(unescape(encodeURIComponent(a)))
                }
            },
            r = d.BufferedBlockAlgorithm = k.extend({
                reset: function() {
                    this._data = new p.init;
                    this._nDataBytes = 0
                },
                _append: function(a) {
                    "string" == typeof a && (a = h.parse(a));
                    this._data.concat(a);
                    this._nDataBytes += a.sigBytes
                },
                _process: function(a) {
                    var c = this._data,
                        b = c.words,
                        f = c.sigBytes,
                        d = this.blockSize,
                        e = f / (4 * d),
                        e = a ? g.ceil(e) : g.max((e | 0) - this._minBufferSize, 0);
                    a = e * d;
                    f = g.min(4 * a, f);
                    if (a) {
                        for (var k = 0; k < a; k += d) this._doProcessBlock(b, k);
                        k = b.splice(0, a);
                        c.sigBytes -= f
                    }
                    return new p.init(k, f)
                },
                clone: function() {
                    var a = k.clone.call(this);
                    a._data = this._data.clone();
                    return a
                },
                _minBufferSize: 0
            });
        d.Hasher = r.extend({
            cfg: k.extend(),
            init: function(a) {
                this.cfg = this.cfg.extend(a);
                this.reset()
            },
            reset: function() {
                r.reset.call(this);
                this._doReset()
            },
            update: function(a) {
                this._append(a);
                this._process();
                return this
            },
            finalize: function(a) {
                a && this._append(a);
                return this._doFinalize()
            },
            blockSize: 16,
            _createHelper: function(a) {
                return function(b, d) {
                    return (new a.init(d)).finalize(b)
                }
            },
            _createHmacHelper: function(a) {
                return function(b, d) {
                    return (new s.HMAC.init(a,
                        d)).finalize(b)
                }
            }
        });
        var s = e.algo = {};
        return e
    }(Math);
    (function() {
        var g = CryptoJS,
            l = g.lib,
            e = l.WordArray,
            d = l.Hasher,
            m = [],
            l = g.algo.SHA1 = d.extend({
                _doReset: function() {
                    this._hash = new e.init([1732584193, 4023233417, 2562383102, 271733878, 3285377520])
                },
                _doProcessBlock: function(d, e) {
                    for (var b = this._hash.words, n = b[0], j = b[1], h = b[2], g = b[3], l = b[4], a = 0; 80 > a; a++) {
                        if (16 > a) m[a] = d[e + a] | 0;
                        else {
                            var c = m[a - 3] ^ m[a - 8] ^ m[a - 14] ^ m[a - 16];
                            m[a] = c << 1 | c >>> 31
                        }
                        c = (n << 5 | n >>> 27) + l + m[a];
                        c = 20 > a ? c + ((j & h | ~j & g) + 1518500249) : 40 > a ? c + ((j ^ h ^ g) + 1859775393) : 60 > a ? c + ((j & h | j & g | h & g) - 1894007588) : c + ((j ^ h ^
                            g) - 899497514);
                        l = g;
                        g = h;
                        h = j << 30 | j >>> 2;
                        j = n;
                        n = c
                    }
                    b[0] = b[0] + n | 0;
                    b[1] = b[1] + j | 0;
                    b[2] = b[2] + h | 0;
                    b[3] = b[3] + g | 0;
                    b[4] = b[4] + l | 0
                },
                _doFinalize: function() {
                    var d = this._data,
                        e = d.words,
                        b = 8 * this._nDataBytes,
                        g = 8 * d.sigBytes;
                    e[g >>> 5] |= 128 << 24 - g % 32;
                    e[(g + 64 >>> 9 << 4) + 14] = Math.floor(b / 4294967296);
                    e[(g + 64 >>> 9 << 4) + 15] = b;
                    d.sigBytes = 4 * e.length;
                    this._process();
                    return this._hash
                },
                clone: function() {
                    var e = d.clone.call(this);
                    e._hash = this._hash.clone();
                    return e
                }
            });
        g.SHA1 = d._createHelper(l);
        g.HmacSHA1 = d._createHmacHelper(l)
    })();
    (function() {
        var g = CryptoJS,
            l = g.enc.Utf8;
        g.algo.HMAC = g.lib.Base.extend({
            init: function(e, d) {
                e = this._hasher = new e.init;
                "string" == typeof d && (d = l.parse(d));
                var g = e.blockSize,
                    k = 4 * g;
                d.sigBytes > k && (d = e.finalize(d));
                d.clamp();
                for (var p = this._oKey = d.clone(), b = this._iKey = d.clone(), n = p.words, j = b.words, h = 0; h < g; h++) n[h] ^= 1549556828, j[h] ^= 909522486;
                p.sigBytes = b.sigBytes = k;
                this.reset()
            },
            reset: function() {
                var e = this._hasher;
                e.reset();
                e.update(this._iKey)
            },
            update: function(e) {
                this._hasher.update(e);
                return this
            },
            finalize: function(e) {
                var d =
                    this._hasher;
                e = d.finalize(e);
                d.reset();
                return d.finalize(this._oKey.clone().concat(e))
            }
        })
    })();
    /* eslint-enable */


    var startFrom = 33;
    var endAt = 126;
    var i = [];
    do {
      i.push(startFrom);
    } while(startFrom++ < endAt);
    var chars = String.fromCharCode.apply(String, i);


    function toHex(str) {
      if ("string" !== typeof str) return "";

      var ii = 0;
      var res = "";
      while (ii < str.length) {
        res += str.charCodeAt(ii).toString(16);
        ii++
      }
      return res;
    }

    function replacer(match) {
      return match.charAt(0);
    }


    var ret = {
      getHmacSha1: function(message, secret) {
        return CryptoJS.HmacSHA1(message, secret).toString();
      },
      getRandomString: function(l, alphabet) {
        l = parseInt(l, 10);
        if (!isFinite(l)) {
          return '';
        }
        alphabet = alphabet || chars;
        var m = new MersenneTwister();

        var r = '';
        while(l--) {
          r += alphabet.charAt(Math.floor(m.random() * alphabet.length));
        }

        return r;
      },
      getRandomString2: function() {
        var random = ret.getRandomString(20);
        return toHex(random) + CryptoJS.HmacSHA1(platformSpecific.userAgent, random).toString();
      },
      cookie: {
        setItem: function(key, value, end) {
          if (!key || /^(?:expires|max\-age|path|domain|secure)$/i.test(key)) { return false; }
          var sExpires = "";
          if (end) {
            switch (end.constructor) {
              case Date:
                sExpires = "; expires=" + end.toUTCString();
                break;
            }
          }
          document.cookie = encodeURIComponent(key) + "=" + encodeURIComponent(JSON.stringify(value)) + sExpires + "; path=/;";
          return true;
        },
        removeItem: function (sKey) {
          document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";
          return true;
        },
        getItem: function (key) {
          if (!key) { return null; }
          // key = encodeURIComponent(key);
          var cookies = document.cookie.split(";");
          for (var i = 0; i < cookies.length; i++) {
            var splitted = cookies[i].split("=");
            var name = (splitted.shift() || '').trim();
            if (name === key) {
              var value = splitted.shift();
              try {
                return JSON.parse(decodeURIComponent(value));
              } catch(e) { }
            }
          }
          return null;
        }
      },
      localStorage: {
        getItem: function(k) {
          try {
            return JSON.parse(parent.localStorage.getItem(k));
          } catch(e) {
            return null;
          }
        },
        setItem: function(k, v) { return parent.localStorage.setItem(k, JSON.stringify(v)); },
        removeItem: function(k) { return parent.localStorage.removeItem(k); }
      },
      createRamStorage: function() {
        var __storage = {};
        return {
          setItem: function(key, value) {
            __storage[key] = value;
          },
          removeItem: function (key) {
            delete __storage[key];
          },
          getItem: function (key) {
            return __storage[key] || null;
          }
        };
      },
      getCurrentTimestamp: function() {
        return Math.ceil((new Date()).getTime() / 1000);
      },
      redirectTo: function(url) {
        parent.location = url;
      },
      getCurrentUrl: function() {
        return parent.location + '';
      },
      getHostname: function() {
        if((parent.location.protocol === 'file:' || parent.location.protocol === 'cdvfile:') && parent.location.__hostname__) {
          //cordova local file support
          return parent.location.__hostname__ + '';
        }
        return parent.location.hostname + '';
      },
      getQueryString: function() {
        return parent.location.search.split('?')[1] + '';
      },
      removeParameters: function(url, params) {
        while(params.length) {
          var parameterToRemove = params.pop();
          url = url.replace(new RegExp('[?&]' + encodeURIComponent(parameterToRemove) + '=[^&#]*', 'g'), replacer);
        }
        return url;
      },
      getParameterByName: function (name) {
        var match = new RegExp('[?&]' + encodeURIComponent(name) + '=([^&]*)').exec(parent.location.search);
        return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
      },
      removeDomain: function(url) {
        // Second replace is to sanitize malformed url with multiple slashes after the domain
        return url.replace(/https?:\/\/[^\/]*/, '', 'i').replace(/^\/+/, '/', 'i') || '/';
      }
    };

    if (!platformSpecific.isLocalStorageSupported) {
      ret.localStorage = ret.createRamStorage();
    }
    if (!platformSpecific.isCookieSupported) {
      ret.cookie = ret.createRamStorage();
    }

    return ret;
  }
};

/* global assertionInitializator, platformSpecificInitializator, utilsInitializator, errorInitializator, consoleInitializator, exceptionInitializator, simpleObjectInitializator, httpSingletonInitializator, statusSingletonInitializator, eventModuleInitializator, EventListenerInitializator, loginBuilderInitializator, userModuleInitializator, initInitializator, paymentManagerInitializator, autorevision, identityInitializator, identityManagerInizializator, pushManagerInitializator */
/* exported start */

function start(win) {
  var moduleInitializators = {
    NewtonError: errorInitializator,
    assertion: assertionInitializator,
    platformSpecific: platformSpecificInitializator,
    utils: utilsInitializator,
    console: consoleInitializator,
    exception: exceptionInitializator,
    simpleObject: simpleObjectInitializator,
    httpSingleton: httpSingletonInitializator,
    statusSingleton: statusSingletonInitializator,
    eventSingleton: eventModuleInitializator,
    EventListener: EventListenerInitializator,
    identityModule: typeof identityInitializator === 'undefined' ? false : identityInitializator,
    loginBuilder: typeof loginBuilderInitializator === 'undefined' ? false : loginBuilderInitializator,
    identityManager: typeof identityManagerInizializator === 'undefined' ? false : identityManagerInizializator,
    userModule: typeof userModuleInitializator === 'undefined' ? false : userModuleInitializator,
    paymentManager: typeof paymentManagerInitializator === 'undefined' ? false : paymentManagerInitializator,
    pushManager: typeof pushManagerInitializator === 'undefined' ? false : pushManagerInitializator,
    init: initInitializator
  };

  function Newton() { }

  var dependencies = {
    window: win,
    staticInterface: {},
    /**
     * Newton
     *
     * @class Newton
     * @name Newton
     * @constructor
     * @private
     */
    publicInterface: new Newton(),
    autorevision: autorevision
  };

  function getArgument(deps) {
    var args = [];
    for(var i in deps) {
      if (dependencies[deps[i]] === undefined) { throw new Error('Cannot build dependencies: ' + deps[i]); }
      args.push(dependencies[deps[i]]);
    }
    return args;
  }

  for (var k in moduleInitializators) {
    if (moduleInitializators[k] === false) {
      dependencies[k] = false;
      continue;
    }

    var deps = moduleInitializators[k].deps;
    // all modules define own dependencies. An empty list is accepted
    if (!deps) { throw new Error('dependencies is undefined'); }


    var args = getArgument(deps);
    var ret = moduleInitializators[k].init.apply(null, args);
    dependencies[k] = ret;
  }

  dependencies.console.info('Initialization phase is ended successfully');
  dependencies.console.info('Start setupInstances...');

  return dependencies.staticInterface;
}


win.Newton = start(win);

})(window);