@financial-times/n-tracking
Advanced tools
Comparing version 7.3.0 to 7.4.0
@@ -181,3 +181,3 @@ import oTracking from '@financial-times/o-tracking'; | ||
// Flatten the seed string or build one from local entropy if needed. | ||
var shortseed = mixkey(flatten( | ||
mixkey(flatten( | ||
use_entropy ? [seed, tostring(pool)] : | ||
@@ -322,3 +322,3 @@ 0 in arguments ? seed : autoseed(), 3), key); | ||
}); | ||
var seedRandom_1 = seedRandom.resetGlobal; | ||
seedRandom.resetGlobal; | ||
@@ -346,3 +346,3 @@ function seedIsInSample (seed, percent) { | ||
const defaultSampleRate = 10; | ||
const defaultSampleRate$1 = 10; | ||
@@ -361,3 +361,3 @@ const isContextComplete = (context) => { | ||
sampleRate = Number.isFinite(sampleRate) && sampleRate <= 100 && sampleRate >= 0 ? sampleRate : defaultSampleRate; | ||
sampleRate = Number.isFinite(sampleRate) && sampleRate <= 100 && sampleRate >= 0 ? sampleRate : defaultSampleRate$1; | ||
@@ -437,6 +437,68 @@ // Gather metrics for only a cohort of users. | ||
var e,t,n,r,a=function(){return window.performance&&performance.getEntriesByType&&performance.getEntriesByType("navigation")[0]},o=function(e){if("loading"===document.readyState)return "loading";var t=a();if(t){if(e<t.domInteractive)return "loading";if(0===t.domContentLoadedEventStart||e<t.domContentLoadedEventStart)return "dom-interactive";if(0===t.domComplete||e<t.domComplete)return "dom-content-loaded"}return "complete"},u=function(e){var t=e.nodeName;return 1===e.nodeType?t.toLowerCase():t.toUpperCase().replace(/^#/,"")},c=function(e,t){var n="";try{for(;e&&9!==e.nodeType;){var r=e,i=r.id?"#"+r.id:u(r)+(r.classList&&r.classList.value&&r.classList.value.trim()&&r.classList.value.trim().length?"."+r.classList.value.trim().replace(/\s+/g,"."):"");if(n.length+i.length>(t||100)-1)return n||i;if(n=n?i+">"+n:i,r.id)break;e=r.parentNode;}}catch(e){}return n},s=-1,f=function(){return s},d=function(e){addEventListener("pageshow",(function(t){t.persisted&&(s=t.timeStamp,e(t));}),!0);},l=function(){var e=a();return e&&e.activationStart||0},m=function(e,t){var n=a(),r="navigate";f()>=0?r="back-forward-cache":n&&(document.prerendering||l()>0?r="prerender":document.wasDiscarded?r="restore":n.type&&(r=n.type.replace(/_/g,"-")));return {name:e,value:void 0===t?-1:t,rating:"good",delta:0,entries:[],id:"v3-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12),navigationType:r}},v=function(e,t,n){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){var r=new PerformanceObserver((function(e){Promise.resolve().then((function(){t(e.getEntries());}));}));return r.observe(Object.assign({type:e,buffered:!0},n||{})),r}}catch(e){}},p=function(e,t,n,r){var i,a;return function(o){t.value>=0&&(o||r)&&((a=t.value-(i||0))||void 0===i)&&(i=t.value,t.delta=a,t.rating=function(e,t){return e>t[1]?"poor":e>t[0]?"needs-improvement":"good"}(t.value,n),e(t));}},h=function(e){requestAnimationFrame((function(){return requestAnimationFrame((function(){return e()}))}));},g=function(e){var t=function(t){"pagehide"!==t.type&&"hidden"!==document.visibilityState||e(t);};addEventListener("visibilitychange",t,!0),addEventListener("pagehide",t,!0);},T=function(e){var t=!1;return function(n){t||(e(n),t=!0);}},y=-1,E=function(){return "hidden"!==document.visibilityState||document.prerendering?1/0:0},S=function(e){"hidden"===document.visibilityState&&y>-1&&(y="visibilitychange"===e.type?e.timeStamp:0,b());},L=function(){addEventListener("visibilitychange",S,!0),addEventListener("prerenderingchange",S,!0);},b=function(){removeEventListener("visibilitychange",S,!0),removeEventListener("prerenderingchange",S,!0);},C=function(){return y<0&&(y=E(),L(),d((function(){setTimeout((function(){y=E(),L();}),0);}))),{get firstHiddenTime(){return y}}},w=function(e){document.prerendering?addEventListener("prerenderingchange",(function(){return e()}),!0):e();},M=[1800,3e3],x=function(e,t){t=t||{},w((function(){var n,r=C(),i=m("FCP"),a=v("paint",(function(e){e.forEach((function(e){"first-contentful-paint"===e.name&&(a.disconnect(),e.startTime<r.firstHiddenTime&&(i.value=Math.max(e.startTime-l(),0),i.entries.push(e),n(!0)));}));}));a&&(n=p(e,i,M,t.reportAllChanges),d((function(r){i=m("FCP"),n=p(e,i,M,t.reportAllChanges),h((function(){i.value=performance.now()-r.timeStamp,n(!0);}));})));}));},A=[.1,.25],F=function(e,t){!function(e,t){t=t||{},x(T((function(){var n,r=m("CLS",0),i=0,a=[],o=function(e){e.forEach((function(e){if(!e.hadRecentInput){var t=a[0],n=a[a.length-1];i&&e.startTime-n.startTime<1e3&&e.startTime-t.startTime<5e3?(i+=e.value,a.push(e)):(i=e.value,a=[e]);}})),i>r.value&&(r.value=i,r.entries=a,n());},u=v("layout-shift",o);u&&(n=p(e,r,A,t.reportAllChanges),g((function(){o(u.takeRecords()),n(!0);})),d((function(){i=0,r=m("CLS",0),n=p(e,r,A,t.reportAllChanges),h((function(){return n()}));})),setTimeout(n,0));})));}((function(t){!function(e){if(e.entries.length){var t=e.entries.reduce((function(e,t){return e&&e.value>t.value?e:t}));if(t&&t.sources&&t.sources.length){var n=(r=t.sources).find((function(e){return e.node&&1===e.node.nodeType}))||r[0];if(n)return void(e.attribution={largestShiftTarget:c(n.node),largestShiftTime:t.startTime,largestShiftValue:t.value,largestShiftSource:n,largestShiftEntry:t,loadState:o(t.startTime)})}}var r;e.attribution={};}(t),e(t);}),t);},P={passive:!0,capture:!0},B=new Date,D=function(r,i){e||(e=i,t=r,n=new Date,q(removeEventListener),k());},k=function(){if(t>=0&&t<n-B){var i={entryType:"first-input",name:e.type,target:e.target,cancelable:e.cancelable,startTime:e.timeStamp,processingStart:e.timeStamp+t};r.forEach((function(e){e(i);})),r=[];}},R=function(e){if(e.cancelable){var t=(e.timeStamp>1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,t){var n=function(){D(e,t),i();},r=function(){i();},i=function(){removeEventListener("pointerup",n,P),removeEventListener("pointercancel",r,P);};addEventListener("pointerup",n,P),addEventListener("pointercancel",r,P);}(t,e):D(t,e);}},q=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(t){return e(t,R,P)}));},H=[100,300],N=function(n,i){i=i||{},w((function(){var a,o=C(),u=m("FID"),c=function(e){e.startTime<o.firstHiddenTime&&(u.value=e.processingStart-e.startTime,u.entries.push(e),a(!0));},s=function(e){e.forEach(c);},f=v("first-input",s);a=p(n,u,H,i.reportAllChanges),f&&g(T((function(){s(f.takeRecords()),f.disconnect();}))),f&&d((function(){var o;u=m("FID"),a=p(n,u,H,i.reportAllChanges),r=[],t=-1,e=null,q(addEventListener),o=c,r.push(o),k();}));}));},O=function(e,t){N((function(t){!function(e){var t=e.entries[0];e.attribution={eventTarget:c(t.target),eventType:t.name,eventTime:t.startTime,eventEntry:t,loadState:o(t.startTime)};}(t),e(t);}),t);},ee=[2500,4e3],te={},ne=function(e,t){!function(e,t){t=t||{},w((function(){var n,r=C(),i=m("LCP"),a=function(e){var t=e[e.length-1];t&&t.startTime<r.firstHiddenTime&&(i.value=Math.max(t.startTime-l(),0),i.entries=[t],n());},o=v("largest-contentful-paint",a);if(o){n=p(e,i,ee,t.reportAllChanges);var u=T((function(){te[i.id]||(a(o.takeRecords()),o.disconnect(),te[i.id]=!0,n(!0));}));["keydown","click"].forEach((function(e){addEventListener(e,(function(){return setTimeout(u,0)}),!0);})),g(u),d((function(r){i=m("LCP"),n=p(e,i,ee,t.reportAllChanges),h((function(){i.value=performance.now()-r.timeStamp,te[i.id]=!0,n(!0);}));}));}}));}((function(t){!function(e){if(e.entries.length){var t=a();if(t){var n=t.activationStart||0,r=e.entries[e.entries.length-1],i=r.url&&performance.getEntriesByType("resource").filter((function(e){return e.name===r.url}))[0],o=Math.max(0,t.responseStart-n),u=Math.max(o,i?(i.requestStart||i.startTime)-n:0),s=Math.max(u,i?i.responseEnd-n:0),f=Math.max(s,r?r.startTime-n:0),d={element:c(r.element),timeToFirstByte:o,resourceLoadDelay:u-o,resourceLoadTime:s-u,elementRenderDelay:f-s,navigationEntry:t,lcpEntry:r};return r.url&&(d.url=r.url),i&&(d.lcpResourceEntry=i),void(e.attribution=d)}}e.attribution={timeToFirstByte:0,resourceLoadDelay:0,resourceLoadTime:0,elementRenderDelay:e.value};}(t),e(t);}),t);}; | ||
// 1% of users | ||
const defaultSampleRate = 1; | ||
const dataFromCWV = (metric) => { | ||
if (metric.name === 'LCP') { | ||
return { | ||
name: metric.name, | ||
lcp: metric.value, | ||
lcpDelta: metric.delta, | ||
element: metric.attribution.largestShiftTarget | ||
}; | ||
} | ||
if (metric.name === 'FID') { | ||
return { | ||
name: metric.name, | ||
fid: metric.value, | ||
fidDelta: metric.delta, | ||
element: metric.attribution.eventTarget | ||
}; | ||
} | ||
if (metric.name === 'CLS') { | ||
return { | ||
name: metric.name, | ||
cls: metric.value, | ||
clsDelta: metric.delta, | ||
element: metric.attribution.largestShiftTarget | ||
}; | ||
} | ||
}; | ||
const realUserMonitoringWithAttribution = ({ sampleRate } = {}) => { | ||
const broadcastCWV = (data) => { | ||
broadcast('oTracking.event', { | ||
action: 'performance-attribution', | ||
category: 'page', | ||
...dataFromCWV(data), | ||
}); | ||
}; | ||
// Check browser support. | ||
// @see https://developer.mozilla.org/en-US/docs/Web/API/PerformanceLongTaskTiming | ||
if (!'PerformanceLongTaskTiming' in window) return; | ||
const spoorId = getSpoorId(); | ||
const proportionToSample = sampleRate || defaultSampleRate; | ||
// Gather metrics for only a cohort of users. | ||
if (!seedIsInSample(spoorId, proportionToSample)) return; | ||
// Proceed only if the page load event is a "navigate". | ||
// @see: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming/type | ||
// When testing, you will need to open a new browser window and paste the URL in i.e. simply reloading the page will not work. | ||
const navigation = performance.getEntriesByType('navigation') && performance.getEntriesByType('navigation')[0]; | ||
if (!navigation || navigation.type !== 'navigate') return; | ||
F(broadcastCWV, { reportAllChanges: true }); | ||
O(broadcastCWV); | ||
ne(broadcastCWV); | ||
}; | ||
// Create markers at each of these percentage points | ||
const DEPTH_MARKERS = [25, 50, 75, 100]; | ||
const defaultOptions = { | ||
const defaultOptions$1 = { | ||
onScroll: () => {}, | ||
@@ -449,3 +511,3 @@ target: 'body', | ||
constructor (options) { | ||
this.options = { ...defaultOptions, ...options }; | ||
this.options = { ...defaultOptions$1, ...options }; | ||
@@ -524,3 +586,3 @@ this.init(); | ||
const defaultOptions$1 = { | ||
const defaultOptions = { | ||
onExit: () => {}, | ||
@@ -532,3 +594,3 @@ debug: false | ||
constructor (options) { | ||
this.options = { ...defaultOptions$1, ...options }; | ||
this.options = { ...defaultOptions, ...options }; | ||
@@ -715,7 +777,6 @@ this.totalAttentionTime = 0; | ||
var index = /*#__PURE__*/Object.freeze({ | ||
__proto__: null, | ||
realUserMonitoringForPerformance: realUserMonitoringForPerformance, | ||
realUserMonitoringWithAttribution: realUserMonitoringWithAttribution, | ||
pageAttention: pageAttention, | ||
@@ -722,0 +783,0 @@ textCopying: textCopying |
@@ -5,6 +5,8 @@ 'use strict'; | ||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var React = require('react'); | ||
var React = _interopDefault(require('react')); | ||
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } | ||
var React__default = /*#__PURE__*/_interopDefaultLegacy(React); | ||
// For a list of all the properties available see the app context schema: | ||
@@ -45,9 +47,11 @@ // <https://github.com/Financial-Times/dotcom-page-kit/blob/HEAD/packages/dotcom-server-app-context/schema.md> | ||
if (/\bcore\b/.test(document.documentElement.className)) { | ||
// eslint-disable-next-line no-var | ||
var currentScript = document.scripts[document.scripts.length - 1]; | ||
// eslint-disable-next-line no-var | ||
var img = new Image(); | ||
img.alt = ""; | ||
img.alt = ''; | ||
img.src = currentScript.getAttribute('data-pixel-src'); | ||
} | ||
} | ||
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("script", { | ||
return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement("script", { | ||
// To pass this information to the client-side | ||
@@ -58,3 +62,3 @@ "data-pixel-src": pixel + encodedTrackingData.replace(placeholder, 'core-experience'), | ||
} | ||
}), /*#__PURE__*/React.createElement("noscript", null, /*#__PURE__*/React.createElement("img", { | ||
}), /*#__PURE__*/React__default["default"].createElement("noscript", null, /*#__PURE__*/React__default["default"].createElement("img", { | ||
src: pixel + encodedTrackingData.replace(placeholder, 'no-js'), | ||
@@ -61,0 +65,0 @@ alt: "" |
@@ -50,1 +50,22 @@ # Real user performance metrics | ||
https://docs.google.com/document/d/1jG9f2DHs1AH-5kiV2cscpcheOckPM5EbzGA17uEGm9M/edit# | ||
## With attribution | ||
The above approach has a few issues: Firstly it takes the first bunch of events and sends them, whereas you want to capture the last events if possible. | ||
Secondly, it doesn't tell us _why_ these values are what they are. | ||
The workaround is to provide a more comprehensive real user monitoring option, which can be used simultaneously with the previous option. | ||
The workaround utilises Web vital [attribution](https://www.npmjs.com/package/web-vitals#send-attribution-data) to these events, which helps determine what causes the scores, and can send multiple events of the same type per user. | ||
To use this, alongside the previous option: | ||
```js | ||
import * as nTracking from '@financial-times/n-tracking'; | ||
if (flags.get('realUserMonitoringForPerformance')) { | ||
nTracking.trackers.realUserMonitoringForPerformance(); | ||
nTracking.trackers.realUserMonitoringWithAttribution(); | ||
} | ||
``` |
@@ -6,10 +6,9 @@ { | ||
"browser": "dist/browser.js", | ||
"version": "7.3.0", | ||
"version": "7.4.0", | ||
"license": "MIT", | ||
"repository": "Financial-Times/n-tracking.git", | ||
"scripts": { | ||
"clean": "rm -rf ./dist", | ||
"build": "npm run clean && rollup -c", | ||
"prepare": "npm run build", | ||
"test": "jest --config jest.config.js" | ||
"build": "dotcom-tool-kit build:local", | ||
"test": "dotcom-tool-kit test:local", | ||
"start": "dotcom-tool-kit run:local" | ||
}, | ||
@@ -20,13 +19,20 @@ "devDependencies": { | ||
"@babel/preset-env": "^7.5.5", | ||
"@financial-times/n-gage": "^8.3.2", | ||
"@dotcom-tool-kit/babel": "^3.1.6", | ||
"@dotcom-tool-kit/component": "^4.1.0", | ||
"@dotcom-tool-kit/eslint": "^3.2.0", | ||
"@dotcom-tool-kit/husky-npm": "^4.2.0", | ||
"@dotcom-tool-kit/jest": "^3.4.0", | ||
"@dotcom-tool-kit/lint-staged-npm": "^3.2.0", | ||
"@financial-times/eslint-config-next": "^7.1.0", | ||
"@rollup/plugin-commonjs": "^11.0.2", | ||
"@rollup/plugin-node-resolve": "^7.1.1", | ||
"babel-jest": "^25.0.0", | ||
"babel-jest": "^27.5.1", | ||
"check-engine": "^1.10.1", | ||
"dotcom-tool-kit": "^3.4.0", | ||
"eslint-plugin-react": "^7.14.3", | ||
"jest": "^25.0.0", | ||
"jest-environment-jsdom-fifteen": "^1.0.0", | ||
"jest": "^29.7.0", | ||
"jest-environment-jsdom": "^29.7.0", | ||
"jsdom": "^15.1.0", | ||
"react": "^16.9.0", | ||
"rollup": "^1.31.0", | ||
"rollup": "^2.71.1", | ||
"rollup-plugin-babel": "^4.3.3", | ||
@@ -53,5 +59,4 @@ "seed-random": "^2.2.0" | ||
"hooks": { | ||
"commit-msg": "secret-squirrel-commitmsg", | ||
"pre-commit": "secret-squirrel", | ||
"pre-push": "make verify -j3" | ||
"commit-msg": "dotcom-tool-kit git:commitmsg", | ||
"pre-commit": "dotcom-tool-kit git:precommit" | ||
} | ||
@@ -61,3 +66,6 @@ }, | ||
"node": "18.16.0" | ||
}, | ||
"lint-staged": { | ||
"**/*.js": "dotcom-tool-kit format:staged test:staged --" | ||
} | ||
} |
export * from './realUserMonitoringForPerformance'; | ||
export * from './realUserMonitoringWithAttribution'; | ||
export * from './pageAttention'; | ||
export * from './textCopying'; |
@@ -8,3 +8,3 @@ import React from 'react'; | ||
export function CoreTracking({ appContext }) { | ||
export function CoreTracking ({ appContext }) { | ||
// We only need the basics as the full data cannot be assembled | ||
@@ -30,7 +30,9 @@ // on the server without the client-side JS. | ||
// NOTE: This function will be stringified and embedded so use ES5 only! | ||
function coreExperience() { | ||
function coreExperience () { | ||
if (/\bcore\b/.test(document.documentElement.className)) { | ||
// eslint-disable-next-line no-var | ||
var currentScript = document.scripts[document.scripts.length - 1]; | ||
// eslint-disable-next-line no-var | ||
var img = new Image(); | ||
img.alt = "" | ||
img.alt = ''; | ||
img.src = currentScript.getAttribute('data-pixel-src'); | ||
@@ -37,0 +39,0 @@ } |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
NPM Shrinkwrap
Supply chain riskPackage contains a shrinkwrap file. This may allow the package to bypass normal install procedures.
Found 1 instance in 1 package
51
0
86724
23
1850
1