@guardian/ophan-tracker-js 2.0.4 → 2.1.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/assets/adblock-detection.js +24 -0
  2. package/assets/attention.js +187 -0
  3. package/assets/campaign.js +40 -0
  4. package/assets/click-path-capture.js +107 -0
  5. package/assets/components.js +209 -0
  6. package/assets/contribution.js +25 -0
  7. package/assets/core.js +282 -0
  8. package/assets/embed.js +11 -0
  9. package/assets/fb-instant.js +10 -0
  10. package/assets/heatmap.js +47 -0
  11. package/assets/holidays.js +11 -0
  12. package/assets/http-status.js +20 -0
  13. package/assets/iframe-tracking.js +82 -0
  14. package/assets/interactive.js +25 -0
  15. package/assets/jobs-courses.js +11 -0
  16. package/assets/jobs.js +11 -0
  17. package/assets/manage-my-account.js +50 -0
  18. package/assets/membership.js +25 -0
  19. package/assets/ng.js +41 -0
  20. package/assets/perf.js +43 -0
  21. package/assets/privatebrowsing.js +79 -0
  22. package/{build → assets}/r2.js +3 -3
  23. package/{build → assets}/smart-news.js +4 -4
  24. package/assets/support.js +44 -0
  25. package/assets/transmit.js +150 -0
  26. package/assets/vendor/adBlockDetectionLib.js +366 -0
  27. package/assets/visibility.js +20 -0
  28. package/{build → assets}/witness.js +3 -3
  29. package/package.json +28 -24
  30. package/build/adblock-detection.js +0 -24
  31. package/build/attention.js +0 -149
  32. package/build/campaign.js +0 -30
  33. package/build/click-path-capture.js +0 -81
  34. package/build/components.js +0 -167
  35. package/build/contribution.js +0 -25
  36. package/build/core.js +0 -234
  37. package/build/embed.js +0 -11
  38. package/build/fb-instant.js +0 -10
  39. package/build/heatmap.js +0 -32
  40. package/build/holidays.js +0 -11
  41. package/build/http-status.js +0 -20
  42. package/build/iframe-tracking.js +0 -68
  43. package/build/interactive.js +0 -25
  44. package/build/jobs-courses.js +0 -11
  45. package/build/jobs.js +0 -11
  46. package/build/manage-my-account.js +0 -47
  47. package/build/membership.js +0 -25
  48. package/build/ng.js +0 -33
  49. package/build/perf.js +0 -39
  50. package/build/privatebrowsing.js +0 -71
  51. package/build/support.js +0 -41
  52. package/build/transmit.js +0 -107
  53. package/build/vendor/adBlockDetectionLib.js +0 -367
  54. package/build/visibility.js +0 -7
