mixpanel-browser
Version:
The official Mixpanel JavaScript browser client library
486 lines (423 loc) • 14.9 kB
JavaScript
/* eslint camelcase: "off" */
import {
SET_ACTION,
SET_ONCE_ACTION,
UNSET_ACTION,
ADD_ACTION,
APPEND_ACTION,
REMOVE_ACTION,
UNION_ACTION
} from './api-actions';
import { _, console } from './utils';
/*
* Constants
*/
/** @const */ var SET_QUEUE_KEY = '__mps';
/** @const */ var SET_ONCE_QUEUE_KEY = '__mpso';
/** @const */ var UNSET_QUEUE_KEY = '__mpus';
/** @const */ var ADD_QUEUE_KEY = '__mpa';
/** @const */ var APPEND_QUEUE_KEY = '__mpap';
/** @const */ var REMOVE_QUEUE_KEY = '__mpr';
/** @const */ var UNION_QUEUE_KEY = '__mpu';
// This key is deprecated, but we want to check for it to see whether aliasing is allowed.
/** @const */ var PEOPLE_DISTINCT_ID_KEY = '$people_distinct_id';
/** @const */ var ALIAS_ID_KEY = '__alias';
/** @const */ var EVENT_TIMERS_KEY = '__timers';
/** @const */ var RESERVED_PROPERTIES = [
SET_QUEUE_KEY,
SET_ONCE_QUEUE_KEY,
UNSET_QUEUE_KEY,
ADD_QUEUE_KEY,
APPEND_QUEUE_KEY,
REMOVE_QUEUE_KEY,
UNION_QUEUE_KEY,
PEOPLE_DISTINCT_ID_KEY,
ALIAS_ID_KEY,
EVENT_TIMERS_KEY
];
/**
* Mixpanel Persistence Object
* @constructor
*/
var MixpanelPersistence = function(config) {
this['props'] = {};
this.campaign_params_saved = false;
if (config['persistence_name']) {
this.name = 'mp_' + config['persistence_name'];
} else {
this.name = 'mp_' + config['token'] + '_mixpanel';
}
var storage_type = config['persistence'];
if (storage_type !== 'cookie' && storage_type !== 'localStorage') {
console.critical('Unknown persistence type ' + storage_type + '; falling back to cookie');
storage_type = config['persistence'] = 'cookie';
}
if (storage_type === 'localStorage' && _.localStorage.is_supported()) {
this.storage = _.localStorage;
} else {
this.storage = _.cookie;
}
this.load();
this.update_config(config);
this.upgrade(config);
this.save();
};
MixpanelPersistence.prototype.properties = function() {
var p = {};
// Filter out reserved properties
_.each(this['props'], function(v, k) {
if (!_.include(RESERVED_PROPERTIES, k)) {
p[k] = v;
}
});
return p;
};
MixpanelPersistence.prototype.load = function() {
if (this.disabled) { return; }
var entry = this.storage.parse(this.name);
if (entry) {
this['props'] = _.extend({}, entry);
}
};
MixpanelPersistence.prototype.upgrade = function(config) {
var upgrade_from_old_lib = config['upgrade'],
old_cookie_name,
old_cookie;
if (upgrade_from_old_lib) {
old_cookie_name = 'mp_super_properties';
// Case where they had a custom cookie name before.
if (typeof(upgrade_from_old_lib) === 'string') {
old_cookie_name = upgrade_from_old_lib;
}
old_cookie = this.storage.parse(old_cookie_name);
// remove the cookie
this.storage.remove(old_cookie_name);
this.storage.remove(old_cookie_name, true);
if (old_cookie) {
this['props'] = _.extend(
this['props'],
old_cookie['all'],
old_cookie['events']
);
}
}
if (!config['cookie_name'] && config['name'] !== 'mixpanel') {
// special case to handle people with cookies of the form
// mp_TOKEN_INSTANCENAME from the first release of this library
old_cookie_name = 'mp_' + config['token'] + '_' + config['name'];
old_cookie = this.storage.parse(old_cookie_name);
if (old_cookie) {
this.storage.remove(old_cookie_name);
this.storage.remove(old_cookie_name, true);
// Save the prop values that were in the cookie from before -
// this should only happen once as we delete the old one.
this.register_once(old_cookie);
}
}
if (this.storage === _.localStorage) {
old_cookie = _.cookie.parse(this.name);
_.cookie.remove(this.name);
_.cookie.remove(this.name, true);
if (old_cookie) {
this.register_once(old_cookie);
}
}
};
MixpanelPersistence.prototype.save = function() {
if (this.disabled) { return; }
this.storage.set(
this.name,
_.JSONEncode(this['props']),
this.expire_days,
this.cross_subdomain,
this.secure,
this.cross_site,
this.cookie_domain
);
};
MixpanelPersistence.prototype.remove = function() {
// remove both domain and subdomain cookies
this.storage.remove(this.name, false, this.cookie_domain);
this.storage.remove(this.name, true, this.cookie_domain);
};
// removes the storage entry and deletes all loaded data
// forced name for tests
MixpanelPersistence.prototype.clear = function() {
this.remove();
this['props'] = {};
};
/**
* @param {Object} props
* @param {*=} default_value
* @param {number=} days
*/
MixpanelPersistence.prototype.register_once = function(props, default_value, days) {
if (_.isObject(props)) {
if (typeof(default_value) === 'undefined') { default_value = 'None'; }
this.expire_days = (typeof(days) === 'undefined') ? this.default_expiry : days;
_.each(props, function(val, prop) {
if (!this['props'].hasOwnProperty(prop) || this['props'][prop] === default_value) {
this['props'][prop] = val;
}
}, this);
this.save();
return true;
}
return false;
};
/**
* @param {Object} props
* @param {number=} days
*/
MixpanelPersistence.prototype.register = function(props, days) {
if (_.isObject(props)) {
this.expire_days = (typeof(days) === 'undefined') ? this.default_expiry : days;
_.extend(this['props'], props);
this.save();
return true;
}
return false;
};
MixpanelPersistence.prototype.unregister = function(prop) {
if (prop in this['props']) {
delete this['props'][prop];
this.save();
}
};
MixpanelPersistence.prototype.update_campaign_params = function() {
if (!this.campaign_params_saved) {
this.register_once(_.info.campaignParams());
this.campaign_params_saved = true;
}
};
MixpanelPersistence.prototype.update_search_keyword = function(referrer) {
this.register(_.info.searchInfo(referrer));
};
// EXPORTED METHOD, we test this directly.
MixpanelPersistence.prototype.update_referrer_info = function(referrer) {
// If referrer doesn't exist, we want to note the fact that it was type-in traffic.
this.register_once({
'$initial_referrer': referrer || '$direct',
'$initial_referring_domain': _.info.referringDomain(referrer) || '$direct'
}, '');
};
MixpanelPersistence.prototype.get_referrer_info = function() {
return _.strip_empty_properties({
'$initial_referrer': this['props']['$initial_referrer'],
'$initial_referring_domain': this['props']['$initial_referring_domain']
});
};
// safely fills the passed in object with stored properties,
// does not override any properties defined in both
// returns the passed in object
MixpanelPersistence.prototype.safe_merge = function(props) {
_.each(this['props'], function(val, prop) {
if (!(prop in props)) {
props[prop] = val;
}
});
return props;
};
MixpanelPersistence.prototype.update_config = function(config) {
this.default_expiry = this.expire_days = config['cookie_expiration'];
this.set_disabled(config['disable_persistence']);
this.set_cookie_domain(config['cookie_domain']);
this.set_cross_site(config['cross_site_cookie']);
this.set_cross_subdomain(config['cross_subdomain_cookie']);
this.set_secure(config['secure_cookie']);
};
MixpanelPersistence.prototype.set_disabled = function(disabled) {
this.disabled = disabled;
if (this.disabled) {
this.remove();
} else {
this.save();
}
};
MixpanelPersistence.prototype.set_cookie_domain = function(cookie_domain) {
if (cookie_domain !== this.cookie_domain) {
this.remove();
this.cookie_domain = cookie_domain;
this.save();
}
};
MixpanelPersistence.prototype.set_cross_site = function(cross_site) {
if (cross_site !== this.cross_site) {
this.cross_site = cross_site;
this.remove();
this.save();
}
};
MixpanelPersistence.prototype.set_cross_subdomain = function(cross_subdomain) {
if (cross_subdomain !== this.cross_subdomain) {
this.cross_subdomain = cross_subdomain;
this.remove();
this.save();
}
};
MixpanelPersistence.prototype.get_cross_subdomain = function() {
return this.cross_subdomain;
};
MixpanelPersistence.prototype.set_secure = function(secure) {
if (secure !== this.secure) {
this.secure = secure ? true : false;
this.remove();
this.save();
}
};
MixpanelPersistence.prototype._add_to_people_queue = function(queue, data) {
var q_key = this._get_queue_key(queue),
q_data = data[queue],
set_q = this._get_or_create_queue(SET_ACTION),
set_once_q = this._get_or_create_queue(SET_ONCE_ACTION),
unset_q = this._get_or_create_queue(UNSET_ACTION),
add_q = this._get_or_create_queue(ADD_ACTION),
union_q = this._get_or_create_queue(UNION_ACTION),
remove_q = this._get_or_create_queue(REMOVE_ACTION, []),
append_q = this._get_or_create_queue(APPEND_ACTION, []);
if (q_key === SET_QUEUE_KEY) {
// Update the set queue - we can override any existing values
_.extend(set_q, q_data);
// if there was a pending increment, override it
// with the set.
this._pop_from_people_queue(ADD_ACTION, q_data);
// if there was a pending union, override it
// with the set.
this._pop_from_people_queue(UNION_ACTION, q_data);
this._pop_from_people_queue(UNSET_ACTION, q_data);
} else if (q_key === SET_ONCE_QUEUE_KEY) {
// only queue the data if there is not already a set_once call for it.
_.each(q_data, function(v, k) {
if (!(k in set_once_q)) {
set_once_q[k] = v;
}
});
this._pop_from_people_queue(UNSET_ACTION, q_data);
} else if (q_key === UNSET_QUEUE_KEY) {
_.each(q_data, function(prop) {
// undo previously-queued actions on this key
_.each([set_q, set_once_q, add_q, union_q], function(enqueued_obj) {
if (prop in enqueued_obj) {
delete enqueued_obj[prop];
}
});
_.each(append_q, function(append_obj) {
if (prop in append_obj) {
delete append_obj[prop];
}
});
unset_q[prop] = true;
});
} else if (q_key === ADD_QUEUE_KEY) {
_.each(q_data, function(v, k) {
// If it exists in the set queue, increment
// the value
if (k in set_q) {
set_q[k] += v;
} else {
// If it doesn't exist, update the add
// queue
if (!(k in add_q)) {
add_q[k] = 0;
}
add_q[k] += v;
}
}, this);
this._pop_from_people_queue(UNSET_ACTION, q_data);
} else if (q_key === UNION_QUEUE_KEY) {
_.each(q_data, function(v, k) {
if (_.isArray(v)) {
if (!(k in union_q)) {
union_q[k] = [];
}
// We may send duplicates, the server will dedup them.
union_q[k] = union_q[k].concat(v);
}
});
this._pop_from_people_queue(UNSET_ACTION, q_data);
} else if (q_key === REMOVE_QUEUE_KEY) {
remove_q.push(q_data);
this._pop_from_people_queue(APPEND_ACTION, q_data);
} else if (q_key === APPEND_QUEUE_KEY) {
append_q.push(q_data);
this._pop_from_people_queue(UNSET_ACTION, q_data);
}
console.log('MIXPANEL PEOPLE REQUEST (QUEUED, PENDING IDENTIFY):');
console.log(data);
this.save();
};
MixpanelPersistence.prototype._pop_from_people_queue = function(queue, data) {
var q = this._get_queue(queue);
if (!_.isUndefined(q)) {
_.each(data, function(v, k) {
if (queue === APPEND_ACTION || queue === REMOVE_ACTION) {
// list actions: only remove if both k+v match
// e.g. remove should not override append in a case like
// append({foo: 'bar'}); remove({foo: 'qux'})
_.each(q, function(queued_action) {
if (queued_action[k] === v) {
delete queued_action[k];
}
});
} else {
delete q[k];
}
}, this);
this.save();
}
};
MixpanelPersistence.prototype._get_queue_key = function(queue) {
if (queue === SET_ACTION) {
return SET_QUEUE_KEY;
} else if (queue === SET_ONCE_ACTION) {
return SET_ONCE_QUEUE_KEY;
} else if (queue === UNSET_ACTION) {
return UNSET_QUEUE_KEY;
} else if (queue === ADD_ACTION) {
return ADD_QUEUE_KEY;
} else if (queue === APPEND_ACTION) {
return APPEND_QUEUE_KEY;
} else if (queue === REMOVE_ACTION) {
return REMOVE_QUEUE_KEY;
} else if (queue === UNION_ACTION) {
return UNION_QUEUE_KEY;
} else {
console.error('Invalid queue:', queue);
}
};
MixpanelPersistence.prototype._get_queue = function(queue) {
return this['props'][this._get_queue_key(queue)];
};
MixpanelPersistence.prototype._get_or_create_queue = function(queue, default_val) {
var key = this._get_queue_key(queue);
default_val = _.isUndefined(default_val) ? {} : default_val;
return this['props'][key] || (this['props'][key] = default_val);
};
MixpanelPersistence.prototype.set_event_timer = function(event_name, timestamp) {
var timers = this['props'][EVENT_TIMERS_KEY] || {};
timers[event_name] = timestamp;
this['props'][EVENT_TIMERS_KEY] = timers;
this.save();
};
MixpanelPersistence.prototype.remove_event_timer = function(event_name) {
var timers = this['props'][EVENT_TIMERS_KEY] || {};
var timestamp = timers[event_name];
if (!_.isUndefined(timestamp)) {
delete this['props'][EVENT_TIMERS_KEY][event_name];
this.save();
}
return timestamp;
};
export {
MixpanelPersistence,
SET_QUEUE_KEY,
SET_ONCE_QUEUE_KEY,
UNSET_QUEUE_KEY,
ADD_QUEUE_KEY,
APPEND_QUEUE_KEY,
REMOVE_QUEUE_KEY,
UNION_QUEUE_KEY,
PEOPLE_DISTINCT_ID_KEY,
ALIAS_ID_KEY,
EVENT_TIMERS_KEY
};