Using navigator.sendBeacon() To Publish Analytics Back To ColdFusion
Last week, on Episode 227 of the Web Rush podcast, Sasha Shynkevich, talked about Browser APIs You May Not Know About. In that discussion, she mentioned the Beacon API as a means to efficiently and predictably send analytics data back to the server. I had never heard of the Beacon API before; but, it sounded liked something that would be very helpful in customer-facing applications (where delivering metrics and analytics is key for a product's continuous improvement). As such, I thought I would experiment with using the navigator.sendBeacon()
method to publish analytics data back to my ColdFusion server.
It seems that the Beacon API was designed and built for sending analytics data back to the server. Beacon requests get queued-up in the background, run asynchronously, and are guaranteed to run to completion (as long as they are successfully queued). Much like a UDP request, there is no guaranteed response; nor, is any response exposed to the client.
NOTE: The
navigator.sendBeacon()
method does return aBoolean
, which indicates whether or not the request was successfully queued-up for processing.
When sending a Beacon API request, data can be provided in various formats (String
, FormData
, URLSearchParams
, Blob
, etc); but, the request is always sent as an HTTP POST
. To make this demo a bit more exciting, I wanted to try initiating a Beacon API call using a variety of input formats so that I could see how ColdFusion would receive that data on the server-side.
To prepare for this experiment, I created a simple ColdFusion template, analytics.cfm
, that echoes-back the data that it received. This way, we can see how the various input formats affect the HTTP transport:
<cfscript>
param name="url.test" type="string";
requestData = getHttpRequestData();
// We're going to echo-back the type of beacon data that we received.
response = [
contentType: requestData.headers[ "Content-Type" ],
method: cgi.request_method,
form: form,
url: url,
body: requestData.content
];
// NOTE: According to the sendBeacon() specification, since the API doesn't expose a
// response on the client, the server is encouraged to omit any content in the
// response body and just send back a "204 No Content" status. However, for the sake
// of experimentation, I am echoing back the data that was sent (so we can see it in
// the Developer tools Network tab).
content
type = "application/x-json"
variable = charsetDecode( serializeJson( response ), "utf-8" )
;
</cfscript>
In this ColdFusion page, the url.test
parameter is a unique token that will be provided by each navigator.sendBeacon()
data format test. This just makes it easier for me to match the various data input formats to the various ColdFusion outputs.
On the client side, all we're going to do is bind to a click
event on a button and then send 4 different Beacons using:
- String literal.
FormData
instance.URLSearchParams
instance.Blob
of JSON (JavaScript Object Notation).
Since navigator.sendBeacon()
doesn't expose the response to the calling context, there's nothing for us to console.log()
in the demo; but, we can see the ColdFusion responses in the browser's Network tab.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<h1>
Using navigator.sendBeacon() To Publish Analytics Back To ColdFusion
</h1>
<button id="myButton">
Send Beacon
</button>
<script type="text/javascript">
var clickCount = 0;
myButton.addEventListener(
"click",
( event ) => {
clickCount++;
// When sending Beacon data, we can use a variety of different types of
// payload. Let's try sending various encodings so that we can see how
// they arrive on the ColdFusion server.
sendAsString();
sendAsFormData();
sendAsSearchData();
sendAsBlob();
}
);
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
function sendAsString() {
var data = `click|clickCount:${ clickCount }`;
navigator.sendBeacon( "./analytics.cfm?test=sendAsString", data );
}
function sendAsFormData() {
var data = new FormData();
data.set( "interaction", "click" );
data.set( "clickCount", clickCount );
navigator.sendBeacon( "./analytics.cfm?test=sendAsFormData", data );
}
function sendAsSearchData() {
var data = new URLSearchParams();
data.set( "interaction", "click" );
data.set( "clickCount", clickCount );
navigator.sendBeacon( "./analytics.cfm?test=sendAsSearchData", data );
}
function sendAsBlob() {
var data = new Blob(
[
JSON.stringify({
interaction: "click",
clickCount: clickCount
})
],
{
type: "application/x-json"
}
);
navigator.sendBeacon( "./analytics.cfm?test=sendAsBlob", data );
}
</script>
</body>
</html>
When we invoke the navigator.sendBeacon()
method with a String literal, here's what we see in the Network response:
This is a strange one since the Beacon is always sent as a POST
; but, here the content-type
is text/plain
. As such, ColdFusion exposes it as the body of the incoming HTTP request, but also seems to populate the form
scope with some nonsense.
When we invoke the navigator.sendBeacon()
method with a FormData
instance, here's what we see in the Network response:
This looks more like a traditional HTTP POST
with a form that has the attribute, enctype="multipart/form-data"
. The data ends up populating the form
scope on the ColdFusion server.
When we invoke the navigator.sendBeacon()
method with a URLSearchParams
instance, here's what we see in the Network response:
This also looks more like a traditional HTTP POST
with a form that has no enctype
attribute. The data is being encoded as application/x-www-form-urlencoded
(the default enctype
for a form); and, ends up populating the form
scope on the ColdFusion server.
When we invoke the navigator.sendBeacon()
method with a Blob
instance, here's what we see in the Network response:
The Blob
type allows us to post the data as the body of the HTTP request. In this case, that I'm posting the data as a JSON (JavaScript Object Notation) payload; but, I assume we could encode the Blob
with any mime-type we want.
The navigator.sendBeacon()
API is really well supported in all modern browsers (and even some not-so-modern ones - looks sideways at Mobile Safari). It looks like a great way to publish / POST
analytics data back to the ColdFusion server. I'll definitely be making good use of this API going forward.
Want to use code from this post? Check out the license.
Reader Comments
@Ben
This is cool...so you'd use this to track feature usage?
@Chris,
Feature usage is definitely a use-case. Also, I think it could be used for any type of request that just needs to happen in the background where a success guarantee isn't critical. For example, in my production app, when a user opens a document we make a request in the background to log "now" as the last date the user opened the document. I don't really care if that request works or not - I could see this
navigator.sendBeacon()
as a use-case for things like that.Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →