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

Commit b27641a

Browse files
committed
feat(ids): check LocalStorage when cookies are not available
This updates entity.js and user.js to dual write ids to localStorage and the cookies. This applies to userId, groupId and anonymousIds. Reads will check the cookie first, and fallback to the localStorage when a cookie is not available. When cookies are not available during reads, and localStorage is, we write the value to the cookie as well.
1 parent b7a3737 commit b27641a

File tree

4 files changed

+188
-14
lines changed

4 files changed

+188
-14
lines changed

lib/entity.js

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,45 @@ Entity.prototype.id = function(id) {
106106
*/
107107

108108
Entity.prototype._getId = function() {
109-
var ret = this._options.persist
110-
? this.storage().get(this._options.cookie.key)
111-
: this._id;
112-
return ret === undefined ? null : ret;
109+
if (!this._options.persist) {
110+
return this._id === undefined ? null : this._id;
111+
}
112+
113+
// Check cookies.
114+
var cookieId = this._getIdFromCookie();
115+
if (cookieId) {
116+
return cookieId;
117+
}
118+
119+
// Check localStorage.
120+
var lsId = this._getIdFromLocalStorage();
121+
if (lsId) {
122+
// Copy the id to cookies so we can read it directly from cookies next time.
123+
this._setIdInCookies(lsId);
124+
return lsId;
125+
}
126+
127+
return null;
128+
};
129+
130+
/**
131+
* Get the entity's id from cookies.
132+
*
133+
* @return {String}
134+
*/
135+
136+
Entity.prototype._getIdFromCookie = function() {
137+
return this.storage().get(this._options.cookie.key);
138+
};
139+
140+
/**
141+
* Get the entity's id from cookies.
142+
*
143+
* @return {String}
144+
*/
145+
146+
Entity.prototype._getIdFromLocalStorage = function() {
147+
return store.get(this._options.cookie.key);
113148
};
114149

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

121156
Entity.prototype._setId = function(id) {
122157
if (this._options.persist) {
123-
this.storage().set(this._options.cookie.key, id);
158+
this._setIdInCookies(id);
159+
this._setIdInLocalStorage(id);
124160
} else {
125161
this._id = id;
126162
}
127163
};
128164

165+
/**
166+
* Set the entity's `id` in cookies.
167+
*
168+
* @param {String} id
169+
*/
170+
171+
Entity.prototype._setIdInCookies = function(id) {
172+
this.storage().set(this._options.cookie.key, id);
173+
};
174+
175+
/**
176+
* Set the entity's `id` in local storage.
177+
*
178+
* @param {String} id
179+
*/
180+
181+
Entity.prototype._setIdInLocalStorage = function(id) {
182+
store.set(this._options.cookie.key, id);
183+
};
184+
129185
/**
130186
* Get or set the entity's `traits`.
131187
*
@@ -201,8 +257,8 @@ Entity.prototype.identify = function(id, traits) {
201257

202258
Entity.prototype.save = function() {
203259
if (!this._options.persist) return false;
204-
cookie.set(this._options.cookie.key, this.id());
205-
store.set(this._options.localStorage.key, this.traits());
260+
this._setId(this.id());
261+
this._setTraits(this.traits());
206262
return true;
207263
};
208264

@@ -213,7 +269,8 @@ Entity.prototype.save = function() {
213269
Entity.prototype.logout = function() {
214270
this.id(null);
215271
this.traits({});
216-
cookie.remove(this._options.cookie.key);
272+
this.storage().remove(this._options.cookie.key);
273+
store.remove(this._options.cookie.key);
217274
store.remove(this._options.localStorage.key);
218275
};
219276

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

233290
Entity.prototype.load = function() {
234-
this.id(cookie.get(this._options.cookie.key));
235-
this.traits(store.get(this._options.localStorage.key));
291+
this.id(this.id());
292+
this.traits(this.traits());
236293
};

lib/user.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ var debug = require('debug')('analytics:user');
1111
var inherit = require('inherits');
1212
var rawCookie = require('component-cookie');
1313
var uuid = require('uuid');
14+
var localStorage = require('./store');
1415

1516
/**
1617
* User defaults
@@ -99,12 +100,25 @@ User.prototype.anonymousId = function(anonymousId) {
99100
// set / remove
100101
if (arguments.length) {
101102
store.set('ajs_anonymous_id', anonymousId);
103+
localStorage.set('ajs_anonymous_id', anonymousId);
102104
return this;
103105
}
104106

105107
// new
106108
anonymousId = store.get('ajs_anonymous_id');
107109
if (anonymousId) {
110+
// value exist in cookie, copy it to localStorage
111+
localStorage.set('ajs_anonymous_id', anonymousId);
112+
// refresh cookie to extend expiry
113+
store.set('ajs_anonymous_id', anonymousId);
114+
return anonymousId;
115+
}
116+
117+
// if anonymousId doesn't exist in cookies, check localStorage
118+
anonymousId = localStorage.get('ajs_anonymous_id');
119+
if (anonymousId) {
120+
// Write to cookies if available in localStorage but not cookies
121+
store.set('ajs_anonymous_id', anonymousId);
108122
return anonymousId;
109123
}
110124

@@ -113,13 +127,15 @@ User.prototype.anonymousId = function(anonymousId) {
113127
if (anonymousId) {
114128
anonymousId = anonymousId.split('----')[0];
115129
store.set('ajs_anonymous_id', anonymousId);
130+
localStorage.set('ajs_anonymous_id', anonymousId);
116131
store.remove('_sio');
117132
return anonymousId;
118133
}
119134

120135
// empty
121136
anonymousId = uuid.v4();
122137
store.set('ajs_anonymous_id', anonymousId);
138+
localStorage.set('ajs_anonymous_id', anonymousId);
123139
return store.get('ajs_anonymous_id');
124140
};
125141

test/group.test.js

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,24 @@ describe('group', function() {
3939
assert(group.id() === 'gid');
4040
assert(group.traits().trait === true);
4141
});
42+
43+
it('id() should fallback to localStorage', function() {
44+
var group = new Group();
45+
46+
group.id('gid');
47+
48+
// delete the cookie.
49+
cookie.remove(cookieKey);
50+
51+
// verify cookie is deleted.
52+
assert.equal(cookie.get(cookieKey), null);
53+
54+
// verify id() returns the id even when cookie is deleted.
55+
assert.equal(group.id(), 'gid');
56+
57+
// verify cookie value is retored from localStorage.
58+
assert.equal(cookie.get(cookieKey), 'gid');
59+
});
4260
});
4361

4462
describe('#id', function() {
@@ -226,6 +244,12 @@ describe('group', function() {
226244
assert(cookie.get(cookieKey) === 'id');
227245
});
228246

247+
it('should save an id to localStorage', function() {
248+
group.id('id');
249+
group.save();
250+
assert(store.get(cookieKey) === 'id');
251+
});
252+
229253
it('should save properties to local storage', function() {
230254
group.properties({ property: true });
231255
group.save();
@@ -249,14 +273,21 @@ describe('group', function() {
249273
assert.deepEqual(group.properties(), {});
250274
});
251275

252-
it('should clear a cookie', function() {
276+
it('should clear id in cookie', function() {
253277
group.id('id');
254278
group.save();
255279
group.logout();
256280
assert(cookie.get(cookieKey) === null);
257281
});
258282

259-
it('should clear local storage', function() {
283+
it('should clear id in localStorage', function() {
284+
group.id('id');
285+
group.save();
286+
group.logout();
287+
assert(store.get(cookieKey) === undefined);
288+
});
289+
290+
it('should clear traits in local storage', function() {
260291
group.properties({ property: true });
261292
group.save();
262293
group.logout();

test/user.test.js

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,24 @@ describe('user', function() {
4343
assert(user.traits().trait === true);
4444
});
4545

46+
it('id() should fallback to localStorage', function() {
47+
var user = new User();
48+
49+
user.id('id');
50+
51+
// delete the cookie.
52+
cookie.remove(cookieKey);
53+
54+
// verify cookie is deleted.
55+
assert.equal(cookie.get(cookieKey), null);
56+
57+
// verify id() returns the id even when cookie is deleted.
58+
assert.equal(user.id(), 'id');
59+
60+
// verify cookie value is retored from localStorage.
61+
assert.equal(cookie.get(cookieKey), 'id');
62+
});
63+
4664
it('should pick the old "_sio" anonymousId', function() {
4765
rawCookie('_sio', 'anonymous-id----user-id');
4866
var user = new User();
@@ -319,6 +337,45 @@ describe('user', function() {
319337
};
320338
assert(user.anonymousId() === undefined);
321339
});
340+
341+
it('should set anonymousId in both cookie and localStorage', function() {
342+
var user = new User();
343+
user.anonymousId('anon0');
344+
assert.equal(cookie.get('ajs_anonymous_id'), 'anon0');
345+
assert.equal(store.get('ajs_anonymous_id'), 'anon0');
346+
});
347+
348+
it('should copy value from cookie to localStorage', function() {
349+
var user = new User();
350+
cookie.set('ajs_anonymous_id', 'anon1');
351+
assert.equal(user.anonymousId(), 'anon1');
352+
assert.equal(store.get('ajs_anonymous_id'), 'anon1');
353+
});
354+
355+
it('should fall back to localStorage when cookie is not set', function() {
356+
var user = new User();
357+
358+
user.anonymousId('anon12');
359+
assert.equal(cookie.get('ajs_anonymous_id'), 'anon12');
360+
361+
// delete the cookie
362+
cookie.remove('ajs_anonymous_id');
363+
assert.equal(cookie.get('ajs_anonymous_id'), null);
364+
365+
// verify anonymousId() returns the correct id even when there's no cookie
366+
assert.equal(user.anonymousId(), 'anon12');
367+
368+
// verify cookie value is retored from localStorage
369+
assert.equal(cookie.get('ajs_anonymous_id'), 'anon12');
370+
});
371+
372+
it('should write to both cookie and localStorage when generating a new anonymousId', function() {
373+
var user = new User();
374+
var anonId = user.anonymousId();
375+
assert.notEqual(anonId, null);
376+
assert.equal(cookie.get('ajs_anonymous_id'), anonId);
377+
assert.equal(store.get('ajs_anonymous_id'), anonId);
378+
});
322379
});
323380
});
324381

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

460+
it('should save an id to localStorage', function() {
461+
user.id('id');
462+
user.save();
463+
assert.equal(store.get(cookieKey), 'id');
464+
});
465+
403466
it('should save traits to local storage', function() {
404467
user.traits({ trait: true });
405468
user.save();
@@ -425,14 +488,21 @@ describe('user', function() {
425488
assert(user.traits(), {});
426489
});
427490

428-
it('should clear a cookie', function() {
491+
it('should clear id in cookie', function() {
429492
user.id('id');
430493
user.save();
431494
user.logout();
432495
assert(cookie.get(cookieKey) === null);
433496
});
434497

435-
it('should clear local storage', function() {
498+
it('should clear id in local storage', function() {
499+
user.id('id');
500+
user.save();
501+
user.logout();
502+
assert(store.get(cookieKey) === undefined);
503+
});
504+
505+
it('should clear traits in local storage', function() {
436506
user.traits({ trait: true });
437507
user.save();
438508
user.logout();

0 commit comments

Comments
 (0)