Skip to main content
Ben Nadel at NCDevCon 2016 (Raleigh, NC) with: Masha Edelen
Ben Nadel at NCDevCon 2016 (Raleigh, NC) with: Masha Edelen

Using navigator.sendBeacon() To Publish Analytics Back To ColdFusion

By
Published in , Comments (2)

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 a Boolean, 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:

Network response from String input.

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:

Network response from FormData input.

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:

Network response from URLSearchParams input.

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:

Network response from Blob input.

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

15,848 Comments

@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

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel