Skip to content

Instantly share code, notes, and snippets.

@nicjansma
Last active April 22, 2022 17:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nicjansma/3940d4b8cbf5cae4caff43460cb2bd71 to your computer and use it in GitHub Desktop.
Save nicjansma/3940d4b8cbf5cae4caff43460cb2bd71 to your computer and use it in GitHub Desktop.
Boomerang CWV plugin that sends an additional Exit beacon before unload with FID (if it didn't happen by page load) and the SUM of CLS
/**
* The Boomerang CWV plugin assists in sending Core Web Vitals metrics First Input Delay (FID) and
* Cumulative Layout Shift (CLS) at the end of the page session. It does this by sending
* an additional "Page Exit" beacon prior to unload.
*
* The main changes in behavior over the EventTiming plugin (which sends FID) and Continuity
* plugin (which sends CLS) are:
*
* * If FID was _not_ on the Page Load beacon, this plugin sends FID on the Page Exit beacon.
* If FID was on the Page Load beacon, it is _not_ repeated on the Page Exit beacon.
* * Any time CLS is added to a beacon (`c.cls`) by the Continuity plugin, this plugin
* accumulates it locally and removes it from the outgoing beacon. At the end of the page,
* it sends `c.cls` a single time with the SUM of CLS values for that page.
*
* This plugin does not work well in Single Page Applications (SPAs).
*
* To ensure this plugin works, the following configuration must be set:
* ```
* Continuity: {
* enabled: true,
* afterOnload: true,
* afterOnloadMaxLength: false
* },
* EventTiming: {
* enabled: true
* }
* ```
*
* This plugin depends on the Continuity and EventTiming plugins being enabled.
*
* ## Beacon Parameters
*
* This plugin adds the following parameters to the beacon:
*
* * `c.cls`: Accumulated CLS data
* * `et.fid`: First Input Delay
*/
window.BOOMR = window.BOOMR || {};
window.BOOMR.plugins = window.BOOMR.plugins || {};
window.BOOMR.plugins.CWV = {
// local variables
cls: 0.0,
initialized: false,
unloadSent: false,
extraVars: {},
extractedDimensions: false,
// functions
init: function(config) {
if (this.initialized) {
return;
}
this.initialized = true;
// extracts CLS values from all outgoing beacons, keeps the SUM locally
function extractCls(data) {
if (this.unloadSent) {
return;
}
// extract CLS from any previous beacons
if (data["c.cls"]) {
this.cls += parseFloat(data["c.cls"], 10);
// stop CLS from going out on pre-unload beacons
delete data["c.cls"];
}
}
// extracts page dimensions from the first beacon for use with the exit beacon
function extractDimensions(data) {
if (this.unloadSent || this.extractedDimensions) {
return;
}
// extract any other dimensions from the base page
if (!data["http.initiator"]) {
this.extractedDimensions = true;
// page group
if (data["h.pg"]) {
this.extraVars["h.pg"] = data["h.pg"];
}
// custom dimensions
for (var varName in data) {
if (varName.indexOf("cdim.") === 0) {
this.extraVars[varName] = data[varName];
}
}
}
}
// Subscribe to both before_beacon and beacon as it depends on the load
// order when the Continuity plugin will run vs. this plugin
BOOMR.subscribe("before_beacon", extractCls, null, this);
BOOMR.subscribe("beacon", extractCls, null, this);
BOOMR.subscribe("before_beacon", extractDimensions, null, this);
BOOMR.subscribe("beacon", extractDimensions, null, this);
// At page_unload, send an 'Exit' (manual) beacon
BOOMR.subscribe("page_unload", function(data) {
if (this.unloadSent) {
return;
}
this.unloadSent = true;
var data = {};
// when this beacon fired
data["rt.tstart"] = data["rt.end"] = BOOMR.now();
// crumb
data.d = BOOMR.session.domain;
data["h.key"] = BOOMR.getVar("h.key");
data["h.d"] = BOOMR.getVar("h.d");
data["h.cr"] = BOOMR.getVar("h.cr");
data["h.t"] = BOOMR.getVar("h.t");
// page id
data.pid = BOOMR.pageId;
// start event = manual
data["rt.start"] = "manual";
// session data
if (BOOMR.session && BOOMR.session.ID !== false) {
data["rt.si"] = BOOMR.session.ID + "-" + Math.round(BOOMR.session.start / 1000).toString(36);
data["rt.ss"] = BOOMR.session.start;
data["rt.sl"] = BOOMR.session.length;
}
// API info
data.api = 1;
data["api.v"] = 2;
data["api.l"] = "boomr";
data.v = BOOMR.version;
data.n = ++BOOMR.beaconsSent;
// extra dimensions we captured from the first beacon
for (var varName in this.extraVars) {
data[varName] = this.extraVars[varName];
}
// CWV data
var fid = BOOMR.plugins.EventTiming.metrics.firstInputDelay() ||
BOOMR.plugins.Continuity.metrics.firstInputDelay();
if (fid !== null) {
data["et.fid"] = fid;
}
if (window.LayoutShift) {
// add any CLS from the last beacon
this.cls = this.cls + BOOMR.plugins.Continuity.metrics.clsScore();
data["c.cls"] = this.cls;
}
// send the beacon data to the specified URL
if (typeof fid === "number" || typeof this.cls === "number") {
BOOMR.sendBeaconData(data);
}
}, null, this);
},
is_complete: function() {
return true;
}
};
@nicjansma
Copy link
Author

Minified:

window.BOOMR=window.BOOMR||{},window.BOOMR.plugins=window.BOOMR.plugins||{},window.BOOMR.plugins.CWV={cls:0,initialized:!1,unloadSent:!1,extraVars:{},extractedDimensions:!1,init:function(i){function s(i){this.unloadSent||i["c.cls"]&&(this.cls+=parseFloat(i["c.cls"],10),delete i["c.cls"])}function t(i){if(!this.unloadSent&&!this.extractedDimensions&&!i["http.initiator"])for(var s in this.extractedDimensions=!0,i["h.pg"]&&(this.extraVars["h.pg"]=i["h.pg"]),i)0===s.indexOf("cdim.")&&(this.extraVars[s]=i[s])}this.initialized||(this.initialized=!0,BOOMR.subscribe("before_beacon",s,null,this),BOOMR.subscribe("beacon",s,null,this),BOOMR.subscribe("before_beacon",t,null,this),BOOMR.subscribe("beacon",t,null,this),BOOMR.subscribe("page_unload",(function(i){if(!this.unloadSent){this.unloadSent=!0;i={};for(var s in i["rt.tstart"]=i["rt.end"]=BOOMR.now(),i.d=BOOMR.session.domain,i["h.key"]=BOOMR.getVar("h.key"),i["h.d"]=BOOMR.getVar("h.d"),i["h.cr"]=BOOMR.getVar("h.cr"),i["h.t"]=BOOMR.getVar("h.t"),i.pid=BOOMR.pageId,i["rt.start"]="manual",BOOMR.session&&!1!==BOOMR.session.ID&&(i["rt.si"]=BOOMR.session.ID+"-"+Math.round(BOOMR.session.start/1e3).toString(36),i["rt.ss"]=BOOMR.session.start,i["rt.sl"]=BOOMR.session.length),i.api=1,i["api.v"]=2,i["api.l"]="boomr",i.v=BOOMR.version,i.n=++BOOMR.beaconsSent,this.extraVars)i[s]=this.extraVars[s];var t=BOOMR.plugins.EventTiming.metrics.firstInputDelay()||BOOMR.plugins.Continuity.metrics.firstInputDelay();null!==t&&(i["et.fid"]=t),window.LayoutShift&&(this.cls=this.cls+BOOMR.plugins.Continuity.metrics.clsScore(),i["c.cls"]=this.cls),"number"!=typeof t&&"number"!=typeof this.cls||BOOMR.sendBeaconData(i)}}),null,this))},is_complete:function(){return!0}};

@nicjansma
Copy link
Author

nicjansma commented Apr 20, 2022

The following options should be set via BOOMR_config or edge configuration (mPulse):

window.BOOMR_config = window.BOOMR_config || {};
window.BOOMR_config.Continuity = window.BOOMR_config.Continuity || {};
window.BOOMR_config.Continuity.enabled = true;
window.BOOMR_config.Continuity.afterOnload = true;
window.BOOMR_config.Continuity.afterOnloadMaxLength = false;

Edge config:

{
  "Continuity": {
    "enabled": true,
    "afterOnload": true,
    "afterOnloadMaxLength": false
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment