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: "builddir",
	VCS_UUID: "a2751d8e1deb39c894b46bf88920c581f41b2c3a",
	VCS_NUM: 435,
	VCS_DATE: "2017-01-18T16:47:55+0100",
	VCS_BRANCH: "",
	VCS_TAG: "v2.4.1",
	VCS_TICK: 0,
	VCS_EXTRA: "",

	VCS_FULL_HASH: "f3a8c125816e2a5194b434557eaa4f1c08bccf5e",
	VCS_SHORT_HASH: "f3a8c12",

	VCS_WC_MODIFIED: false
};

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

/** end */

/* exported assertionInitializator */

var assertionInitializator = {
  deps: [],
  init: function() {
    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 Error('label should be match ' + labelRegexp.toString());
        }
      },
      assertStringIsAValidUrl: function assertStringIsAValidUrl(str) {
        if (!urlPattern.test(str)) {
          throw new Error('string should be a valid url');
        }
      },
      assertIfIsString: function assertIfIsString(val) {
        if (!this.isString(val)) {
          throw new Error('String expected. Found ' + stringifyValue(val));
        }
      },
      assertIfIsNumber: function assertIfIsNumber(val) {
        if (!this.isNumber(val)) {
          throw new Error('Number 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 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'],
  init: function(parent, _console, assertion, httpSingleton, statusSingleton, publicInterface, utils, platformSpecific, staticInterface, SimpleObject) {
    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 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 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();
    }

    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 addStartSessionEvent(environmentCustomData) {
      try {
        SimpleObject._assertIsInstanceOfOrNull(environmentCustomData);
        environmentCustomData = environmentCustomData.toJSONObject();
      } catch(e) {
        environmentCustomData = undefined;
      }
      queueEvent({
        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
      });
    }

    function flushFunction() {
      var events = removeEventsAndReturn();
      if (!events) return;
      _console.debug('Flushing', events, 'to', httpSingleton.CLIENT_END_DOMAIN + '/events/track_bulk');
      httpSingleton.makeSignedRequest(httpSingleton.CLIENT_END_DOMAIN + '/events/track_bulk', events, onFlushingEventListener);
    }

    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;
    function setupInstance(_onFlushingEventListener, environmentCustomData) {
      onFlushingEventListener = _onFlushingEventListener;

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

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

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

    /**
     * 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 Error('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 Error('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 Error('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 Error('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 Error('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 Error('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 Error('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 Error('Invalid content id');
      }

      var ok = false;
      for (var k in staticInterface.RankingScope) {
        if (staticInterface.RankingScope[k] === scope) {
          ok = true;
          break;
        }
      }
      if (!ok) throw new Error('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);
      },
      heartBeatFunction: heartBeatFunction
    };
  }
};

/* exported exceptionInitializator */

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

    function setRavenUrl(ravenUrl) {
      if (!parent.Raven) {
        _console.error('Please include Raven in your page before Newton');
        throw new Error('Raven should be included');
      }

      isRavenConfigured = true;
      parent.Raven.config(ravenUrl).install();
    }

    publicInterface.logException = function(error) {
      if (isRavenConfigured) {
        parent.Raven.captureException(error);
      } else {
        _console.error(error);
      }
    };

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

/* exported httpSingletonInitializator*/

var httpSingletonInitializator = {
  deps: ['window', 'console', 'platformSpecific', 'assertion', 'utils', 'staticInterface'],
  init: function(parent, _console, platformSpecific, assertion, utils, staticInterface) {
    // Application cretential
    var applicationSecret = null;
    var clientIdentifier = null;

    var jsonpCallbacks = {};
    var connectionTimeout = 10000;

    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);
    }

    function getAuthHeader(method, url, bodyString, queryString, timestamp) {
      return ['iPawn identifier="',
        clientIdentifier + '|JS',
        '", signature="',
        getIpawnHash(url, method, bodyString, queryString, timestamp),
        '", version="2.0", timestamp="',
        timestamp,
        '"'
      ].join('');
    }

    function getQueryString(obj) {
      var str = [];
      for(var i in obj) {
        if (obj.hasOwnProperty(i)) {
          str.push(encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]));
        }
      }
      return str.join('&');
    }

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

      request.onreadystatechange = function() {
        if (request.readyState == 4 || request.isMockedFromNewton) {
          _console.debug('Requesst ended', 'status:', request.status, 'body:', request.responseText);
          callback(request);
        }
      };

      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);
    }

    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);
      callback({status: param.statusCode, responseText: JSON.stringify(param.content)});
    };

    var httpSingleton = {
      makeSignedRequest: function(url, content, callback) {
        assertion.assertStringIsAValidUrl(url);
        _console.info('Making signed request', url, content);

        var timestamp = utils.getCurrentTimestamp();
        if (platformSpecific.hasXHRWithCors) {
          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);

          makeJSONP(url, queryString, callback);
        }
      },
      makeUnsignedRequest: function(url, content, callback) {
        _console.info('Making unsigned request', url, content);
        assertion.assertStringIsAValidUrl(url);

        if (platformSpecific.hasXHRWithCors) {
          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);

          makeJSONP(url, queryString, callback);
        }
      },
      setupInstance: function setupInstance(identifier, secret, urls) {
        applicationSecret = secret;
        clientIdentifier = identifier;
        httpSingleton.CLIENT_END_DOMAIN = urls.client;
        httpSingleton.AUTH_END_DOMAIN = urls.auth;
      },
      is2xx: function is2xx(response) {
        return response.status < 299 && response.status > 199;
      },
      getErrorFromResponse: function getErrorFromResponse(response) {
        var err;
        if (response.status === 400) {
          err = new Error('Invalid parameter');
          err.statusCode = 400;
        }
        if (response.status === 404) {
          err = new Error('Not found');
          err.statusCode = 404;
        }
        if (response.status > 499) {
          err = new Error('Internal Newton Error');
          err.statusCode = response.status;
        }

        // 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 Error(errorString);
        }
        if (body && body.error && body.error.code && assertion.isString(body.error.code)) {
          err.code = body.error.code;
        }
        if (body && body.errors && assertion.isString(body.errors)) {
          err.code = body.errors;
        }
        return err;
      },
      getBodyOrUndefined: function getBodyOrUndefined(response) {
        if (response.status === 204) return undefined;

        var data;
        try {
          data = JSON.parse(response.responseText);
        } catch(e) {
          _console.error('Invalid json', e, response.responseText);
        }
        return data;
      },
      hasErrorCore: function hasErrorCore(response, errorCode) {
        var parsed = httpSingleton.getBodyOrUndefined(response);
        return parsed && parsed.error && (parsed.error.code === errorCode);
      },
      // only for test
      getIpawnHash: getIpawnHash
    };

    return httpSingleton;
  }
};

