@@ -128,6 +128,7 @@ var log = function(s) {
128128
129129var IDENTIFY_EVENT = '$identify' ;
130130var API_VERSION = 2 ;
131+ var MAX_STRING_LENGTH = 1024 ;
131132var DEFAULT_OPTIONS = {
132133 apiEndpoint : 'api.amplitude.com' ,
133134 cookieExpiration : 365 * 10 ,
@@ -149,6 +150,7 @@ var DEFAULT_OPTIONS = {
149150} ;
150151var LocalStorageKeys = {
151152 LAST_EVENT_ID : 'amplitude_lastEventId' ,
153+ LAST_IDENTIFY_ID : 'amplitude_lastIdentifyId' ,
152154 LAST_EVENT_TIME : 'amplitude_lastEventTime' ,
153155 SESSION_ID : 'amplitude_sessionId'
154156} ;
@@ -164,11 +166,14 @@ var Amplitude = function() {
164166} ;
165167
166168Amplitude . prototype . _eventId = 0 ;
169+ Amplitude . prototype . _identifyId = 0 ;
167170Amplitude . prototype . _sending = false ;
168171Amplitude . prototype . _lastEventTime = null ;
169172Amplitude . prototype . _sessionId = null ;
170173Amplitude . prototype . _newSession = false ;
171174
175+ Amplitude . prototype . Identify = Identify ;
176+
172177/**
173178 * Initializes Amplitude.
174179 * apiKey The API Key for your app
@@ -225,10 +230,16 @@ Amplitude.prototype.init = function(apiKey, opt_userId, opt_config) {
225230
226231 if ( this . options . saveEvents ) {
227232 var savedUnsentEventsString = localStorage . getItem ( this . options . unsentKey ) ;
228- var savedUnsentIdentifysString = localStorage . getItem ( this . options . unsentIdentifyKey ) ;
229233 if ( savedUnsentEventsString ) {
230234 try {
231235 this . _unsentEvents = JSON . parse ( savedUnsentEventsString ) ;
236+ } catch ( e ) {
237+ //log(e);
238+ }
239+ }
240+ var savedUnsentIdentifysString = localStorage . getItem ( this . options . unsentIdentifyKey ) ;
241+ if ( savedUnsentIdentifysString ) {
242+ try {
232243 this . _unsentIdentifys = JSON . parse ( savedUnsentIdentifysString ) ;
233244 } catch ( e ) {
234245 //log(e);
@@ -245,6 +256,7 @@ Amplitude.prototype.init = function(apiKey, opt_userId, opt_config) {
245256 this . _lastEventTime = parseInt ( localStorage . getItem ( LocalStorageKeys . LAST_EVENT_TIME ) ) || null ;
246257 this . _sessionId = parseInt ( localStorage . getItem ( LocalStorageKeys . SESSION_ID ) ) || null ;
247258 this . _eventId = localStorage . getItem ( LocalStorageKeys . LAST_EVENT_ID ) || 0 ;
259+ this . _identifyId = localStorage . getItem ( LocalStorageKeys . LAST_IDENTIFY_ID ) || 0 ;
248260 var now = new Date ( ) . getTime ( ) ;
249261 if ( ! this . _sessionId || ! this . _lastEventTime || now - this . _lastEventTime > this . options . sessionTimeout ) {
250262 this . _newSession = true ;
@@ -267,6 +279,11 @@ Amplitude.prototype.nextEventId = function() {
267279 return this . _eventId ;
268280} ;
269281
282+ Amplitude . prototype . nextIdentifyId = function ( ) {
283+ this . _identifyId ++ ;
284+ return this . _identifyId ;
285+ } ;
286+
270287// returns the number of unsent events and identifys
271288Amplitude . prototype . _unsentCount = function ( ) {
272289 return this . _unsentEvents . length + this . _unsentIdentifys . length ;
@@ -444,6 +461,34 @@ Amplitude.prototype.setVersionName = function(versionName) {
444461 }
445462} ;
446463
464+ // truncate string values in event and user properties so that request size does not get too large
465+ Amplitude . prototype . _truncate = function ( value ) {
466+ if ( typeof ( value ) === 'object' ) {
467+ if ( Array . isArray ( value ) ) {
468+ for ( var i = 0 ; i < value . length ; i ++ ) {
469+ value [ i ] = this . _truncate ( value [ i ] ) ;
470+ }
471+ } else {
472+ for ( var key in value ) {
473+ if ( value . hasOwnProperty ( key ) ) {
474+ value [ key ] = this . _truncate ( value [ key ] ) ;
475+ }
476+ }
477+ }
478+
479+ return value ;
480+ }
481+
482+ return _truncateValue ( value ) ;
483+ } ;
484+
485+ var _truncateValue = function ( value ) {
486+ if ( typeof ( value ) === 'string' ) {
487+ return value . length > MAX_STRING_LENGTH ? value . substring ( 0 , MAX_STRING_LENGTH ) : value ;
488+ }
489+ return value ;
490+ } ;
491+
447492/**
448493 * Private logEvent method. Keeps apiProperties from being publicly exposed.
449494 */
@@ -459,16 +504,22 @@ Amplitude.prototype._logEvent = function(eventType, eventProperties, apiProperti
459504 return ;
460505 }
461506 try {
507+ var eventId ;
508+ if ( eventType === IDENTIFY_EVENT ) {
509+ eventId = this . nextIdentifyId ( ) ;
510+ localStorage . setItem ( LocalStorageKeys . LAST_IDENTIFY_ID , eventId ) ;
511+ } else {
512+ eventId = this . nextEventId ( ) ;
513+ localStorage . setItem ( LocalStorageKeys . LAST_EVENT_ID , eventId ) ;
514+ }
462515 var eventTime = new Date ( ) . getTime ( ) ;
463- var eventId = this . nextEventId ( ) ;
464516 var ua = this . _ua ;
465517 if ( ! this . _sessionId || ! this . _lastEventTime || eventTime - this . _lastEventTime > this . options . sessionTimeout ) {
466518 this . _sessionId = eventTime ;
467519 localStorage . setItem ( LocalStorageKeys . SESSION_ID , this . _sessionId ) ;
468520 }
469521 this . _lastEventTime = eventTime ;
470522 localStorage . setItem ( LocalStorageKeys . LAST_EVENT_TIME , this . _lastEventTime ) ;
471- localStorage . setItem ( LocalStorageKeys . LAST_EVENT_ID , eventId ) ;
472523
473524 // Add the utm properties, if any, onto the user properties.
474525 userProperties = userProperties || { } ;
@@ -498,8 +549,8 @@ Amplitude.prototype._logEvent = function(eventType, eventProperties, apiProperti
498549 device_model : ua . os . name || null ,
499550 language : this . options . language ,
500551 api_properties : apiProperties ,
501- event_properties : eventProperties ,
502- user_properties : userProperties ,
552+ event_properties : this . _truncate ( eventProperties ) ,
553+ user_properties : this . _truncate ( userProperties ) ,
503554 uuid : UUID ( ) ,
504555 library : {
505556 name : 'amplitude-js' ,
@@ -508,14 +559,19 @@ Amplitude.prototype._logEvent = function(eventType, eventProperties, apiProperti
508559 // country: null
509560 } ;
510561
511- //log('logged eventType=' + eventType + ', properties=' + JSON.stringify(eventProperties));
512-
513- this . _unsentEvents . push ( event ) ;
562+ if ( eventType === IDENTIFY_EVENT ) {
563+ this . _unsentIdentifys . push ( event ) ;
564+ if ( this . _unsentIdentifys . length > this . options . savedMaxCount ) {
565+ this . _unsentIdentifys . splice ( 0 , this . _unsentIdentifys . length - this . options . savedMaxCount ) ;
566+ }
567+ } else {
568+ this . _unsentEvents . push ( event ) ;
514569
515- // Remove old events from the beginning of the array if too many
516- // have accumulated. Don't want to kill memory. Default is 1000 events.
517- if ( this . _unsentEvents . length > this . options . savedMaxCount ) {
518- this . _unsentEvents . splice ( 0 , this . _unsentEvents . length - this . options . savedMaxCount ) ;
570+ // Remove old events from the beginning of the array if too many
571+ // have accumulated. Don't want to kill memory. Default is 1000 events.
572+ if ( this . _unsentEvents . length > this . options . savedMaxCount ) {
573+ this . _unsentEvents . splice ( 0 , this . _unsentEvents . length - this . options . savedMaxCount ) ;
574+ }
519575 }
520576
521577 if ( this . options . saveEvents ) {
@@ -560,14 +616,26 @@ Amplitude.prototype.logRevenue = function(price, quantity, product) {
560616 * Remove events in storage with event ids up to and including maxEventId. Does
561617 * a true filter in case events get out of order or old events are removed.
562618 */
563- Amplitude . prototype . removeEvents = function ( maxEventId ) {
564- var filteredEvents = [ ] ;
565- for ( var i = 0 ; i < this . _unsentEvents . length ; i ++ ) {
566- if ( this . _unsentEvents [ i ] . event_id > maxEventId ) {
567- filteredEvents . push ( this . _unsentEvents [ i ] ) ;
619+ Amplitude . prototype . removeEvents = function ( maxEventId , maxIdentifyId ) {
620+ if ( maxEventId ) {
621+ var filteredEvents = [ ] ;
622+ for ( var i = 0 ; i < this . _unsentEvents . length ; i ++ ) {
623+ if ( this . _unsentEvents [ i ] . event_id > maxEventId ) {
624+ filteredEvents . push ( this . _unsentEvents [ i ] ) ;
625+ }
626+ }
627+ this . _unsentEvents = filteredEvents ;
628+ }
629+
630+ if ( maxIdentifyId ) {
631+ var filteredIdentifys = [ ] ;
632+ for ( var j = 0 ; j < this . _unsentIdentifys . length ; j ++ ) {
633+ if ( this . _unsentIdentifys [ j ] . event_id > maxIdentifyId ) {
634+ filteredIdentifys . push ( this . _unsentIdentifys [ j ] ) ;
635+ }
568636 }
637+ this . _unsentIdentifys = filteredIdentifys ;
569638 }
570- this . _unsentEvents = filteredEvents ;
571639} ;
572640
573641Amplitude . prototype . sendEvents = function ( callback ) {
@@ -577,10 +645,42 @@ Amplitude.prototype.sendEvents = function(callback) {
577645 this . options . apiEndpoint + '/' ;
578646
579647 // Determine how many events to send and track the maximum event id sent in this batch.
580- var numEvents = Math . min ( this . _unsentEvents . length , this . options . uploadBatchSize ) ;
581- var maxEventId = this . _unsentEvents [ numEvents - 1 ] . event_id ;
648+ var numEvents = Math . min ( this . _unsentCount ( ) , this . options . uploadBatchSize ) ;
649+
650+ // coalesce events from both queues
651+ var eventsToSend = [ ] ;
652+ var eventIndex = 0 ;
653+ var identifyIndex = 0 ;
654+
655+ while ( eventsToSend . length < numEvents ) {
656+ var event ;
657+
658+ // case 1: no identifys - grab from events
659+ if ( identifyIndex >= this . _unsentIdentifys . length ) {
660+ event = this . _unsentEvents [ eventIndex ++ ] ;
661+
662+ // case 2: no events - grab from identifys
663+ } else if ( eventIndex >= this . _unsentEvents . length ) {
664+ event = this . _unsentIdentifys [ identifyIndex ++ ] ;
665+
666+ // case 3: need to compare timestamps
667+ } else {
668+ if ( this . _unsentIdentifys [ identifyIndex ] . timestamp <= this . _unsentEvents [ eventIndex ] . timestamp ) {
669+ event = this . _unsentIdentifys [ identifyIndex ++ ] ;
670+ } else {
671+ event = this . _unsentEvents [ eventIndex ++ ] ;
672+ }
673+ }
674+
675+ eventsToSend . push ( event ) ;
676+ }
677+
678+ var maxEventId = eventIndex > 0 && this . _unsentEvents . length > 0 ?
679+ this . _unsentEvents [ eventIndex - 1 ] . event_id : null ;
680+ var maxIdentifyId = identifyIndex > 0 && this . _unsentIdentifys . length > 0 ?
681+ this . _unsentIdentifys [ identifyIndex - 1 ] . event_id : null ;
682+ var events = JSON . stringify ( eventsToSend ) ;
582683
583- var events = JSON . stringify ( this . _unsentEvents . slice ( 0 , numEvents ) ) ;
584684 var uploadTime = new Date ( ) . getTime ( ) ;
585685 var data = {
586686 client : this . options . apiKey ,
@@ -596,7 +696,7 @@ Amplitude.prototype.sendEvents = function(callback) {
596696 try {
597697 if ( status === 200 && response === 'success' ) {
598698 //log('sucessful upload');
599- scope . removeEvents ( maxEventId ) ;
699+ scope . removeEvents ( maxEventId , maxIdentifyId ) ;
600700
601701 // Update the event cache after the removal of sent events.
602702 if ( scope . options . saveEvents ) {
@@ -612,7 +712,8 @@ Amplitude.prototype.sendEvents = function(callback) {
612712 //log('request too large');
613713 // Can't even get this one massive event through. Drop it.
614714 if ( scope . options . uploadBatchSize === 1 ) {
615- scope . removeEvents ( maxEventId ) ;
715+ // if massive event is identify, still need to drop it
716+ scope . removeEvents ( maxEventId , maxIdentifyId ) ;
616717 }
617718
618719 // The server complained about the length of the request.
0 commit comments