UNPKG

mixpanel-browser

Version:

The official Mixpanel JavaScript browser client library

486 lines (423 loc) 14.9 kB
/* 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 };