@@ -1032,18 +1032,20 @@ var _convertProxyObjectToRealObject = function _convertProxyObjectToRealObject(i
10321032 * @param {Identify } identify_obj - the Identify object containing the user property operations to send.
10331033 * @param {Amplitude~eventCallback } opt_callback - (optional) callback function to run when the identify event has been sent.
10341034 * Note: the server response code and response body from the identify event upload are passed to the callback function.
1035+ * @param {Amplitude~eventCallback } opt_error_callback - (optional) a callback function to run after the event logging
1036+ * fails. The failure can be from the request being malformed or from a network failure
1037+ * Note: the server response code and response body from the event upload are passed to the callback function.
10351038 * @example
10361039 * var identify = new amplitude.Identify().set('colors', ['rose', 'gold']).add('karma', 1).setOnce('sign_up_date', '2016-03-31');
10371040 * amplitude.identify(identify);
10381041 */
1039- AmplitudeClient . prototype . identify = function ( identify_obj , opt_callback ) {
1042+ AmplitudeClient . prototype . identify = function ( identify_obj , opt_callback , opt_error_callback ) {
10401043 if ( this . _shouldDeferCall ( ) ) {
10411044 return this . _q . push ( [ 'identify' ] . concat ( Array . prototype . slice . call ( arguments , 0 ) ) ) ;
10421045 }
10431046 if ( ! this . _apiKeySet ( 'identify()' ) ) {
1044- if ( type ( opt_callback ) === 'function' ) {
1045- opt_callback ( 0 , 'No request sent' , { reason : 'API key is not set' } ) ;
1046- }
1047+ _logErrorsWithCallbacks ( opt_callback , opt_error_callback , 0 , 'No request sent' , { reason : 'API key is not set' } ) ;
1048+
10471049 return ;
10481050 }
10491051
@@ -1064,42 +1066,49 @@ AmplitudeClient.prototype.identify = function (identify_obj, opt_callback) {
10641066 null ,
10651067 null ,
10661068 opt_callback ,
1069+ opt_error_callback ,
10671070 ) ;
10681071 } else {
1069- if ( type ( opt_callback ) === 'function' ) {
1070- opt_callback ( 0 , 'No request sent' , { reason : 'No user property operations' } ) ;
1071- }
1072+ _logErrorsWithCallbacks ( opt_callback , opt_error_callback , 0 , 'No request sent' , {
1073+ reason : 'No user property operations' ,
1074+ } ) ;
10721075 }
10731076 } else {
10741077 utils . log . error ( 'Invalid identify input type. Expected Identify object but saw ' + type ( identify_obj ) ) ;
1075- if ( type ( opt_callback ) === 'function' ) {
1076- opt_callback ( 0 , 'No request sent' , { reason : 'Invalid identify input type' } ) ;
1077- }
1078+ _logErrorsWithCallbacks ( opt_callback , opt_error_callback , 0 , 'No request sent' , {
1079+ reason : 'Invalid identify input type' ,
1080+ } ) ;
10781081 }
10791082} ;
10801083
1081- AmplitudeClient . prototype . groupIdentify = function ( group_type , group_name , identify_obj , opt_callback ) {
1084+ AmplitudeClient . prototype . groupIdentify = function (
1085+ group_type ,
1086+ group_name ,
1087+ identify_obj ,
1088+ opt_callback ,
1089+ opt_error_callback ,
1090+ ) {
10821091 if ( this . _shouldDeferCall ( ) ) {
10831092 return this . _q . push ( [ 'groupIdentify' ] . concat ( Array . prototype . slice . call ( arguments , 0 ) ) ) ;
10841093 }
10851094 if ( ! this . _apiKeySet ( 'groupIdentify()' ) ) {
1086- if ( type ( opt_callback ) === 'function' ) {
1087- opt_callback ( 0 , 'No request sent' , { reason : 'API key is not set' } ) ;
1088- }
1095+ _logErrorsWithCallbacks ( opt_callback , opt_error_callback , 0 , 'No request sent' , {
1096+ reason : 'API key is not set' ,
1097+ } ) ;
10891098 return ;
10901099 }
10911100
10921101 if ( ! utils . validateInput ( group_type , 'group_type' , 'string' ) || utils . isEmptyString ( group_type ) ) {
1093- if ( type ( opt_callback ) === 'function' ) {
1094- opt_callback ( 0 , 'No request sent' , { reason : 'Invalid group type' } ) ;
1095- }
1102+ _logErrorsWithCallbacks ( opt_callback , opt_error_callback , 0 , 'No request sent' , {
1103+ reason : 'Invalid group type' ,
1104+ } ) ;
10961105 return ;
10971106 }
10981107
10991108 if ( group_name === null || group_name === undefined ) {
1100- if ( type ( opt_callback ) === 'function' ) {
1101- opt_callback ( 0 , 'No request sent' , { reason : 'Invalid group name' } ) ;
1102- }
1109+ _logErrorsWithCallbacks ( opt_callback , opt_error_callback , 0 , 'No request sent' , {
1110+ reason : 'Invalid group name' ,
1111+ } ) ;
11031112 return ;
11041113 }
11051114
@@ -1120,17 +1129,18 @@ AmplitudeClient.prototype.groupIdentify = function (group_type, group_name, iden
11201129 identify_obj . userPropertiesOperations ,
11211130 null ,
11221131 opt_callback ,
1132+ opt_error_callback ,
11231133 ) ;
11241134 } else {
1125- if ( type ( opt_callback ) === 'function' ) {
1126- opt_callback ( 0 , 'No request sent' , { reason : 'No group property operations' } ) ;
1127- }
1135+ _logErrorsWithCallbacks ( opt_callback , opt_error_callback , 0 , 'No request sent' , {
1136+ reason : 'No group property operations' ,
1137+ } ) ;
11281138 }
11291139 } else {
11301140 utils . log . error ( 'Invalid identify input type. Expected Identify object but saw ' + type ( identify_obj ) ) ;
1131- if ( type ( opt_callback ) === 'function' ) {
1132- opt_callback ( 0 , 'No request sent' , { reason : 'Invalid identify input type' } ) ;
1133- }
1141+ _logErrorsWithCallbacks ( opt_callback , opt_error_callback , 0 , 'No request sent' , {
1142+ reason : 'Invalid identify input type' ,
1143+ } ) ;
11341144 }
11351145} ;
11361146
@@ -1164,19 +1174,20 @@ AmplitudeClient.prototype._logEvent = function _logEvent(
11641174 groupProperties ,
11651175 timestamp ,
11661176 callback ,
1177+ errorCallback ,
11671178) {
11681179 _loadCookieData ( this ) ; // reload cookie before each log event to sync event meta-data between windows and tabs
11691180
11701181 if ( ! eventType ) {
1171- if ( type ( callback ) === 'function' ) {
1172- callback ( 0 , 'No request sent' , { reason : 'Missing eventType' } ) ;
1173- }
1182+ _logErrorsWithCallbacks ( callback , errorCallback , 0 , 'No request sent' , {
1183+ reason : 'Missing eventType' ,
1184+ } ) ;
11741185 return ;
11751186 }
11761187 if ( this . options . optOut ) {
1177- if ( type ( callback ) === 'function' ) {
1178- callback ( 0 , 'No request sent' , { reason : 'optOut is set to true' } ) ;
1179- }
1188+ _logErrorsWithCallbacks ( callback , errorCallback , 0 , 'No request sent' , {
1189+ reason : 'optOut is set to true' ,
1190+ } ) ;
11801191 return ;
11811192 }
11821193
@@ -1233,10 +1244,10 @@ AmplitudeClient.prototype._logEvent = function _logEvent(
12331244 } ;
12341245
12351246 if ( eventType === Constants . IDENTIFY_EVENT || eventType === Constants . GROUP_IDENTIFY_EVENT ) {
1236- this . _unsentIdentifys . push ( { event, callback } ) ;
1247+ this . _unsentIdentifys . push ( { event, callback, errorCallback } ) ;
12371248 this . _limitEventsQueued ( this . _unsentIdentifys ) ;
12381249 } else {
1239- this . _unsentEvents . push ( { event, callback } ) ;
1250+ this . _unsentEvents . push ( { event, callback, errorCallback } ) ;
12401251 this . _limitEventsQueued ( this . _unsentEvents ) ;
12411252 }
12421253
@@ -1277,11 +1288,9 @@ AmplitudeClient.prototype._limitEventsQueued = function _limitEventsQueued(queue
12771288 if ( queue . length > this . options . savedMaxCount ) {
12781289 const deletedEvents = queue . splice ( 0 , queue . length - this . options . savedMaxCount ) ;
12791290 deletedEvents . forEach ( ( event ) => {
1280- if ( type ( event . callback ) === 'function' ) {
1281- event . callback ( 0 , 'No request sent' , {
1282- reason : 'Event dropped because options.savedMaxCount exceeded. User may be offline or have a content blocker' ,
1283- } ) ;
1284- }
1291+ _logErrorsWithCallbacks ( event . callback , event . errorCallback , 0 , 'No request sent' , {
1292+ reason : 'Event dropped because options.savedMaxCount exceeded. User may be offline or have a content blocker' ,
1293+ } ) ;
12851294 } ) ;
12861295 }
12871296} ;
@@ -1302,13 +1311,16 @@ AmplitudeClient.prototype._limitEventsQueued = function _limitEventsQueued(queue
13021311 * @param {object } eventProperties - (optional) an object with string keys and values for the event properties.
13031312 * @param {Amplitude~eventCallback } opt_callback - (optional) a callback function to run after the event is logged.
13041313 * Note: the server response code and response body from the event upload are passed to the callback function.
1314+ * @param {Amplitude~eventCallback } opt_error_callback - (optional) a callback function to run after the event logging
1315+ * fails. The failure can be from the request being malformed or from a network failure
1316+ * Note: the server response code and response body from the event upload are passed to the callback function.
13051317 * @example amplitudeClient.logEvent('Clicked Homepage Button', {'finished_flow': false, 'clicks': 15});
13061318 */
1307- AmplitudeClient . prototype . logEvent = function logEvent ( eventType , eventProperties , opt_callback ) {
1319+ AmplitudeClient . prototype . logEvent = function logEvent ( eventType , eventProperties , opt_callback , opt_error_callback ) {
13081320 if ( this . _shouldDeferCall ( ) ) {
13091321 return this . _q . push ( [ 'logEvent' ] . concat ( Array . prototype . slice . call ( arguments , 0 ) ) ) ;
13101322 }
1311- return this . logEventWithTimestamp ( eventType , eventProperties , null , opt_callback ) ;
1323+ return this . logEventWithTimestamp ( eventType , eventProperties , null , opt_callback , opt_error_callback ) ;
13121324} ;
13131325
13141326/**
@@ -1319,36 +1331,52 @@ AmplitudeClient.prototype.logEvent = function logEvent(eventType, eventPropertie
13191331 * @param {number } timestamp - (optional) the custom timestamp as milliseconds since epoch.
13201332 * @param {Amplitude~eventCallback } opt_callback - (optional) a callback function to run after the event is logged.
13211333 * Note: the server response code and response body from the event upload are passed to the callback function.
1334+ * @param {Amplitude~eventCallback } opt_error_callback - (optional) a callback function to run after the event logging
1335+ * fails. The failure can be from the request being malformed or from a network failure
1336+ * Note: the server response code and response body from the event upload are passed to the callback function.
13221337 * @example amplitudeClient.logEvent('Clicked Homepage Button', {'finished_flow': false, 'clicks': 15});
13231338 */
13241339AmplitudeClient . prototype . logEventWithTimestamp = function logEvent (
13251340 eventType ,
13261341 eventProperties ,
13271342 timestamp ,
13281343 opt_callback ,
1344+ opt_error_callback ,
13291345) {
13301346 if ( this . _shouldDeferCall ( ) ) {
13311347 return this . _q . push ( [ 'logEventWithTimestamp' ] . concat ( Array . prototype . slice . call ( arguments , 0 ) ) ) ;
13321348 }
13331349 if ( ! this . _apiKeySet ( 'logEvent()' ) ) {
1334- if ( type ( opt_callback ) === 'function' ) {
1335- opt_callback ( 0 , 'No request sent' , { reason : 'API key not set' } ) ;
1336- }
1350+ _logErrorsWithCallbacks ( opt_callback , opt_error_callback , 0 , 'No request sent' , {
1351+ reason : 'API key not set' ,
1352+ } ) ;
1353+
13371354 return - 1 ;
13381355 }
13391356 if ( ! utils . validateInput ( eventType , 'eventType' , 'string' ) ) {
1340- if ( type ( opt_callback ) === 'function' ) {
1341- opt_callback ( 0 , 'No request sent' , { reason : 'Invalid type for eventType' } ) ;
1342- }
1357+ _logErrorsWithCallbacks ( opt_callback , opt_error_callback , 0 , 'No request sent' , {
1358+ reason : 'Invalid type for eventType' ,
1359+ } ) ;
1360+
13431361 return - 1 ;
13441362 }
13451363 if ( utils . isEmptyString ( eventType ) ) {
1346- if ( type ( opt_callback ) === 'function' ) {
1347- opt_callback ( 0 , 'No request sent' , { reason : 'Missing eventType' } ) ;
1348- }
1364+ _logErrorsWithCallbacks ( opt_callback , opt_error_callback , 0 , 'No request sent' , {
1365+ reason : 'Missing eventType' ,
1366+ } ) ;
13491367 return - 1 ;
13501368 }
1351- return this . _logEvent ( eventType , eventProperties , null , null , null , null , timestamp , opt_callback ) ;
1369+ return this . _logEvent (
1370+ eventType ,
1371+ eventProperties ,
1372+ null ,
1373+ null ,
1374+ null ,
1375+ null ,
1376+ timestamp ,
1377+ opt_callback ,
1378+ opt_error_callback ,
1379+ ) ;
13521380} ;
13531381
13541382/**
@@ -1365,25 +1393,35 @@ AmplitudeClient.prototype.logEventWithTimestamp = function logEvent(
13651393 * groupName can be a string or an array of strings.
13661394 * @param {Amplitude~eventCallback } opt_callback - (optional) a callback function to run after the event is logged.
13671395 * Note: the server response code and response body from the event upload are passed to the callback function.
1396+ * @param {Amplitude~eventCallback } opt_error_callback - (optional) a callback function to run after the event logging
1397+ * fails. The failure can be from the request being malformed or from a network failure
1398+ * Note: the server response code and response body from the event upload are passed to the callback function.
13681399 * @example amplitudeClient.logEventWithGroups('Clicked Button', null, {'orgId': 24});
13691400 */
1370- AmplitudeClient . prototype . logEventWithGroups = function ( eventType , eventProperties , groups , opt_callback ) {
1401+ AmplitudeClient . prototype . logEventWithGroups = function (
1402+ eventType ,
1403+ eventProperties ,
1404+ groups ,
1405+ opt_callback ,
1406+ opt_error_callback ,
1407+ ) {
13711408 if ( this . _shouldDeferCall ( ) ) {
13721409 return this . _q . push ( [ 'logEventWithGroups' ] . concat ( Array . prototype . slice . call ( arguments , 0 ) ) ) ;
13731410 }
13741411 if ( ! this . _apiKeySet ( 'logEventWithGroups()' ) ) {
1375- if ( type ( opt_callback ) === 'function' ) {
1376- opt_callback ( 0 , 'No request sent' , { reason : 'API key not set' } ) ;
1377- }
1412+ _logErrorsWithCallbacks ( event . callback , event . errorCallback , 0 , 'No request sent' , {
1413+ reason : 'API key not set' ,
1414+ } ) ;
1415+
13781416 return - 1 ;
13791417 }
13801418 if ( ! utils . validateInput ( eventType , 'eventType' , 'string' ) ) {
1381- if ( type ( opt_callback ) === 'function' ) {
1382- opt_callback ( 0 , 'No request sent' , { reason : 'Invalid type for eventType' } ) ;
1383- }
1419+ _logErrorsWithCallbacks ( event . callback , event . errorCallback , 0 , 'No request sent' , {
1420+ reason : 'Invalid type for eventType' ,
1421+ } ) ;
13841422 return - 1 ;
13851423 }
1386- return this . _logEvent ( eventType , eventProperties , null , null , groups , null , null , opt_callback ) ;
1424+ return this . _logEvent ( eventType , eventProperties , null , null , groups , null , null , opt_callback , opt_error_callback ) ;
13871425} ;
13881426
13891427/**
@@ -1394,6 +1432,25 @@ var _isNumber = function _isNumber(n) {
13941432 return ! isNaN ( parseFloat ( n ) ) && isFinite ( n ) ;
13951433} ;
13961434
1435+ /**
1436+ * Handles errors that are sent to both callbacks
1437+ * @private
1438+ */
1439+ var _logErrorsWithCallbacks = function _logErrorsWithCallbacks (
1440+ opt_callback ,
1441+ opt_error_callback ,
1442+ status ,
1443+ response ,
1444+ details ,
1445+ ) {
1446+ if ( type ( opt_callback ) === 'function' ) {
1447+ opt_callback ( status , response , details ) ;
1448+ }
1449+ if ( type ( opt_error_callback ) === 'function' ) {
1450+ opt_error_callback ( status , response , details ) ;
1451+ }
1452+ } ;
1453+
13971454/**
13981455 * Log revenue with Revenue interface. The new revenue interface allows for more revenue fields like
13991456 * revenueType and event properties.
@@ -1468,6 +1525,33 @@ if (BUILD_COMPAT_2_0) {
14681525 } ;
14691526}
14701527
1528+ /**
1529+ * Calls error callback on unsent events
1530+ * @private
1531+ */
1532+ AmplitudeClient . prototype . _logErrorsOnEvents = function _logErrorsOnEvents (
1533+ maxEventId ,
1534+ maxIdentifyId ,
1535+ status ,
1536+ response ,
1537+ ) {
1538+ const queues = [ '_unsentEvents' , '_unsentIdentifys' ] ;
1539+
1540+ for ( var j = 0 ; j < queues . length ; j ++ ) {
1541+ const queue = queues [ j ] ;
1542+ const maxId = queue === '_unsentEvents' ? maxEventId : maxIdentifyId ;
1543+ for ( var i = 0 ; i < this [ queue ] . length || 0 ; i ++ ) {
1544+ const unsentEvent = this [ queue ] [ i ] ;
1545+
1546+ if ( unsentEvent . event . event_id <= maxId ) {
1547+ if ( unsentEvent . errorCallback ) {
1548+ unsentEvent . errorCallback ( status , response ) ;
1549+ }
1550+ }
1551+ }
1552+ }
1553+ } ;
1554+
14711555/**
14721556 * Remove events in storage with event ids up to and including maxEventId.
14731557 * @private
@@ -1565,16 +1649,19 @@ AmplitudeClient.prototype.sendEvents = function sendEvents() {
15651649 scope . _sendEventsIfReady ( ) ;
15661650
15671651 // handle payload too large
1568- } else if ( status === 413 ) {
1569- // utils.log('request too large');
1570- // Can't even get this one massive event through. Drop it, even if it is an identify.
1571- if ( scope . options . uploadBatchSize === 1 ) {
1572- scope . removeEvents ( maxEventId , maxIdentifyId , status , response ) ;
1652+ } else {
1653+ scope . _logErrorsOnEvents ( maxEventId , maxIdentifyId , status , response ) ;
1654+ if ( status === 413 ) {
1655+ // utils.log('request too large');
1656+ // Can't even get this one massive event through. Drop it, even if it is an identify.
1657+ if ( scope . options . uploadBatchSize === 1 ) {
1658+ scope . removeEvents ( maxEventId , maxIdentifyId , status , response ) ;
1659+ }
1660+
1661+ // The server complained about the length of the request. Backoff and try again.
1662+ scope . options . uploadBatchSize = Math . ceil ( numEvents / 2 ) ;
1663+ scope . sendEvents ( ) ;
15731664 }
1574-
1575- // The server complained about the length of the request. Backoff and try again.
1576- scope . options . uploadBatchSize = Math . ceil ( numEvents / 2 ) ;
1577- scope . sendEvents ( ) ;
15781665 }
15791666 // else {
15801667 // all the events are still queued, and will be retried when the next
0 commit comments