@guardian/ophan-tracker-js 2.1.0-next.1 → 2.1.0-next.2
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.
- package/assets/adblock-detection.js +7 -12
- package/assets/attention.js +45 -72
- package/assets/campaign.js +16 -31
- package/assets/click-path-capture.js +38 -65
- package/assets/components.js +10 -10
- package/assets/http-status.js +3 -10
- package/assets/iframe-tracking.js +44 -38
- package/assets/interactive.js +5 -13
- package/assets/manage-my-account.js +10 -22
- package/assets/perf.js +5 -8
- package/assets/privatebrowsing.js +31 -39
- package/assets/support.js +11 -21
- package/assets/transmit.js +97 -104
- package/assets/visibility.js +21 -18
- package/package.json +1 -1
- package/readme.md +15 -15
|
@@ -1,24 +1,19 @@
|
|
|
1
|
-
// Generated by CoffeeScript 2.7.0
|
|
2
1
|
import abd from './vendor/adBlockDetectionLib.js';
|
|
3
2
|
|
|
4
3
|
export default {
|
|
5
|
-
run
|
|
6
|
-
return new Promise(
|
|
7
|
-
var e;
|
|
4
|
+
run() {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
8
6
|
if (abd.init) {
|
|
9
7
|
try {
|
|
10
|
-
|
|
11
|
-
complete:
|
|
12
|
-
return resolve(v);
|
|
13
|
-
},
|
|
8
|
+
abd.init({
|
|
9
|
+
complete: resolve,
|
|
14
10
|
});
|
|
15
11
|
} catch (error) {
|
|
16
|
-
|
|
17
|
-
return reject(e);
|
|
12
|
+
reject(error);
|
|
18
13
|
}
|
|
19
14
|
} else {
|
|
20
|
-
|
|
15
|
+
reject(new Error('Failed to initialise adblock detection'));
|
|
21
16
|
}
|
|
22
17
|
});
|
|
23
18
|
},
|
|
24
|
-
};
|
|
19
|
+
};
|
package/assets/attention.js
CHANGED
|
@@ -1,21 +1,4 @@
|
|
|
1
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
2
|
import transmit from './transmit.js';
|
|
20
3
|
|
|
21
4
|
import components from './components.js';
|
|
@@ -32,10 +15,10 @@ import components from './components.js';
|
|
|
32
15
|
// a page where the above conditions have held true."
|
|
33
16
|
|
|
34
17
|
// our definition of "within a certain timeout" above:
|
|
35
|
-
ATTENTIONDECAY = 5000;
|
|
18
|
+
const ATTENTIONDECAY = 5000;
|
|
36
19
|
|
|
37
20
|
// our definition of "the user has interacted with the page"
|
|
38
|
-
EVENTS = [
|
|
21
|
+
const EVENTS = [
|
|
39
22
|
'focus',
|
|
40
23
|
'click',
|
|
41
24
|
'scroll',
|
|
@@ -49,90 +32,82 @@ EVENTS = [
|
|
|
49
32
|
'keydown',
|
|
50
33
|
];
|
|
51
34
|
|
|
52
|
-
REPORTINGINTERVAL = 10000;
|
|
35
|
+
const REPORTINGINTERVAL = 10000;
|
|
53
36
|
|
|
54
37
|
// total attention time so far on this page
|
|
55
|
-
totalAttentionMs = 0;
|
|
38
|
+
let totalAttentionMs = 0;
|
|
56
39
|
|
|
57
40
|
// the time elapsed since the time origin (https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#the_time_origin)
|
|
58
41
|
// when the most recent period of attention (that is, attention
|
|
59
42
|
// which we haven't yet added to totalAttentionMs) began.
|
|
60
43
|
// null if we don't have attention.
|
|
61
|
-
unrecordedAttentionStarted = null;
|
|
44
|
+
let unrecordedAttentionStarted = null;
|
|
62
45
|
|
|
63
46
|
// 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
|
|
47
|
+
let reportedTotalAttentionMs = null; // setting null here forces a first report of attention time, even if it's only 0ms
|
|
65
48
|
|
|
66
49
|
// the decay timer if we've set one
|
|
67
|
-
decayTimerId = null;
|
|
50
|
+
let decayTimerId = null;
|
|
68
51
|
|
|
69
52
|
// is video currently playing?
|
|
70
|
-
videoPlaying = false;
|
|
53
|
+
let videoPlaying = false;
|
|
71
54
|
|
|
72
|
-
cancelDecayTimer =
|
|
55
|
+
const cancelDecayTimer = () => {
|
|
73
56
|
if (decayTimerId != null) {
|
|
74
57
|
window.clearTimeout(decayTimerId);
|
|
75
58
|
}
|
|
76
|
-
|
|
59
|
+
decayTimerId = null;
|
|
77
60
|
};
|
|
78
61
|
|
|
79
|
-
makeActive =
|
|
80
|
-
// mark when the latest period of attention began,
|
|
81
|
-
// if we're not already in an attention period
|
|
62
|
+
const makeActive = () => {
|
|
82
63
|
if (unrecordedAttentionStarted == null) {
|
|
83
64
|
unrecordedAttentionStarted = performance.now();
|
|
84
65
|
}
|
|
85
66
|
components.startMonitoring();
|
|
86
|
-
// start decay timer
|
|
87
67
|
cancelDecayTimer();
|
|
88
|
-
|
|
68
|
+
decayTimerId = window.setTimeout(() => {
|
|
89
69
|
if (!videoPlaying) {
|
|
90
|
-
|
|
70
|
+
makeInactive();
|
|
91
71
|
}
|
|
92
|
-
}, ATTENTIONDECAY)
|
|
72
|
+
}, ATTENTIONDECAY);
|
|
93
73
|
};
|
|
94
74
|
|
|
95
|
-
incrementTotalAttentionTimeByUnrecordedAmount =
|
|
96
|
-
var now, unrecordedMs;
|
|
75
|
+
const incrementTotalAttentionTimeByUnrecordedAmount = () => {
|
|
97
76
|
if (unrecordedAttentionStarted != null) {
|
|
98
|
-
now = performance.now();
|
|
99
|
-
|
|
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(
|
|
77
|
+
const now = performance.now();
|
|
78
|
+
const unrecordedMs = Math.min(
|
|
103
79
|
now - unrecordedAttentionStarted,
|
|
104
80
|
REPORTINGINTERVAL,
|
|
105
81
|
);
|
|
106
82
|
totalAttentionMs += unrecordedMs;
|
|
107
|
-
|
|
83
|
+
unrecordedAttentionStarted = now;
|
|
108
84
|
}
|
|
109
85
|
};
|
|
110
86
|
|
|
111
|
-
makeInactive =
|
|
87
|
+
const makeInactive = () => {
|
|
112
88
|
cancelDecayTimer();
|
|
113
89
|
components.stopMonitoring();
|
|
114
90
|
incrementTotalAttentionTimeByUnrecordedAmount();
|
|
115
|
-
|
|
91
|
+
unrecordedAttentionStarted = null;
|
|
116
92
|
};
|
|
117
93
|
|
|
118
|
-
reporter =
|
|
119
|
-
var componentAttentionTimes, report;
|
|
94
|
+
const reporter = () => {
|
|
120
95
|
incrementTotalAttentionTimeByUnrecordedAmount();
|
|
121
96
|
if (totalAttentionMs !== reportedTotalAttentionMs) {
|
|
122
|
-
report = {
|
|
97
|
+
const report = {
|
|
123
98
|
attentionMs: Math.round(totalAttentionMs),
|
|
124
99
|
};
|
|
125
|
-
componentAttentionTimes = components.getAttentionTimes();
|
|
100
|
+
const componentAttentionTimes = components.getAttentionTimes();
|
|
126
101
|
if (Object.keys(componentAttentionTimes).length) {
|
|
127
102
|
report.componentAttentionMs = componentAttentionTimes;
|
|
128
103
|
}
|
|
129
104
|
transmit.sendMore(report);
|
|
130
|
-
|
|
105
|
+
reportedTotalAttentionMs = totalAttentionMs;
|
|
131
106
|
}
|
|
132
107
|
};
|
|
133
108
|
|
|
134
|
-
initComponent =
|
|
135
|
-
|
|
109
|
+
const initComponent = (name, el, visibilityThreshold = 0.5) => {
|
|
110
|
+
components.registerComponent(
|
|
136
111
|
name,
|
|
137
112
|
el,
|
|
138
113
|
visibilityThreshold,
|
|
@@ -140,48 +115,46 @@ initComponent = function (name, el, visibilityThreshold = 0.5) {
|
|
|
140
115
|
);
|
|
141
116
|
};
|
|
142
117
|
|
|
143
|
-
initAttention =
|
|
144
|
-
|
|
145
|
-
for (i = 0, len = EVENTS.length; i < len; i++) {
|
|
146
|
-
event = EVENTS[i];
|
|
147
|
-
// user interaction tracking
|
|
118
|
+
const initAttention = (visibility) => {
|
|
119
|
+
EVENTS.forEach((event) => {
|
|
148
120
|
window.addEventListener(event, makeActive);
|
|
149
|
-
}
|
|
150
|
-
|
|
121
|
+
});
|
|
122
|
+
|
|
151
123
|
document.addEventListener(
|
|
152
124
|
visibility.changeEvent,
|
|
153
|
-
|
|
125
|
+
() => {
|
|
154
126
|
if (visibility.state() === 'visible') {
|
|
155
|
-
|
|
127
|
+
makeActive();
|
|
156
128
|
} else {
|
|
157
|
-
|
|
129
|
+
makeInactive();
|
|
158
130
|
}
|
|
159
131
|
},
|
|
160
132
|
false,
|
|
161
133
|
);
|
|
162
|
-
|
|
163
|
-
document.addEventListener('videoPlaying',
|
|
134
|
+
|
|
135
|
+
document.addEventListener('videoPlaying', () => {
|
|
164
136
|
videoPlaying = true;
|
|
165
|
-
|
|
137
|
+
makeActive();
|
|
166
138
|
});
|
|
167
|
-
document.addEventListener('videoEnded',
|
|
139
|
+
document.addEventListener('videoEnded', () => {
|
|
168
140
|
videoPlaying = false;
|
|
169
|
-
|
|
141
|
+
makeInactive();
|
|
170
142
|
});
|
|
171
|
-
document.addEventListener('videoPause',
|
|
143
|
+
document.addEventListener('videoPause', () => {
|
|
172
144
|
videoPlaying = false;
|
|
173
|
-
|
|
145
|
+
makeInactive();
|
|
174
146
|
});
|
|
147
|
+
|
|
175
148
|
window.setTimeout(reporter, 100);
|
|
176
|
-
|
|
149
|
+
window.setInterval(reporter, REPORTINGINTERVAL);
|
|
177
150
|
};
|
|
178
151
|
|
|
179
|
-
setEventEmitter =
|
|
180
|
-
|
|
152
|
+
const setEventEmitter = (emitter) => {
|
|
153
|
+
components.setEventEmitter(emitter);
|
|
181
154
|
};
|
|
182
155
|
|
|
183
156
|
export default {
|
|
184
157
|
init: initAttention,
|
|
185
158
|
initComponent: initComponent,
|
|
186
159
|
setEventEmitter: setEventEmitter,
|
|
187
|
-
};
|
|
160
|
+
};
|
package/assets/campaign.js
CHANGED
|
@@ -1,40 +1,25 @@
|
|
|
1
1
|
// Generated by CoffeeScript 2.7.0
|
|
2
|
-
var ref, ref1, start;
|
|
3
|
-
|
|
4
2
|
import transmit from './transmit.js';
|
|
5
|
-
|
|
6
3
|
import perf from './perf.js';
|
|
7
4
|
|
|
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();
|
|
5
|
+
const start = perf?.timing?.domComplete ?? performance.now();
|
|
17
6
|
|
|
18
7
|
// DFP ads
|
|
19
8
|
if (typeof googletag !== 'undefined' && googletag !== null) {
|
|
20
|
-
googletag.cmd.push(
|
|
21
|
-
|
|
22
|
-
.
|
|
23
|
-
.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
timeToRenderEnded: Math.round(performance.now() - start),
|
|
34
|
-
adServer: 'DFP',
|
|
35
|
-
},
|
|
36
|
-
],
|
|
37
|
-
});
|
|
9
|
+
googletag.cmd.push(() => {
|
|
10
|
+
googletag.pubads().addEventListener('slotRenderEnded', (event) => {
|
|
11
|
+
const slotId = event.slot.getSlotId().getDomId();
|
|
12
|
+
transmit.sendMore({
|
|
13
|
+
ads: [
|
|
14
|
+
{
|
|
15
|
+
slot: slotId,
|
|
16
|
+
campaignId: event.isEmpty ? '__empty__' : event.lineItemId,
|
|
17
|
+
creativeId: event.creativeId,
|
|
18
|
+
timeToRenderEnded: Math.round(performance.now() - start),
|
|
19
|
+
adServer: 'DFP',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
38
22
|
});
|
|
23
|
+
});
|
|
39
24
|
});
|
|
40
|
-
}
|
|
25
|
+
}
|
|
@@ -1,81 +1,54 @@
|
|
|
1
|
-
// Generated by CoffeeScript 2.7.0
|
|
2
|
-
var getContainingComponent, getDataLinkNames, validAncestorAnchorElement;
|
|
3
|
-
|
|
4
1
|
import ophan from './core.js';
|
|
5
2
|
|
|
6
3
|
import transmit from './transmit.js';
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return null;
|
|
20
|
-
} else {
|
|
21
|
-
return validAncestorAnchorElement(el.parentNode);
|
|
22
|
-
}
|
|
5
|
+
/**
|
|
6
|
+
* Finds the closest ancestor anchor (`<a>`) element of the given element.
|
|
7
|
+
*
|
|
8
|
+
* @param {Element} el - The element to start searching from.
|
|
9
|
+
* @returns {Element|null} The closest ancestor anchor element or null if none found.
|
|
10
|
+
*/
|
|
11
|
+
export const validAncestorAnchorElement = (el) => {
|
|
12
|
+
if (!el || el.nodeName?.toLowerCase() === 'body') return null;
|
|
13
|
+
return el.nodeName.toLowerCase() === 'a'
|
|
14
|
+
? el
|
|
15
|
+
: validAncestorAnchorElement(el.parentNode);
|
|
23
16
|
};
|
|
24
17
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
(typeof el.getAttribute === 'function'
|
|
38
|
-
? el.getAttribute('data-component')
|
|
39
|
-
: void 0) || getContainingComponent(el.parentNode)
|
|
40
|
-
);
|
|
41
|
-
}
|
|
18
|
+
/**
|
|
19
|
+
* Retrieves the value of the `data-component` attribute from the closest ancestor.
|
|
20
|
+
*
|
|
21
|
+
* @param {Element} el - The element to start searching from.
|
|
22
|
+
* @returns {string|null} The `data-component` attribute value or null if not found.
|
|
23
|
+
*/
|
|
24
|
+
export const getContainingComponent = (el) => {
|
|
25
|
+
if (!el || el.nodeName?.toLowerCase() === 'body') return null;
|
|
26
|
+
return (
|
|
27
|
+
el.getAttribute('data-component') || getContainingComponent(el.parentNode)
|
|
28
|
+
);
|
|
42
29
|
};
|
|
43
30
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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());
|
|
31
|
+
/**
|
|
32
|
+
* Collects all `data-link-name` attribute values from the ancestor elements.
|
|
33
|
+
*
|
|
34
|
+
* @param {Element} el - The element to start searching from.
|
|
35
|
+
* @param {string[]} dataLinkNames - Initial array of data link names (optional).
|
|
36
|
+
* @returns {string[]} Array of `data-link-name` values.
|
|
37
|
+
*/
|
|
38
|
+
const getDataLinkNames = (el, dataLinkNames = []) => {
|
|
39
|
+
if (!el || el === document.body || el === document) return dataLinkNames;
|
|
40
|
+
|
|
41
|
+
const dataLinkName = el.getAttribute('data-link-name');
|
|
42
|
+
if (dataLinkName) dataLinkNames.push(dataLinkName);
|
|
43
|
+
|
|
44
|
+
return getDataLinkNames(el.parentNode, dataLinkNames);
|
|
72
45
|
};
|
|
73
46
|
|
|
74
47
|
if (typeof document.addEventListener === 'function') {
|
|
75
48
|
document.addEventListener(
|
|
76
49
|
'click',
|
|
77
50
|
function (e) {
|
|
78
|
-
|
|
51
|
+
let anchorTarget, info;
|
|
79
52
|
anchorTarget = validAncestorAnchorElement(e.target);
|
|
80
53
|
info = {
|
|
81
54
|
from: [location.protocol, '//', location.host, location.pathname].join(
|
|
@@ -104,4 +77,4 @@ if (typeof document.addEventListener === 'function') {
|
|
|
104
77
|
|
|
105
78
|
export default {
|
|
106
79
|
getDataLinkNames: getDataLinkNames,
|
|
107
|
-
};
|
|
80
|
+
};
|
package/assets/components.js
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
const components = [];
|
|
5
5
|
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
6
16
|
/**
|
|
7
17
|
* theguardian.com uses a custom scroll event for performance reasons.
|
|
8
18
|
* We pass in the event emitter so that we can listen to this custom scroll event.
|
|
@@ -196,14 +206,4 @@ export default {
|
|
|
196
206
|
new Component(name, el, visibilityThreshold, reportingInterval),
|
|
197
207
|
);
|
|
198
208
|
},
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Exports for testing purposes.
|
|
203
|
-
*/
|
|
204
|
-
export const _testExports = {
|
|
205
|
-
startMonitoring,
|
|
206
|
-
stopMonitoring,
|
|
207
|
-
getAttentionTimes,
|
|
208
|
-
components,
|
|
209
209
|
};
|
package/assets/http-status.js
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
// Generated by CoffeeScript 2.7.0
|
|
2
|
-
var reportStatus;
|
|
3
|
-
|
|
4
1
|
import core from './core.js';
|
|
5
|
-
|
|
6
2
|
import './transmit.js';
|
|
7
|
-
|
|
8
3
|
import './click-path-capture.js';
|
|
9
|
-
|
|
10
4
|
import './perf.js';
|
|
11
|
-
|
|
12
5
|
import './campaign.js';
|
|
13
6
|
|
|
14
|
-
reportStatus =
|
|
7
|
+
const reportStatus = (platform, status) => {
|
|
15
8
|
return core.init(platform, status);
|
|
16
9
|
};
|
|
17
10
|
|
|
18
11
|
export default {
|
|
19
|
-
reportStatus
|
|
20
|
-
};
|
|
12
|
+
reportStatus,
|
|
13
|
+
};
|
|
@@ -1,56 +1,57 @@
|
|
|
1
|
-
// Generated by CoffeeScript 2.7.0
|
|
2
|
-
var componentEventIsValid,
|
|
3
|
-
handleIframeMessage,
|
|
4
|
-
recordIframeClickEvent,
|
|
5
|
-
recordIframeComponentEvent,
|
|
6
|
-
retrieveOriginDomain,
|
|
7
|
-
validIframeDomains;
|
|
8
|
-
|
|
9
1
|
import clickCapture from './click-path-capture.js';
|
|
10
2
|
|
|
11
|
-
validIframeDomains = [
|
|
3
|
+
const validIframeDomains = [
|
|
12
4
|
'.theguardian.com',
|
|
13
5
|
'.dev-theguardian.com',
|
|
14
6
|
'.thegulocal.com', // NB - leading dot is essential here to avoid matching on non-Guardian-owned domains
|
|
15
7
|
];
|
|
16
8
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Handles messages received from iframes, recording events as necessary.
|
|
11
|
+
* @param {MessageEvent} messageEvent - The message event received from an iframe.
|
|
12
|
+
*/
|
|
13
|
+
const handleIframeMessage = (messageEvent) => {
|
|
14
|
+
const originDomain = retrieveOriginDomain(messageEvent.origin);
|
|
20
15
|
if (
|
|
21
16
|
originDomain &&
|
|
22
|
-
validIframeDomains.some((domain) =>
|
|
23
|
-
return originDomain.endsWith(domain);
|
|
24
|
-
})
|
|
17
|
+
validIframeDomains.some((domain) => originDomain.endsWith(domain))
|
|
25
18
|
) {
|
|
26
19
|
if (messageEvent.data.type === 'ophan-iframe-click-event') {
|
|
27
|
-
eventObject = messageEvent.data.value;
|
|
28
|
-
iFrame = document.getElementById(messageEvent.data.iframeId);
|
|
20
|
+
const eventObject = messageEvent.data.value;
|
|
21
|
+
const iFrame = document.getElementById(messageEvent.data.iframeId);
|
|
29
22
|
recordIframeClickEvent(iFrame, eventObject);
|
|
30
23
|
} else if (messageEvent.data.type === 'ophan-iframe-component-event') {
|
|
31
|
-
eventObject = messageEvent.data.value;
|
|
24
|
+
const eventObject = messageEvent.data.value;
|
|
32
25
|
recordIframeComponentEvent(eventObject);
|
|
33
26
|
}
|
|
34
27
|
}
|
|
35
28
|
};
|
|
36
29
|
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Retrieves the domain from an event's origin URL.
|
|
32
|
+
* @param {string} eventOrigin - The origin URL of the event.
|
|
33
|
+
* @returns {(string|null)} The domain of the origin URL or null if it cannot be parsed.
|
|
34
|
+
*/
|
|
35
|
+
const retrieveOriginDomain = (eventOrigin) => {
|
|
39
36
|
try {
|
|
40
|
-
originURL = new URL(eventOrigin);
|
|
37
|
+
const originURL = new URL(eventOrigin);
|
|
41
38
|
return originURL.host;
|
|
42
39
|
} catch (error) {
|
|
43
|
-
|
|
44
|
-
if (e instanceof TypeError) {
|
|
40
|
+
if (error instanceof TypeError) {
|
|
45
41
|
// We failed to parse origin as an URL, we should ignore the event
|
|
46
42
|
return null;
|
|
47
43
|
} else {
|
|
48
|
-
throw
|
|
44
|
+
throw error;
|
|
49
45
|
}
|
|
50
46
|
}
|
|
51
47
|
};
|
|
52
48
|
|
|
53
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Records a click event occurring within an iframe.
|
|
51
|
+
* @param {HTMLElement} iFrame - The iframe element where the click event occurred.
|
|
52
|
+
* @param {Object} clickEvent - The click event object to record.
|
|
53
|
+
*/
|
|
54
|
+
const recordIframeClickEvent = (iFrame, clickEvent) => {
|
|
54
55
|
if (iFrame) {
|
|
55
56
|
clickEvent.clickLinkNames =
|
|
56
57
|
clickCapture.getDataLinkNames(iFrame, clickEvent.clickLinkNames) ||
|
|
@@ -59,24 +60,29 @@ recordIframeClickEvent = function (iFrame, clickEvent) {
|
|
|
59
60
|
window.guardian.ophan.record(clickEvent);
|
|
60
61
|
};
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Validates if an event object is a valid component event.
|
|
65
|
+
* @param {Object} eventObject - The event object to validate.
|
|
66
|
+
* @returns {boolean} True if the event object is valid, false otherwise.
|
|
67
|
+
*/
|
|
68
|
+
const componentEventIsValid = (eventObject) => {
|
|
69
|
+
return (
|
|
70
|
+
eventObject &&
|
|
71
|
+
typeof eventObject === 'object' &&
|
|
72
|
+
eventObject.componentEvent &&
|
|
73
|
+
typeof eventObject.componentEvent === 'object'
|
|
74
|
+
);
|
|
73
75
|
};
|
|
74
76
|
|
|
75
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Records a component event from an iframe.
|
|
79
|
+
* @param {Object} eventObject - The component event object to record.
|
|
80
|
+
*/
|
|
81
|
+
const recordIframeComponentEvent = (eventObject) => {
|
|
76
82
|
if (!componentEventIsValid(eventObject)) {
|
|
77
83
|
return;
|
|
78
84
|
}
|
|
79
85
|
window.guardian.ophan.record(eventObject);
|
|
80
86
|
};
|
|
81
87
|
|
|
82
|
-
window.addEventListener('message', handleIframeMessage, false);
|
|
88
|
+
window.addEventListener('message', handleIframeMessage, false);
|
package/assets/interactive.js
CHANGED
|
@@ -1,25 +1,17 @@
|
|
|
1
|
-
// Generated by CoffeeScript 2.7.0
|
|
2
|
-
var ref;
|
|
3
|
-
|
|
4
1
|
import core from './core.js';
|
|
5
|
-
|
|
6
2
|
import transmit from './transmit.js';
|
|
7
|
-
|
|
8
3
|
import attention from './attention.js';
|
|
9
|
-
|
|
10
4
|
import './click-path-capture.js';
|
|
11
5
|
|
|
12
|
-
if (
|
|
13
|
-
|
|
6
|
+
if (window.guardian?.ophan) {
|
|
7
|
+
guardian.ophan;
|
|
14
8
|
} else {
|
|
15
9
|
core.init('embed');
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
window.guardian.ophan = {
|
|
10
|
+
window.guardian = guardian || {};
|
|
11
|
+
guardian.ophan = {
|
|
20
12
|
setEventEmitter: attention.setEventEmitter,
|
|
21
13
|
trackComponentAttention: attention.initComponent,
|
|
22
14
|
record: transmit.sendMore,
|
|
23
15
|
viewId: transmit.viewId,
|
|
24
16
|
};
|
|
25
|
-
}
|
|
17
|
+
}
|
|
@@ -1,50 +1,38 @@
|
|
|
1
1
|
// Generated by CoffeeScript 2.7.0
|
|
2
|
-
var exports, sendInitialEvent;
|
|
3
|
-
|
|
4
2
|
import core from './core.js';
|
|
5
|
-
|
|
6
3
|
import transmit from './transmit.js';
|
|
7
|
-
|
|
8
4
|
import attention from './attention.js';
|
|
9
|
-
|
|
10
5
|
import visibility from './visibility.js';
|
|
11
6
|
|
|
12
7
|
import './click-path-capture.js';
|
|
13
|
-
|
|
14
8
|
import './perf.js';
|
|
15
|
-
|
|
16
9
|
import './campaign.js';
|
|
17
10
|
|
|
18
11
|
core.init('manage-my-account');
|
|
19
12
|
|
|
20
|
-
if (window.addEventListener
|
|
13
|
+
if (window.addEventListener) {
|
|
21
14
|
attention.init(visibility);
|
|
22
15
|
}
|
|
23
16
|
|
|
24
|
-
sendInitialEvent =
|
|
17
|
+
const sendInitialEvent = (
|
|
25
18
|
url = location.href,
|
|
26
|
-
referrer =
|
|
27
|
-
) {
|
|
28
|
-
var e;
|
|
19
|
+
referrer = document.referrer,
|
|
20
|
+
) => {
|
|
29
21
|
try {
|
|
30
22
|
transmit.bumpViewId();
|
|
31
|
-
|
|
23
|
+
core.sendInitialEvent(null, url, referrer);
|
|
32
24
|
} catch (error) {
|
|
33
|
-
|
|
34
|
-
return console.log(e);
|
|
25
|
+
console.log(error);
|
|
35
26
|
}
|
|
36
27
|
};
|
|
37
28
|
|
|
38
|
-
exports = {
|
|
39
|
-
sendInitialEvent
|
|
29
|
+
const exports = {
|
|
30
|
+
sendInitialEvent,
|
|
40
31
|
record: transmit.sendMore,
|
|
41
32
|
viewId: transmit.viewId,
|
|
42
33
|
};
|
|
43
34
|
|
|
44
|
-
|
|
45
|
-
window.guardian = {};
|
|
46
|
-
}
|
|
47
|
-
|
|
35
|
+
window.guardian = window.guardian || {};
|
|
48
36
|
window.guardian.ophan = exports;
|
|
49
37
|
|
|
50
|
-
export default exports;
|
|
38
|
+
export default exports;
|
package/assets/perf.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
// Generated by CoffeeScript 2.7.0
|
|
2
|
-
var perf, sendTimingData;
|
|
3
|
-
|
|
4
2
|
import core from './core.js';
|
|
5
3
|
|
|
6
|
-
perf =
|
|
4
|
+
const perf =
|
|
7
5
|
window.performance ||
|
|
8
6
|
window.msPerformance ||
|
|
9
7
|
window.webkitPerformance ||
|
|
10
8
|
window.mozPerformance;
|
|
11
9
|
|
|
12
|
-
sendTimingData =
|
|
13
|
-
|
|
14
|
-
t
|
|
15
|
-
if (t != null) {
|
|
10
|
+
const sendTimingData = () => {
|
|
11
|
+
const t = perf?.timing;
|
|
12
|
+
if (t) {
|
|
16
13
|
return {
|
|
17
14
|
performance: {
|
|
18
15
|
// Time required for domain lookup.
|
|
@@ -40,4 +37,4 @@ sendTimingData = function () {
|
|
|
40
37
|
|
|
41
38
|
core.onLoadCapture(sendTimingData);
|
|
42
39
|
|
|
43
|
-
export default perf;
|
|
40
|
+
export default perf;
|
|
@@ -1,48 +1,41 @@
|
|
|
1
|
-
// Generated by CoffeeScript 2.7.0
|
|
2
|
-
// Check if a visitor is using private browsing mode (true or false)
|
|
3
|
-
// User agent checking and browser mode checking based on: https://gist.github.com/cou929/7973956#gistcomment-1769682
|
|
4
|
-
var privateBrowsingMode;
|
|
5
|
-
|
|
6
1
|
import transmit from './transmit.js';
|
|
7
2
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
3
|
+
/*
|
|
4
|
+
TO-DO:
|
|
5
|
+
This function could probably be simplified with an async function.
|
|
6
|
+
It basically returns a `boolean`, but uses a callback to do so... I can only guess it’s because of [`requestFileSystem`’s signature](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestFileSystem).
|
|
7
|
+
*/
|
|
8
|
+
const privateBrowsingMode = (callback) => {
|
|
9
|
+
const reportNormalMode = () => callback(false);
|
|
10
|
+
const reportPrivateMode = () => callback(true);
|
|
11
|
+
|
|
12
|
+
const tryLocalStorage = () => {
|
|
18
13
|
try {
|
|
19
14
|
if (localStorage.length) {
|
|
20
|
-
|
|
15
|
+
reportNormalMode();
|
|
21
16
|
} else {
|
|
22
17
|
localStorage.x = 1;
|
|
23
18
|
localStorage.removeItem('x');
|
|
24
|
-
|
|
19
|
+
reportNormalMode();
|
|
25
20
|
}
|
|
26
|
-
} catch (
|
|
27
|
-
|
|
28
|
-
return reportPrivateMode();
|
|
21
|
+
} catch (error) {
|
|
22
|
+
reportPrivateMode();
|
|
29
23
|
}
|
|
30
24
|
};
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
|
|
26
|
+
const tryIndexedDB = () => {
|
|
33
27
|
try {
|
|
34
|
-
db = indexedDB.open('test');
|
|
28
|
+
const db = indexedDB.open('test');
|
|
35
29
|
db.onsuccess = reportNormalMode;
|
|
36
|
-
|
|
37
|
-
} catch (
|
|
38
|
-
|
|
39
|
-
return reportPrivateMode();
|
|
30
|
+
db.onerror = reportPrivateMode;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
reportPrivateMode();
|
|
40
33
|
}
|
|
41
34
|
};
|
|
42
35
|
|
|
43
36
|
// Blink
|
|
44
37
|
if (window.webkitRequestFileSystem) {
|
|
45
|
-
|
|
38
|
+
window.webkitRequestFileSystem(
|
|
46
39
|
window.TEMPORARY,
|
|
47
40
|
1,
|
|
48
41
|
reportNormalMode,
|
|
@@ -50,30 +43,29 @@ privateBrowsingMode = function (callback) {
|
|
|
50
43
|
);
|
|
51
44
|
// Firefox
|
|
52
45
|
} else if ('MozAppearance' in document.documentElement.style) {
|
|
53
|
-
|
|
46
|
+
tryIndexedDB();
|
|
54
47
|
// Safari
|
|
55
48
|
} else if (/constructor/i.test(window.HTMLElement)) {
|
|
56
|
-
|
|
49
|
+
tryLocalStorage();
|
|
57
50
|
} else if (
|
|
58
51
|
!window.indexedDB &&
|
|
59
52
|
(window.PointerEvent || window.MSPointerEvent)
|
|
60
53
|
) {
|
|
61
|
-
|
|
54
|
+
reportPrivateMode();
|
|
62
55
|
} else {
|
|
63
56
|
// Rest
|
|
64
|
-
|
|
57
|
+
reportNormalMode();
|
|
65
58
|
}
|
|
66
59
|
};
|
|
67
60
|
|
|
68
61
|
export default {
|
|
69
|
-
init
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
report = {
|
|
62
|
+
init() {
|
|
63
|
+
privateBrowsingMode((value) => {
|
|
64
|
+
const report = {
|
|
73
65
|
inPrivateBrowsingMode: value,
|
|
74
66
|
};
|
|
75
|
-
|
|
67
|
+
transmit.sendMore(report);
|
|
76
68
|
});
|
|
77
69
|
},
|
|
78
|
-
privateBrowsingMode
|
|
79
|
-
};
|
|
70
|
+
privateBrowsingMode,
|
|
71
|
+
};
|
package/assets/support.js
CHANGED
|
@@ -1,44 +1,34 @@
|
|
|
1
|
-
// Generated by CoffeeScript 2.7.0
|
|
2
|
-
var init, sendInitialEvent;
|
|
3
|
-
|
|
4
1
|
import core from './core.js';
|
|
5
|
-
|
|
6
2
|
import transmit from './transmit.js';
|
|
7
|
-
|
|
8
3
|
import attention from './attention.js';
|
|
9
|
-
|
|
10
4
|
import visibility from './visibility.js';
|
|
11
5
|
|
|
12
6
|
import './click-path-capture.js';
|
|
13
|
-
|
|
14
7
|
import './perf.js';
|
|
15
|
-
|
|
16
8
|
import './campaign.js';
|
|
17
9
|
|
|
18
|
-
init =
|
|
10
|
+
const init = () => {
|
|
19
11
|
core.init('support');
|
|
20
|
-
if (window.addEventListener
|
|
21
|
-
|
|
12
|
+
if (window.addEventListener) {
|
|
13
|
+
attention.init(visibility);
|
|
22
14
|
}
|
|
23
15
|
};
|
|
24
16
|
|
|
25
|
-
sendInitialEvent =
|
|
17
|
+
const sendInitialEvent = (
|
|
26
18
|
url = location.href,
|
|
27
|
-
referrer =
|
|
28
|
-
) {
|
|
29
|
-
var e;
|
|
19
|
+
referrer = document.referrer,
|
|
20
|
+
) => {
|
|
30
21
|
try {
|
|
31
22
|
transmit.bumpViewId();
|
|
32
|
-
|
|
23
|
+
core.sendInitialEvent(null, url, referrer);
|
|
33
24
|
} catch (error) {
|
|
34
|
-
|
|
35
|
-
return console.log(e);
|
|
25
|
+
console.log(error);
|
|
36
26
|
}
|
|
37
27
|
};
|
|
38
28
|
|
|
39
29
|
export default {
|
|
40
|
-
init
|
|
41
|
-
sendInitialEvent
|
|
30
|
+
init,
|
|
31
|
+
sendInitialEvent,
|
|
42
32
|
record: transmit.sendMore,
|
|
43
33
|
viewId: transmit.viewId,
|
|
44
|
-
};
|
|
34
|
+
};
|
package/assets/transmit.js
CHANGED
|
@@ -1,64 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
sendInitial,
|
|
12
|
-
sendMore,
|
|
13
|
-
smartEncode,
|
|
14
|
-
viewId,
|
|
15
|
-
hasProp = {}.hasOwnProperty;
|
|
16
|
-
|
|
17
|
-
ophanRemoteHost =
|
|
18
|
-
(typeof window !== 'undefined' && window !== null
|
|
19
|
-
? window.ophanRemoteHost
|
|
20
|
-
: void 0) || 'https://ophan.theguardian.com';
|
|
21
|
-
|
|
22
|
-
generatePageViewId = function () {
|
|
23
|
-
return (
|
|
24
|
-
new Date().getTime().toString(36) +
|
|
25
|
-
'xxxxxxxxxxxx'.replace(/x/g, function () {
|
|
26
|
-
return Math.floor(Math.random() * 36).toString(36);
|
|
27
|
-
})
|
|
28
|
-
);
|
|
1
|
+
const ophanRemoteHost = window?.ophanRemoteHost || '//ophan.theguardian.com';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @returns {string}
|
|
5
|
+
*/
|
|
6
|
+
const generatePageViewId = () => {
|
|
7
|
+
return `${new Date().getTime().toString(36)}${'xxxxxxxxxxxx'.replace(
|
|
8
|
+
/x/g,
|
|
9
|
+
() => Math.floor(Math.random() * 36).toString(36),
|
|
10
|
+
)}`;
|
|
29
11
|
};
|
|
30
12
|
|
|
31
13
|
// guardian.config.ophan.pageViewId is now pregenerated in
|
|
32
14
|
// the Guardian Frontend Project (https://github.com/guardian/frontend)
|
|
33
15
|
// frontend/common/app/templates/inlineJS/blocking/config.scala.js
|
|
34
16
|
// If you're going to change the generation method, please update that one too
|
|
35
|
-
viewId
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
? ref
|
|
45
|
-
: generatePageViewId();
|
|
46
|
-
|
|
47
|
-
bumpViewId = function () {
|
|
48
|
-
return (viewId = generatePageViewId());
|
|
17
|
+
let viewId;
|
|
18
|
+
if (typeof guardian !== 'undefined') {
|
|
19
|
+
viewId = guardian.config?.ophan?.pageViewId ?? generatePageViewId();
|
|
20
|
+
} else {
|
|
21
|
+
viewId = generatePageViewId();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const bumpViewId = () => {
|
|
25
|
+
viewId = generatePageViewId();
|
|
49
26
|
};
|
|
50
27
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
28
|
+
/**
|
|
29
|
+
* send the initial event associated with this page view
|
|
30
|
+
* store the resulting page view id
|
|
31
|
+
* this must only ever be called *once* subsequent calls will
|
|
32
|
+
* be ignored
|
|
33
|
+
* @param {Object} obj - The data to be sent.
|
|
34
|
+
*/
|
|
35
|
+
const sendInitial = (obj) => {
|
|
36
|
+
send(`${ophanRemoteHost}/img/1?${buildQueryString(obj)}`);
|
|
57
37
|
};
|
|
58
38
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Encodes a value for URL transmission.
|
|
41
|
+
* @param {(string|number)} value - The value to encode.
|
|
42
|
+
* @returns {string} The encoded value.
|
|
43
|
+
*/
|
|
44
|
+
const smartEncode = (value) => {
|
|
62
45
|
if (typeof value === 'string' || typeof value === 'number') {
|
|
63
46
|
return encodeURIComponent(value);
|
|
64
47
|
} else {
|
|
@@ -66,85 +49,95 @@ smartEncode = function (value) {
|
|
|
66
49
|
}
|
|
67
50
|
};
|
|
68
51
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
results.push(`${encodeURIComponent(prop)}=${smartEncode(value)}`);
|
|
82
|
-
}
|
|
52
|
+
/**
|
|
53
|
+
* Used for sending additional events after the initial PageView event has been sent, such as attention events.
|
|
54
|
+
* @param {Object} obj - The data object to be sent.
|
|
55
|
+
* @param {Function} [f] - An optional callback function. This is used by consumers of this library.
|
|
56
|
+
*/
|
|
57
|
+
const sendMore = (obj, f) => {
|
|
58
|
+
if (!JSON) return;
|
|
59
|
+
|
|
60
|
+
const values = [];
|
|
61
|
+
for (const prop in obj) {
|
|
62
|
+
if (obj.hasOwnProperty(prop) && isDefined(obj[prop])) {
|
|
63
|
+
values.push(`${encodeURIComponent(prop)}=${smartEncode(obj[prop])}`);
|
|
83
64
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (values.length === 0) {
|
|
87
68
|
return;
|
|
88
69
|
}
|
|
89
|
-
|
|
90
|
-
|
|
70
|
+
|
|
71
|
+
const url = `${ophanRemoteHost}/img/2?viewId=${viewId}&${values.join('&')}`;
|
|
72
|
+
send(url, f);
|
|
91
73
|
};
|
|
92
74
|
|
|
93
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Checks if a value is defined and not null.
|
|
77
|
+
* @param {*} v - The value to check.
|
|
78
|
+
* @returns {boolean} True if the value is defined, false otherwise.
|
|
79
|
+
*/
|
|
80
|
+
const isDefined = (v) => {
|
|
94
81
|
if (Array.isArray(v)) {
|
|
95
|
-
return v.length;
|
|
82
|
+
return !!v.length;
|
|
96
83
|
} else {
|
|
97
84
|
return v != null;
|
|
98
85
|
}
|
|
99
86
|
};
|
|
100
87
|
|
|
101
|
-
|
|
102
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Builds a query string from an object.
|
|
90
|
+
* @param {Object} obj - The object to convert to a query string.
|
|
91
|
+
* @returns {string} The resulting query string.
|
|
92
|
+
*/
|
|
93
|
+
const buildQueryString = (obj) => {
|
|
103
94
|
obj.viewId = viewId;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
value = obj[prop];
|
|
110
|
-
if (value != null) {
|
|
111
|
-
results.push(
|
|
112
|
-
`${encodeURIComponent(prop)}=${encodeURIComponent(value)}`,
|
|
113
|
-
);
|
|
114
|
-
}
|
|
95
|
+
|
|
96
|
+
const values = [];
|
|
97
|
+
for (const prop in obj) {
|
|
98
|
+
if (obj.hasOwnProperty(prop) && obj[prop] != null) {
|
|
99
|
+
values.push(`${encodeURIComponent(prop)}=${encodeURIComponent(obj[prop])}`);
|
|
115
100
|
}
|
|
116
|
-
|
|
117
|
-
|
|
101
|
+
}
|
|
102
|
+
|
|
118
103
|
return values.join('&');
|
|
119
104
|
};
|
|
120
105
|
|
|
121
|
-
|
|
122
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Sends data to a specified URL, i.e.: the Ophan Tracker backend service.
|
|
108
|
+
* @param {string} url - The URL to send data to.
|
|
109
|
+
* @param {() => void} [f] - An optional callback function.
|
|
110
|
+
*/
|
|
111
|
+
const send = (url, f) => {
|
|
123
112
|
if (typeof fetch === 'function') {
|
|
124
|
-
|
|
125
|
-
method: 'GET',
|
|
126
|
-
keepalive: true,
|
|
127
|
-
};
|
|
128
|
-
return fetch(url, fetchOptions).then(function () {
|
|
129
|
-
return typeof f === 'function' ? f() : void 0;
|
|
130
|
-
});
|
|
113
|
+
return fetch(url, { method: 'GET', mode: 'no-cors', keepalive: true, credentials: "include" }).then(() => f?.());
|
|
131
114
|
} else {
|
|
132
|
-
image = new Image();
|
|
133
|
-
checker = setInterval(
|
|
115
|
+
const image = new Image();
|
|
116
|
+
const checker = setInterval(() => {
|
|
134
117
|
if (image.complete) {
|
|
135
118
|
if (typeof f === 'function') {
|
|
136
119
|
f();
|
|
137
120
|
}
|
|
138
|
-
|
|
121
|
+
clearInterval(checker);
|
|
139
122
|
}
|
|
140
123
|
}, 10);
|
|
141
|
-
|
|
124
|
+
image.src = url;
|
|
142
125
|
}
|
|
143
126
|
};
|
|
144
127
|
|
|
145
128
|
export default {
|
|
146
|
-
sendInitial
|
|
147
|
-
sendMore
|
|
148
|
-
viewId
|
|
149
|
-
bumpViewId
|
|
129
|
+
sendInitial,
|
|
130
|
+
sendMore,
|
|
131
|
+
viewId,
|
|
132
|
+
bumpViewId,
|
|
150
133
|
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Exports for testing purposes.
|
|
137
|
+
*/
|
|
138
|
+
export const _testExports = {
|
|
139
|
+
generatePageViewId,
|
|
140
|
+
smartEncode,
|
|
141
|
+
isDefined,
|
|
142
|
+
buildQueryString,
|
|
143
|
+
};
|
package/assets/visibility.js
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
// Generated by CoffeeScript 2.7.0
|
|
2
1
|
export default {
|
|
3
|
-
state:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
changeEvent: document.visibilityState
|
|
12
|
-
? 'visibilitychange'
|
|
13
|
-
: document.webkitVisibilityState
|
|
14
|
-
? 'webkitvisibilitychange'
|
|
15
|
-
: document.mozVisibilityState
|
|
16
|
-
? 'mozvisibilitychange'
|
|
17
|
-
: document.msVisibilityState
|
|
18
|
-
? 'msvisibilitychange'
|
|
19
|
-
: void 0,
|
|
2
|
+
state: () =>
|
|
3
|
+
document.visibilityState ||
|
|
4
|
+
document.webkitVisibilityState ||
|
|
5
|
+
document.mozVisibilityState ||
|
|
6
|
+
document.msVisibilityState,
|
|
7
|
+
|
|
8
|
+
changeEvent: getChangeEvent(),
|
|
9
|
+
|
|
20
10
|
};
|
|
11
|
+
|
|
12
|
+
function getChangeEvent() {
|
|
13
|
+
if (document.visibilityState) {
|
|
14
|
+
return 'visibilitychange';
|
|
15
|
+
} else if (document.webkitVisibilityState) {
|
|
16
|
+
return 'webkitvisibilitychange';
|
|
17
|
+
} else if (document.mozVisibilityState) {
|
|
18
|
+
return 'mozvisibilitychange';
|
|
19
|
+
} else if (document.msVisibilityState) {
|
|
20
|
+
return 'msvisibilitychange';
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -10,7 +10,7 @@ $ npm install ophan-tracker-js
|
|
|
10
10
|
|
|
11
11
|
## Contribute
|
|
12
12
|
|
|
13
|
-
#### Proxy tracker-
|
|
13
|
+
#### Proxy tracker-js:
|
|
14
14
|
|
|
15
15
|
1. Install dependencies
|
|
16
16
|
|
|
@@ -19,28 +19,22 @@ $ npm install ophan-tracker-js
|
|
|
19
19
|
.../ophan/tracker-js $ npm install
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
2. In
|
|
23
|
-
|
|
24
|
-
```
|
|
25
|
-
.../ophan/tracker-js $ npm run coffee-to-esm:watch
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
3. In another terminal window:
|
|
22
|
+
2. In another terminal window:
|
|
29
23
|
|
|
30
24
|
```
|
|
31
25
|
.../ophan/tracker-js $ lighttpd -f lighttpd.conf -D
|
|
32
26
|
```
|
|
33
27
|
|
|
34
|
-
|
|
28
|
+
3. Install FoxyProxy (https://getfoxyproxy.org/downloads/) in your browser and create a rule that forces j.ophan.co.uk
|
|
35
29
|
to localhost 8000. (For me FoxyProxy needed a few switches on and off before it decided to start talking to lighttpd.)
|
|
36
30
|
|
|
37
|
-
|
|
31
|
+
4. In another terminal window, start a server that is [running the Guardian frontend](https://github.com/guardian/frontend/blob/main/docs/01-start-here/01-installation-steps.md)
|
|
38
32
|
|
|
39
|
-
|
|
33
|
+
5. Navigate to where you are running the Guardian locally (e.g. https://localhost:9000/uk)
|
|
40
34
|
|
|
41
|
-
|
|
35
|
+
6. Ensure FoxyProxy is enabled
|
|
42
36
|
|
|
43
|
-
|
|
37
|
+
7. Develop with pleasure
|
|
44
38
|
|
|
45
39
|
## Publish
|
|
46
40
|
|
|
@@ -62,8 +56,14 @@ We use `changesets` for automated publishing of the NPM package:
|
|
|
62
56
|
|
|
63
57
|
3. When the PR is merged, the [`changesets` github action](https://github.com/changesets/action) will create a new PR, [example here](https://github.com/guardian/ophan/pull/5626). On merging this generated PR, the action will publish to NPM. (This checks the subdirectory correctly.)
|
|
64
58
|
|
|
65
|
-
_Note:_ This seems like an unnecessary extra step and another PR to merge. This action is useful for multi-package repos, and packages with more frequent changes where it consolidates changes from multiple PR's into a single release. This therefore does not apply to us, but we've had issues publishing straight to NPM via the initial action on merge to main.
|
|
66
|
-
|
|
67
59
|
##### S3 Bucket
|
|
68
60
|
|
|
69
61
|
* [https://j.ophan.co.uk/](https://j.ophan.co.uk/jobs.js) - CDN/S3, [deployed through RiffRaff](https://riffraff.gutools.co.uk/deployment/history?projectName=ophan%3A%3Aophan-tracker-js&page=1) and used by some Guardian sites like https://jobs.theguardian.com/. An advantage of this approach is that sites using it immediately get Tracker JS updates, without developer intervention.
|
|
62
|
+
|
|
63
|
+
##### Rollup
|
|
64
|
+
|
|
65
|
+
The generated files from Rollup are only used for publishing to the CDN to use SystemJS / AMD
|
|
66
|
+
|
|
67
|
+
##### Example Usage
|
|
68
|
+
|
|
69
|
+
You can see example usage of the library on theguardian.com [here](https://github.com/guardian/dotcom-rendering/blob/main/dotcom-rendering/src/client/ophan/ophan.ts)
|