'use strict';
var jsonpCallbacks = { };
var __newtonUtils = {
random: {
chars: (function() {
var c = 'qwertyuiopasdfghjklzxcvbnm';
return {
LOWER: c,
UPPER: c.toUpperCase(),
NUMBER: '1234567890',
SYMBOL: '\\|!$%&/()=?^\'<>#@-_+*'
};
})(),
string: function(l, alphabet) {
l = parseInt(l, 10);
if (!isFinite(l)) {
return null;
}
var chars = __newtonUtils.random.chars;
alphabet = alphabet || (chars.LOWER + chars.UPPER + chars.NUMBER + chars.SYMBOL);
var m = new MersenneTwister();
var r = '';
for(var i=0; i < l; i++) {
var index = Math.floor(m.random() * alphabet.length);
r += alphabet.charAt(index);
}
return r;
}
},
http: {
isXMLHttpRequestSupported: function() {
return !!window.XMLHttpRequest;
},
isXMLHttpRequestWithCORSSupported: function() {
var request = new XMLHttpRequest();
return 'withCredentials' in request;
},
isXDomainRequestSupported: function() {
return !!window.XDomainRequest;
},
makeAsyncRequest: function(url, method, body, query, headers, callback) {
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if ((request.readyState == 4) || request.isMockedFromNewton) {
callback(request);
}
};
if (query) {
url += (url.indexOf('?') > -1 ? '&' : '?') + query;
}
request.open(method.toUpperCase(), url, true);
request.timeout = CONNECTION_TIMEOUT;
for (var k in headers) {
request.setRequestHeader(k, headers[k]);
}
request.setRequestHeader('Content-type', 'application/json; charset=UTF-8');
_log('Sending', body, 'body to', url);
request.send(body);
},
makeAsyncRequestWithJSONP: function(url, query, callback) {
var scriptTag = document.createElement('script');
scriptTag.src = url + '?' + query;
document.body.appendChild(scriptTag);
},
getQueryString: function(obj) {
var str = [];
for(var i in obj) {
if (obj.hasOwnProperty(i)) {
str.push(encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]));
}
}
return str.join('&');
},
getIpawnHash: function(secret, method, url, body, query, timestamp) {
url = __newtonUtils.http.getUrlForSignature(url);
var msg = [url, method, body, query, timestamp].join('|');
return CryptoJS.HmacSHA1(msg, secret).toString();
},
getAuthHeader: function getAuthHeader(clientIdentifier, secret, method, url, body, query) {
var timestamp = __newtonUtils.date.getCurrentUTCTimestamp();
return ['iPawn identifier="',
clientIdentifier,
'", signature="',
__newtonUtils.http.getIpawnHash(secret, method, url, body, query, timestamp),
'", version="2.0", timestamp="',
timestamp,
'"'
].join('');
},
getUrlForSignature: function(url) {
return url.replace(/^https?:\/\//,'');
}
},
cookie: {
setItem: function(key, value, end, path, domain) {
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 + (domain ? "; domain=" + domain : "") + (path ? "; path=" + path : "");
return true;
},
removeItem: function (sKey, sPath, sDomain) {
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(localStorage.getItem(k));
} catch(e) {
return null;
}
},
setItem: function(k, v) { return localStorage.setItem(k, JSON.stringify(v)); },
removeItem: function(k) { return localStorage.removeItem(k); }
},
date: {
getCurrentUTCTimestamp: function() {
return Math.ceil(Date.now() / 1000);
},
getCurrentUTCDate: function() {
var now = new Date();
return new Date(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), now.getUTCHours(), now.getUTCMinutes(), now.getUTCSeconds(), now.getUTCMilliseconds());
},
diffDate: function(date1, date2) {
return Math.ceil((date1.getTime() - date2.getTime()) / 1000);
}
},
other: {
mergeSimpleObject: function(so1, so2) {
var so = SimpleObject.fromJSONObject(so1.toJSONObject());
var hash = so2.toJSONObject();
for (var k in hash) {
so._set(k, hash[k]);
}
return so;
},
mergeObjects: function(o1, o2) {
var ret = {};
for (var k in o1) {
ret[k] = o1[k];
}
for (k in o2) {
ret[k] = o2[k];
}
return ret;
}
}
};
var isUsingNativeCookies = navigator.cookieEnabled && !__newton__forceCookieInRam;
if (!isUsingNativeCookies) {
__newtonUtils.cookie = createRamStorage();
}
var isUsingNativeLocalStorage = !__newton__forceLocalStorageInRam;
try {
localStorage.setItem('newton-test-lc', 1);
localStorage.removeItem('newton-test-lc');
} catch (e) {
isUsingNativeLocalStorage = false;
}
function createRamStorage() {
var __storage = {};
return {
setItem: function(key, value) {
__storage[key] = value;
},
removeItem: function (key) {
delete __storage[key];
},
getItem: function (key) {
return __storage[key] || null;
}
};
}
if (!isUsingNativeLocalStorage) {
__newtonUtils.localStorage = createRamStorage();
}
function getPersistentStorage() {
return isUsingNativeLocalStorage ? __newtonUtils.localStorage :
(isUsingNativeCookies ? __newtonUtils.cookie : null);
}
var QUEUED_EVENTS_KEY = 'newton-queued-events';
var FLOW_KEY = 'newton-flow';
var DEVICE_ID = 'newton-device-id';
var SESSION_KEY = 'newton-session';
var OAUTH_LOGIN_PROVIDER = 'newton-login-oauth-provider';
var CUSTOM_LOGIN_KEY = 'newton-custom-login';
var NEWTON_LOGIN_KEY = 'newton-newton-login';
var USER_LOGIN_KEY = 'newton-user-login';
var EXPIRE_DELTA_IN_MINUTES = 24 * 60; // 1 day
var ID_LENGTH = 12;
var FLOW_ID_LENGTH = 12;
var DEVICE_ID_LENGHT = 20;
var MIN_FLUSHING_PERIOD = 2; // 2 seconds
var HEARTBEAT_PERIOD = 10; // 10 seconds
var CONNECTION_TIMEOUT = 10000;
var USER_TOKEN_EXPIRE_IN_DAYS = 30;
var NEWTON_TOKEN_EXPIRE_IN_MINUTES = 30;
var UserManager = (function() {
function UserManager() {
this.deviceId = undefined;
this.customUserToken = undefined;
}
UserManager.prototype.loadFromCookie = function() {
this.customUserToken = __newtonUtils.cookie.getItem(CUSTOM_LOGIN_KEY) || undefined;
};
UserManager.prototype.setCustomToken = function(token, isPersistentOnRefreshing) {
if (!/^C_/.test(token)) {
throw new Error('Invalid custom token');
}
this.customUserToken = token;
if (isPersistentOnRefreshing) {
__newtonUtils.cookie.setItem(CUSTOM_LOGIN_KEY, this.customUserToken, null, '/');
}
};
UserManager.prototype.setNewtonToken = function(token) {
if (!/^N_/.test(token)) {
throw new Error('Invalid newton token');
}
var expireDate = new Date();
expireDate.setMinutes(expireDate.getMinutes() + NEWTON_TOKEN_EXPIRE_IN_MINUTES);
__newtonUtils.cookie.setItem(NEWTON_LOGIN_KEY, token, expireDate, '/');
};
UserManager.prototype.setUserObjectToken = function(token) {
if (!/^U_/.test(token)) {
throw new Error('Invalid user object token');
}
var expireDate = new Date();
expireDate.setDate(expireDate.getDate() + USER_TOKEN_EXPIRE_IN_DAYS);
__newtonUtils.cookie.setItem(USER_LOGIN_KEY, token, expireDate, '/');
};
UserManager.prototype.logout = function() {
this.customUserToken = null;
__newtonUtils.cookie.removeItem(CUSTOM_LOGIN_KEY);
__newtonUtils.cookie.removeItem(NEWTON_LOGIN_KEY);
__newtonUtils.cookie.removeItem(USER_LOGIN_KEY);
};
UserManager.prototype.isUserLogged = function() {
return !!this.customUserToken ||
!!__newtonUtils.cookie.getItem(NEWTON_LOGIN_KEY) ||
!!__newtonUtils.cookie.getItem(USER_LOGIN_KEY);
};
UserManager.prototype.createNewDeviceIdAndStore = function() {
this.deviceId = __newtonUtils.localStorage.getItem(DEVICE_ID);
if (!this.deviceId) {
this.deviceId = __newtonUtils.random.string(DEVICE_ID_LENGHT);
__newtonUtils.localStorage.setItem(DEVICE_ID, this.deviceId);
}
};
UserManager.prototype.getDeviceId = function() {
if (this.deviceId) {
return this.deviceId;
}
this.createNewDeviceIdAndStore();
return this.deviceId;
};
UserManager.prototype.getCurrentUserToken = function() {
if (this.customUserToken) {
return this.customUserToken;
}
var newtonUserId = __newtonUtils.cookie.getItem(NEWTON_LOGIN_KEY);
if (newtonUserId) {
return newtonUserId;
}
var userUserId = __newtonUtils.cookie.getItem(USER_LOGIN_KEY);
if (userUserId) {
return userUserId;
}
return 'A_' + this.getDeviceId();
};
UserManager.prototype.setEventQueueManager = function(eventQueueManager) {
this.eventQueueManager = eventQueueManager;
};
UserManager.prototype.setServerClientManager = function(serverClientManager) {
this.serverClientManager = serverClientManager;
};
UserManager.prototype.startAutologinFlow = function(credentials, callback) {
callback = callback || function() {};
var url = getAuthEndpoint(credentials.secret) + '/token/refresh';
var bhandle = (__newtonUtils.cookie.getItem(USER_LOGIN_KEY) || '');
var content = { bhandle: bhandle };
if (!bhandle) {
return callback(null, false);
}
__newtonUtils.cookie.removeItem(NEWTON_LOGIN_KEY);
var self = this;
this.serverClientManager.refreshUserToken(bhandle, function(err, data) {
if (err) {
_log(err);
self.eventQueueManager.addEvent({
event_type: 'logout'
});
self.logout();
return callback(null, false);
}
self.setNewtonToken(data.newton_token);
self.setUserObjectToken(data.user_token);
self.eventQueueManager.addEvent({
event_type: 'identify',
login_type: 'autologin'
});
return callback(null, true);
});
};
UserManager.prototype.start = function(credentials, callback) {
this.loadFromCookie();
var token = this.getCurrentUserToken();
if (/^U_/.test(token)) {
this.startAutologinFlow(credentials, callback);
}
};
return UserManager;
})();
var SessionManager = (function() {
function SessionManager() {
this.sessionId = undefined;
this.eventQueueManager = undefined;
}
SessionManager.prototype.setEventQueueManager = function(eventQueueManager) {
this.eventQueueManager = eventQueueManager;
};
SessionManager.prototype.start = function() {
if (this.isValid()) {
this.sessionId = __newtonUtils.cookie.getItem(SESSION_KEY);
return;
}
this.sessionId = __newtonUtils.random.string(ID_LENGTH);
__newtonUtils.localStorage.setItem(SESSION_KEY, this.sessionId);
var date = __newtonUtils.date.getCurrentUTCDate();
date.setMinutes(date.getMinutes() + EXPIRE_DELTA_IN_MINUTES);
__newtonUtils.cookie.setItem(SESSION_KEY, this.sessionId, date, '/');
// emit start event
if (this.eventQueueManager) {
this.eventQueueManager.startSessionEvent(this.sessionId);
}
};
SessionManager.prototype.isValid = function() {
// start session
return (!!this.sessionId) || (!!__newtonUtils.cookie.getItem(SESSION_KEY));
};
SessionManager.prototype.stop = function() {
this.sessionId = undefined;
__newtonUtils.localStorage.removeItem(SESSION_KEY);
__newtonUtils.cookie.removeItem(SESSION_KEY);
};
SessionManager.prototype.getSessionId = function() {
// cookie can be disabilited, so avoiding some recursion, I must do this check
if (this.sessionId) {
return this.sessionId;
}
// refresh internal sessionId if needed
this.start();
return this.sessionId;
};
return SessionManager;
})();
var EventQueueManager = (function() {
function EventQueueManager() {
this.userManager = undefined;
this.sessionManager = undefined;
this.lastFlushTimestamp = 0;
this.queuedEvents = [];
this.intervalId = undefined;
}
EventQueueManager.prototype.setUserManager = function(userManager) {
this.userManager = userManager;
};
EventQueueManager.prototype.setSessionManager = function(sessionManager) {
this.sessionManager = sessionManager;
};
EventQueueManager.prototype.setOnFlushEvents = function(onFlushEvents) {
this.onFlushEvents = onFlushEvents;
};
EventQueueManager.prototype.inflatingEvent = function(event) {
event.session_id = this.sessionManager.getSessionId();
event.creation_date = __newtonUtils.date.getCurrentUTCDate().toISOString();
event.user_id = this.userManager.getCurrentUserToken();
return event;
};
EventQueueManager.prototype.addEvent = function(event) {
this.inflatingEvent(event);
var events = __newtonUtils.localStorage.getItem(QUEUED_EVENTS_KEY);
events = events ? events : '';
events += ',' + JSON.stringify(event);
__newtonUtils.localStorage.setItem(QUEUED_EVENTS_KEY, events);
var now = __newtonUtils.date.getCurrentUTCTimestamp();
if (now < this.lastFlushTimestamp + MIN_FLUSHING_PERIOD) {
// queued and not flushed
if (!this.timeoutId) {
var self = this;
this.timeoutId = setTimeout(function() {
self.timeoutId = undefined;
var events = __newtonUtils.localStorage.getItem(QUEUED_EVENTS_KEY);
if (!events) return;
__newtonUtils.localStorage.removeItem(QUEUED_EVENTS_KEY);
self.onFlushEvents('[' + events.substring(1) + ']');
}, MIN_FLUSHING_PERIOD * 1000);
}
return;
}
// throw ???
if (!this.onFlushEvents) { return; }
this.lastFlushTimestamp = now;
__newtonUtils.localStorage.removeItem(QUEUED_EVENTS_KEY);
this.onFlushEvents('[' + events.substring(1) + ']');
};
EventQueueManager.prototype.startSessionEvent = function(sessionId) {
var event = getDeviceField();
event.event_type = 'start session';
event.device_id = this.userManager.getDeviceId();
event.sdk_version = 'JS ' + publicInterface.getVersionString();
event.user_agent = window.navigator.userAgent;
event.with_cookie = isUsingNativeCookies;
event.with_ls = isUsingNativeLocalStorage;
this.addEvent(event);
};
return EventQueueManager;
})();
var HttpWrapper = (function() {
function HttpWrapper() {
this.credentials = {};
}
HttpWrapper.prototype.isToSignWithIPawn = function(credentials) {
this.credentials = credentials;
return this;
};
HttpWrapper.prototype.makeRequestWithAResponse = function(url, content, callback) {
var timestamp = __newtonUtils.date.getCurrentUTCTimestamp();
if (__newtonUtils.http.isXMLHttpRequestSupported() && __newtonUtils.http.isXMLHttpRequestWithCORSSupported()) {
var headers = { };
content = JSON.stringify(content);
if (Object.keys(this.credentials).length !== 0) {
headers = {
Authorization: __newtonUtils.http.getAuthHeader(this.credentials.identifier, this.credentials.secret, 'POST', url, content, '', timestamp)
};
}
__newtonUtils.http.makeAsyncRequest(url, 'POST', content, '', headers, callback);
return;
} else {
// jsonp
var callbackId = __newtonUtils.random.string(5);
jsonpCallbacks[callbackId] = callback;
var query = content;
query.callback = 'Newton.__jsonpCallback';
query.callbackId = callbackId;
if (Object.keys(this.credentials).length !== 0) {
var hash = __newtonUtils.http.getIpawnHash(this.credentials.secret, 'GET', url, '', __newtonUtils.http.getQueryString(query), timestamp);
query.identifier = this.credentials.identifier;
query.signature = hash;
query.timestamp = timestamp;
query.version = '2.0';
}
query = __newtonUtils.http.getQueryString(query);
__newtonUtils.http.makeAsyncRequestWithJSONP(url, query, callback);
return;
}
};
HttpWrapper.prototype.makeRequestWithoutAResponse = function(url, content, callback) {
var timestamp = __newtonUtils.date.getCurrentUTCTimestamp();
if (__newtonUtils.http.isXMLHttpRequestSupported() && __newtonUtils.http.isXMLHttpRequestWithCORSSupported()) {
var headers = { };
if (Object.keys(this.credentials).length !== 0) {
headers = {
Authorization: __newtonUtils.http.getAuthHeader(this.credentials.identifier, this.credentials.secret, 'POST', url, content, '', timestamp)
};
}
__newtonUtils.http.makeAsyncRequest(url, 'POST', content, '', headers, callback);
return;
}
var query = {
events: content
};
var hash = __newtonUtils.http.getIpawnHash(this.credentials.secret, 'GET', url, '', __newtonUtils.http.getQueryString(query), timestamp);
query.identifier = this.credentials.identifier;
query.signature = hash;
query.timestamp = timestamp;
query.version = '2.0';
query = __newtonUtils.http.getQueryString(query);
__newtonUtils.http.makeAsyncRequestWithJSONP(url, query, callback);
};
return HttpWrapper;
})();
var ServerClientManager = (function() {
function ServerClientManager(credentials, url) {
this.credentials = credentials;
this.url = url;
}
ServerClientManager.prototype.flushEvents = function(events, callback) {
callback = callback || function() {};
var http = new HttpWrapper();
http.isToSignWithIPawn(this.credentials)
.makeRequestWithoutAResponse(this.url, events, callback);
};
ServerClientManager.prototype.refreshUserToken = function(bhandle, callback) {
var url = getAuthEndpoint(this.credentials.secret) + '/token/refresh';
var content = { bhandle: bhandle };
if (!bhandle) {
return callback(null, false);
}
var self = this;
var http = new HttpWrapper();
http.isToSignWithIPawn(this.credentials)
.makeRequestWithAResponse(url, content, function(response) {
var err;
if(response.status > 299) {
return callback(new Error('Invalid http request'));
}
var body = JSON.parse(response.responseText);
callback(null, body);
});
};
return ServerClientManager;
})();
var TimerManager = (function() {
function TimerManager() {
this.timers = {};
this.eventQueueManager = undefined;
// avoiding bind
var self = this;
this.heartbeatIntervalId = setInterval(function() {
var names = Object.keys(self.timers);
if (names.length < 1) {
return;
}
var ev = {
name: 'heartbeat',
event_type: 'timer heartbeat',
timers: names,
period: HEARTBEAT_PERIOD
};
self.eventQueueManager.addEvent(ev);
}, HEARTBEAT_PERIOD * 1000);
}
TimerManager.prototype.setEventQueueManager = function(eventQueueManager) {
this.eventQueueManager = eventQueueManager;
};
TimerManager.prototype.startTimer = function(name, cd) {
this.timers[name] = {
name: name,
startDate: __newtonUtils.date.getCurrentUTCTimestamp()
};
var timedEvent = {
name: name,
event_type: 'timer start'
};
if (cd) {
timedEvent.custom_data = cd;
}
this.eventQueueManager.addEvent(timedEvent);
};
TimerManager.prototype.stopTimer = function(name, cd) {
var timedEvent = this.timers[name];
if (!timedEvent) {
throw new Error('Cannot stop a non started timedEvent');
}
timedEvent.end = __newtonUtils.date.getCurrentUTCDate().toISOString();
timedEvent.delta = Math.abs(__newtonUtils.date.getCurrentUTCTimestamp() - timedEvent.startDate);
timedEvent.startDate = new Date(timedEvent.startDate * 1000);
if (cd) {
timedEvent.custom_data = cd;
}
timedEvent.event_type = 'timer stop';
this.eventQueueManager.addEvent(timedEvent);
delete this.timers[name];
};
return TimerManager;
})();
var FlowManager = (function() {
function FlowManager() {
this.eventQueueManager = undefined;
}
FlowManager.prototype.setEventQueueManager = function(eventQueueManager) {
this.eventQueueManager = eventQueueManager;
};
FlowManager.prototype.flowBegin = function(name, cd) {
var flowId = __newtonUtils.localStorage.getItem(FLOW_KEY);
if (flowId) {
this.flowFail('flow-start', {'started-flow-id': flowId});
}
flowId = __newtonUtils.random.string(FLOW_ID_LENGTH);
__newtonUtils.localStorage.setItem(FLOW_KEY, flowId);
var ev = {
event_type: 'flow start',
name: name + '',
flow_id: flowId
};
if (cd) {
ev.custom_data = cd;
}
this.eventQueueManager.addEvent(ev);
};
FlowManager.prototype.flowStep = function(name, cd) {
var flowId = __newtonUtils.localStorage.getItem(FLOW_KEY);
if (!flowId) {
throw new Error('No flow found');
}
var ev = {
event_type: 'flow step',
flow_id: flowId,
name: name
};
if (cd) {
ev.custom_data = cd;
}
this.eventQueueManager.addEvent(ev);
};
FlowManager.prototype.flowSucceed = function(reason, cd) {
var flowId = __newtonUtils.localStorage.getItem(FLOW_KEY);
if (!flowId) {
throw new Error('No flow found');
}
var ev = {
event_type: 'flow succeeded',
flow_id: flowId,
name: reason ? reason + '' : 'ok'
};
if (cd) {
ev.custom_data = cd;
}
this.eventQueueManager.addEvent(ev);
};
FlowManager.prototype.flowFail = function(reason, cd) {
var flowId = __newtonUtils.localStorage.getItem(FLOW_KEY);
if (!flowId) {
throw new Error('No flow found');
}
__newtonUtils.localStorage.removeItem(FLOW_KEY);
var ev = {
event_type: 'flow failed',
flow_id: flowId,
name: reason
};
if (cd) {
ev.custom_data = cd;
}
this.eventQueueManager.addEvent(ev);
};
FlowManager.prototype.flowCancel = function(reason, cd) {
var flowId = __newtonUtils.localStorage.getItem(FLOW_KEY);
if (!flowId) {
throw new Error('No flow found');
}
__newtonUtils.localStorage.removeItem(FLOW_KEY);
var ev = {
event_type: 'flow canceled',
flow_id: flowId,
name: reason
};
if (cd) {
ev.custom_data = cd;
}
this.eventQueueManager.addEvent(ev);
};
return FlowManager;
})();
var SimpleObject = (function() {
/**
* @class
*/
function SimpleObject() {
this.prop = {};
}
/**
* Never use directly. Use the alias instead
* @method
* @memberOf SimpleObject
*
* @param {String} key - The key
* @param {String|null|Number|Boolean} value - The value
*/
SimpleObject.prototype._set = function (key, value) {
// TODO: do this better. instance only when is needed
var e = new Error('cannot set ' + value + ' as ' + key);
e.key = key;
e.value = value;
if (value === undefined) {
throw e;
}
if (value !== null) {
var valueConstructor = value.constructor;
if (valueConstructor !== Number && valueConstructor !== String && valueConstructor !== Boolean) {
throw e;
}
}
this.prop[key] = value;
};
/**
* @method setString
* @memberOf SimpleObject
* @instance
* @alias SimpleObject#_set
*/
SimpleObject.prototype.setString = SimpleObject.prototype._set;
/**
* @method setInt
* @memberOf SimpleObject
* @instance
* @alias SimpleObject#_set
*/
SimpleObject.prototype.setInt = SimpleObject.prototype._set;
/**
* @method setBool
* @memberOf SimpleObject
* @instance
* @alias SimpleObject#_set
*/
SimpleObject.prototype.setBool = SimpleObject.prototype._set;
/**
* @method setFloat
* @memberOf SimpleObject
* @instance
* @alias SimpleObject#_set
*/
SimpleObject.prototype.setFloat = SimpleObject.prototype._set;
/**
* @method setNull
* @memberOf SimpleObject
* @instance
*
* @param {String} key - The key
*/
SimpleObject.prototype.setNull = function(key) {
this._set(key, null);
};
/**
* @method toJSONObject
* @memberOf 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;
};
/**
* @method fromJSONObject
* @memberOf SimpleObject
* @static
*
* @param {Object} obj - The object you would convert
* @return {SimpleObject}
*/
SimpleObject.fromJSONObject = function(obj) {
if (!obj || obj.constructor !== Object) {
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;
};
return SimpleObject;
})();
function assertCustomDataIsNullOrIsInstanceOfSimpleObject(customData) {
if (customData && customData.constructor !== SimpleObject) {
throw new Error('Custom data should be an instance of SimpleObject');
}
}
function getHashFromSimpleObjectOrUndefined(customData) {
return customData ? customData.toJSONObject() : undefined;
}
function assertIsALabel(name) {
/* jshint -W044 */
name = name + '';
if (!(name && name.match(/^[\w-]{1,20}$/))) {
throw new Error('label should be match /^[\w-]{1,20}$/');
}
}
/*
* UseSetTransaction
*/
function createUserSetTransaction() {
return {
setUserProperty: function(key, value) {
},
setOnceUserProperty: function(key, value) {
},
appendUserProperty: function(key, value) {
},
incrementUserProperty: function(key) {
},
decrementUserProperty: function(key) {
},
commit: function(callback) {
}
};
}
function getIdentifier(hostname) {
return hostname + '|JS';
}
function getSubdomainSuffix(hostname, secret) {
var isSandbox = !!secret.match('_');
var isDev = !!hostname.match(/^.*2\.newton\.pm$/);
return (isSandbox ? '-sandbox' : '') + (isDev ? '2' : '');
}
function getAuthEndpoint(secret) {
var isSandbox = !!secret.match('_');
return isSandbox ? AUTH_END_SANDBOX_URL : AUTH_END_URL;
}
function getClientEndpoint(secret) {
var isSandbox = !!secret.match('_');
return isSandbox ? CLIENT_END_EVENT_SANDBOX_URL : CLIENT_END_EVENT_URL;
}
var _log;
function redirectTo(url) {
window.location = url;
}
function getParameterByName(name) {
var match = new RegExp('[?&]' + encodeURIComponent(name) + '=([^&]*)').exec(window.location.search);
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
}
function getCurrentUrl() {
return window.location + '';
}
function getHostname() {
return window.location.hostname + '';
}
function replacer(match) {
return match.charAt(0);
}
function getUrlWithoutParamenter(url, parameterToRemove) {
return url.replace(new RegExp('[?&]' + encodeURIComponent(parameterToRemove) + '=[^&]*'), replacer, 'mi');
}
function removeDomain(url) {
return url.replace(/https?:\/\/[^\/]*/, '', 'i') || '/';
}
function assertStringIsAValidUrl(str) {
// FROM http://stackoverflow.com/a/14582229/1956082
var pattern = /^https?:\/\/[\.\w\d]+(:\d+)?/;
if (!pattern.test(str)) {
throw new Error('string should be a valid url');
}
}
function replaceState(state, title, url) {
window.history.replaceState(state, title, url);
}
/**
* NewtonSDK
*
* @class NewtonSDK
* @name NewtonSDK
* @constructor
* @private
*/
function createNewtonInstance(secret) {
var debug = false;
_log = function() {
if (debug) { console.log(arguments); }
};
var ravenUrl;
secret = secret || {};
if (secret.constructor === Object ) {
ravenUrl = secret.ravenUrl;
secret = secret.secret;
}
if (!secret) {
throw new Error('secret should be given');
}
var hostname = getHostname().toLowerCase();
var identifier = getIdentifier(hostname);
var endpoint = getClientEndpoint(secret);
var credentials = {identifier: identifier, secret: secret};
var userManager = new UserManager();
var sessionManager = new SessionManager();
var eventQueueManager = new EventQueueManager();
var serverClientManager = new ServerClientManager(credentials, endpoint);
var timerManager = new TimerManager();
var flowManager = new FlowManager();
sessionManager.setEventQueueManager(eventQueueManager);
eventQueueManager.setSessionManager(sessionManager);
eventQueueManager.setUserManager(userManager);
eventQueueManager.setOnFlushEvents(function(events) {
serverClientManager.flushEvents(events, function(response) {
if (response.status === 205) {
// need to start the autologin flow!
userManager.startAutologinFlow(credentials, function(err, isLogged) {
if (err || !isLogged) {
loginCallback(err, isLogged);
}
});
}
});
});
timerManager.setEventQueueManager(eventQueueManager);
flowManager.setEventQueueManager(eventQueueManager);
userManager.setEventQueueManager(eventQueueManager);
userManager.setServerClientManager(serverClientManager);
var isRavenConfigured = false;
function setRavenUrl(ravenUrl) {
if (!Raven) {
throw new Error('Raven should be included');
}
isRavenConfigured = true;
Raven.config(ravenUrl).install();
}
if (ravenUrl) {
setRavenUrl(ravenUrl);
}
var newtonUserTokenFromLoginFlow = getParameterByName('_nt');
var userObjectUserTokenFromLoginFlow = getParameterByName('_uot');
var errorFromLoginFlow = getParameterByName('_ne');
var isReturningFromLoginFlow = !!((newtonUserTokenFromLoginFlow && userObjectUserTokenFromLoginFlow) || errorFromLoginFlow);
if (isReturningFromLoginFlow) {
if (window.history) {
replaceState(window.history.state, document.title, removeDomain(getUrlWithoutParamenter(getUrlWithoutParamenter(getUrlWithoutParamenter(getCurrentUrl(), '_ne'), '_nt'), '_uot')));
}
if (newtonUserTokenFromLoginFlow && userObjectUserTokenFromLoginFlow) {
userManager.setNewtonToken(newtonUserTokenFromLoginFlow);
userManager.setUserObjectToken(userObjectUserTokenFromLoginFlow);
var oauthProvider = __newtonUtils.cookie.getItem(OAUTH_LOGIN_PROVIDER);
if (oauthProvider && !errorFromLoginFlow) {
setTimeout(function() {
var ev = {
event_type: 'identify',
login_type: oauthProvider
};
eventQueueManager.addEvent(ev);
}, 0);
}
}
}
sessionManager.start();
userManager.start(credentials, function(err, isLogged) {
if (err || !isLogged) {
loginCallback(err, isLogged);
}
});
var oauthProviders = [
'facebook',
'google'
];
var loginCallback = function() { };
return {
logInfo: function(line) { },
logNotice: function(line) { },
logWarning: function(line) { },
logError: function(line) { },
makeABTestDecision: function(name, defaultDecision, params, callback) { },
ABTestGoalAchived: function(abTestId) { },
IAPValidateTransaction: function(params, receip, callback) { },
logDebug: function(line) { },
startSetTransaction: function() {
return createUserSetTransaction();
},
getUserProperty: function(key, callback) {
},
/**
* Log the user out
* @method
* @instance
* @memberOf NewtonSDK
*/
userLogout: function() {
if (userManager.isUserLogged()) {
eventQueueManager.addEvent({
event_type: 'logout'
});
}
userManager.logout();
},
isLoginFlowRunning: function() {
return false;
},
/**
* Return a list of available and supported oauth provider
* @method
* @instance
* @memberOf NewtonSDK
*
* @return {Array} the list
*/
getOAuthProviders: function() {
return oauthProviders;
},
/**
* Login utilities
* @class LoginBuilder
* @name LoginBuilder
* @private
*/
/**
* Custom Login
* @class CustomLoginFlow
* @name CustomLoginFlow
* @private
*/
/**
* Custom Login
* @class OAuthLoginFlow
* @name OAuthLoginFlow
* @private
*/
/**
* Return a login builder
* @method
* @instance
* @memberOf NewtonSDK
*
* @return LoginBuilder
*/
getLoginBuilder: function() {
var props = {};
function setWrapper(c) {
return function(k) {
props[c] = k;
return this;
};
}
return {
/**
* Set callback
* @method
* @instance
* @memberOf LoginBuilder
* @param callback
*
* @return {LoginBuilder} the same instance
*/
setCallback: setWrapper('callback'),
/**
* Set login data. Useful to track campaign parameters
* @method
* @instance
* @memberOf LoginBuilder
* @param {SimpleObject} loginData - the custom data for the login event
*
* @return {LoginBuilder} the same instance
*/
setLoginData: setWrapper('custom_data'),
/**
* Set custom id. Used in custom flow.
* @method
* @instance
* @memberOf LoginBuilder
* @param {string} customID - custom user id
*
* @return {LoginBuilder} the same instance
*/
setCustomID: setWrapper('customId'),
/**
* Set return url. Used in OAuthLoginFlow
* @method
* @instance
* @memberOf LoginBuilder
* @param {string} returnUrl
*
* @return {LoginBuilder} the same instance
*/
setReturnUrl: setWrapper('returnUrl'),
/**
* Set return url. Used in OAuthLoginFlow
* @method
* @instance
* @memberOf LoginBuilder
* @param {string} errorUrl
*
* @return {LoginBuilder} the same instance
*/
setErrorUrl: setWrapper('errorUrl'),
/**
* Set return url. Used in OAuthLoginFlow
* @method
* @instance
* @memberOf LoginBuilder
* @param {string} waitingUrl
*
* @return {LoginBuilder} the same instance
*/
setWaitingUrl: setWrapper('waitingUrl'),
/**
* Set oauth provider needed for OAuthLoginFlow
* @method
* @instance
* @memberOf LoginBuilder
* @param {string} oauth provider
*
* @return {LoginBuilder} the same instance
*/
setOAuthProvider: setWrapper('oauthProvider'),
/**
* Persist the custom login among the page refreshing. A session cookie is set
* @method
* @instance
* @memberOf LoginBuilder
*
* @return {LoginBuilder} the same instance
*/
setAllowCustomLoginSession: setWrapper('allowCustomLoginSession'),
/**
* Create a custom login flow.
* @method
* @instance
* @memberOf LoginBuilder
*
* @return {CustomLoginFlow}
*/
getCustomFlow: function() {
if (!props.callback || !props.customId) {
throw new Error('callback and customId must be provided');
}
assertCustomDataIsNullOrIsInstanceOfSimpleObject(props.custom_data);
// Storing here to prevent the changes
var customId = 'C_' + props.customId;
var cd = props.custom_data;
var callback = props.callback;
var allowCustomLoginSession = props.allowCustomLoginSession;
return {
/**
* Return a string representation
* @method
* @instance
* @memberOf CustomLoginFlow
*
* @return {string}
*/
toString : function() {
return 'CustomLoginFlow(' + JSON.stringify(props) + ')';
},
/**
* Start the custom login flow
* @method
* @instance
* @memberOf CustomLoginFlow
*
*/
startLoginFlow: function() {
if (userManager.isUserLogged()) {
throw new Error('User already logged in');
}
userManager.setCustomToken(customId, allowCustomLoginSession);
var ev = {
event_type: 'identify',
login_type: 'CUSTOM'
};
if (cd) {
ev.custom_data = cd.toJSONObject();
}
eventQueueManager.addEvent(ev);
setTimeout(callback, 0);
}
};
},
/**
* Create a custom login flow.
* @method
* @instance
* @memberOf LoginBuilder
*
* @return {OAuthLoginFlow}
*/
getOAuthFlow: function() {
assertCustomDataIsNullOrIsInstanceOfSimpleObject(props.custom_data);
var oauthProvider = props.oauthProvider;
if (oauthProviders.indexOf(oauthProvider) < 0) {
throw new Error('Unkown oauth provider');
}
var returnUrl = props.returnUrl || getCurrentUrl();
assertStringIsAValidUrl(returnUrl);
var errorUrl = props.errorUrl || getCurrentUrl();
assertStringIsAValidUrl(errorUrl);
var waitingUrl = props.waitingUrl;
if (waitingUrl) {
assertStringIsAValidUrl(waitingUrl);
}
return {
/**
* Return a string representation
* @method
* @instance
* @memberOf OAuthLoginFlow
*
* @return {string}
*/
toString : function() {
return 'OAuthLoginFlow(' + JSON.stringify(props) + ')';
},
/**
* Start the oauth login flow
* @method
* @instance
* @memberOf OAuthLoginFlow
*/
startLoginFlow: function() {
var userId = userManager.isUserLogged() ? userManager.getCurrentUserToken() : undefined;
__newtonUtils.cookie.setItem(OAUTH_LOGIN_PROVIDER, oauthProvider);
var url = getAuthEndpoint(secret) + '/' + oauthProvider + '/start';
url = url + '?';
if (waitingUrl) {
url = url + 'waitingUrl=' + encodeURIComponent(waitingUrl) + '&';
}
url = url + 'returnUrl=' + encodeURIComponent(returnUrl) +
'&errorUrl=' + encodeURIComponent(errorUrl) +
'&application=' + hostname;
if (userId) {
url += '&userId=' + encodeURIComponent(userId);
}
// used to set cookie or localStorage
setTimeout(function() {
redirectTo(url);
}, 0);
}
};
}
};
},
/**
* Invoked on login complete successfully or not.
* @method
* @instance
* @memberOf NewtonSDK
*/
setLoginCallback: function(callback) {
loginCallback = callback;
if (isReturningFromLoginFlow) {
var error;
if (errorFromLoginFlow) {
error = new Error(errorFromLoginFlow);
}
loginCallback(error);
}
},
/**
* Finalize login flow
* Used only for customized waiting page
* @method
* @instance
* @memberOf NewtonSDK
*/
finalizeLoginFlow: function() {
var state = getParameterByName('state');
var oauthProvider = (state || '').split('_')[0];
if (!state || !oauthProvider) {
if (/^auth-api(-sandbox)?2?\.newton\.pm$/.test(getHostname())) {
return redirectTo('https://' + getHostname() + '/error_page.html?step=2');
}
return loginCallback(new Error('No login flow found'));
}
var url = getAuthEndpoint(secret) + '/' + oauthProvider + '/finalize';
var body = {
code: getParameterByName('code'),
state: state
};
var http = new HttpWrapper();
http.makeRequestWithAResponse(url, body, function(response) {
var parsed;
try {
parsed = JSON.parse(response.responseText);
} catch(e) {
console.log(e);
return loginCallback(new Error('Unknown error'));
}
// newton default loading page
if (/^auth-api(-sandbox)?2?\.newton\.pm$/.test(getHostname())) {
return redirectTo(parsed.url);
}
if (parsed.error) {
return loginCallback(new Error(parsed.error));
}
if (!parsed.uoToken || !parsed.newtonToken) {
return loginCallback(new Error('Unknown error'));
}
userManager.setUserObjectToken(parsed.uoToken);
userManager.setNewtonToken(parsed.newtonToken);
var ev = {
event_type: 'identify',
login_type: oauthProvider
};
eventQueueManager.addEvent(ev);
setTimeout(loginCallback, 0);
});
},
/**
* Return true if Newton has a set userId
* @method
* @instance
* @memberOf NewtonSDK
*
* @return {Boolean} - true if the userId is set
*/
isUserLogged: function() {
return userManager.isUserLogged();
},
/**
* This callback is used on NewtonSDK#isUserLoggedAsyncCallback
* @callback NewtonSDK~isUserLoggedAsyncCallback
* @param {Error} [err] - If something was wrong
* @param {Boolean} [isLogged] - If the user is logged
*/
/**
* Make sure the knowledge about login user state
* @method
* @instance
* @memberOf NewtonSDK
* @param {NewtonSDK~isUserLoggedAsyncCallback}
*/
isUserLoggedAsync: function(callback) {
return userManager.startAutologinFlow(credentials, callback);
},
/**
* Return The user token. This is not the user Id
* @method
* @instance
* @memberOf NewtonSDK
*
* @return {String} - The user token
*/
getUserToken: function() {
return userManager.getCurrentUserToken();
},
/**
* This callback is used on NewtonSDK#flushEvents
* @callback NewtonSDK~flushEventsCallback
* @param {Error} [err] - If something was wrong
*/
/**
* Flush event queue
* @method
* @instance
* @memberOf NewtonSDK
* @deprecated
*
* @param {NewtonSDK~flushEventsCallback} callback - The callback called when the request is done
*/
flushEvents: function(callback) {
callback = callback || (function() {});
callback();
},
/**
* Log exception to Sentry if is configured
* @method
* @instance
* @memberOf NewtonSDK
* @throws Throw if there's not a begun flow
*
* @param {Error} error - The error
*/
logException: function(error) {
if (isRavenConfigured) {
Raven.captureException(error);
} else {
console.log('Raven is not configured yet.', error);
}
},
/**
* End a flow with a cancellation
* @method
* @instance
* @memberOf NewtonSDK
* @throws Throw if there's not a begun flow
*
* @param {String} name - The reason of the cancellation
* @param {SimpleObject} [customData] - Some data
*/
flowCancel: function(reason, customData) {
_log('flow cancel', arguments);
assertIsALabel(reason);
assertCustomDataIsNullOrIsInstanceOfSimpleObject(customData);
flowManager.flowCancel(reason + '', getHashFromSimpleObjectOrUndefined(customData));
},
/**
* End a flow with a failure
* @method
* @instance
* @memberOf NewtonSDK
* @throws Throw if there's not a begun flow
*
* @param {String} name - The reason of the failure
* @param {SimpleObject} [customData] - Some data
*/
flowFail: function(reason, customData) {
_log('flow fails', arguments);
assertIsALabel(reason);
assertCustomDataIsNullOrIsInstanceOfSimpleObject(customData);
flowManager.flowFail(reason +'', getHashFromSimpleObjectOrUndefined(customData));
},
/**
* End a flow with a success
* @method
* @instance
* @memberOf NewtonSDK
* @throws Throw if there's not a begun flow
*
* @param {String} [name="ok"] - A name that identify the end
* @param {SimpleObject} [customData] - Some data
*/
flowSucceed: function(reason, customData) {
_log('flow succeeds', arguments);
if (!reason || reason.constructor === SimpleObject) {
customData = reason;
reason = 'ok';
}
assertIsALabel(reason);
assertCustomDataIsNullOrIsInstanceOfSimpleObject(customData);
var cd = getHashFromSimpleObjectOrUndefined(customData);
flowManager.flowSucceed(reason + '', cd);
},
/**
* Make a step for a flow
* @method
* @instance
* @memberOf NewtonSDK
* @throws Throw if there's not a begun flow
*
* @param {String} name - The name of the step
* @param {SimpleObject} [customData] - Some data
*/
flowStep: function(stepName, customData) {
_log('flow step', arguments);
assertIsALabel(stepName);
assertCustomDataIsNullOrIsInstanceOfSimpleObject(customData);
flowManager.flowStep(stepName + '', getHashFromSimpleObjectOrUndefined(customData));
},
/**
* Begin a flow. Close a previous flow with a fail if need.
* @method
* @instance
* @memberOf NewtonSDK
*
* @param {String} name - The name of the flow
* @param {SimpleObject} [customData] - Some data
*/
flowBegin: function(name, customData) {
_log('flow begin', arguments);
assertIsALabel(name);
assertCustomDataIsNullOrIsInstanceOfSimpleObject(customData);
flowManager.flowBegin(name + '', getHashFromSimpleObjectOrUndefined(customData));
},
/**
* Stop a timed event.
* The event has a merge of this customData and the customData passed on timedEventStart method
* @method
* @instance
* @memberOf NewtonSDK
* @throws Throw if there's no started event the given name
*
* @param {String} name - The name of this event
* @param {SimpleObject} [customData] - Some data
*/
timedEventStop: function(name, customData) {
_log('stop timed event', arguments);
assertIsALabel(name);
assertCustomDataIsNullOrIsInstanceOfSimpleObject(customData);
timerManager.stopTimer(name + '', getHashFromSimpleObjectOrUndefined(customData));
},
/**
* Start a timed event
* @method
* @instance
* @memberOf NewtonSDK
*
* @param {String} name - The name of the timed event
* @param {SimpleObject} [customData] - Some data
*/
timedEventStart: function(name, customData) {
_log('start timed event', arguments);
assertIsALabel(name);
assertCustomDataIsNullOrIsInstanceOfSimpleObject(customData);
timerManager.startTimer(name + '', getHashFromSimpleObjectOrUndefined(customData));
},
/**
* Queue an event
* @method
* @instance
* @memberOf NewtonSDK
*
* @param {String} name - The name of this event
* @param {SimpleObject} [customData] - Some data attached to this event
*/
sendEvent: function(name, customData) {
_log('add event', arguments);
assertIsALabel(name);
assertCustomDataIsNullOrIsInstanceOfSimpleObject(customData);
var ev = {
event_type: 'custom event',
name: name + ''
};
var cd = getHashFromSimpleObjectOrUndefined(customData);
if (cd) {
ev.custom_data = cd;
}
eventQueueManager.addEvent(ev);
},
setDebug: function(_debug) {
debug = !!_debug;
}
};
}
var newtonInstance;
/**
* The Newton SDK public Interface
* @namespace Newton
*/
var publicInterface = {
/**
* Get the shared instance
* @throws {Error} - Throw if getSharedInstanceWithConfig hasn't been called
* @memberOf Newton
* @return {NewtonSDK}
*/
getSharedInstance: function() {
if (!newtonInstance) {
throw new Error('Call getSharedInstanceWithConfig before');
}
return newtonInstance;
},
/**
* Get the shared instance. Create one if no sharedInstance found
* @memberOf Newton
* @return {NewtonSDK}
*/
getSharedInstanceWithConfig: function(conf_or_secret) {
if (!newtonInstance) {
newtonInstance = createNewtonInstance(conf_or_secret);
}
return newtonInstance;
},
/**
* SimpleObject
* @memberOf Newton
* @alias SimpleObject
*/
SimpleObject: SimpleObject,
/**
* Get current Newton version
* @memberOf Newton
* @return {String}
*/
getVersionString: function getVersionString() {
return [
autorevision.VCS_TAG,
autorevision.VCS_SHORT_HASH,
autorevision.VCS_DATE
].join(' ');
},
__jsonpCallback: function(param) {
var callback = jsonpCallbacks[param.callbackId];
delete jsonpCallbacks[param.callbackId];
callback({status: param.statusCode, responseText: JSON.stringify(param.content)});
}
};