Skip to content

Commit 60c9129

Browse files
authored
Merge pull request #95 from amplitude/limit_property_keys
Limit property keys
2 parents 306de78 + 78b6596 commit 60c9129

9 files changed

+86
-12
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## Unreleased
22

3+
* Block event property and user property dictionaries that have more than 1000 items. This is to block properties that are set unintentionally (for example in a loop). A single call to `logEvent` should not have more than 1000 event properties. Similarly a single call to `setUserProperties` should not have more than 1000 user properties.
4+
5+
### 3.1.0 (September 14, 2016)
6+
37
* Add configuration option `forceHttps`, which when set to `true` forces the SDK to always upload to HTTPS endpoint. By default the SDK uses the endpoint that matches the embedding site's protocol (for example if your site is HTTP, it will use the HTTP endpoint).
48

59
### 3.0.2 (July 6, 2016)

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,9 @@ Having large amounts of distinct event types, event properties and user properti
124124
* 2000 distinct event properties
125125
* 1000 distinct user properties
126126

127-
Anything past the above thresholds will not be visualized. **Note that the raw data is not impacted by this in any way, meaning you can still see the values in the raw data, but they will not be visualized on the platform.** We have put in very conservative estimates for the event and property caps which we don’t expect to be exceeded in any practical use case. If you feel that your use case will go above those limits please reach out to [email protected].
127+
Anything past the above thresholds will not be visualized. **Note that the raw data is not impacted by this in any way, meaning you can still see the values in the raw data, but they will not be visualized on the platform.**
128+
129+
A single call to `logEvent` should not have more than 1000 event properties. Likewise a single call to `setUserProperties` should not have more than 1000 user properties. If the 1000 item limit is exceeded then the properties will be dropped and a warning will be printed to console. We have put in very conservative estimates for the event and property caps which we don’t expect to be exceeded in any practical use case. If you feel that your use case will go above those limits please reach out to [email protected].
128130

129131
# Setting Custom User IDs #
130132

amplitude.js

