Skip to content

Commit 1a56a9b

Browse files
authored
feat(privacy): Add storage option (#320)
Fix #317
1 parent c764513 commit 1a56a9b

File tree

5 files changed

+103
-24
lines changed

5 files changed

+103
-24
lines changed

src/amplitude-client.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ AmplitudeClient.prototype.init = function init(apiKey, opt_userId, opt_config, o
119119
expirationDays: this.options.cookieExpiration,
120120
domain: this.options.domain,
121121
secure: this.options.secureCookie,
122-
sameSite: this.options.sameSiteCookie
122+
sameSite: this.options.sameSiteCookie,
123+
storage: this.options.storage
123124
});
124125

125126
const hasOldCookie = !!this.cookieStorage.get(this._oldCookiename);

src/constants.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ export default {
2121
COOKIE_TEST_PREFIX: 'amp_cookie_test',
2222
COOKIE_PREFIX: "amp",
2323

24+
// Storage options
25+
STORAGE_DEFAULT: '',
26+
STORAGE_COOKIES: 'cookies',
27+
STORAGE_NONE: 'none',
28+
STORAGE_LOCAL: 'localStorage',
29+
STORAGE_SESSION: 'sessionStorage',
30+
2431
// revenue keys
2532
REVENUE_EVENT: 'revenue_amount',
2633
REVENUE_PRODUCT_ID: '$productId',

src/metadata-storage.js

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,22 @@
55

66
import Base64 from './base64';
77
import baseCookie from './base-cookie';
8+
import Constants from './constants';
89
import getLocation from './get-location';
910
import localStorage from './localstorage'; // jshint ignore:line
1011
import topDomain from './top-domain';
1112

13+
const storageOptionExists = {
14+
[Constants.STORAGE_COOKIES]: true,
15+
[Constants.STORAGE_NONE]: true,
16+
[Constants.STORAGE_LOCAL]: true,
17+
[Constants.STORAGE_SESSION]: true,
18+
};
19+
1220
/**
1321
* MetadataStorage involves SDK data persistance
1422
* storage priority: cookies -> localStorage -> in memory
23+
* This priority can be overriden by setting the storage options.
1524
* if in localStorage, unable track users between subdomains
1625
* if in memory, then memory can't be shared between different tabs
1726
*/
@@ -23,6 +32,7 @@ class MetadataStorage {
2332
secure,
2433
sameSite,
2534
expirationDays,
35+
storage,
2636
}) {
2737
this.storageKey = storageKey;
2838
this.domain = domain;
@@ -38,14 +48,23 @@ class MetadataStorage {
3848
domain || (writableTopDomain ? '.' + writableTopDomain : null);
3949
}
4050

41-
this.disableCookieStorage =
42-
disableCookies ||
43-
!baseCookie.areCookiesEnabled({
44-
domain: this.cookieDomain,
45-
secure: this.secure,
46-
sameSite: this.sameSite,
47-
expirationDays: this.expirationDays,
48-
});
51+
if (storageOptionExists[storage]) {
52+
this.storage = storage;
53+
} else {
54+
const disableCookieStorage =
55+
disableCookies ||
56+
!baseCookie.areCookiesEnabled({
57+
domain: this.cookieDomain,
58+
secure: this.secure,
59+
sameSite: this.sameSite,
60+
expirationDays: this.expirationDays,
61+
});
62+
if (disableCookieStorage) {
63+
this.storage = Constants.STORAGE_LOCAL;
64+
} else {
65+
this.storage = Constants.STORAGE_COOKIES;
66+
}
67+
}
4968
}
5069

