Skip to content

Commit 5207d22

Browse files
committed
Merge pull request #37 from amplitude/initial_referrer
Track referrer the right way
2 parents ad61f0c + 1c041a5 commit 5207d22

File tree

8 files changed

+281
-49
lines changed

8 files changed

+281
-49
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
* Add getSessionId helper method to fetch the current sessionId.
44
* Add support for append user property operation.
5+
* Add tracking of each user's initial_referrer property (which is captured as a set once operation). Referrer property captured once per user session.
56

67
## 2.7.0 (December 1, 2015)
78

README.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ If your app has its own login system that you want to track users with, you can
4444
amplitude.setUserId('USER_ID_HERE');
4545
```
4646

47-
A user's data will be merged on the backend so that any events up to that point from the same browser will be tracked under the same user.
47+
A user's data will be merged on the backend so that any events up to that point from the same browser will be tracked under the same user. Note: if a user logs out, or you want to log the events under an anonymous user, you may set the userId to `null` like so:
48+
49+
```javascript
50+
amplitude.setUserId(null); // not string 'null'
51+
```
4852

4953
You can also add the user ID as an argument to the `init` call:
5054

@@ -62,7 +66,7 @@ eventProperties.key = 'value';
6266
amplitude.logEvent('EVENT_IDENTIFIER_HERE', eventProperties);
6367
```
6468

65-
# User Property Operations #
69+
# User Properties and User Property Operations #
6670

6771
The SDK supports the operations `set`, `setOnce`, `unset`, and `add` on individual user properties. The operations are declared via a provided `Identify` interface. Multiple operations can be chained together in a single `Identify` object. The `Identify` object is then passed to the Amplitude client to send to the server. The results of the operations will be visible immediately in the dashboard, and take effect for events logged after.
6872

