diff --git a/src/xhr.js b/src/xhr.js index 1c1110cc..4e38b02f 100644 --- a/src/xhr.js +++ b/src/xhr.js @@ -61,9 +61,14 @@ Request.prototype.send = function (callback) { responseStatus = response.status; return response.text(); }) - .then((responseText) => { - callback(responseStatus, responseText); - }); + .then( + (responseText) => { + callback(responseStatus, responseText); + }, + () => { + callback(0, ''); + }, + ); } //log('sent request to ' + this.url + ' with data ' + decodeURIComponent(queryString(this.data))); }; diff --git a/test/amplitude-client.js b/test/amplitude-client.js index 52d551fc..edea1480 100644 --- a/test/amplitude-client.js +++ b/test/amplitude-client.js @@ -12,6 +12,7 @@ import constants from '../src/constants.js'; import { mockCookie, restoreCookie, getCookie } from './mock-cookie'; import { AmplitudeServerZone } from '../src/server-zone.js'; import Request from '../src/xhr'; +import GlobalScope from '../src/global-scope'; const deleteAllCookies = () => document.cookie.split(';').forEach(function (c) { @@ -1674,6 +1675,52 @@ describe('AmplitudeClient', function () { }); }); + describe('fetch transport failures', function () { + beforeEach(function () { + reset(); + amplitude.init(apiKey); + sandbox.stub(GlobalScope, 'XMLHttpRequest').value(undefined); + }); + + afterEach(function () { + reset(); + }); + + ['fetch', 'response.text'].forEach(function (failure) { + it('should retain events and allow retry after ' + failure + ' rejects', async function () { + const error = new Error('Network failure'); + const fetchStub = sandbox.stub(GlobalScope, 'fetch'); + if (failure === 'fetch') { + fetchStub.onFirstCall().rejects(error); + } else { + fetchStub.onFirstCall().resolves({ status: 200, text: () => Promise.reject(error) }); + } + fetchStub.onSecondCall().resolves({ status: 200, text: () => Promise.resolve('success') }); + + amplitude.logEvent('Retried event'); + const event = amplitude._unsentEvents[0]; + assert.isTrue(amplitude._sending); + await new Promise((resolve) => setTimeout(resolve, 0)); + + assert.isFalse(amplitude._sending); + assert.lengthOf(amplitude._unsentEvents, 1); + assert.strictEqual(amplitude._unsentEvents[0], event); + assert.isTrue(fetchStub.calledOnce); + + amplitude.sendEvents(); + await new Promise((resolve) => setTimeout(resolve, 0)); + + assert.isTrue(fetchStub.calledTwice); + assert.equal( + queryString.parse(fetchStub.secondCall.args[1].body).e, + queryString.parse(fetchStub.firstCall.args[1].body).e, + ); + assert.isFalse(amplitude._sending); + assert.lengthOf(amplitude._unsentEvents, 0); + }); + }); + }); + describe('logEvent', function () { let clock, startTime;