/*!
* 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: 440,
VCS_DATE: "2017-02-21T15:36:21+0100",
VCS_BRANCH: "remotes/origin/master",
VCS_TAG: "v2.5.0",
VCS_TICK: 0,
VCS_EXTRA: "",
VCS_FULL_HASH: "6a95f3c48b180b50416b55c33ccc60ff9b2da41a",
VCS_SHORT_HASH: "6a95f3c",
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 getNEEnrichment() {
var key = 'ne_enrichment';
// key = encodeURIComponent(key);
var cookies = document.cookie.split(";");
for (var i = 0; i < cookies.length; i++) {
var splitted = cookies[i].split("=");
var name = (splitted.shift() || '').trim();
if (name === key) {
var value = splitted.shift();
try {
return JSON.parse(value);
} catch(e) { }
}
}
return null;
}
// Inplace enrichment
function mergeObject(obj1, obj2) {
for (var k in obj2) {
if (obj1[k]) continue
obj1[k] = obj2[k]
}
}
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();
if (ev.type !== 'heartbeat') {
var _neEnrichment = getNEEnrichment();
var neEnrichment;
if (typeof _neEnrichment === 'string') {
try {
neEnrichment = JSON.parse(_neEnrichment)
} catch(e) { }
} else {
neEnrichment = _neEnrichment;
}
if (neEnrichment) {
if (!ev.custom_data) ev.custom_data = {}
mergeObject(ev.custom_data, neEnrichment);
}
}
}
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);