@@ -0,0 +1,24 @@
1
+ // Generated by CoffeeScript 2.7.0
2
+ import abd from './vendor/adBlockDetectionLib.js';
3
+
4
+ export default {
5
+ run: function () {
6
+ return new Promise(function (resolve, reject) {
7
+ var e;
8
+ if (abd.init) {
9
+ try {
10
+ return abd.init({
11
+ complete: function (v) {
12
+ return resolve(v);
13
+ },
14
+ });
15
+ } catch (error) {
16
+ e = error;
17
+ return reject(e);
18
+ }
19
+ } else {
20
+ return reject(new Error('Failed to initialise adblock detection'));
21
+ }
22
+ });
23
+ },
24
+ };
@@ -0,0 +1,187 @@
1
+ // Generated by CoffeeScript 2.7.0
2
+ var ATTENTIONDECAY,
3
+ EVENTS,
4
+ REPORTINGINTERVAL,
5
+ cancelDecayTimer,
6
+ decayTimerId,
7
+ incrementTotalAttentionTimeByUnrecordedAmount,
8
+ initAttention,
9
+ initComponent,
10
+ makeActive,
11
+ makeInactive,
12
+ reportedTotalAttentionMs,
13
+ reporter,
14
+ setEventEmitter,
15
+ totalAttentionMs,
16
+ unrecordedAttentionStarted,
17
+ videoPlaying;
18
+
19
+ import transmit from './transmit.js';
20
+
21
+ import components from './components.js';
22
+
23
+ // The upworthy definition of attention minutes, from
24
+ // http://upworthy.github.io/2014/06/implementing-attention-minutes-part-1/ :
25
+
26
+ // "We consider the page to have the user’s attention if
27
+ // 1. some activity is happening on the page. For our purposes this means:
28
+ // 1. the page currently has focus
29
+ // 2. and the user has interacted with the page within a certain timeout
30
+ // 2. or a video is playing on the page
31
+ // Attention Minutes, then, are the total number of minutes that a user has spent on
32
+ // a page where the above conditions have held true."
33
+
34
+ // our definition of "within a certain timeout" above:
35
+ ATTENTIONDECAY = 5000;
36
+
37
+ // our definition of "the user has interacted with the page"
38
+ EVENTS = [
39
+ 'focus',
40
+ 'click',
41
+ 'scroll',
42
+ 'mousemove',
43
+ 'touchstart',
44
+ 'touchend',
45
+ 'touchcancel',
46
+ 'touchleave',
47
+ 'touchmove',
48
+ 'keyup',
49
+ 'keydown',
50
+ ];
51
+
52
+ REPORTINGINTERVAL = 10000;
53
+
54
+ // total attention time so far on this page
55
+ totalAttentionMs = 0;
56
+
57
+ // the time elapsed since the time origin (https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#the_time_origin)
58
+ // when the most recent period of attention (that is, attention
59
+ // which we haven't yet added to totalAttentionMs) began.
60
+ // null if we don't have attention.
61
+ unrecordedAttentionStarted = null;
62
+
63
+ // the attention time we last reported to ophan
64
+ reportedTotalAttentionMs = null; // setting null here forces a first report of attention time, even if it's only 0ms
65
+
66
+ // the decay timer if we've set one
67
+ decayTimerId = null;
68
+
69
+ // is video currently playing?
70
+ videoPlaying = false;
71
+
72
+ cancelDecayTimer = function () {
73
+ if (decayTimerId != null) {
74
+ window.clearTimeout(decayTimerId);
75
+ }
76
+ return (decayTimerId = null);
77
+ };
78
+
79
+ makeActive = function () {
80
+ // mark when the latest period of attention began,
81
+ // if we're not already in an attention period
82
+ if (unrecordedAttentionStarted == null) {
83
+ unrecordedAttentionStarted = performance.now();
84
+ }
85
+ components.startMonitoring();
86
+ // start decay timer
87
+ cancelDecayTimer();
88
+ return (decayTimerId = window.setTimeout(function () {
89
+ if (!videoPlaying) {
90
+ return makeInactive();
91
+ }
92
+ }, ATTENTIONDECAY));
93
+ };
94
+
95
+ incrementTotalAttentionTimeByUnrecordedAmount = function () {
96
+ var now, unrecordedMs;
97
+ if (unrecordedAttentionStarted != null) {
98
+ now = performance.now();
99
+ // the Math.min here is to deal with occasions where we don't get an event indicating that the user
100
+ // has become inactive - this can happen e.g. due to users suspending their phone. Never send
101
+ // crazy big values!
102
+ unrecordedMs = Math.min(
103
+ now - unrecordedAttentionStarted,
104
+ REPORTINGINTERVAL,
105
+ );
106
+ totalAttentionMs += unrecordedMs;
107
+ return (unrecordedAttentionStarted = now);
108
+ }
109
+ };
110
+
111
+ makeInactive = function () {
112
+ cancelDecayTimer();
113
+ components.stopMonitoring();
114
+ incrementTotalAttentionTimeByUnrecordedAmount();
115
+ return (unrecordedAttentionStarted = null);
116
+ };
117
+
118
+ reporter = function () {
119
+ var componentAttentionTimes, report;
120
+ incrementTotalAttentionTimeByUnrecordedAmount();
121
+ if (totalAttentionMs !== reportedTotalAttentionMs) {
122
+ report = {
123
+ attentionMs: Math.round(totalAttentionMs),
124
+ };
125
+ componentAttentionTimes = components.getAttentionTimes();
126
+ if (Object.keys(componentAttentionTimes).length) {
127
+ report.componentAttentionMs = componentAttentionTimes;
128
+ }
129
+ transmit.sendMore(report);
130
+ return (reportedTotalAttentionMs = totalAttentionMs);
131
+ }
132
+ };
133
+
134
+ initComponent = function (name, el, visibilityThreshold = 0.5) {
135
+ return components.registerComponent(
136
+ name,
137
+ el,
138
+ visibilityThreshold,
139
+ REPORTINGINTERVAL,
140
+ );
141
+ };
142
+
143
+ initAttention = function (visibility) {
144
+ var event, i, len;
145
+ for (i = 0, len = EVENTS.length; i < len; i++) {
146
+ event = EVENTS[i];
147
+ // user interaction tracking
148
+ window.addEventListener(event, makeActive);
149
+ }
150
+ // focus tracking
151
+ document.addEventListener(
152
+ visibility.changeEvent,
153
+ function () {
154
+ if (visibility.state() === 'visible') {
155
+ return makeActive();
156
+ } else {
157
+ return makeInactive();
158
+ }
159
+ },
160
+ false,
161
+ );
162
+ // video tracking
163
+ document.addEventListener('videoPlaying', function () {
164
+ videoPlaying = true;
165
+ return makeActive();
166
+ });
167
+ document.addEventListener('videoEnded', function () {
168
+ videoPlaying = false;
169
+ return makeInactive();
170
+ });
171
+ document.addEventListener('videoPause', function () {
172
+ videoPlaying = false;
173
+ return makeInactive();
174
+ });
175
+ window.setTimeout(reporter, 100);
176
+ return window.setInterval(reporter, REPORTINGINTERVAL);
177
+ };
178
+
179
+ setEventEmitter = function (emitter) {
180
+ return components.setEventEmitter(emitter);
181
+ };
182
+
183
+ export default {
184
+ init: initAttention,
185
+ initComponent: initComponent,
186
+ setEventEmitter: setEventEmitter,
187
+ };
@@ -0,0 +1,40 @@
1
+ // Generated by CoffeeScript 2.7.0
2
+ var ref, ref1, start;
3
+
4
+ import transmit from './transmit.js';
5
+
6
+ import perf from './perf.js';
7
+
8
+ start =
9
+ (ref =
10
+ perf != null
11
+ ? (ref1 = perf.timing) != null
12
+ ? ref1.domComplete
13
+ : void 0
14
+ : void 0) != null
15
+ ? ref
16
+ : performance.now();
17
+
18
+ // DFP ads
19
+ if (typeof googletag !== 'undefined' && googletag !== null) {
20
+ googletag.cmd.push(function () {
21
+ return googletag
22
+ .pubads()
23
+ .addEventListener('slotRenderEnded', function (event) {
24
+ var slotId;
25
+ slotId = event.slot.getSlotId().getDomId();
26
+ return transmit.sendMore({
27
+ ads: [
28
+ {
29
+ slot: slotId,
30
+ campaignId: event.isEmpty ? '__empty__' : event.lineItemId,
31
+ creativeId: event.creativeId,
32
+ // overall time to render an ad
33
+ timeToRenderEnded: Math.round(performance.now() - start),
34
+ adServer: 'DFP',
35
+ },
36
+ ],
37
+ });
38
+ });
39
+ });
40
+ }
@@ -0,0 +1,107 @@
1
+ // Generated by CoffeeScript 2.7.0
2
+ var getContainingComponent, getDataLinkNames, validAncestorAnchorElement;
3
+
4
+ import ophan from './core.js';
5
+
6
+ import transmit from './transmit.js';
7
+
8
+ validAncestorAnchorElement = function (el) {
9
+ var elementType, ref;
10
+ elementType =
11
+ el != null
12
+ ? (ref = el.nodeName) != null
13
+ ? ref.toLowerCase()
14
+ : void 0
15
+ : void 0;
16
+ if (elementType === 'a') {
17
+ return el;
18
+ } else if (elementType == null || elementType === 'body') {
19
+ return null;
20
+ } else {
21
+ return validAncestorAnchorElement(el.parentNode);
22
+ }
23
+ };
24
+
25
+ getContainingComponent = function (el) {
26
+ var elementType, ref;
27
+ elementType =
28
+ el != null
29
+ ? (ref = el.nodeName) != null
30
+ ? ref.toLowerCase()
31
+ : void 0
32
+ : void 0;
33
+ if (elementType == null || elementType === 'body') {
34
+ return null;
35
+ } else {
36
+ return (
37
+ (typeof el.getAttribute === 'function'
38
+ ? el.getAttribute('data-component')
39
+ : void 0) || getContainingComponent(el.parentNode)
40
+ );
41
+ }
42
+ };
43
+
44
+ getDataLinkNames = function (el, dataLinkNames = []) {
45
+ var isBody, isDocument, walkTree;
46
+ isBody = function (elem) {
47
+ var ref;
48
+ return (
49
+ (elem != null
50
+ ? (ref = elem.nodeName) != null
51
+ ? ref.toLowerCase()
52
+ : void 0
53
+ : void 0) === document.body.nodeName.toLowerCase()
54
+ );
55
+ };
56
+ isDocument = function (elem) {
57
+ return elem === document;
58
+ };
59
+ walkTree = function (elem, dataLinkNames) {
60
+ var dataLinkName;
61
+ if (elem != null && !isBody(elem) && !isDocument(elem)) {
62
+ dataLinkName = elem.getAttribute('data-link-name');
63
+ if (dataLinkName != null) {
64
+ dataLinkNames.push(dataLinkName);
65
+ }
66
+ return walkTree(elem.parentNode, dataLinkNames);
67
+ } else {
68
+ return dataLinkNames;
69
+ }
70
+ };
71
+ return walkTree(el, dataLinkNames.slice());
72
+ };
73
+
74
+ if (typeof document.addEventListener === 'function') {
75
+ document.addEventListener(
76
+ 'click',
77
+ function (e) {
78
+ var anchorTarget, info;
79
+ anchorTarget = validAncestorAnchorElement(e.target);
80
+ info = {
81
+ from: [location.protocol, '//', location.host, location.pathname].join(
82
+ '',
83
+ ),
84
+ to: anchorTarget ? anchorTarget.href : void 0,
85
+ referringComponent: getContainingComponent(e.target),
86
+ referringDataLinkNames: getDataLinkNames(e.target),
87
+ refPlatform: ophan.servingPlatform(),
88
+ refViewId: ophan.viewId,
89
+ };
90
+ if (info.referringDataLinkNames != null) {
91
+ transmit.sendMore({
92
+ clickComponent: info.referringComponent,
93
+ clickLinkNames: info.referringDataLinkNames,
94
+ });
95
+ }
96
+ if (anchorTarget != null) {
97
+ // then we may be about to navigate away from this page, so store to send on next request
98
+ return ophan.storeDataToSendOnNextEvent(info);
99
+ }
100
+ },
101
+ false,
102
+ );
103
+ }
104
+
105
+ export default {
106
+ getDataLinkNames: getDataLinkNames,
107
+ };
@@ -0,0 +1,209 @@
1
+ /**
2
+ * @type {Component[]}
3
+ */
4
+ const components = [];
5
+
6
+ /**
7
+ * theguardian.com uses a custom scroll event for performance reasons.
8
+ * We pass in the event emitter so that we can listen to this custom scroll event.
9
+ * If not on theguardian.com, we default to the native scroll event
10
+ * @type {Object|null}
11
+ */
12
+ let eventEmitter = null;
13
+
14
+ // Tracks attention time of elements on the page with data-component attributes.
15
+ // This should always be less than or equal to the page attention time.
16
+ // If the page does not have attention, no component has attention.
17
+ // If the page does have attention, a given component *may* also have
18
+ // attention, if it is within the viewport and not hidden.
19
+ class Component {
20
+ /**
21
+ * Component constructor.
22
+ * @param {string} name - The name of the component.
23
+ * @param {HTMLElement} element - The DOM element of the component.
24
+ * @param {number} visibilityThreshold1 -
25
+ * visibilityThreshold represents the fraction of each component that must
26
+ * be in the viewport before it is considered visible.
27
+ * e.g. 0.5 means that half the height or width must be in the viewport
28
+ * 1 means that the entire element must be in the viewport
29
+ * @param {number} reportingInterval1 - ??
30
+ */
31
+ constructor(name, element, visibilityThreshold1, reportingInterval1) {
32
+ this.name = name;
33
+ this.element = element;
34
+ this.visibilityThreshold = visibilityThreshold1;
35
+ this.reportingInterval = reportingInterval1;
36
+ this.visible = false;
37
+ // total attention time so far for this element
38
+ this.totalAttentionMs = 0;
39
+ // the time elapsed since the time origin (https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#the_time_origin)
40
+ // when the most recent period of attention (that is, attention
41
+ // which we haven't yet added to totalAttentionMs) began.
42
+ // null if we don't have attention.
43
+ this.unrecordedAttentionStarted = null;
44
+ // the attention time when getAttentionTime() was last called
45
+ this.reportedTotalAttentionMs = 0;
46
+ this.usingEmitter = false;
47
+ this.visCheck = this.checkVisibility.bind(this);
48
+ if (eventEmitter != null) {
49
+ this.usingEmitter = true;
50
+ eventEmitter.on('window:throttledScroll', this.visCheck);
51
+ } else {
52
+ window.addEventListener('scroll', this.visCheck);
53
+ }
54
+ window.addEventListener('resize', this.visCheck);
55
+ }
56
+
57
+ isVisible(threshold = 1) {
58
+ if (!this.element.offsetParent) {
59
+ // exclude hidden elements
60
+ // will also exclude position: fixed elements but we don't care about these
61
+ // (because their attention time would always be the same as the whole page)
62
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent
63
+ return false;
64
+ }
65
+
66
+ const box = this.element.getBoundingClientRect();
67
+ const width = box.width;
68
+ const height = box.height;
69
+ const windowHeight =
70
+ window.innerHeight || document.documentElement.clientHeight;
71
+ const windowWidth =
72
+ window.innerWidth || document.documentElement.clientWidth;
73
+
74
+ return (
75
+ box.left >= -(width * (1 - threshold)) &&
76
+ box.top >= -(height * (1 - threshold)) &&
77
+ box.right <= windowWidth + width * (1 - threshold) &&
78
+ box.bottom <= windowHeight + height * (1 - threshold)
79
+ );
80
+ }
81
+
82
+ visibilityHasChanged() {
83
+ const wasVisible = this.visible;
84
+ this.visible = this.isVisible(this.visibilityThreshold);
85
+ return wasVisible !== this.visible;
86
+ }
87
+
88
+ rebindToEventEmitter() {
89
+ if (!this.usingEmitter && eventEmitter != null) {
90
+ window.removeEventListener('scroll', this.visCheck);
91
+ this.usingEmitter = true;
92
+ return eventEmitter.on('window:throttledScroll', this.visCheck);
93
+ }
94
+ }
95
+
96
+ checkVisibility() {
97
+ this.rebindToEventEmitter();
98
+ if (this.visibilityHasChanged()) {
99
+ return this.visible ? this.makeActive() : this.makeInactive();
100
+ }
101
+ }
102
+
103
+ makeActive() {
104
+ // mark when the latest period of attention began,
105
+ // if we're not already in an attention period
106
+ return this.unrecordedAttentionStarted != null
107
+ ? this.unrecordedAttentionStarted
108
+ : (this.unrecordedAttentionStarted = performance.now());
109
+ }
110
+
111
+ makeInactive() {
112
+ this.incrementTotalAttentionTimeByUnrecordedAmount();
113
+ return (this.unrecordedAttentionStarted = null);
114
+ }
115
+
116
+ hadAttentionSinceLastGet() {
117
+ this.incrementTotalAttentionTimeByUnrecordedAmount();
118
+ return this.totalAttentionMs !== this.reportedTotalAttentionMs;
119
+ }
120
+
121
+ getAttentionTime() {
122
+ this.incrementTotalAttentionTimeByUnrecordedAmount();
123
+ this.reportedTotalAttentionMs = this.totalAttentionMs;
124
+ return this.totalAttentionMs;
125
+ }
126
+
127
+ incrementTotalAttentionTimeByUnrecordedAmount() {
128
+ if (this.unrecordedAttentionStarted != null) {
129
+ const now = performance.now();
130
+ // the Math.min here is to deal with occasions where we don't get an event indicating that the user
131
+ // has become inactive - this can happen e.g. due to users suspending their phone. Never send
132
+ // crazy big values!
133
+ const unrecordedMs = Math.min(
134
+ now - this.unrecordedAttentionStarted,
135
+ this.reportingInterval,
136
+ );
137
+ this.totalAttentionMs += unrecordedMs;
138
+ return (this.unrecordedAttentionStarted = now);
139
+ }
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Begins monitoring the components on the page for attention time.
145
+ * @returns {Array} Array of results from makeActive() for each visible component.
146
+ */
147
+ const startMonitoring = () => {
148
+ return components
149
+ .filter((component) => component.isVisible())
150
+ .map((component) => component.makeActive());
151
+ };
152
+
153
+ /**
154
+ * Stops monitoring the components on the page for attention time.
155
+ * @returns {Array} Array of results from makeInactive() for each component.
156
+ */
157
+ const stopMonitoring = () => {
158
+ return components.map((component) => component.makeInactive());
159
+ };
160
+
161
+ /**
162
+ * Retrieves the attention times for all components.
163
+ * @returns {Object}
164
+ */
165
+ const getAttentionTimes = () => {
166
+ const obj = {};
167
+ for (const component of components) {
168
+ if (component.hadAttentionSinceLastGet()) {
169
+ // if there are duplicate component names, only the last one survives
170
+ obj[component.name] = Math.round(component.getAttentionTime());
171
+ }
172
+ }
173
+ return obj;
174
+ };
175
+
176
+ export default {
177
+ /**
178
+ * @param {Object} emitter - The event emitter to set.
179
+ * @returns {Object}
180
+ */
181
+ setEventEmitter: function (emitter) {
182
+ return (eventEmitter = emitter);
183
+ },
184
+ startMonitoring,
185
+ stopMonitoring,
186
+ getAttentionTimes,
187
+
188
+ /** @type {(...args: ConstructorParameters<typeof Component>) => void} */
189
+ registerComponent: function (
190
+ name,
191
+ el,
192
+ visibilityThreshold,
193
+ reportingInterval,
194
+ ) {
195
+ return components.push(
196
+ new Component(name, el, visibilityThreshold, reportingInterval),
197
+ );
198
+ },
199
+ };
200
+
201
+ /**
202
+ * Exports for testing purposes.
203
+ */
204
+ export const _testExports = {
205
+ startMonitoring,
206
+ stopMonitoring,
207
+ getAttentionTimes,
208
+ components,
209
+ };
@@ -0,0 +1,25 @@
1
+ // Generated by CoffeeScript 2.7.0
2
+ import core from './core.js';
3
+
4
+ import transmit from './transmit.js';
5
+
6
+ import attention from './attention.js';
7
+
8
+ import visibility from './visibility.js';
9
+
10
+ import './click-path-capture.js';
11
+
12
+ import './perf.js';
13
+
14
+ import './campaign.js';
15
+
16
+ core.init('contribution');
17
+
18
+ if (window.addEventListener != null) {
19
+ attention.init(visibility);
20
+ }
21
+
22
+ export default {
23
+ record: transmit.sendMore,
24
+ viewId: transmit.viewId,
25
+ };