@financial-times/o-tracking
Advanced tools
Comparing version 3.0.4 to 3.1.0
import Delegate from "ftdomdelegate"; | ||
import { Queue } from "../core/queue.js"; | ||
import core from "../core.js"; | ||
import { sanitise, assignIfUndefined, merge, onPage } from "../utils.js"; | ||
import { sanitise, assignIfUndefined, merge } from "../utils.js"; | ||
import { get as getSetting } from "../core/settings.js"; | ||
import { getTrace } from "../../libs/get-trace.js"; | ||
var internalQueue; | ||
var delegate; // Trigger the event tracking | ||
var track = eventData => { | ||
var firstDomPathToken = eventData.context.domPathTokens[0]; | ||
var href = firstDomPathToken.href || null; | ||
var oTrackingSkipQueueAttr = firstDomPathToken['data-o-tracking-skip-queue']; // TODO: Decide if we should also allow the attribute without a value to mean the same as having a value of `'true'`. | ||
// TODO: Decide is all anchor elements with a download attribute should also skip the queue. | ||
var skipQueue = oTrackingSkipQueueAttr && oTrackingSkipQueueAttr.toLowerCase() === 'true' || false; | ||
var isInternal = href && href.indexOf(window.document.location.hostname) > -1; | ||
if (isInternal && !skipQueue) { | ||
eventData.context.source_id = core.getRootID(); // Queue the event and send it on the next page load | ||
internalQueue.add(eventData).save(); | ||
} else { | ||
// Send now, before leaving this page | ||
core.track(eventData); | ||
} | ||
}; | ||
var delegate; | ||
var eventPropertiesToCollect = ["ctrlKey", "altKey", "shiftKey", "metaKey"]; // Get properties for the event (as opposed to properties of the clicked element) | ||
@@ -67,28 +45,5 @@ // Available properties include mouse x- and y co-ordinates, for example. | ||
var config = merge(getSetting('config'), eventData); | ||
track(config); | ||
core.track(config); | ||
}; | ||
/** | ||
* If there are any requests queued, attempts to send the next one | ||
* Otherwise, does nothing | ||
* | ||
* @returns {void} | ||
*/ | ||
/*eslint-disable no-unused-vars*/ | ||
var runQueue = _ => { | ||
var next = _ => { | ||
runQueue(); | ||
}; | ||
var nextLink = internalQueue.shift(); | ||
if (nextLink) { | ||
core.track(nextLink, next); | ||
} | ||
}; | ||
/*eslint-enable no-unused-vars*/ | ||
/** | ||
* Listen for click events. | ||
@@ -113,8 +68,3 @@ * | ||
delegate = delegate || new Delegate(document.body); | ||
delegate.on('click', elementsToTrack, handleClickEvent(eventData), true); // Track any queued events | ||
internalQueue = internalQueue || new Queue('clicks'); | ||
runQueue(); // Listen for page requests. If this is a single page app, we can send link requests now. | ||
onPage(runQueue); | ||
delegate.on('click', elementsToTrack, handleClickEvent(eventData), true); | ||
}; | ||
@@ -121,0 +71,0 @@ |
@@ -10,4 +10,2 @@ "use strict"; | ||
var _queue = require("../core/queue.js"); | ||
var _core = _interopRequireDefault(require("../core.js")); | ||
@@ -23,24 +21,3 @@ | ||
var internalQueue; | ||
var delegate; // Trigger the event tracking | ||
var track = eventData => { | ||
var firstDomPathToken = eventData.context.domPathTokens[0]; | ||
var href = firstDomPathToken.href || null; | ||
var oTrackingSkipQueueAttr = firstDomPathToken['data-o-tracking-skip-queue']; // TODO: Decide if we should also allow the attribute without a value to mean the same as having a value of `'true'`. | ||
// TODO: Decide is all anchor elements with a download attribute should also skip the queue. | ||
var skipQueue = oTrackingSkipQueueAttr && oTrackingSkipQueueAttr.toLowerCase() === 'true' || false; | ||
var isInternal = href && href.indexOf(window.document.location.hostname) > -1; | ||
if (isInternal && !skipQueue) { | ||
eventData.context.source_id = _core.default.getRootID(); // Queue the event and send it on the next page load | ||
internalQueue.add(eventData).save(); | ||
} else { | ||
// Send now, before leaving this page | ||
_core.default.track(eventData); | ||
} | ||
}; | ||
var delegate; | ||
var eventPropertiesToCollect = ["ctrlKey", "altKey", "shiftKey", "metaKey"]; // Get properties for the event (as opposed to properties of the clicked element) | ||
@@ -84,27 +61,5 @@ // Available properties include mouse x- and y co-ordinates, for example. | ||
var config = (0, _utils.merge)((0, _settings.get)('config'), eventData); | ||
track(config); | ||
}; | ||
/** | ||
* If there are any requests queued, attempts to send the next one | ||
* Otherwise, does nothing | ||
* | ||
* @returns {void} | ||
*/ | ||
/*eslint-disable no-unused-vars*/ | ||
var runQueue = _ => { | ||
var next = _ => { | ||
runQueue(); | ||
}; | ||
var nextLink = internalQueue.shift(); | ||
if (nextLink) { | ||
_core.default.track(nextLink, next); | ||
} | ||
_core.default.track(config); | ||
}; | ||
/*eslint-enable no-unused-vars*/ | ||
/** | ||
@@ -130,8 +85,3 @@ * Listen for click events. | ||
delegate = delegate || new _ftdomdelegate.default(document.body); | ||
delegate.on('click', elementsToTrack, handleClickEvent(eventData), true); // Track any queued events | ||
internalQueue = internalQueue || new _queue.Queue('clicks'); | ||
runQueue(); // Listen for page requests. If this is a single page app, we can send link requests now. | ||
(0, _utils.onPage)(runQueue); | ||
delegate.on('click', elementsToTrack, handleClickEvent(eventData), true); | ||
}; | ||
@@ -138,0 +88,0 @@ |
@@ -16,3 +16,3 @@ { | ||
"name": "@financial-times/o-tracking", | ||
"version": "3.0.4", | ||
"version": "3.1.0", | ||
"description": "Origami module for FT tracking.", | ||
@@ -19,0 +19,0 @@ "dependencies": { |
@@ -138,5 +138,3 @@ # o-tracking | ||
- If the element being clicked is a link which goes to a page on the same domain as the current page, o-tracking will put the tracking data into a queue to send to Spoor at a later time. | ||
- Add the attribute `data-o-tracking-skip-queue` to the element to send the data to Spoor immediately. | ||
- O-tracking click events will also track the path from the root of the DOM tree to the element which was clicked. This is recorded in a property called `domPathTokens`. | ||
- o-tracking click events will also track the path from the root of the DOM tree to the element which was clicked. This is recorded in a property called `domPathTokens`. | ||
- If the clicked element has the `data-trackable` attribute set, sibling elements will also be included within the `domPathTokens` property. | ||
@@ -151,2 +149,4 @@ | ||
*Note: The attribute `data-o-tracking-skip-queue` is no longer used, it is now the default behaviour.* | ||
##### oTracking.view.init | ||
@@ -153,0 +153,0 @@ |
import Delegate from 'ftdomdelegate'; | ||
import {Queue} from '../core/queue.js'; | ||
import core from '../core.js'; | ||
import {sanitise, assignIfUndefined, merge, onPage} from '../utils.js'; | ||
import {sanitise, assignIfUndefined, merge } from '../utils.js'; | ||
import {get as getSetting} from '../core/settings.js'; | ||
import {getTrace} from '../../libs/get-trace.js'; | ||
let internalQueue; | ||
let delegate; | ||
// Trigger the event tracking | ||
const track = eventData => { | ||
const firstDomPathToken = eventData.context.domPathTokens[0]; | ||
const href = firstDomPathToken.href || null; | ||
const oTrackingSkipQueueAttr = firstDomPathToken['data-o-tracking-skip-queue']; | ||
// TODO: Decide if we should also allow the attribute without a value to mean the same as having a value of `'true'`. | ||
// TODO: Decide is all anchor elements with a download attribute should also skip the queue. | ||
const skipQueue = oTrackingSkipQueueAttr && oTrackingSkipQueueAttr.toLowerCase() === 'true' || false; | ||
const isInternal = href && href.indexOf(window.document.location.hostname) > -1; | ||
if (isInternal && !skipQueue) { | ||
eventData.context.source_id = core.getRootID(); | ||
// Queue the event and send it on the next page load | ||
internalQueue.add(eventData).save(); | ||
} | ||
else { | ||
// Send now, before leaving this page | ||
core.track(eventData); | ||
} | ||
}; | ||
const eventPropertiesToCollect = [ | ||
@@ -73,22 +49,6 @@ "ctrlKey", | ||
const config = merge(getSetting('config'), eventData); | ||
track(config); | ||
core.track(config); | ||
}; | ||
/** | ||
* If there are any requests queued, attempts to send the next one | ||
* Otherwise, does nothing | ||
* | ||
* @returns {void} | ||
*/ | ||
/*eslint-disable no-unused-vars*/ | ||
const runQueue = _ => { | ||
const next = _ => { runQueue(); }; | ||
const nextLink = internalQueue.shift(); | ||
if (nextLink) { | ||
core.track(nextLink, next); | ||
} | ||
}; | ||
/*eslint-enable no-unused-vars*/ | ||
/** | ||
* Listen for click events. | ||
@@ -113,9 +73,2 @@ * | ||
delegate.on('click', elementsToTrack, handleClickEvent(eventData), true); | ||
// Track any queued events | ||
internalQueue = internalQueue || new Queue('clicks'); | ||
runQueue(); | ||
// Listen for page requests. If this is a single page app, we can send link requests now. | ||
onPage(runQueue); | ||
}; | ||
@@ -122,0 +75,0 @@ |
@@ -154,85 +154,2 @@ /* eslint-env mocha */ | ||
}); | ||
it('should not track straight away when the link points to the same domain we are currently on', function (done) { | ||
sinon.spy(core, 'track'); | ||
click.init("blah", '#anchorD'); | ||
core.track.resetHistory(); // click.init() makes a call to track() so clearing the history here to avoid false positives | ||
const aLinkToPageOnSameDomain = document.createElement('a'); | ||
const currentHost = window.document.location.hostname; | ||
aLinkToPageOnSameDomain.href = "https://" + currentHost + "/a-page-on-the-same-domain"; | ||
aLinkToPageOnSameDomain.text = "A link to another page on the same domain"; | ||
aLinkToPageOnSameDomain.id = "anchorD"; | ||
aLinkToPageOnSameDomain.addEventListener('click', function(e){ | ||
e.preventDefault(); | ||
}); //we don't want the browser to follow click in test | ||
const event = new MouseEvent('click', { | ||
'view': window, | ||
'bubbles': true, | ||
'cancelable': true | ||
}); | ||
document.body.appendChild(aLinkToPageOnSameDomain); | ||
aLinkToPageOnSameDomain.dispatchEvent(event, true); | ||
setTimeout(() => { | ||
try { | ||
proclaim.equal(core.track.notCalled, true, "click event not tracked"); | ||
core.track.restore(); | ||
done(); | ||
} catch (error) { | ||
done(error); | ||
} | ||
}, 10); | ||
}); | ||
it('should skip the queue when data-o-tracking-skip-queue is "true" on the link', function (done) { | ||
sinon.spy(core, 'track'); | ||
click.init("blah", '#anchorE'); | ||
core.track.resetHistory(); // click.init() makes a call to track() so clearing the history here to avoid false positives | ||
const aLinkToPageOnSameDomain = document.createElement('a'); | ||
const currentHost = window.document.location.hostname; | ||
aLinkToPageOnSameDomain.href = "https://" + currentHost + "/a-page-on-the-same-domain"; | ||
aLinkToPageOnSameDomain.text = "A link to another page on the same domain"; | ||
aLinkToPageOnSameDomain.id = "anchorE"; | ||
aLinkToPageOnSameDomain.setAttribute("data-o-tracking-skip-queue", "true"); | ||
aLinkToPageOnSameDomain.addEventListener('click', function(e){ | ||
e.preventDefault(); | ||
}); //we don't want the browser to follow click in test | ||
const event = new MouseEvent('click', { | ||
'view': window, | ||
'bubbles': true, | ||
'cancelable': true | ||
}); | ||
document.body.appendChild(aLinkToPageOnSameDomain); | ||
aLinkToPageOnSameDomain.dispatchEvent(event, true); | ||
setTimeout(() => { | ||
try { | ||
proclaim.equal(core.track.calledOnce, true, "click event not tracked"); | ||
core.track.restore(); | ||
done(); | ||
} catch (error) { | ||
done(error); | ||
} | ||
}, 10); | ||
}); | ||
}); |
@@ -5,7 +5,14 @@ /* eslint-env mocha */ | ||
import './setup.js'; | ||
import {destroy, get} from '../src/javascript/core/settings.js'; | ||
import {Queue} from '../src/javascript/core/queue.js'; | ||
import { Queue } from '../src/javascript/core/queue.js'; | ||
import oTracking from '../main.js'; | ||
import { sendSpy } from './setup.js'; | ||
const clickEvent = new MouseEvent('click', { | ||
'view': window, | ||
'bubbles': true, | ||
'cancelable': true | ||
}); | ||
describe('main', function () { | ||
@@ -19,3 +26,3 @@ let root_id; | ||
it('should only allow a single tracking instance to exist', function() { | ||
it('should only allow a single tracking instance to exist per context', function() { | ||
oTracking.destroy(); | ||
@@ -44,2 +51,85 @@ const confEl = document.createElement('script'); | ||
describe('a second instance of o-tracking exists in a separate browser context (e.g. new tab)', function () { | ||
let iframe; | ||
let iframeSrc; | ||
let parentLink; | ||
const config = { | ||
context: { | ||
product: 'desktop' | ||
}, | ||
user: { | ||
user_id: '023ur9jfokwenvcklwnfiwhfoi324' | ||
} | ||
}; | ||
beforeEach(function () { | ||
// Create iframe (new context to mimic a new tab) | ||
// and a local link click to track. | ||
document.documentElement.innerHTML = ` | ||
<a href="#mock">A local link to click.</a> | ||
<iframe></iframe> | ||
`; | ||
iframe = document.querySelector('iframe'); | ||
parentLink = document.querySelector('a'); | ||
// Initialise o-tracking on the parent context. | ||
const parentTracking = oTracking.init(config); | ||
parentTracking.click.init(); | ||
// Generate iframe content. | ||
const karmaFile = '/base/test/o-tracking-test.js'; | ||
const bundleUrl = | ||
window.__karma__ && | ||
window.__karma__.files && | ||
window.__karma__.files[karmaFile] ? karmaFile : null; | ||
proclaim.ok( | ||
bundleUrl, | ||
'Could not find the o-tracking bundle served from a url, to ' + | ||
'test instances across different browser contexts. Unfortunately ' + | ||
'this depends on the Karma test runner. Has the test setup changed?' | ||
); | ||
const iframeContent = new Blob([` | ||
<html> | ||
<head> | ||
<!-- load o-tracking fixture --> | ||
<script src="${window.location.origin}${bundleUrl}"></script> | ||
</head> | ||
<body> | ||
</body> | ||
</html> | ||
`], { type: 'text/html' }); | ||
iframeSrc = URL.createObjectURL(iframeContent); | ||
}); | ||
it('should not send the same click event multiple times from the parent page', function(done) { | ||
// First click, e.g. like a cmd-click on macOS which opens a new tab | ||
// (the iframe is used to mimic this) | ||
parentLink.dispatchEvent(clickEvent, true); | ||
iframe.onload = () => { | ||
// init second instance of o-tracking in the iframe | ||
// fire a page event, as if navigated to a new page | ||
// in a new tab e.g. with cmd-click on macOS | ||
iframe.contentWindow.oTracking.init(config); | ||
iframe.contentWindow.oTracking.click.init(); | ||
iframe.contentWindow.oTracking.page(config); | ||
// another click in the parent context, after a second instance | ||
// of o-tracking has loaded in the iframe (e.g. a new tab) | ||
sendSpy.resetHistory(); | ||
parentLink.dispatchEvent(clickEvent, true); | ||
try { | ||
proclaim.equal(sendSpy.callCount, 1, 'Expected 1 event to be sent, the latest click event, no more.'); | ||
} catch (error) { | ||
done(error); | ||
} | ||
done(); | ||
}; | ||
iframe.src = iframeSrc; | ||
}); | ||
}); | ||
it('should quit without any config to init with', function() { | ||
@@ -46,0 +136,0 @@ oTracking.destroy(); |
@@ -7,6 +7,8 @@ /* global sinon*/ | ||
const sendSpy = sinon.spy(); | ||
export function mockTransport() { | ||
mock.transport = function () { | ||
return { | ||
send: sinon.spy(), | ||
send: sendSpy, | ||
complete: function (callback) { | ||
@@ -24,2 +26,4 @@ if (willError) { | ||
export { sendSpy }; | ||
export function unmockTransport() { | ||
@@ -26,0 +30,0 @@ delete mock.transport; |
139
334713
8237