5170
getCookieStorageKey() {
@@ -73,6 +92,9 @@ class MetadataStorage {
7392
identifyId,
7493
sequenceNumber,
7594
}) {
95+
if (this.storage === Constants.STORAGE_NONE) {
96+
return;
97+
}
7698
const value = [
7799
deviceId,
78100
Base64.encode(userId || ''), // used to convert not unicode to alphanumeric since cookies only use alphanumeric
@@ -84,26 +106,37 @@ class MetadataStorage {
84106
sequenceNumber ? sequenceNumber.toString(32) : '0',
85107
].join('.');
86108

87-
if (this.disableCookieStorage) {
88-
localStorage.setItem(this.storageKey, value);
89-
} else {
90-
baseCookie.set(this.getCookieStorageKey(), value, {
91-
domain: this.cookieDomain,
92-
secure: this.secure,
93-
sameSite: this.sameSite,
94-
expirationDays: this.expirationDays,
95-
});
109+
switch (this.storage) {
110+
case Constants.STORAGE_SESSION:
111+
if (window.sessionStorage) {
112+
window.sessionStorage.setItem(this.storageKey, value);
113+
}
114+
break;
115+
case Constants.STORAGE_LOCAL:
116+
localStorage.setItem(this.storageKey, value);
117+
break;
118+
case Constants.STORAGE_COOKIES:
119+
baseCookie.set(this.getCookieStorageKey(), value, {
120+
domain: this.cookieDomain,
121+
secure: this.secure,
122+
sameSite: this.sameSite,
123+
expirationDays: this.expirationDays,
124+
});
125+
break;
96126
}
97127
}
98128

99129
load() {
100130
let str;
101-
if (!this.disableCookieStorage) {
131+
if (this.storage === Constants.STORAGE_COOKIES) {
102132
str = baseCookie.get(this.getCookieStorageKey() + '=');
103133
}
104134
if (!str) {
105135
str = localStorage.getItem(this.storageKey);
106136
}
137+
if (!str) {
138+
str = window.sessionStorage && window.sessionStorage.getItem(this.storageKey);
139+
}
107140

108141
if (!str) {
109142
return null;

src/options.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Constants from './constants';
12
import language from './language';
23

34
let platform = 'Web';
@@ -56,7 +57,7 @@ export default {
5657
sameSiteCookie: 'Lax', // cookie privacy policy
5758
cookieForceUpgrade: false,
5859
deferInitialization: false,
59-
disableCookies: false,
60+
disableCookies: false, // this is a deprecated option
6061
deviceIdFromUrlParam: false,
6162
domain: '',
6263
eventUploadPeriodMillis: 30 * 1000, // 30s
@@ -76,6 +77,7 @@ export default {
7677
saveParamsReferrerOncePerSession: true,
7778
secureCookie: false,
7879
sessionTimeout: 30 * 60 * 1000,
80+
storage: Constants.STORAGE_DEFAULT,
7981
trackingOptions: {
8082
city: true,
8183
country: true,

test/amplitude-client.js

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,16 @@ describe('AmplitudeClient', function() {
2424
var userId = 'user';
2525
var amplitude;
2626
var server;
27+
var sandbox
2728

2829
beforeEach(function() {
2930
amplitude = new AmplitudeClient();
3031
server = sinon.fakeServer.create();
32+
sandbox = sinon.sandbox.create();
3133
});
3234

3335
afterEach(function() {
36+
sandbox.restore();
3437
server.restore();
3538
});
3639

@@ -304,8 +307,8 @@ describe('AmplitudeClient', function() {
304307
cookie.set(oldCookieName, cookieData);
305308

306309
amplitude.init(apiKey, null, { cookieForceUpgrade: true });
307-
const cookieData = cookie.getRaw(cookieName);
308-
assert.equal('old_device_id', cookieData.slice(0, 'old_device_id'.length));
310+
const cookieRawData = cookie.getRaw(cookieName);
311+
assert.equal('old_device_id', cookieRawData.slice(0, 'old_device_id'.length));
309312
});
310313

311314
it('should delete the old old cookie if forceUpgrade is on', function(){
@@ -323,8 +326,8 @@ describe('AmplitudeClient', function() {
323326
cookie.set(oldCookieName, cookieData);
324327

325328
amplitude.init(apiKey, null, { cookieForceUpgrade: true });
326-
const cookieData = cookie.get(oldCookieName);
327-
assert.isNull(cookieData);
329+
const cookieRawData = cookie.get(oldCookieName);
330+
assert.isNull(cookieRawData);
328331
});
329332

330333
it('should use device id from the old cookie if a new cookie does not exist', function(){
@@ -447,6 +450,39 @@ describe('AmplitudeClient', function() {
447450
assert.equal(amplitude2._sequenceNumber, 70);
448451
});
449452

453+
it('should not persist anything if storage options is none', function() {
454+
const clock = sandbox.useFakeTimers();
455+
clock.tick(1000);
456+
457+
const amplitude2 = new AmplitudeClient();
458+
amplitude2.init(apiKey, null, {storage: 'none'});
459+
clock.tick(10);
460+
461+
const amplitude3 = new AmplitudeClient();
462+
amplitude3.init(apiKey, null, {storage: 'none'});
463+
464+
assert.notEqual(amplitude2._sessionId, amplitude3._sessionId);
465+
});
466+
467+
it('should load sessionId if storage options is sessionStorage', function() {
468+
const clock = sandbox.useFakeTimers();
469+
clock.tick(1000);
470+
// Disable cookies read.
471+
sandbox.stub(baseCookie, 'get').returns(null);
472+
473+
const amplitude2 = new AmplitudeClient();
474+
amplitude2.init(apiKey, null, {storage: 'sessionStorage'});
475+
clock.tick(10);
476+
477+
// Clear local storage to make sure it's not used.
478+
localStorage.clear();
479+
480+
const amplitude3 = new AmplitudeClient();
481+
amplitude3.init(apiKey, null, {storage: 'sessionStorage'});
482+
483+
assert.equal(amplitude2._sessionId, amplitude3._sessionId);
484+
});
485+
450486
it('should load saved events from localStorage for default instance', function() {
451487
var existingEvent = '[{"device_id":"test_device_id","user_id":"test_user_id","timestamp":1453769146589,' +
452488
'"event_id":49,"session_id":1453763315544,"event_type":"clicked","version_name":"Web","platform":"Web"' +

0 commit comments

Comments
 (0)