The Wonderful Tale of sendBeacon
When we proposed to do this workshop, about 6 months ago, we had a lovely laundry list of things we'd like to cover. We wanted to teach y'all folks about all the emerging application development techniques. Among those things was network priorization.
Networking in the browser is different from on servers. Because browsers are sandboxed VMs that need to run untrusted code, for security reasons networking is rather restricted. And with these restrictions come constraints — browser networking can feel rather "high level" or "indirect" depending on your perspective.
Take a browser, and a server. The browser wants to show a page, and needs to
request some resources. It'll fire a request for /index.html
first, parse the
HTML, and then fire more requests to get subsequent resources from the same
host.
Now it's good to remember that throughput on any given device is going to be limited. There can only flow so much data through a single tube. This means we need to prioritize what comes first.
This is the murky grounds of network prioritization, where the best possible tool will be to try out stuff, and create intuition.
So in terms of hierarchy, loading .html
files, or files linked from within
the <head>
is usually the highest priority. There's special attributes such
as rel="preload"
that lower that priority, but it not the default.
Enter sendBeacon API — this is an API that makes request with the lowest
priority possible. Preloading something? Yep, more important than a beacon.
Performing a fetch()
yep; beacon will wait.
sendBeacon was meant for one thing, and one thing only: to guarantee sending
analytics data to servers, without affecting performance. sendBeacon allows
scheduling a request — if the scheduler returns true
, the browser will send
off the request in the background, whenever the network is available.
Exercise 1
- Create a listener for the window's
'unload'
event. - Create a new listener for the window's
'DOMContentLoaded
event. - When the page is unloaded, calculate the total time spent on a page using the previous two listeners.
- Create a basic HTTP server that prints out all data it receives to the command line, and run it.
- When the page is unloaded, create a new XMLHTTPRequest with the data as JSON, and submit it to the HTTP server.
Cool! Now you have a basic hook that can send data to a server. However, in a heavily populated environment, it will cause a good amount of slowdown. Let's pull out the sendBeacon API.
Exercise 2
- Create an object with some values.
- Convert the object to a JSON string.
- Create a
new Blob()
from the JSON string, and pass{ type: 'text/plain; charset=UTF-8' }
as the second argument. - Pass the blob down to
navigator.sendBeacon(url, blob)
- Validate the return value is
true
. - Send an event down when the page loads.
- Send an event down when the page unloads.
Notes
Idealy we could send JSON down as text/json
, but due to a Chrome CORS bug,
it's been temporarily disabled. Given that Chrome is widely used, it's probably
worth sending data down as text/plain
for the time being, and not relying on
CORS checks. Not great, but there's no other way.
Something to worry about is that the sendBeacon API can only send up to 65536
bytes. If the number is higher than that, it might reject it — in this case
it's best to assert()
that the size is lower. If the size is larger than
that, there's been a programmer error, and it should be explicit why things can
fail.
Also not all browsers may support the sendBeacon API; we should feature detect it and fall back if it doesn't exist.
Exercise 3
- Make sure that blobs aren't larger than
65536
bytes. - Make sure we fall back to sending data with XHR/Fetch if sendBeacon doesn't exist.
Now the final cherry on the sendBeacon cake, is to respect doNotTrack
. Caring
about privacy means, we should respect people's wishes.
- No-op (do nothing) if
navigator.doNotTrack
istrue
.
Wrapping up
And that's it! — you now know the ins and outs of navigator.sendBeacon
.
Google Analytics has a beacon: true
flag you can toggle to send things down,
and some other providers are working on integrating it. You can be ahead of the
pack, and do a better job — today.
If you want to use a module out of the box that does all of this for you, check out nanobeacon. Swooosh.