@@ -262,14 +262,6 @@ var version = require('./version');
262262var type = require ( './type' ) ;
263263var DEFAULT_OPTIONS = require ( './options' ) ;
264264
265- var log = function ( s ) {
266- try {
267- console . log ( '[Amplitude] ' + s ) ;
268- } catch ( e ) {
269- // console logging not available
270- }
271- } ;
272-
273265var DEFAULT_INSTANCE = '$default_instance' ;
274266var IDENTIFY_EVENT = '$identify' ;
275267var API_VERSION = 2 ;
@@ -375,12 +367,19 @@ AmplitudeClient.prototype.init = function(apiKey, opt_userId, opt_config, callba
375367 this . _lastEventTime = now ;
376368 _saveCookieData ( this ) ;
377369
378- //log('initialized with apiKey=' + apiKey);
379- //opt_userId !== undefined && opt_userId !== null && log('initialized with userId=' + opt_userId);
370+ //utils. log('initialized with apiKey=' + apiKey);
371+ //opt_userId !== undefined && opt_userId !== null && utils. log('initialized with userId=' + opt_userId);
380372
381373 if ( this . options . saveEvents ) {
382374 this . _unsentEvents = this . _loadSavedUnsentEvents ( this . options . unsentKey ) || this . _unsentEvents ;
383375 this . _unsentIdentifys = this . _loadSavedUnsentEvents ( this . options . unsentIdentifyKey ) || this . _unsentIdentifys ;
376+
377+ // validate event properties for unsent events
378+ for ( var i = 0 ; i < this . _unsentEvents . length ; i ++ ) {
379+ var eventProperties = this . _unsentEvents [ i ] . event_properties ;
380+ this . _unsentEvents [ i ] . event_properties = utils . validateProperties ( eventProperties ) ;
381+ }
382+
384383 this . _sendEventsIfReady ( ) ;
385384 }
386385
@@ -392,7 +391,7 @@ AmplitudeClient.prototype.init = function(apiKey, opt_userId, opt_config, callba
392391 this . _saveReferrer ( this . _getReferrer ( ) ) ;
393392 }
394393 } catch ( e ) {
395- log ( e ) ;
394+ utils . log ( e ) ;
396395 }
397396
398397 if ( callback && type ( callback ) === 'function' ) {
@@ -404,7 +403,7 @@ AmplitudeClient.prototype.Identify = Identify;
404403
405404AmplitudeClient . prototype . _apiKeySet = function ( methodName ) {
406405 if ( ! this . options . apiKey ) {
407- log ( 'apiKey cannot be undefined or null, set apiKey with init() before calling ' + methodName ) ;
406+ utils . log ( 'apiKey cannot be undefined or null, set apiKey with init() before calling ' + methodName ) ;
408407 return false ;
409408 }
410409 return true ;
@@ -426,7 +425,7 @@ AmplitudeClient.prototype._loadSavedUnsentEvents = function(unsentKey) {
426425 try {
427426 return JSON . parse ( savedUnsentEventsString ) ;
428427 } catch ( e ) {
429- //log(e);
428+ // utils. log(e);
430429 }
431430 }
432431 return null ;
@@ -640,7 +639,7 @@ AmplitudeClient.prototype._saveReferrer = function(referrer) {
640639 hasSessionStorage = true ;
641640 }
642641 } catch ( e ) {
643- // log(e); // sessionStorage disabled
642+ // utils. log(e); // sessionStorage disabled
644643 }
645644
646645 if ( ( hasSessionStorage && ! ( this . _getFromStorage ( sessionStorage , LocalStorageKeys . REFERRER ) ) ) || ! hasSessionStorage ) {
@@ -663,7 +662,7 @@ AmplitudeClient.prototype.saveEvents = function() {
663662 this . _setInStorage ( localStorage , this . options . unsentKey , JSON . stringify ( this . _unsentEvents ) ) ;
664663 this . _setInStorage ( localStorage , this . options . unsentIdentifyKey , JSON . stringify ( this . _unsentIdentifys ) ) ;
665664 } catch ( e ) {
666- //log(e);
665+ // utils. log(e);
667666 }
668667} ;
669668
@@ -679,9 +678,9 @@ AmplitudeClient.prototype.setDomain = function(domain) {
679678 this . options . domain = this . cookieStorage . options ( ) . domain ;
680679 _loadCookieData ( this ) ;
681680 _saveCookieData ( this ) ;
682- //log('set domain=' + domain);
681+ // utils. log('set domain=' + domain);
683682 } catch ( e ) {
684- log ( e ) ;
683+ utils . log ( e ) ;
685684 }
686685} ;
687686
@@ -693,9 +692,9 @@ AmplitudeClient.prototype.setUserId = function(userId) {
693692 try {
694693 this . options . userId = ( userId !== undefined && userId !== null && ( '' + userId ) ) || null ;
695694 _saveCookieData ( this ) ;
696- //log('set userId=' + userId);
695+ // utils. log('set userId=' + userId);
697696 } catch ( e ) {
698- log ( e ) ;
697+ utils . log ( e ) ;
699698 }
700699} ;
701700
@@ -707,9 +706,9 @@ AmplitudeClient.prototype.setOptOut = function(enable) {
707706 try {
708707 this . options . optOut = enable ;
709708 _saveCookieData ( this ) ;
710- //log('set optOut=' + enable);
709+ // utils. log('set optOut=' + enable);
711710 } catch ( e ) {
712- log ( e ) ;
711+ utils . log ( e ) ;
713712 }
714713} ;
715714
@@ -724,7 +723,7 @@ AmplitudeClient.prototype.setDeviceId = function(deviceId) {
724723 _saveCookieData ( this ) ;
725724 }
726725 } catch ( e ) {
727- log ( e ) ;
726+ utils . log ( e ) ;
728727 }
729728} ;
730729
@@ -778,9 +777,9 @@ AmplitudeClient.prototype.identify = function(identify) {
778777AmplitudeClient . prototype . setVersionName = function ( versionName ) {
779778 try {
780779 this . options . versionName = versionName ;
781- //log('set versionName=' + versionName);
780+ // utils. log('set versionName=' + versionName);
782781 } catch ( e ) {
783- log ( e ) ;
782+ utils . log ( e ) ;
784783 }
785784} ;
786785
@@ -862,7 +861,7 @@ AmplitudeClient.prototype._logEvent = function(eventType, eventProperties, apiPr
862861 device_model : ua . os . name || null ,
863862 language : this . options . language ,
864863 api_properties : apiProperties ,
865- event_properties : this . _truncate ( eventProperties ) ,
864+ event_properties : this . _truncate ( utils . validateProperties ( eventProperties ) ) ,
866865 user_properties : this . _truncate ( userProperties ) ,
867866 uuid : UUID ( ) ,
868867 library : {
@@ -891,7 +890,7 @@ AmplitudeClient.prototype._logEvent = function(eventType, eventProperties, apiPr
891890
892891 return eventId ;
893892 } catch ( e ) {
894- log ( e ) ;
893+ utils . log ( e ) ;
895894 }
896895} ;
897896
@@ -918,7 +917,7 @@ var _isNumber = function(n) {
918917AmplitudeClient . prototype . logRevenue = function ( price , quantity , product ) {
919918 // Test that the parameters are of the right type.
920919 if ( ! this . _apiKeySet ( 'logRevenue()' ) || ! _isNumber ( price ) || quantity !== undefined && ! _isNumber ( quantity ) ) {
921- // log('Price and quantity arguments to logRevenue must be numbers');
920+ // utils. log('Price and quantity arguments to logRevenue must be numbers');
922921 return - 1 ;
923922 }
924923
@@ -987,7 +986,7 @@ AmplitudeClient.prototype.sendEvents = function(callback) {
987986 scope . _sending = false ;
988987 try {
989988 if ( status === 200 && response === 'success' ) {
990- //log('sucessful upload');
989+ // utils. log('sucessful upload');
991990 scope . removeEvents ( maxEventId , maxIdentifyId ) ;
992991
993992 // Update the event cache after the removal of sent events.
@@ -1001,7 +1000,7 @@ AmplitudeClient.prototype.sendEvents = function(callback) {
10011000 }
10021001
10031002 } else if ( status === 413 ) {
1004- //log('request too large');
1003+ // utils. log('request too large');
10051004 // Can't even get this one massive event through. Drop it.
10061005 if ( scope . options . uploadBatchSize === 1 ) {
10071006 // if massive event is identify, still need to drop it
@@ -1017,7 +1016,7 @@ AmplitudeClient.prototype.sendEvents = function(callback) {
10171016 callback ( status , response ) ;
10181017 }
10191018 } catch ( e ) {
1020- //log('failed upload');
1019+ // utils. log('failed upload');
10211020 }
10221021 } ) ;
10231022 } else if ( callback ) {
@@ -2227,6 +2226,7 @@ module.exports = getUtmData;
22272226} , { } ] ,
222822274 : [ function ( require , module , exports ) {
22292228var type = require ( './type' ) ;
2229+ var utils = require ( './utils' ) ;
22302230
22312231/*
22322232 * Wrapper for a user properties JSON object that supports operations.
@@ -2241,14 +2241,6 @@ var AMP_OP_SET = '$set';
22412241var AMP_OP_SET_ONCE = '$setOnce' ;
22422242var AMP_OP_UNSET = '$unset' ;
22432243
2244- var log = function ( s ) {
2245- try {
2246- console . log ( '[Amplitude] ' + s ) ;
2247- } catch ( e ) {
2248- // console logging not available
2249- }
2250- } ;
2251-
22522244var Identify = function ( ) {
22532245 this . userPropertiesOperations = { } ;
22542246 this . properties = [ ] ; // keep track of keys that have been added
@@ -2258,7 +2250,7 @@ Identify.prototype.add = function(property, value) {
22582250 if ( type ( value ) === 'number' || type ( value ) === 'string' ) {
22592251 this . _addOperation ( AMP_OP_ADD , property , value ) ;
22602252 } else {
2261- log ( 'Unsupported type for value: ' + type ( value ) + ', expecting number or string' ) ;
2253+ utils . log ( 'Unsupported type for value: ' + type ( value ) + ', expecting number or string' ) ;
22622254 }
22632255 return this ;
22642256} ;
@@ -2274,7 +2266,7 @@ Identify.prototype.append = function(property, value) {
22742266Identify . prototype . clearAll = function ( ) {
22752267 if ( Object . keys ( this . userPropertiesOperations ) . length > 0 ) {
22762268 if ( ! this . userPropertiesOperations . hasOwnProperty ( AMP_OP_CLEAR_ALL ) ) {
2277- log ( 'Need to send $clearAll on its own Identify object without any other operations, skipping $clearAll' ) ;
2269+ utils . log ( 'Need to send $clearAll on its own Identify object without any other operations, skipping $clearAll' ) ;
22782270 }
22792271 return this ;
22802272 }
@@ -2300,13 +2292,13 @@ Identify.prototype.unset = function(property) {
23002292Identify . prototype . _addOperation = function ( operation , property , value ) {
23012293 // check that the identify doesn't already contain a clearAll
23022294 if ( this . userPropertiesOperations . hasOwnProperty ( AMP_OP_CLEAR_ALL ) ) {
2303- log ( 'This identify already contains a $clearAll operation, skipping operation ' + operation ) ;
2295+ utils . log ( 'This identify already contains a $clearAll operation, skipping operation ' + operation ) ;
23042296 return ;
23052297 }
23062298
23072299 // check that property wasn't already used in this Identify
23082300 if ( this . properties . indexOf ( property ) !== - 1 ) {
2309- log ( 'User property "' + property + '" already used in this identify, skipping operation ' + operation ) ;
2301+ utils . log ( 'User property "' + property + '" already used in this identify, skipping operation ' + operation ) ;
23102302 return ;
23112303 }
23122304
@@ -2319,7 +2311,7 @@ Identify.prototype._addOperation = function(operation, property, value) {
23192311
23202312module . exports = Identify ;
23212313
2322- } , { "./type" :6 } ] ,
2314+ } , { "./type" :6 , "./utils" : 7 } ] ,
232323156 : [ function ( require , module , exports ) {
23242316/* Taken from: https://github.com/component/type */
23252317
@@ -2368,6 +2360,93 @@ module.exports = function(val){
23682360} ;
23692361
23702362} , { } ] ,
2363+ 7 : [ function ( require , module , exports ) {
2364+ var type = require ( './type' ) ;
2365+
2366+ var log = function ( s ) {
2367+ try {
2368+ console . log ( '[Amplitude] ' + s ) ;
2369+ } catch ( e ) {
2370+ // console logging not available
2371+ }
2372+ } ;
2373+
2374+ var isEmptyString = function ( str ) {
2375+ return ( ! str || str . length === 0 ) ;
2376+ } ;
2377+
2378+ var validateProperties = function ( properties ) {
2379+ var propsType = type ( properties ) ;
2380+ if ( propsType !== 'object' ) {
2381+ log ( 'Error: invalid event properties format. Expecting Javascript object, received ' + propsType + ', ignoring' ) ;
2382+ return { } ;
2383+ }
2384+
2385+ var copy = { } ; // create a copy with all of the valid properties
2386+ for ( var property in properties ) {
2387+ if ( ! properties . hasOwnProperty ( property ) ) {
2388+ continue ;
2389+ }
2390+
2391+ // validate key
2392+ var key = property ;
2393+ var keyType = type ( key ) ;
2394+ if ( keyType !== 'string' ) {
2395+ log ( 'WARNING: Non-string property key, received type ' + keyType + ', coercing to string "' + key + '"' ) ;
2396+ key = String ( key ) ;
2397+ }
2398+
2399+ // validate value
2400+ var value = validatePropertyValue ( key , properties [ property ] ) ;
2401+ if ( value === null ) {
2402+ continue ;
2403+ }
2404+ copy [ key ] = value ;
2405+ }
2406+ return copy ;
2407+ } ;
2408+
2409+ var invalidValueTypes = [
2410+ 'null' , 'nan' , 'undefined' , 'function' , 'arguments' , 'regexp' , 'element'
2411+ ] ;
2412+
2413+ var validatePropertyValue = function ( key , value ) {
2414+ var valueType = type ( value ) ;
2415+ if ( invalidValueTypes . indexOf ( valueType ) !== - 1 ) {
2416+ log ( 'WARNING: Property key "' + key + '" with invalid value type ' + valueType + ', ignoring' ) ;
2417+ value = null ;
2418+ }
2419+ else if ( valueType === 'error' ) {
2420+ value = String ( value ) ;
2421+ log ( 'WARNING: Property key "' + key + '" with value type error, coercing to ' + value ) ;
2422+ }
2423+ else if ( valueType === 'array' ) {
2424+ // check for nested arrays or objects
2425+ var arrayCopy = [ ] ;
2426+ for ( var i = 0 ; i < value . length ; i ++ ) {
2427+ var element = value [ i ] ;
2428+ var elemType = type ( element ) ;
2429+ if ( elemType === 'array' || elemType === 'object' ) {
2430+ log ( 'WARNING: Cannot have ' + elemType + ' nested in an array property value, skipping' ) ;
2431+ continue ;
2432+ }
2433+ arrayCopy . push ( validatePropertyValue ( key , element ) ) ;
2434+ }
2435+ value = arrayCopy ;
2436+ }
2437+ else if ( valueType === 'object' ) {
2438+ value = validateProperties ( value ) ;
2439+ }
2440+ return value ;
2441+ } ;
2442+
2443+ module . exports = {
2444+ log : log ,
2445+ isEmptyString : isEmptyString ,
2446+ validateProperties : validateProperties
2447+ } ;
2448+
2449+ } , { "./type" :6 } ] ,
2371245014 : [ function ( require , module , exports ) {
23722451/*
23732452 * JavaScript MD5 1.0.1
@@ -3816,16 +3895,6 @@ function isBuffer(obj) {
38163895
38173896} ) ( this ) ;
38183897
3819- } , { } ] ,
3820- 7 : [ function ( require , module , exports ) {
3821- var isEmptyString = function ( str ) {
3822- return ( ! str || str . length === 0 ) ;
3823- } ;
3824-
3825- module . exports = {
3826- isEmptyString : isEmptyString
3827- } ;
3828-
38293898} , { } ] ,
3830389917 : [ function ( require , module , exports ) {
38313900/* jshint bitwise: false, laxbreak: true */
0 commit comments