/* exported identityInitializator*/

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

    /**
     * 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;
    }
    /**
     * 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;
    };
    /**
     * 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') {
        return callback(new Error('Cannot update credential for this identity type'));
      }
      _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(response) {
        var error;
        if (httpSingleton.hasErrorCore(response, 'EMAIL_ALREADY_EXIST')) {
          error = new Error('EMAIL_ALREADY_EXIST');
          error.error_code = 'EMAIL_ALREADY_EXIST';
          return callback(error);
        }
        if (httpSingleton.hasErrorCore(response, 'WRONG_PASSWORD')) {
          error = new Error('WRONG_PASSWORD');
          error.error_code = 'WRONG_PASSWORD';
          return callback(error);
        }
        if (!httpSingleton.is2xx(response)) {
          return callback(httpSingleton.getErrorFromResponse(response));
        }
        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
     * @param {String} newSecret - the new password
     * @param {UpdateSecretCallback} callback - the callback called after the operation is done
     */
    Identity.prototype.updateSecret = function updateCredential(oldSecret, newSecret, callback) {
      if (this.getType() !== 'email') {
        return callback(new Error('Cannot update credential for this identity type'));
      }
      _console.info('identity: Updating secret');

      var body = {
        user_id: statusSingleton.getUserToken(),
        identity_id: this._id,
        new_password: newSecret,
        password: oldSecret
      };
      var self = this;
      httpSingleton.makeSignedRequest(httpSingleton.AUTH_END_DOMAIN + '/handling/direct/identity/update/password', body, function(response) {
        if (httpSingleton.hasErrorCore(response, 'WRONG_PASSWORD')) {
          var error = new Error('WRONG_PASSWORD');
          error.error_code = 'WRONG_PASSWORD';
          return callback(error);
        }
        if (!httpSingleton.is2xx(response)) {
          return callback(httpSingleton.getErrorFromResponse(response));
        }
        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(response) {
        var error;
        if (httpSingleton.hasErrorCore(response, 'PAYMENT_ACTIVE')) {
          error = new Error('PAYMENT_ACTIVE');
          error.error_code = 'PAYMENT_ACTIVE';
          return callback(error);
        }

        if (httpSingleton.hasErrorCore(response, 'LAST_IDENTITY')) {
          error = new Error('LAST_IDENTITY');
          error.error_code = 'LAST_IDENTITY';
          return callback(error);
        }

        if (!httpSingleton.is2xx(response)) {
          return callback(httpSingleton.getErrorFromResponse(response));
        }
        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'],
  init: function(_console, httpSingleton, statusSingleton, publicInterface, IdentityModule) {
    var Identity = IdentityModule.Identity;

    function deleteUser(newtonToken, callback) {
      httpSingleton.makeSignedRequest(
        httpSingleton.AUTH_END_DOMAIN + '/user/delete',
        {user_id: newtonToken},
        function(response) {
          if (!httpSingleton.is2xx(response)) {
            return callback(httpSingleton.getErrorFromResponse(response));
          }

          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.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 Error('Some parameters are missing'));
      }

      var body = {
        user_id: this.user_id,
        email: this.email,
        password: this.password
      };
      var self = this;
      httpSingleton.makeSignedRequest(httpSingleton.AUTH_END_DOMAIN + '/handling/direct/identity/add/email/identify', body, function(response) {
        if (httpSingleton.hasErrorCore(response, 'EMAIL_ALREADY_EXIST')) {
          var error = new Error('EMAIL_ALREADY_EXIST');
          error.error_code = 'EMAIL_ALREADY_EXIST';
          return self.onFlowCompletecallback(error);
        }
        if (!httpSingleton.is2xx(response)) {
          return self.onFlowCompletecallback(httpSingleton.getErrorFromResponse(response));
        }

        self.onFlowCompletecallback();
      });
    };
    /**
     * Start the confirm identity flow. Call onFlowCompleteCallback with the result
     * @method confirmIdentityFlow
     * @memberOf AddEmailIdentityFlow
     */
    AddEmailIdentityFlow.prototype.confirmIdentityFlow = function confirmIdentityFlow() {
      _console.info('AddEmailIdentityFlow: confirmIdentityFlow');

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

      var body = {
        user_id: this.user_id,
        token: this.confirmToken
      };
      var self = this;
      httpSingleton.makeSignedRequest(httpSingleton.AUTH_END_DOMAIN + '/handling/direct/identity/add/email/confirm', body, function(response) {

        if (!httpSingleton.is2xx(response)) {
          return self.onFlowCompletecallback(httpSingleton.getErrorFromResponse(response));
        }

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

    /**
     * @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 Error('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(response) {

        if (!httpSingleton.is2xx(response)) {
          return self.onFlowCompletecallback(httpSingleton.getErrorFromResponse(response));
        }

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

    /**
     * @class IdentityBuilder
     * @name IdentityBuilder
     * @private
     */
    function IdentityBuilder() { this.params = {}; }
    /**
     * Set the email
     * @method setEmail
     * @memberOf IdentityBuilder
     * @param {string} email - The email
     * @return {LoginBuilder} the same instance
     */
    IdentityBuilder.prototype.setEmail = function setEmail(email) {
      this.params.email = email;
      return this;
    };
    /**
     * Set the callback called after the flow is completed
     * @method setOnFlowCompleteCallback
     * @memberOf IdentityBuilder
     * @param {FlowCompleteCallback} onFlowCompletecallback - The callback
     * @return {LoginBuilder} 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 {LoginBuilder} the same instance
     */
    IdentityBuilder.prototype.setPassword = function setPassword(password) {
      this.params.password = password;
      return this;
    };
    /**
     * Set the confirm token
     * @method setConfirmToken
     * @memberOf IdentityBuilder
     * @param {string} confirmToken - The confirm token
     * @return {LoginBuilder} the same instance
     */
    IdentityBuilder.prototype.setConfirmToken = function setConfirmToken(confirmToken) {
      this.params.confirmToken = confirmToken;
      return this;
    };
    /**
     * Set the access token
     * @method setAccessToken
     * @memberOf IdentityBuilder
     * @param {string} accessToken - The access token
     * @return {LoginBuilder} 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 {LoginBuilder} the same instance
     */
    IdentityBuilder.prototype.setOAuthProvider = function setOAuthProvider(oauthProvider) {
      this.params.oauthProvider = oauthProvider;
      return this;
    };

    /**
     * Return a AddEmailIdentityFlow
     * @method getAddEmailIdentityFlow
     * @memberOf IdentityBuilder
     * @return {AddEmailIdentityFlow}
     */
    IdentityBuilder.prototype.getAddEmailIdentityFlow = function() {
      return new AddEmailIdentityFlow({
        user_id: statusSingleton.getUserToken(),
        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() {}
      });
    };

    function getIdentities(callback) {
      _console.info('identity: get');

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

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

        callback(null, identities);
      });
    }

    function getIdentityBuilder() {
      return new IdentityBuilder();
    }

    function getIdentityManager() {
      if (!publicInterface.isUserLogged()) {
        throw new Error('User is not logged');
      }
      if (!statusSingleton.isTheTokenType(statusSingleton.getUserObjectToken(), ['U'])) {
        throw new Error('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) {
          var userToken = statusSingleton.getUserToken();
          var type = userToken.charAt(0);
          switch(type) {
            case 'A':
              setTimeout(function() {
                userDeleteCallback(new Error('User is unlogged'));
              });
              break;
            case 'C':
              setTimeout(function() {
                userDeleteCallback(new Error('Cannot delete the current user'));
              });
              break;
            case 'U':
            case 'N':
            case 'E':
              return deleteUser(userToken, userDeleteCallback);
            default:
              return setTimeout(function() { userDeleteCallback(new Error('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', 'payment'],
  init: function(_console, staticInterface, autorevision, httpSingleton, eventSingleton, SimpleObject, utils, publicInterface, loginBuilder, statusSingleton, exception, userModule, payment) {
    var initialized = false;

    var isDev = /api2.newton.pm/.test(AUTH_END_DOMAIN);
    var isSandbox;
    /**
     * Get the shared instance. Create one if no sharedInstance found
     * @method getSharedInstanceWithConfig
     * @memberOf Newton
     * @static
     * @throws {Error} - Throw if no secret is given
     *
     * @return {Newton}
     */
    staticInterface.getSharedInstanceWithConfig = function(secret, environmentCustomData) {
      _console.debug('configuring Newton sdk');
      if (initialized) {
        return publicInterface;
      }

      var ravenUrl;
      secret = secret || {};
      if (secret.constructor === Object ) {
        ravenUrl = secret.ravenUrl;
        secret = secret.secret;
      }

      if (!secret) {
        throw new Error('secret should be given');
      }

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

      if (ravenUrl) {
        exception.setupInstance(ravenUrl);
      }


      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
      });

      var onFlushingEventListener = function(response) {
        _console.debug('on events flushed', response);
        if (loginBuilder && response.status === 205 || /^U_/.test(statusSingleton.getUserToken()) || /^E_/.test(statusSingleton.getUserToken())) {
          loginBuilder.queueCallbackOnAutoLoginFlowQueue(function(err) {
            if (err) { _console.error(err); }
          });
        }
      };
      eventSingleton.setupInstance(onFlushingEventListener, environmentCustomData);

      if (loginBuilder && userModule && payment) {
        loginBuilder.setupInstance(applicationIdentifier);
        userModule.setupInstance();
        payment.setupInstance();
      }

      initialized = true;

      _console.warn('Newton run 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 Error('Call getSharedInstanceWithConfig before');
      }
      return publicInterface;
    };

    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
     *
     * @return {String}
     */
    publicInterface.getEnvironmentString = function() {
      return (isDev ? 'DEV' : 'PROD') + '-' + (isSandbox ? 'SANDBOX' : 'RELEASE');
    };

    /**
     * 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'],
  init: function(_console, assertion, statusSingleton, eventSingleton, publicInterface, utils, parent, httpSingleton, SimpleObject, EventListener) {
    var oauthProviders = [ 'facebook', 'google' ];
    var oauthLoginProviderCookieKey = 'newton-login-oauth-provider';
    var oauthLoginDataLocalStorageKey = 'newton-login-oauth-data';
    var applicationIdentifier;
    var errorFromLoginFlow;
    var isReturningFromLoginFlow;
    var isCustomLoginFlowRunning = false;
    var isExternalLoginFlowRunning = false;
    var isDirectLoginFlowRunning = false;
    var isAutoLoginFlowRunning = false;
    var isForgotFlowRunning = false;
    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(response) {
          if (response.status !== 200) {
            _console.warn('Api /usser/delete returns', response);
            var e = new Error('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 Error('User is unlogged'));
          });
          break;
        case 'C':
          setTimeout(function() {
            userDeleteCallback(new Error('Cannot delete the current user'));
          });
          break;
        case 'U':
        case 'N':
        case 'E':
          return deleteUser(userToken, userDeleteCallback);
        default:
          return setTimeout(function() { userDeleteCallback(new Error('Unknown user type')); }, 10);
      }
    };

    var instance = null;
    function AutoLoginFlow() {
      var el = new EventListener();
      this.addCallback = function(callback) {
        el.onOnce('end', callback);
      };

      function callAllCallbacks(err) {
        el.emit('end', err);
        instance = null;
      }

      this.toString = function() {
        return 'AutoLoginFlow({})';
      };
      this.startLoginFlow = function() {
        if (isAutoLoginFlowRunning ||
            publicInterface.isLoginFlowRunning()) {
          return callAllCallbacks(new Error('Another flow is running'));
        }
        if (!publicInterface.isUserLogged() ||
            /^C_/.test(statusSingleton.getUserToken())) {
          return callAllCallbacks(new Error('User is not logged using Newton'));
        }
        isAutoLoginFlowRunning = true;

        httpSingleton.makeSignedRequest(httpSingleton.AUTH_END_DOMAIN + '/handling/direct/refreshtoken', {user_id: statusSingleton.getUserObjectToken()}, function(response) {
          isAutoLoginFlowRunning = false;

          var body = httpSingleton.getBodyOrUndefined(response);
          if (httpSingleton.is2xx(response) && 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'
            });

            return callAllCallbacks();
          }

          var e;
          if (response.status === 401 && body && body.reason === 'expired') {
            userLogout('refresh');
            e = new Error('Token expired');
            e.type = body.reason;
            stateChangeListener.onLoginStateChange();
            return callAllCallbacks(e);
          }

          // no logout is performed
          e = new Error(body && body.reason || 'Unable to parse JSON string');
          stateChangeListener.onLoginStateChange(e);
          callAllCallbacks(e);
        });
      };
    }
    function queueCallbackOnAutoLoginFlowQueue(callback) {
      var started = !!instance;
      if (!started) {
        instance = new AutoLoginFlow();
      }
      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);
        }
        isCustomLoginFlowRunning = 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();

          isCustomLoginFlowRunning = 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 Error('User is already logged');
          options.callback(err);
          return globalLoginFlowEventListener.emit('end', err);
        }

        if (options.access_token) {
          isDirectLoginFlowRunning = true;
          httpSingleton.makeSignedRequest(httpSingleton.AUTH_END_DOMAIN + '/login/direct/oauth', {
            access_token: options.access_token,
            oauther: options.provider
          }, function(response) {
            var err;
            if (!httpSingleton.is2xx(response)) {
              isDirectLoginFlowRunning = false;
              err = httpSingleton.getErrorFromResponse(response);
              options.callback(err);
              globalLoginFlowEventListener.emit('end', err);
              return;
            }

            var body;
            try {
              body = JSON.parse(response.responseText);
            } catch(e) {
              isDirectLoginFlowRunning = false;
              _console.error(e, response.responseText);
              err = new Error('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();
            isDirectLoginFlowRunning = 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 + '/' + options.provider + '/start';

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

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

        url = url + 'returnUrl=' + encodeURIComponent(options.returnUrl) +
          '&errorUrl=' + encodeURIComponent(options.errorUrl) +
          '&application=' + applicationIdentifier;
        // 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 Error('User is already logged'));
          }, 1);
        }
        isExternalLoginFlowRunning = true;

        httpSingleton.makeSignedRequest(
          httpSingleton.AUTH_END_DOMAIN + '/login/direct/external',
          {external_token: options.external_id},
          function(response) {
            var err;
            if (!httpSingleton.is2xx(response)) {
              isExternalLoginFlowRunning = false;
              err = httpSingleton.getErrorFromResponse(response);
              options.callback(err);
              globalLoginFlowEventListener.emit('end', err);
              return;
            }

            var body;
            try {
              body = JSON.parse(response.responseText);
            } catch(e) {
              isExternalLoginFlowRunning = false;
              _console.error(e, response.responseText);
              err = new Error('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();
            isExternalLoginFlowRunning = 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 Error('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 Error('User is already logged'));
          }, 1);
        }
        isDirectLoginFlowRunning = true;

        var requestBody = {
          msisdn: options.msisdn,
          pin: options.pin
        };
        httpSingleton.makeSignedRequest(
          httpSingleton.AUTH_END_DOMAIN + '/login/direct/msisdn/PINidentify',
          requestBody,
          function(response) {
            var err;
            if (!httpSingleton.is2xx(response)) {
              isDirectLoginFlowRunning = false;
              err = httpSingleton.getErrorFromResponse(response);
              options.callback(err);
              globalLoginFlowEventListener.emit('end', err);
              return;
            }

            var body;
            try {
              body = JSON.parse(response.responseText);
            } catch(e) {
              isDirectLoginFlowRunning = false;
              _console.error(e, response.responseText);
              err = new Error('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: 'msisdn-pin',
              custom_data: options.custom_data ? options.custom_data.toJSONObject() : undefined
            });

            stateChangeListener.onLoginStateChange();
            isDirectLoginFlowRunning = 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 Error('User is already logged'));
          }, 1);
        }
        if (publicInterface.isLoginFlowRunning()) {
          return setTimeout(function() {
            options.callback(new Error('A login flow is already running'));
          }, 1);
        }
        isForgotFlowRunning = true;

        var requestBody = {
          msisdn: options.msisdn
        };
        httpSingleton.makeSignedRequest(
          httpSingleton.AUTH_END_DOMAIN + '/login/direct/msisdn/PINforgot',
          requestBody,
          function(response) {
            var err;
            if (!httpSingleton.is2xx(response)) {
              isForgotFlowRunning = false;
              err = httpSingleton.getErrorFromResponse(response);
              options.callback(err);
              return;
            }

            isForgotFlowRunning = false;

            options.callback();
        });
      };
    }

    /**
     * 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 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 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.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 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 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 MSISDN. Used in MSISDNPINLoginFlow
     * @method setMSISDN
     * @memberOf LoginBuilder
     * @instance
     * @param {String} msisdn
     *
     * @return {LoginBuilder} the same instance
     */
    LoginBuilder.prototype.setMSISDN = setInnerField('msisdn');

    /**
     * Create a custom login flow.
     * @method getCustomLoginFlow
     * @memberOf LoginBuilder
     * @instance
     *
     * @return {CustomLoginFlow}
     */
    LoginBuilder.prototype.getCustomLoginFlow = function() {
      if (!this.options.custom_id) {
        throw new Error('Custom_id must be provided');
      }
      if (!isUserTokenGood(this.options.custom_id)) {
        throw new Error('invalid custom_id');
      }
      if (isUserLogged()) {
        throw new Error('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 Error('Unkown oauth provider');
      }
      if (isUserLogged()) {
        throw new Error('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 Error('External_id must be provided');
      }
      if (!isUserTokenGood(this.options.external_id)) {
        throw new Error('invalid externalID');
      }
      if (isUserLogged()) {
        throw new Error('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 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 Error('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 Error('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,
        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);
      assertion.assertIfIsString(this.options.msisdn);

      if (isUserLogged()) {
        throw new Error('User is already logged');
      }
      return new MSISDNPINLoginFlow({
        msisdn: this.options.msisdn,
        pin: this.options.pin,
        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() {
      assertion.assertIfIsString(this.options.msisdn);

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

      return new MSISDNPINForgotFlow({
        msisdn: this.options.msisdn,
        custom_data: this.options.custom_data,
        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
     */
    publicInterface.setUserStateChangeListener = function(listener) {
      if (!assertion.isObject(listener) || !assertion.isFunction(listener.onLoginStateChange)) {
        throw new Error('Invalid listener');
      }

      stateChangeListener = listener;

      if (isReturningFromLoginFlow) {
        isReturningFromLoginFlow = false;
        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;
          }
        }

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

        eventSingleton.addOtherEvent({
          event_type: 'identify',
          login_type: oauthProvider,
          custom_data: customData
        });
        var err = errorFromLoginFlow ? new Error(errorFromLoginFlow) : undefined;
        stateChangeListener.onLoginStateChange(err);
        globalLoginFlowEventListener.emit('end', err);
      }
    };

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

    /**
     * Log the user out
     * @method userLogout
     * @memberOf Newton
     * @instance
     */
    publicInterface.userLogout = function() {
      userLogout('explicit');
    };

    /**
     * Return true is a login flow is running
     * @method isLoginFlowRunning
     * @memberOf Newton
     * @instance
     *
     * @return {Boolean} - true if a login flow is running
     */
    publicInterface.isLoginFlowRunning = function() {
      return isCustomLoginFlowRunning ||
        isExternalLoginFlowRunning ||
        !!utils.cookie.getItem(oauthLoginProviderCookieKey) ||
        isReturningFromLoginFlow ||
        isDirectLoginFlowRunning ||
        isForgotFlowRunning ||
        false;
    };

    /**
     * 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(response) {
        var parsed, err;
        try {
          parsed = JSON.parse(response.responseText);
        } catch(e) {
          _console.debug('Response is not a json', response.responseText);
          _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 Error('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);
          err = new Error(parsed.error);
          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 Error('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 Error('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);
      });
    };

    function setupInstance(_applicationIdentifier) {
      applicationIdentifier = _applicationIdentifier;

      var newtonUserTokenFromLoginFlow = utils.getParameterByName('_nt');
      var userObjectUserTokenFromLoginFlow = utils.getParameterByName('_uot');
      var externalUserObjectToken = utils.getParameterByName('_bet');
      errorFromLoginFlow = utils.getParameterByName('_ne');
      isReturningFromLoginFlow = !!((newtonUserTokenFromLoginFlow && userObjectUserTokenFromLoginFlow) || errorFromLoginFlow) || !!externalUserObjectToken;

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

      isReturningFromLoginFlow = false;
      if (parent.history) {
        var cleanedUrl = utils.removeDomain(utils.removeParameters(utils.getCurrentUrl(), ['_nmc', '_ne', '_nt', '_uot', '_bet']));
        parent.history.replaceState(parent.history.state, document.title, cleanedUrl);
      }

      if (externalUserObjectToken) {
        statusSingleton.setUserObjectToken('U_' + externalUserObjectToken);
        eventSingleton.addOtherEvent({
          event_type: 'identify',
          login_type: 'external'
        });

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

      if (errorFromLoginFlow) {
        _console.error('Error in login', errorFromLoginFlow);
        return;
      }
      if (!isUserTokenGood(userObjectUserTokenFromLoginFlow)) {
        errorFromLoginFlow = 'Invalid user token';
        _console.error('User token is very bad', newtonUserTokenFromLoginFlow);
        return;
      }

      statusSingleton.setNewtonToken(newtonUserTokenFromLoginFlow);
      statusSingleton.setUserObjectToken(userObjectUserTokenFromLoginFlow);

      isReturningFromLoginFlow = 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 (!publicInterface.isLoginFlowRunning()) {
        setTimeout(syncStateCallback, 0);
      } else {
        globalLoginFlowEventListener.onOnce('end', function() {
          syncStateCallback();
        });
      }
    };

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

/* exported paymentInitializator */

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

    publicInterface._temporaryIsUserPayingForDefault = function(callback) {
      var user_id = statusSingleton.getUserToken();
      _console.log('_temporaryIsUserPayingForDefault with', user_id);

      httpSingleton.makeSignedRequest(
        httpSingleton.CLIENT_END_DOMAIN + '/payment/is_paying_for',
        {user_id: user_id},
        function(response) {
          var err;
          if(response.status > 299) {
            err = new Error('Invalid http response');
            _console.log('_temporaryIsUserPayingForDefault invalid response', response, err);
            return callback(err);
          }

          _console.log('_temporaryIsUserPayingForDefault returns', response);
          var body = httpSingleton.getBodyOrUndefined(response);
          if (!body) {
            return callback(new Error('Invalid JSON'));
          }
          callback(err, body);
        }
      );
    };

    publicInterface._temporaryValidateReceipt = function(receipt, callback) {
      _console.log('_temporaryValidateReceipt dummy implementation');
      setTimeout(function() {
        callback(new Error('Cannot validate the receipt'));
      }, 1);
    };

    return {
      setupInstance: function() {
        _console.log('payment setupInstance');
      }
    };
  }
};
/* 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 undefined;
      }
    })();

    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 undefined;
      }
    })();

    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 undefined;
      }
    })();

    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];
    }

    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
    };
  }
};

/* jshint ignore:start */
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();
  };
}
/* jshint ignore:end */

/* exported simpleObjectInitializator */

var simpleObjectInitializator = {
  deps: ['assertion'],
  init: function(assertion) {
    /**
     * 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 Error('cannot set ' + key + ' to undefined');
      }
      if (!assertion.isNull(value) &&
          !assertion.isNumber(value) &&
          !assertion.isString(value) &&
          !assertion.isBoolean(value))
      {
        var e = new Error('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 Error('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 Error('Custom data should be an instance of SimpleObject');
      }
    };

    return SimpleObject;
  }
};

/* exported statusSingletonInitializator */
var statusSingletonInitializator = {
  deps: ['console', 'utils', 'publicInterface'],
  init: function(_console, utils, publicInterface) {
    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 customTokenRegExp = /^C_/;
    var userObjectTokenRegExp = /^U_/;
    var externalTokenRegExp = /^E_/;
    var expirationMinutesForInactivity = 30;

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

    function getDeviceId() {
      return deviceId;
    }

    /*
     * 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);
      utils.cookie.setItem(sessionCookieKey, sessionId, expireDate, '/');
    }
    function getSessionId() {
      var sessionId = utils.cookie.getItem(sessionCookieKey);
      if (sessionId) {
        setSessionId(sessionId);
      }
      return sessionId;
    }

    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 Error('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 Error('Invalid newton token');
      }
      var expirationDate = new Date();
      expirationDate.setMinutes(expirationDate.getMinutes() + newtonTokenExpireInMinutes);
      utils.cookie.setItem(newtonTokenCookieKey, newtonToken, expirationDate, '/');
    }
    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 Error('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 userLogout() {
      _console.debug('User logout');
      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;
    }

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

    return {
      setSessionId: setSessionId,
      getSessionId: getSessionId,
      getDeviceId: getDeviceId,
      getAnonymousUserToken: getAnonymousUserToken,
      getCustomUserToken: getCustomUserToken,
      setCustomUserToken: setCustomUserToken,
      getNewtonToken: getNewtonToken,
      setNewtonToken: setNewtonToken,
      getUserObjectToken: getUserObjectToken,
      setUserObjectToken: setUserObjectToken,
      getUserToken: getUserToken,
      userLogout: userLogout,
      isTheTokenType: isTheTokenType
    };
  }
};

/* exported userModuleInitializator */

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

    function _getUserMetaInfo(newtonToken, callback) {
      httpSingleton.makeSignedRequest(
        httpSingleton.AUTH_END_DOMAIN + '/handling/direct/userinfo',
        {user_id: newtonToken},
        function(response) {
          if(response.status > 299) {
            return callback(new Error('Invalid http response'));
          }
          var body = JSON.parse(response.responseText);
          callback(null, body);
        }
      );
    }

    /**
     * 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 Error('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 Error('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 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 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 === '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) + '=[^&]*'), replacer, 'mi');
        }
        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) {
        return url.replace(/https?:\/\/[^\/]*/, '', 'i') || '/';
      }
    };

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

    return ret;
  }
};

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

function start(win) {
  var moduleInitializators = {
    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,
    payment:  typeof paymentInitializator === 'undefined' ? false : paymentInitializator,
    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);