Skip to content
This repository was archived by the owner on Sep 3, 2022. It is now read-only.

feat(ids): check LocalStorage when cookies are not available #108

Merged
merged 1 commit into from
Mar 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 67 additions & 10 deletions lib/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,45 @@ Entity.prototype.id = function(id) {
*/

Entity.prototype._getId = function() {
var ret = this._options.persist
? this.storage().get(this._options.cookie.key)
: this._id;
return ret === undefined ? null : ret;
if (!this._options.persist) {
return this._id === undefined ? null : this._id;
}

// Check cookies.
var cookieId = this._getIdFromCookie();
if (cookieId) {
return cookieId;
}

// Check localStorage.
var lsId = this._getIdFromLocalStorage();
if (lsId) {
// Copy the id to cookies so we can read it directly from cookies next time.
this._setIdInCookies(lsId);
return lsId;
}

return null;
};

/**
* Get the entity's id from cookies.
*
* @return {String}
*/

Entity.prototype._getIdFromCookie = function() {
return this.storage().get(this._options.cookie.key);
};

/**
* Get the entity's id from cookies.
*
* @return {String}
*/

Entity.prototype._getIdFromLocalStorage = function() {
return store.get(this._options.cookie.key);
};

/**
Expand All @@ -120,12 +155,33 @@ Entity.prototype._getId = function() {

Entity.prototype._setId = function(id) {
if (this._options.persist) {
this.storage().set(this._options.cookie.key, id);
this._setIdInCookies(id);
this._setIdInLocalStorage(id);
} else {
this._id = id;
}
};

/**
* Set the entity's `id` in cookies.
*
* @param {String} id
*/

Entity.prototype._setIdInCookies = function(id) {
this.storage().set(this._options.cookie.key, id);
};

/**
* Set the entity's `id` in local storage.
*
* @param {String} id
*/

Entity.prototype._setIdInLocalStorage = function(id) {
store.set(this._options.cookie.key, id);
};

/**
* Get or set the entity's `traits`.
*
Expand Down Expand Up @@ -201,8 +257,8 @@ Entity.prototype.identify = function(id, traits) {

Entity.prototype.save = function() {
if (!this._options.persist) return false;
cookie.set(this._options.cookie.key, this.id());
store.set(this._options.localStorage.key, this.traits());
this._setId(this.id());
this._setTraits(this.traits());
return true;
};

Expand All @@ -213,7 +269,8 @@ Entity.prototype.save = function() {
Entity.prototype.logout = function() {
this.id(null);
this.traits({});
cookie.remove(this._options.cookie.key);
this.storage().remove(this._options.cookie.key);
store.remove(this._options.cookie.key);
store.remove(this._options.localStorage.key);
};

Expand All @@ -231,6 +288,6 @@ Entity.prototype.reset = function() {
*/

Entity.prototype.load = function() {
this.id(cookie.get(this._options.cookie.key));
this.traits(store.get(this._options.localStorage.key));
this.id(this.id());
this.traits(this.traits());
};
16 changes: 16 additions & 0 deletions lib/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ var debug = require('debug')('analytics:user');
var inherit = require('inherits');
var rawCookie = require('component-cookie');
var uuid = require('uuid');
var localStorage = require('./store');

/**
* User defaults
Expand Down Expand Up @@ -99,12 +100,25 @@ User.prototype.anonymousId = function(anonymousId) {
// set / remove
if (arguments.length) {
store.set('ajs_anonymous_id', anonymousId);
localStorage.set('ajs_anonymous_id', anonymousId);
return this;
}

// new
anonymousId = store.get('ajs_anonymous_id');
if (anonymousId) {
// value exist in cookie, copy it to localStorage
localStorage.set('ajs_anonymous_id', anonymousId);
// refresh cookie to extend expiry
store.set('ajs_anonymous_id', anonymousId);
return anonymousId;
}

// if anonymousId doesn't exist in cookies, check localStorage
anonymousId = localStorage.get('ajs_anonymous_id');
if (anonymousId) {
// Write to cookies if available in localStorage but not cookies
store.set('ajs_anonymous_id', anonymousId);
return anonymousId;
}

Expand All @@ -113,13 +127,15 @@ User.prototype.anonymousId = function(anonymousId) {
if (anonymousId) {
anonymousId = anonymousId.split('----')[0];
store.set('ajs_anonymous_id', anonymousId);
localStorage.set('ajs_anonymous_id', anonymousId);
store.remove('_sio');
return anonymousId;
}

// empty
anonymousId = uuid.v4();
store.set('ajs_anonymous_id', anonymousId);
localStorage.set('ajs_anonymous_id', anonymousId);
return store.get('ajs_anonymous_id');
};

Expand Down
35 changes: 33 additions & 2 deletions test/group.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ describe('group', function() {
assert(group.id() === 'gid');
assert(group.traits().trait === true);
});

it('id() should fallback to localStorage', function() {
var group = new Group();

group.id('gid');

// delete the cookie.
cookie.remove(cookieKey);

// verify cookie is deleted.
assert.equal(cookie.get(cookieKey), null);

// verify id() returns the id even when cookie is deleted.
assert.equal(group.id(), 'gid');

// verify cookie value is retored from localStorage.
assert.equal(cookie.get(cookieKey), 'gid');
});
});

describe('#id', function() {
Expand Down Expand Up @@ -226,6 +244,12 @@ describe('group', function() {
assert(cookie.get(cookieKey) === 'id');
});

it('should save an id to localStorage', function() {
group.id('id');
group.save();
assert(store.get(cookieKey) === 'id');
});

it('should save properties to local storage', function() {
group.properties({ property: true });
group.save();
Expand All @@ -249,14 +273,21 @@ describe('group', function() {
assert.deepEqual(group.properties(), {});
});

it('should clear a cookie', function() {
it('should clear id in cookie', function() {
group.id('id');
group.save();
group.logout();
assert(cookie.get(cookieKey) === null);
});

it('should clear local storage', function() {
it('should clear id in localStorage', function() {
group.id('id');
group.save();
group.logout();
assert(store.get(cookieKey) === undefined);
});

it('should clear traits in local storage', function() {
group.properties({ property: true });
group.save();
group.logout();
Expand Down
74 changes: 72 additions & 2 deletions test/user.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,24 @@ describe('user', function() {
assert(user.traits().trait === true);
});

it('id() should fallback to localStorage', function() {
var user = new User();

user.id('id');

// delete the cookie.
cookie.remove(cookieKey);

// verify cookie is deleted.
assert.equal(cookie.get(cookieKey), null);

// verify id() returns the id even when cookie is deleted.
assert.equal(user.id(), 'id');

// verify cookie value is retored from localStorage.
assert.equal(cookie.get(cookieKey), 'id');
});

it('should pick the old "_sio" anonymousId', function() {
rawCookie('_sio', 'anonymous-id----user-id');
var user = new User();
Expand Down Expand Up @@ -319,6 +337,45 @@ describe('user', function() {
};
assert(user.anonymousId() === undefined);
});

it('should set anonymousId in both cookie and localStorage', function() {
var user = new User();
user.anonymousId('anon0');
assert.equal(cookie.get('ajs_anonymous_id'), 'anon0');
assert.equal(store.get('ajs_anonymous_id'), 'anon0');
});

it('should copy value from cookie to localStorage', function() {
var user = new User();
cookie.set('ajs_anonymous_id', 'anon1');
assert.equal(user.anonymousId(), 'anon1');
assert.equal(store.get('ajs_anonymous_id'), 'anon1');
});

it('should fall back to localStorage when cookie is not set', function() {
var user = new User();

user.anonymousId('anon12');
assert.equal(cookie.get('ajs_anonymous_id'), 'anon12');

// delete the cookie
cookie.remove('ajs_anonymous_id');
assert.equal(cookie.get('ajs_anonymous_id'), null);

// verify anonymousId() returns the correct id even when there's no cookie
assert.equal(user.anonymousId(), 'anon12');

// verify cookie value is retored from localStorage
assert.equal(cookie.get('ajs_anonymous_id'), 'anon12');
});

it('should write to both cookie and localStorage when generating a new anonymousId', function() {
var user = new User();
var anonId = user.anonymousId();
assert.notEqual(anonId, null);
assert.equal(cookie.get('ajs_anonymous_id'), anonId);
assert.equal(store.get('ajs_anonymous_id'), anonId);
});
});
});

Expand Down Expand Up @@ -400,6 +457,12 @@ describe('user', function() {
assert(cookie.get(cookieKey) === 'id');
});

it('should save an id to localStorage', function() {
user.id('id');
user.save();
assert.equal(store.get(cookieKey), 'id');
});

it('should save traits to local storage', function() {
user.traits({ trait: true });
user.save();
Expand All @@ -425,14 +488,21 @@ describe('user', function() {
assert(user.traits(), {});
});

it('should clear a cookie', function() {
it('should clear id in cookie', function() {
user.id('id');
user.save();
user.logout();
assert(cookie.get(cookieKey) === null);
});

it('should clear local storage', function() {
it('should clear id in local storage', function() {
user.id('id');
user.save();
user.logout();
assert(store.get(cookieKey) === undefined);
});

it('should clear traits in local storage', function() {
user.traits({ trait: true });
user.save();
user.logout();
Expand Down