@@ -128,16 +128,18 @@ var log = function(s) {
128128var API_VERSION = 2 ;
129129var DEFAULT_OPTIONS = {
130130 apiEndpoint : 'api.amplitude.com' ,
131- cookieName : 'amplitude_id' ,
132131 cookieExpiration : 365 * 10 ,
133- unsentKey : 'amplitude_unsent' ,
134- saveEvents : true ,
132+ cookieName : 'amplitude_id' ,
135133 domain : undefined ,
136- sessionTimeout : 30 * 60 * 1000 ,
137- platform : 'Web' ,
138- language : language . language ,
139134 includeUtm : false ,
140- optOut : false
135+ language : language . language ,
136+ optOut : false ,
137+ platform : 'Web' ,
138+ savedMaxCount : 1000 ,
139+ saveEvents : true ,
140+ sessionTimeout : 30 * 60 * 1000 ,
141+ unsentKey : 'amplitude_unsent' ,
142+ uploadBatchSize : 100 ,
141143} ;
142144var LocalStorageKeys = {
143145 LAST_EVENT_ID : 'amplitude_lastEventId' ,
@@ -185,6 +187,8 @@ Amplitude.prototype.init = function(apiKey, opt_userId, opt_config) {
185187 this . options . platform = opt_config . platform || this . options . platform ;
186188 this . options . language = opt_config . language || this . options . language ;
187189 this . options . sessionTimeout = opt_config . sessionTimeout || this . options . sessionTimeout ;
190+ this . options . uploadBatchSize = opt_config . uploadBatchSize || this . options . uploadBatchSize ;
191+ this . options . savedMaxCount = opt_config . savedMaxCount || this . options . savedMaxCount ;
188192 }
189193
190194 Cookie . options ( {
@@ -433,19 +437,31 @@ Amplitude.prototype._logEvent = function(eventType, eventProperties, apiProperti
433437 }
434438 // country: null
435439 } ;
440+
441+ //log('logged eventType=' + eventType + ', properties=' + JSON.stringify(eventProperties));
442+
436443 this . _unsentEvents . push ( event ) ;
444+
445+ // Remove old events from the beginning of the array if too many
446+ // have accumulated. Don't want to kill memory. Default is 1000 events.
447+ if ( this . _unsentEvents . length > this . options . savedMaxCount ) {
448+ this . _unsentEvents . splice ( 0 , this . _unsentEvents . length - this . options . savedMaxCount ) ;
449+ }
450+
437451 if ( this . options . saveEvents ) {
438452 this . saveEvents ( ) ;
439453 }
440- //log('logged eventType=' + eventType + ', properties=' + JSON.stringify(eventProperties));
454+
441455 this . sendEvents ( ) ;
456+
457+ return eventId ;
442458 } catch ( e ) {
443459 log ( e ) ;
444460 }
445461} ;
446462
447463Amplitude . prototype . logEvent = function ( eventType , eventProperties ) {
448- this . _logEvent ( eventType , eventProperties ) ;
464+ return this . _logEvent ( eventType , eventProperties ) ;
449465} ;
450466
451467// Test that n is a number or a numeric value.
@@ -460,20 +476,39 @@ Amplitude.prototype.logRevenue = function(price, quantity, product) {
460476 return ;
461477 }
462478
463- this . _logEvent ( 'revenue_amount' , { } , {
479+ return this . _logEvent ( 'revenue_amount' , { } , {
464480 productId : product ,
465481 special : 'revenue_amount' ,
466482 quantity : quantity || 1 ,
467483 price : price
468484 } ) ;
469485} ;
470486
487+ /**
488+ * Remove events in storage with event ids up to and including maxEventId. Does
489+ * a true filter in case events get out of order or old events are removed.
490+ */
491+ Amplitude . prototype . removeEvents = function ( maxEventId ) {
492+ var filteredEvents = [ ] ;
493+ for ( var i = 0 ; i < this . _unsentEvents . length ; i ++ ) {
494+ if ( this . _unsentEvents [ i ] . event_id > maxEventId ) {
495+ filteredEvents . push ( this . _unsentEvents [ i ] ) ;
496+ }
497+ }
498+ this . _unsentEvents = filteredEvents ;
499+ } ;
500+
471501Amplitude . prototype . sendEvents = function ( ) {
472502 if ( ! this . _sending && ! this . options . optOut ) {
473503 this . _sending = true ;
474504 var url = ( 'https:' === window . location . protocol ? 'https' : 'http' ) + '://' +
475505 this . options . apiEndpoint + '/' ;
476- var events = JSON . stringify ( this . _unsentEvents ) ;
506+
507+ // Determine how many events to send and track the maximum event id sent in this batch.
508+ var numEvents = Math . min ( this . _unsentEvents . length , this . options . uploadBatchSize ) ;
509+ var maxEventId = this . _unsentEvents [ numEvents - 1 ] . event_id ;
510+
511+ var events = JSON . stringify ( this . _unsentEvents . slice ( 0 , numEvents ) ) ;
477512 var uploadTime = new Date ( ) . getTime ( ) ;
478513 var data = {
479514 client : this . options . apiKey ,
@@ -482,20 +517,35 @@ Amplitude.prototype.sendEvents = function() {
482517 upload_time : uploadTime ,
483518 checksum : md5 ( API_VERSION + this . options . apiKey + events + uploadTime )
484519 } ;
485- var numEvents = this . _unsentEvents . length ;
520+
486521 var scope = this ;
487- new Request ( url , data ) . send ( function ( response ) {
522+ new Request ( url , data ) . send ( function ( status , response ) {
488523 scope . _sending = false ;
489524 try {
490- if ( response === 'success' ) {
525+ if ( status === 200 && response === 'success' ) {
491526 //log('sucessful upload');
492- scope . _unsentEvents . splice ( 0 , numEvents ) ;
527+ scope . removeEvents ( maxEventId ) ;
528+
529+ // Update the event cache after the removal of sent events.
493530 if ( scope . options . saveEvents ) {
494531 scope . saveEvents ( ) ;
495532 }
533+
534+ // Send more events if any queued during previous send.
496535 if ( scope . _unsentEvents . length > 0 ) {
497536 scope . sendEvents ( ) ;
498537 }
538+ } else if ( status === 413 ) {
539+ //log('request too large');
540+ // Can't even get this one massive event through. Drop it.
541+ if ( scope . options . uploadBatchSize === 1 ) {
542+ scope . removeEvents ( maxEventId ) ;
543+ }
544+
545+ // The server complained about the length of the request.
546+ // Backoff and try again.
547+ scope . options . uploadBatchSize = Math . ceil ( numEvents / 2 ) ;
548+ scope . sendEvents ( ) ;
499549 }
500550 } catch ( e ) {
501551 //log('failed upload');
@@ -1462,9 +1512,7 @@ Request.prototype.send = function(callback) {
14621512 xhr . open ( 'POST' , this . url , true ) ;
14631513 xhr . onreadystatechange = function ( ) {
14641514 if ( xhr . readyState === 4 ) {
1465- if ( xhr . status === 200 ) {
1466- callback ( xhr . responseText ) ;
1467- }
1515+ callback ( xhr . status , xhr . responseText ) ;
14681516 }
14691517 } ;
14701518 xhr . setRequestHeader ( 'Content-Type' , 'application/x-www-form-urlencoded; charset=UTF-8' ) ;
0 commit comments