+17-4
Original file line numberDiff line numberDiff line change
@@ -1134,11 +1134,17 @@ AmplitudeClient.prototype.setUserProperties = function setUserProperties(userPro
11341134
if (!this._apiKeySet('setUserProperties()') || !utils.validateInput(userProperties, 'userProperties', 'object')) {
11351135
return;
11361136
}
1137+
// sanitize the userProperties dict before converting into identify
1138+
var sanitized = utils.truncate(utils.validateProperties(userProperties));
1139+
if (Object.keys(sanitized).length === 0) {
1140+
return;
1141+
}
1142+
11371143
// convert userProperties into an identify call
11381144
var identify = new Identify();
1139-
for (var property in userProperties) {
1140-
if (userProperties.hasOwnProperty(property)) {
1141-
identify.set(property, userProperties[property]);
1145+
for (var property in sanitized) {
1146+
if (sanitized.hasOwnProperty(property)) {
1147+
identify.set(property, sanitized[property]);
11421148
}
11431149
}
11441150
this.identify(identify);
@@ -1618,6 +1624,7 @@ module.exports = {
16181624
DEFAULT_INSTANCE: '$default_instance',
16191625
API_VERSION: 2,
16201626
MAX_STRING_LENGTH: 4096,
1627+
MAX_PROPERTY_KEYS: 1000,
16211628
IDENTIFY_EVENT: '$identify',
16221629

16231630
// localStorageKeys
@@ -2725,10 +2732,16 @@ var validateInput = function validateInput(input, name, expectedType) {
27252732
return true;
27262733
};
27272734

2735+
// do some basic sanitization and type checking, also catch property dicts with more than 1000 key/value pairs
27282736
var validateProperties = function validateProperties(properties) {
27292737
var propsType = type(properties);
27302738
if (propsType !== 'object') {
2731-
log('Error: invalid event properties format. Expecting Javascript object, received ' + propsType + ', ignoring');
2739+
log('Error: invalid properties format. Expecting Javascript object, received ' + propsType + ', ignoring');
2740+
return {};
2741+
}
2742+
2743+
if (Object.keys(properties).length > constants.MAX_PROPERTY_KEYS) {
2744+
log('Error: too many properties (more than 1000), ignoring');
27322745
return {};
27332746
}
27342747

amplitude.min.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/amplitude-client.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -653,11 +653,17 @@ AmplitudeClient.prototype.setUserProperties = function setUserProperties(userPro
653653
if (!this._apiKeySet('setUserProperties()') || !utils.validateInput(userProperties, 'userProperties', 'object')) {
654654
return;
655655
}
656+
// sanitize the userProperties dict before converting into identify
657+
var sanitized = utils.truncate(utils.validateProperties(userProperties));
658+
if (Object.keys(sanitized).length === 0) {
659+
return;
660+
}
661+
656662
// convert userProperties into an identify call
657663
var identify = new Identify();
658-
for (var property in userProperties) {
659-
if (userProperties.hasOwnProperty(property)) {
660-
identify.set(property, userProperties[property]);
664+
for (var property in sanitized) {
665+
if (sanitized.hasOwnProperty(property)) {
666+
identify.set(property, sanitized[property]);
661667
}
662668
}
663669
this.identify(identify);

src/constants.js

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module.exports = {
22
DEFAULT_INSTANCE: '$default_instance',
33
API_VERSION: 2,
44
MAX_STRING_LENGTH: 4096,
5+
MAX_PROPERTY_KEYS: 1000,
56
IDENTIFY_EVENT: '$identify',
67

78
// localStorageKeys

src/utils.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,16 @@ var validateInput = function validateInput(input, name, expectedType) {
5656
return true;
5757
};
5858

59+
// do some basic sanitization and type checking, also catch property dicts with more than 1000 key/value pairs
5960
var validateProperties = function validateProperties(properties) {
6061
var propsType = type(properties);
6162
if (propsType !== 'object') {
62-
log('Error: invalid event properties format. Expecting Javascript object, received ' + propsType + ', ignoring');
63+
log('Error: invalid properties format. Expecting Javascript object, received ' + propsType + ', ignoring');
64+
return {};
65+
}
66+
67+
if (Object.keys(properties).length > constants.MAX_PROPERTY_KEYS) {
68+
log('Error: too many properties (more than 1000), ignoring');
6369
return {};
6470
}
6571

test/amplitude-client.js

+34-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ describe('AmplitudeClient', function() {
1111
var JSON = require('json');
1212
var Identify = require('../src/identify.js');
1313
var Revenue = require('../src/revenue.js');
14+
var constants = require('../src/constants.js');
1415
var apiKey = '000000';
1516
var keySuffix = '_' + apiKey.slice(0,6);
1617
var userId = 'user';
@@ -1921,14 +1922,46 @@ describe('setVersionName', function() {
19211922
});
19221923
});
19231924

1924-
it('should validate user propeorties', function() {
1925+
it('should validate user properties', function() {
19251926
var identify = new Identify().set(10, 10);
19261927
amplitude.init(apiKey, null, {batchEvents: true});
19271928
amplitude.identify(identify);
19281929

19291930
assert.deepEqual(amplitude._unsentIdentifys[0].user_properties, {'$set': {'10': 10}});
19301931
});
19311932

1933+
it('should ignore event and user properties with too many items', function() {
1934+
amplitude.init(apiKey, null, {batchEvents: true, eventUploadThreshold: 2});
1935+
var eventProperties = {};
1936+
var userProperties = {};
1937+
var identify = new Identify();
1938+
for (var i = 0; i < constants.MAX_PROPERTY_KEYS + 1; i++) {
1939+
eventProperties[i] = i;
1940+
userProperties[i*2] = i*2;
1941+
identify.set(i, i);
1942+
}
1943+
1944+
// verify that setUserProperties ignores the dict completely
1945+
amplitude.setUserProperties(userProperties);
1946+
assert.lengthOf(amplitude._unsentIdentifys, 0);
1947+
assert.lengthOf(server.requests, 0);
1948+
1949+
// verify that the event properties and user properties are scrubbed
1950+
amplitude.logEvent('test event', eventProperties);
1951+
amplitude.identify(identify);
1952+
1953+
assert.lengthOf(server.requests, 1);
1954+
var events = JSON.parse(querystring.parse(server.requests[0].requestBody).e);
1955+
assert.lengthOf(events, 2);
1956+
1957+
assert.equal(events[0].event_type, 'test event');
1958+
assert.deepEqual(events[0].event_properties, {});
1959+
assert.deepEqual(events[0].user_properties, {});
1960+
assert.equal(events[1].event_type, '$identify');
1961+
assert.deepEqual(events[1].event_properties, {});
1962+
assert.deepEqual(events[1].user_properties, {'$set': {}});
1963+
});
1964+
19321965
it('should synchronize event data across multiple amplitude instances that share the same cookie', function() {
19331966
// this test fails if logEvent does not reload cookie data every time
19341967
var amplitude1 = new AmplitudeClient();

test/utils.js

+9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
describe('utils', function() {
22
var utils = require('../src/utils.js');
3+
var constants = require('../src/constants.js');
34

45
describe('isEmptyString', function() {
56
it('should detect empty strings', function() {
@@ -123,5 +124,13 @@ describe('utils', function() {
123124
}
124125
assert.deepEqual(utils.validateProperties(properties), expected);
125126
});
127+
128+
it('should block properties with too many items', function() {
129+
var properties = {};
130+
for (var i = 0; i < constants.MAX_PROPERTY_KEYS + 1; i++) {
131+
properties[i] = i;
132+
}
133+
assert.deepEqual(utils.validateProperties(properties), {});
134+
});
126135
});
127136
});

0 commit comments

Comments
 (0)