Skip to content

Commit

Permalink
feat: support for sendBeacon requests
Browse files Browse the repository at this point in the history
  • Loading branch information
bardisg committed Sep 7, 2023
1 parent de92c27 commit 3d927d9
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 12 deletions.
79 changes: 67 additions & 12 deletions lib/interceptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ var interceptor = {
window.sessionStorage.removeItem(NAMESPACE);
}

if (typeof window.navigator.sendBeacon == 'function') {
replaceSendBeacon();
} else {
console.error(PKG_PREFIX + 'sendBeacon API preconditions not met!');
}

if (typeof window.fetch == 'function') {
replaceFetch();
if (
Expand All @@ -54,6 +60,48 @@ var interceptor = {

done(window[NAMESPACE]);

function replaceSendBeacon() {
var _sendBeacon = window.navigator.sendBeacon;
var interceptSendBeacon = function (url, payload) {
var request = {
method: 'POST',
requestHeaders: {},
requestBody: payload,
url: url,
};

addPendingRequest(request);

var isRequestQueuedForTransfer = _sendBeacon.apply(window.navigator, [
url,
payload,
]);

completeSendBeaconRequest(request, {
body: isRequestQueuedForTransfer,
});

// Forward the original response to the application on the current tick.
return isRequestQueuedForTransfer;
};

window.navigator.sendBeacon = function (target, data) {
var url = target;

if (target instanceof URL) {
url = target.href;
}

if (data instanceof Blob) {
return data.text().then(function (payload) {
return interceptSendBeacon(url, payload);
});
}

return interceptSendBeacon(url, data);
};
}

function replaceFetch() {
var _fetch = window.fetch;
window.fetch = function () {
Expand Down Expand Up @@ -98,16 +146,16 @@ var interceptor = {

// After decoding the request's body (which may have come from Request#text())
// and the response body, we can store the completed request.
Promise.all([request.requestBody, responsePromise]).then(function (
results
) {
completeFetchRequest(request, {
requestBody: results[0],
body: results[1],
statusCode: clonedResponse.status,
headers: parseHeaders(clonedResponse.headers),
});
});
Promise.all([request.requestBody, responsePromise]).then(
function (results) {
completeFetchRequest(request, {
requestBody: results[0],
body: results[1],
statusCode: clonedResponse.status,
headers: parseHeaders(clonedResponse.headers),
});
},
);

// Forward the original response to the application on the current tick.
return response;
Expand Down Expand Up @@ -240,6 +288,13 @@ var interceptor = {
pushToSessionStorage(startedRequest);
}

function completeSendBeaconRequest(startedRequest, completedRequest) {
startedRequest.body = completedRequest.body;
startedRequest.statusCode = completedRequest.body === true ? 200 : 500;
startedRequest.__fulfilled = Date.now();
replaceInSessionStorage(startedRequest);
}

function completeFetchRequest(startedRequest, completedRequest) {
// Merge the completed data with the started request.
startedRequest.requestBody = completedRequest.requestBody;
Expand All @@ -266,7 +321,7 @@ var interceptor = {
return JSON.parse(rawData);
} catch (e) {
throw new Error(
PKG_PREFIX + 'Could not parse sessionStorage data: ' + e.message
PKG_PREFIX + 'Could not parse sessionStorage data: ' + e.message,
);
}
}
Expand Down Expand Up @@ -355,7 +410,7 @@ var interceptor = {
parsed = JSON.parse(rawData);
} catch (e) {
throw new Error(
PKG_PREFIX + 'Could not parse sessionStorage data: ' + e.message
PKG_PREFIX + 'Could not parse sessionStorage data: ' + e.message,
);
}
}
Expand Down
41 changes: 41 additions & 0 deletions test/site/sendBeacon.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>sendBeacon request</title>
</head>
<body>
<p id="text">This file makes a sendBeacon request to /post.json</p>
<button id="buttonstring">Press me for string data</button>
<button id="buttonjson">Press me for JSON data as Blob</button>
<div id="response"></div>
<script>
'use strict';

(function (window, document) {
var buttonstring = document.querySelector('#buttonstring');
var buttonjson = document.querySelector('#buttonjson');

buttonstring.addEventListener('click', function (evt) {
var data = 'bar';
var isQueuedForDelivery = window.navigator.sendBeacon(new URL('/post.json', window.location.origin), data);

if(isQueuedForDelivery) {
document.querySelector('#response').textContent += "queued string for delivery\n";
}
});

buttonjson.addEventListener('click', function (evt) {
var data = new Blob([JSON.stringify({ foo: 'bar' })], { type: 'text/plain' });
var isQueuedForDelivery = window.navigator.sendBeacon('/post.json', data);

if(isQueuedForDelivery) {
document.querySelector('#response').textContent += "queued json for delivery\n";
}
});

})(window, window.document);
</script>
</body>
</html>
52 changes: 52 additions & 0 deletions test/spec/plugin_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,58 @@ describe('webdriverajax', function testSuite() {
});
});

describe('sendBeacon API', async function () {
it('can intercept a simple POST request', async function () {
await browser.url('/sendBeacon.html');
await browser.setupInterceptor();
await browser.expectRequest('POST', '/post.json', 200);
await completedRequest('#buttonjson');
await browser.assertRequests();
await browser.assertExpectedRequestsOnly();
});

it('can intercept when target is URL object', async function () {
await browser.url('/sendBeacon.html');
await browser.setupInterceptor();
await browser.expectRequest('POST', /\/post\.json/, 200);
await completedRequest('#buttonstring');
await browser.assertRequests();
await browser.assertExpectedRequestsOnly();
});

it('can access a certain request', async function () {
await browser.url('/sendBeacon.html');
await browser.setupInterceptor();
await completedRequest('#buttonjson');
const request = await browser.getRequest(0);
assert.equal(request.method, 'POST');
assert.equal(request.url, '/post.json');
assert.equal(request.response.body, true);
assert.equal(request.response.statusCode, 200);
assert.deepEqual(request.response.headers, {});
});

it('can assess the request body using string data', async function () {
await browser.url('/sendBeacon.html');
await browser.setupInterceptor();
await completedRequest('#buttonstring');
const request = await browser.getRequest(0);
assert.equal(request.url, 'http://localhost:8080/post.json');
assert.equal(request.response.body, true);
assert.deepEqual(request.body, 'bar');
});

it('can assess the request body using JSON data as Blob', async function () {
await browser.url('/sendBeacon.html');
await browser.setupInterceptor();
await completedRequest('#buttonjson');
const request = await browser.getRequest(0);
assert.equal(request.url, '/post.json');
assert.equal(request.response.body, true);
assert.deepEqual(request.body, { foo: 'bar' });
});
});

describe('fetch API', async function () {
it('can intercept a simple GET request', async function () {
await browser.url('/get.html');
Expand Down

0 comments on commit 3d927d9

Please sign in to comment.