@@ -114,6 +118,18 @@ var identify = new amplitude.Identify()
114118
amplitude.identify(identify);
115119
```
116120

121+
### Arrays in User Properties ###
122+
123+
The SDK supports arrays in user properties. Any of the user property operations above (with the exception of `add`) can accept a Javascript array. You can directly `set` arrays, or use `append` to generate an array.
124+
125+
```javascript
126+
var identify = new amplitude.Identify()
127+
.set('colors', ['rose', 'gold'])
128+
.append('ab-tests', 'campaign_a')
129+
.append('existing_list', [4, 5]);
130+
amplitude.identify(identify);
131+
```
132+
117133
### Setting Multiple Properties with `setUserProperties` ###
118134

119135
You may use `setUserProperties` shorthand to set multiple user properties at once. This method is simply a wrapper around `Identify.set` and `identify`.
@@ -176,7 +192,7 @@ amplitude.init('YOUR_API_KEY_HERE', null, {
176192
| savedMaxCount | Maximum number of events to save in localStorage. If more events are logged while offline, old events are removed. | 1000 |
177193
| uploadBatchSize | Maximum number of events to send to the server per request. | 100 |
178194
| includeUtm | If `true`, finds utm parameters in the query string or the __utmz cookie, parses, and includes them as user propeties on all events uploaded. | `false` |
179-
| includeReferrer | If `true`, includes `referrer` and `referring_domain` as user propeties on all events uploaded. | `false` |
195+
| includeReferrer | If `true`, captures the `referrer` and `referring_domain` for each session, as well as the user's `initial_referrer` and `initial_referring_domain` via a set once operation. | `false` |
180196
| batchEvents | If `true`, events are batched together and uploaded only when the number of unsent events is greater than or equal to `eventUploadThreshold` or after `eventUploadPeriodMillis` milliseconds have passed since the first unsent event was logged. | `false` |
181197
| eventUploadThreshold | Minimum number of events to batch together per request if `batchEvents` is `true`. | 30 |
182198
| eventUploadPeriodMillis | Amount of time in milliseconds that the SDK waits before uploading events if `batchEvents` is `true`. | 30\*1000 (30 sec) |

amplitude.js

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,9 @@ var LocalStorageKeys = {
149149
LAST_SEQUENCE_NUMBER: 'amplitude_lastSequenceNumber',
150150
LAST_EVENT_TIME: 'amplitude_lastEventTime',
151151
SESSION_ID: 'amplitude_sessionId',
152+
REFERRER: 'amplitude_referrer',
152153

154+
// Used in cookie as well
153155
DEVICE_ID: 'amplitude_deviceId',
154156
USER_ID: 'amplitude_userId',
155157
OPT_OUT: 'amplitude_optOut'
@@ -256,6 +258,10 @@ Amplitude.prototype.init = function(apiKey, opt_userId, opt_config, callback) {
256258
if (this.options.includeUtm) {
257259
this._initUtmData();
258260
}
261+
262+
if (this.options.includeReferrer) {
263+
this._saveReferrer(this._getReferrer());
264+
}
259265
} catch (e) {
260266
log(e);
261267
}
@@ -467,12 +473,39 @@ Amplitude.prototype._getReferrer = function() {
467473
return document.referrer;
468474
};
469475

470-
Amplitude.prototype._getReferringDomain = function() {
471-
var parts = this._getReferrer().split("/");
476+
Amplitude.prototype._getReferringDomain = function(referrer) {
477+
if (referrer === null || referrer === undefined || referrer === '') {
478+
return null;
479+
}
480+
var parts = referrer.split('/');
472481
if (parts.length >= 3) {
473482
return parts[2];
474483
}
475-
return "";
484+
return null;
485+
};
486+
487+
// since user properties are propagated on the server, only send once per session, don't need to send with every event
488+
Amplitude.prototype._saveReferrer = function(referrer) {
489+
if (referrer === null || referrer === undefined || referrer === '') {
490+
return;
491+
}
492+
493+
// always setOnce initial referrer
494+
var referring_domain = this._getReferringDomain(referrer);
495+
var identify = new Identify().setOnce('initial_referrer', referrer);
496+
identify.setOnce('initial_referring_domain', referring_domain);
497+
498+
// only save referrer if not already in session storage or if storage disabled
499+
var hasSessionStorage = window.sessionStorage ? true : false;
500+
if ((hasSessionStorage && !window.sessionStorage.getItem(LocalStorageKeys.REFERRER)) || !hasSessionStorage) {
501+
identify.set('referrer', referrer).set('referring_domain', referring_domain);
502+
503+
if (hasSessionStorage) {
504+
window.sessionStorage.setItem(LocalStorageKeys.REFERRER, referrer);
505+
}
506+
}
507+
508+
this.identify(identify);
476509
};
477510

478511
Amplitude.prototype.saveEvents = function() {
@@ -628,14 +661,6 @@ Amplitude.prototype._logEvent = function(eventType, eventProperties, apiProperti
628661
// Only add utm properties to user properties for events
629662
if (eventType !== IDENTIFY_EVENT) {
630663
object.merge(userProperties, this._utmProperties);
631-
632-
// Add referral info onto the user properties
633-
if (this.options.includeReferrer) {
634-
object.merge(userProperties, {
635-
'referrer': this._getReferrer(),
636-
'referring_domain': this._getReferringDomain()
637-
});
638-
}
639664
}
640665

641666
apiProperties = apiProperties || {};
@@ -2545,15 +2570,25 @@ module.exports = function(val){
25452570
if (val !== val) return 'nan';
25462571
if (val && val.nodeType === 1) return 'element';
25472572

2548-
if (typeof Buffer != 'undefined' && Buffer.isBuffer(val)) return 'buffer';
2573+
if (isBuffer(val)) return 'buffer';
25492574

25502575
val = val.valueOf
25512576
? val.valueOf()
2552-
: Object.prototype.valueOf.apply(val)
2577+
: Object.prototype.valueOf.apply(val);
25532578

25542579
return typeof val;
25552580
};
25562581

2582+
// code borrowed from https://github.com/feross/is-buffer/blob/master/index.js
2583+
function isBuffer(obj) {
2584+
return !!(obj != null &&
2585+
(obj._isBuffer || // For Safari 5-7 (missing Object.prototype.constructor)
2586+
(obj.constructor &&
2587+
typeof obj.constructor.isBuffer === 'function' &&
2588+
obj.constructor.isBuffer(obj))
2589+
))
2590+
}
2591+
25572592
}, {}],
25582593
10: [function(require, module, exports) {
25592594
/* jshint eqeqeq: false, forin: false */

amplitude.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/amplitude.js

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ var LocalStorageKeys = {
4343
LAST_SEQUENCE_NUMBER: 'amplitude_lastSequenceNumber',
4444
LAST_EVENT_TIME: 'amplitude_lastEventTime',
4545
SESSION_ID: 'amplitude_sessionId',
46+
REFERRER: 'amplitude_referrer',
4647

48+
// Used in cookie as well
4749
DEVICE_ID: 'amplitude_deviceId',
4850
USER_ID: 'amplitude_userId',
4951
OPT_OUT: 'amplitude_optOut'
@@ -150,6 +152,10 @@ Amplitude.prototype.init = function(apiKey, opt_userId, opt_config, callback) {
150152
if (this.options.includeUtm) {
151153
this._initUtmData();
152154
}
155+
156+
if (this.options.includeReferrer) {
157+
this._saveReferrer(this._getReferrer());
158+
}
153159
} catch (e) {
154160
log(e);
155161
}
@@ -361,12 +367,39 @@ Amplitude.prototype._getReferrer = function() {
361367
return document.referrer;
362368
};
363369

364-
Amplitude.prototype._getReferringDomain = function() {
365-
var parts = this._getReferrer().split("/");
370+
Amplitude.prototype._getReferringDomain = function(referrer) {
371+
if (referrer === null || referrer === undefined || referrer === '') {
372+
return null;
373+
}
374+
var parts = referrer.split('/');
366375
if (parts.length >= 3) {
367376
return parts[2];
368377
}
369-
return "";
378+
return null;
379+
};
380+
381+
// since user properties are propagated on the server, only send once per session, don't need to send with every event
382+
Amplitude.prototype._saveReferrer = function(referrer) {
383+
if (referrer === null || referrer === undefined || referrer === '') {
384+
return;
385+
}
386+
387+
// always setOnce initial referrer
388+
var referring_domain = this._getReferringDomain(referrer);
389+
var identify = new Identify().setOnce('initial_referrer', referrer);
390+
identify.setOnce('initial_referring_domain', referring_domain);
391+
392+
// only save referrer if not already in session storage or if storage disabled
393+
var hasSessionStorage = window.sessionStorage ? true : false;
394+
if ((hasSessionStorage && !window.sessionStorage.getItem(LocalStorageKeys.REFERRER)) || !hasSessionStorage) {
395+
identify.set('referrer', referrer).set('referring_domain', referring_domain);
396+
397+
if (hasSessionStorage) {
398+
window.sessionStorage.setItem(LocalStorageKeys.REFERRER, referrer);
399+
}
400+
}
401+
402+
this.identify(identify);
370403
};
371404

372405
Amplitude.prototype.saveEvents = function() {
@@ -522,14 +555,6 @@ Amplitude.prototype._logEvent = function(eventType, eventProperties, apiProperti
522555
// Only add utm properties to user properties for events
523556
if (eventType !== IDENTIFY_EVENT) {
524557
object.merge(userProperties, this._utmProperties);
525-
526-
// Add referral info onto the user properties
527-
if (this.options.includeReferrer) {
528-
object.merge(userProperties, {
529-
'referrer': this._getReferrer(),
530-
'referring_domain': this._getReferringDomain()
531-
});
532-
}
533558
}
534559

535560
apiProperties = apiProperties || {};

test/amplitude.js

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ describe('Amplitude', function() {
2727

2828
function reset() {
2929
localStorage.clear();
30+
sessionStorage.clear();
3031
cookie.remove(amplitude.options.cookieName);
3132
cookie.reset();
3233
}
@@ -1500,41 +1501,92 @@ describe('Amplitude', function() {
15001501
assert.equal(events[0].user_properties.referring_domain, undefined);
15011502
});
15021503

1503-
it('should send referrer data when the includeReferrer flag is true', function() {
1504+
it('should only send referrer via identify call when the includeReferrer flag is true', function() {
15041505
reset();
1505-
amplitude.init(apiKey, undefined, {includeReferrer: true});
1506-
1506+
amplitude.init(apiKey, undefined, {includeReferrer: true, batchEvents: true, eventUploadThreshold: 2});
15071507
amplitude.logEvent('Referrer Test Event', {});
15081508
assert.lengthOf(server.requests, 1);
15091509
var events = JSON.parse(querystring.parse(server.requests[0].requestBody).e);
1510+
assert.lengthOf(events, 2);
1511+
1512+
// first event should be identify with initial_referrer and referrer
1513+
assert.equal(events[0].event_type, '$identify');
15101514
assert.deepEqual(events[0].user_properties, {
1511-
referrer: 'https://amplitude.com/contact',
1512-
referring_domain: 'amplitude.com'
1515+
'$set': {
1516+
'referrer': 'https://amplitude.com/contact',
1517+
'referring_domain': 'amplitude.com'
1518+
},
1519+
'$setOnce': {
1520+
'initial_referrer': 'https://amplitude.com/contact',
1521+
'initial_referring_domain': 'amplitude.com'
1522+
}
15131523
});
1524+
1525+
// second event should be the test event with no referrer information
1526+
assert.equal(events[1].event_type, 'Referrer Test Event');
1527+
assert.deepEqual(events[1].user_properties, {});
1528+
1529+
// referrer should be propagated to session storage
1530+
assert.equal(sessionStorage.getItem('amplitude_referrer'), 'https://amplitude.com/contact');
15141531
});
15151532

1516-
it('should add referrer data to the user properties on events only', function() {
1533+
it('should not set referrer if referrer data already in session storage', function() {
15171534
reset();
1518-
amplitude.init(apiKey, undefined, {includeReferrer: true});
1519-
1520-
amplitude.setUserProperties({user_prop: true});
1535+
sessionStorage.setItem('amplitude_referrer', 'https://www.google.com/search?');
1536+
amplitude.init(apiKey, undefined, {includeReferrer: true, batchEvents: true, eventUploadThreshold: 2});
1537+
amplitude.logEvent('Referrer Test Event', {});
15211538
assert.lengthOf(server.requests, 1);
15221539
var events = JSON.parse(querystring.parse(server.requests[0].requestBody).e);
1540+
assert.lengthOf(events, 2);
1541+
1542+
// first event should be identify with initial_referrer and NO referrer
1543+
assert.equal(events[0].event_type, '$identify');
15231544
assert.deepEqual(events[0].user_properties, {
1524-
$set: {
1525-
'user_prop': true
1545+
'$setOnce': {
1546+
'initial_referrer': 'https://amplitude.com/contact',
1547+
'initial_referring_domain': 'amplitude.com'
15261548
}
15271549
});
1528-
server.respondWith('success');
1529-
server.respond();
15301550

1531-
amplitude.logEvent('Referrer test event');
1532-
assert.lengthOf(server.requests, 2);
1533-
var events = JSON.parse(querystring.parse(server.requests[1].requestBody).e);
1551+
// second event should be the test event with no referrer information
1552+
assert.equal(events[1].event_type, 'Referrer Test Event');
1553+
assert.deepEqual(events[1].user_properties, {});
1554+
});
1555+
1556+
it('should not override any existing initial referrer values in session storage', function() {
1557+
reset();
1558+
sessionStorage.setItem('amplitude_referrer', 'https://www.google.com/search?');
1559+
amplitude.init(apiKey, undefined, {includeReferrer: true, batchEvents: true, eventUploadThreshold: 3});
1560+
amplitude._saveReferrer('https://facebook.com/contact');
1561+
amplitude.logEvent('Referrer Test Event', {});
1562+
assert.lengthOf(server.requests, 1);
1563+
var events = JSON.parse(querystring.parse(server.requests[0].requestBody).e);
1564+
assert.lengthOf(events, 3);
1565+
1566+
// first event should be identify with initial_referrer and NO referrer
1567+
assert.equal(events[0].event_type, '$identify');
15341568
assert.deepEqual(events[0].user_properties, {
1535-
referrer: 'https://amplitude.com/contact',
1536-
referring_domain: 'amplitude.com'
1569+
'$setOnce': {
1570+
'initial_referrer': 'https://amplitude.com/contact',
1571+
'initial_referring_domain': 'amplitude.com'
1572+
}
1573+
});
1574+
1575+
// second event should be another identify but with the new referrer
1576+
assert.equal(events[1].event_type, '$identify');
1577+
assert.deepEqual(events[1].user_properties, {
1578+
'$setOnce': {
1579+
'initial_referrer': 'https://facebook.com/contact',
1580+
'initial_referring_domain': 'facebook.com'
1581+
}
15371582
});
1583+
1584+
// third event should be the test event with no referrer information
1585+
assert.equal(events[2].event_type, 'Referrer Test Event');
1586+
assert.deepEqual(events[2].user_properties, {});
1587+
1588+
// existing value persists
1589+
assert.equal(sessionStorage.getItem('amplitude_referrer'), 'https://www.google.com/search?');
15381590
});
15391591
});
15401592

test/browser/amplitudejs.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
}
7171
</script>
7272
<script>
73-
amplitude.init('a2dbce0e18dfe5f8e74493843ff5c053', null, null, function() {
73+
amplitude.init('a2dbce0e18dfe5f8e74493843ff5c053', null, {includeReferrer: true}, function() {
7474
alert(amplitude.options.deviceId);
7575
});
7676
amplitude.setVersionName('Web');
@@ -96,6 +96,7 @@ <h3>Amplitude JS Test</h3>
9696
<li><a href="javascript:setPhotoCount();">Set photo count</a></li>
9797
<li><a href="javascript:setOncePhotoCount();">Set photo count once</a></li>
9898
<li><a href="javascript:setCity();">Set city via setUserProperties</a></li>
99-
99+
<br><br>
100+
<li><a href="/test/browser/amplitudejs2.html">Go to second page</a></li>
100101
</body>
101102
</html>

0 commit comments

Comments
 (0)