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

Commit 0d6fd8c

Browse files
authored
Merge pull request #108 from segmentio/localstorage-fallback
feat(ids): check LocalStorage when cookies are not available
2 parents b7a3737 + b27641a commit 0d6fd8c

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)