govuk_publishing_components 39.2.0 → 39.2.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.
- checksums.yaml +4 -4
- data/README.md +1 -2
- data/app/assets/javascripts/govuk_publishing_components/lib/govspeak/magna-charta.js +1 -1
- data/app/assets/javascripts/govuk_publishing_components/vendor/lux/lux-reporter.js +637 -328
- data/app/controllers/govuk_publishing_components/audit_controller.rb +16 -0
- data/app/views/govuk_publishing_components/audit/_applications.html.erb +3 -3
- data/app/views/govuk_publishing_components/components/_contents_list.html.erb +1 -1
- data/app/views/govuk_publishing_components/components/docs/contents_list.yml +1 -1
- data/lib/govuk_publishing_components/presenters/contents_list_helper.rb +14 -7
- data/lib/govuk_publishing_components/version.rb +1 -1
- metadata +2 -2
@@ -22,6 +22,242 @@
|
|
22
22
|
(function () {
|
23
23
|
'use strict';
|
24
24
|
|
25
|
+
function now() {
|
26
|
+
return Date.now ? Date.now() : +new Date();
|
27
|
+
}
|
28
|
+
|
29
|
+
var LogEvent = {
|
30
|
+
// Internal events
|
31
|
+
EvaluationStart: 1,
|
32
|
+
EvaluationEnd: 2,
|
33
|
+
InitCalled: 3,
|
34
|
+
MarkCalled: 4,
|
35
|
+
MeasureCalled: 5,
|
36
|
+
AddDataCalled: 6,
|
37
|
+
SendCalled: 7,
|
38
|
+
ForceSampleCalled: 8,
|
39
|
+
DataCollectionStart: 9,
|
40
|
+
UnloadHandlerTriggered: 10,
|
41
|
+
OnloadHandlerTriggered: 11,
|
42
|
+
MarkLoadTimeCalled: 12,
|
43
|
+
SendCancelledPageHidden: 13,
|
44
|
+
// Data collection events
|
45
|
+
SessionIsSampled: 21,
|
46
|
+
SessionIsNotSampled: 22,
|
47
|
+
MainBeaconSent: 23,
|
48
|
+
UserTimingBeaconSent: 24,
|
49
|
+
InteractionBeaconSent: 25,
|
50
|
+
CustomDataBeaconSent: 26,
|
51
|
+
// Metric information
|
52
|
+
NavigationStart: 41,
|
53
|
+
PerformanceEntryReceived: 42,
|
54
|
+
PerformanceEntryProcessed: 43,
|
55
|
+
// Errors
|
56
|
+
PerformanceObserverError: 51,
|
57
|
+
InputEventPermissionError: 52,
|
58
|
+
InnerHtmlAccessError: 53,
|
59
|
+
EventTargetAccessError: 54,
|
60
|
+
CookieReadError: 55,
|
61
|
+
CookieSetError: 56,
|
62
|
+
PageLabelEvaluationError: 57,
|
63
|
+
// Browser support messages
|
64
|
+
NavTimingNotSupported: 71,
|
65
|
+
PaintTimingNotSupported: 72,
|
66
|
+
// POST beacon events
|
67
|
+
PostBeaconInitialised: 80,
|
68
|
+
PostBeaconSendCalled: 81,
|
69
|
+
PostBeaconTimeoutReached: 82,
|
70
|
+
PostBeaconSent: 83,
|
71
|
+
PostBeaconAlreadySent: 84,
|
72
|
+
PostBeaconCancelled: 85,
|
73
|
+
PostBeaconStopRecording: 86,
|
74
|
+
PostBeaconMetricRejected: 87,
|
75
|
+
PostBeaconDisabled: 88,
|
76
|
+
PostBeaconSendFailed: 89,
|
77
|
+
};
|
78
|
+
var Logger = /** @class */ (function () {
|
79
|
+
function Logger() {
|
80
|
+
this.events = [];
|
81
|
+
}
|
82
|
+
Logger.prototype.logEvent = function (event, args) {
|
83
|
+
if (args === void 0) { args = []; }
|
84
|
+
this.events.push([now(), event, args]);
|
85
|
+
};
|
86
|
+
Logger.prototype.getEvents = function () {
|
87
|
+
return this.events;
|
88
|
+
};
|
89
|
+
return Logger;
|
90
|
+
}());
|
91
|
+
|
92
|
+
var START_MARK = "LUX_start";
|
93
|
+
var END_MARK = "LUX_end";
|
94
|
+
var BOOLEAN_TRUE = "true";
|
95
|
+
|
96
|
+
function floor(x) {
|
97
|
+
return Math.floor(x);
|
98
|
+
}
|
99
|
+
var max = Math.max;
|
100
|
+
var round = Math.round;
|
101
|
+
/**
|
102
|
+
* Clamp a number so that it is never less than 0
|
103
|
+
*/
|
104
|
+
function clamp(x) {
|
105
|
+
return max(0, x);
|
106
|
+
}
|
107
|
+
function sortNumeric(a, b) {
|
108
|
+
return a - b;
|
109
|
+
}
|
110
|
+
|
111
|
+
var scriptStartTime = now();
|
112
|
+
|
113
|
+
var _a;
|
114
|
+
// If the various performance APIs aren't available, we export an empty object to
|
115
|
+
// prevent having to make regular typeof checks.
|
116
|
+
var performance = window.performance || {};
|
117
|
+
var timing = performance.timing || {
|
118
|
+
activationStart: 0,
|
119
|
+
// If performance.timing isn't available, we attempt to polyfill the navigationStart value.
|
120
|
+
// Our first attempt is from LUX.ns, which is the time that the snippet execution began. If this
|
121
|
+
// is not available, we fall back to the time that the current script execution began.
|
122
|
+
navigationStart: ((_a = window.LUX) === null || _a === void 0 ? void 0 : _a.ns) || scriptStartTime,
|
123
|
+
};
|
124
|
+
function navigationType() {
|
125
|
+
if (performance.navigation && typeof performance.navigation.type !== "undefined") {
|
126
|
+
return performance.navigation.type;
|
127
|
+
}
|
128
|
+
return "";
|
129
|
+
}
|
130
|
+
function getNavigationEntry() {
|
131
|
+
var navEntries = getEntriesByType("navigation");
|
132
|
+
if (navEntries.length) {
|
133
|
+
var nativeEntry = navEntries[0];
|
134
|
+
var entry_1 = {
|
135
|
+
navigationStart: 0,
|
136
|
+
activationStart: 0,
|
137
|
+
};
|
138
|
+
for (var key in nativeEntry) {
|
139
|
+
entry_1[key] = nativeEntry[key];
|
140
|
+
}
|
141
|
+
return entry_1;
|
142
|
+
}
|
143
|
+
var navType = navigationType();
|
144
|
+
var entry = {
|
145
|
+
navigationStart: 0,
|
146
|
+
activationStart: 0,
|
147
|
+
startTime: 0,
|
148
|
+
type: navType == 2 ? "back_forward" : navType === 1 ? "reload" : "navigate",
|
149
|
+
};
|
150
|
+
if (true) {
|
151
|
+
for (var key in timing) {
|
152
|
+
if (typeof timing[key] === "number" && key !== "navigationStart") {
|
153
|
+
entry[key] = floor(timing[key] - timing.navigationStart);
|
154
|
+
}
|
155
|
+
}
|
156
|
+
}
|
157
|
+
return entry;
|
158
|
+
}
|
159
|
+
/**
|
160
|
+
* Simple wrapper around performance.getEntriesByType to provide fallbacks for
|
161
|
+
* legacy browsers, and work around edge cases where undefined is returned instead
|
162
|
+
* of an empty PerformanceEntryList.
|
163
|
+
*/
|
164
|
+
function getEntriesByType(type) {
|
165
|
+
if (typeof performance.getEntriesByType === "function") {
|
166
|
+
var entries = performance.getEntriesByType(type);
|
167
|
+
if (entries && entries.length) {
|
168
|
+
return entries;
|
169
|
+
}
|
170
|
+
}
|
171
|
+
return [];
|
172
|
+
}
|
173
|
+
/**
|
174
|
+
* Simple wrapper around performance.getEntriesByName to provide fallbacks for
|
175
|
+
* legacy browsers, and work around edge cases where undefined is returned instead
|
176
|
+
* of an empty PerformanceEntryList.
|
177
|
+
*/
|
178
|
+
function getEntriesByName(type) {
|
179
|
+
if (typeof performance.getEntriesByName === "function") {
|
180
|
+
var entries = performance.getEntriesByName(type);
|
181
|
+
if (entries && entries.length) {
|
182
|
+
return entries;
|
183
|
+
}
|
184
|
+
}
|
185
|
+
return [];
|
186
|
+
}
|
187
|
+
|
188
|
+
/**
|
189
|
+
* Milliseconds since navigationStart representing when the page was restored from the bfcache
|
190
|
+
*/
|
191
|
+
var pageRestoreTime;
|
192
|
+
function setPageRestoreTime(time) {
|
193
|
+
pageRestoreTime = time;
|
194
|
+
}
|
195
|
+
function getPageRestoreTime() {
|
196
|
+
return pageRestoreTime;
|
197
|
+
}
|
198
|
+
/**
|
199
|
+
* To measure the way a user experienced a metric, we measure metrics relative to the time the user
|
200
|
+
* started viewing the page. On prerendered pages, this is activationStart. On bfcache restores, this
|
201
|
+
* is the page restore time. On all other pages this value will be zero.
|
202
|
+
*/
|
203
|
+
function getZeroTime() {
|
204
|
+
var _a;
|
205
|
+
return max(getPageRestoreTime() || 0, getNavigationEntry().activationStart, ((_a = getEntriesByName(START_MARK).pop()) === null || _a === void 0 ? void 0 : _a.startTime) || 0);
|
206
|
+
}
|
207
|
+
/**
|
208
|
+
* Most time-based metrics that LUX reports should be relative to the "zero" marker, rounded down
|
209
|
+
* to the nearest unit so as not to report times in the future, and clamped to zero.
|
210
|
+
*/
|
211
|
+
function processTimeMetric(value) {
|
212
|
+
return clamp(floor(value - getZeroTime()));
|
213
|
+
}
|
214
|
+
/**
|
215
|
+
* Returns the number of milliseconds since navigationStart.
|
216
|
+
*/
|
217
|
+
function msSinceNavigationStart() {
|
218
|
+
if (performance.now) {
|
219
|
+
return floor(performance.now());
|
220
|
+
}
|
221
|
+
return now() - timing.navigationStart;
|
222
|
+
}
|
223
|
+
/**
|
224
|
+
* Returns the number of milliseconds since the current page was initialized. For SPAs, this is the
|
225
|
+
* time since the last LUX.init() call.
|
226
|
+
*/
|
227
|
+
function msSincePageInit() {
|
228
|
+
var sinceNavigationStart = msSinceNavigationStart();
|
229
|
+
var startMark = getEntriesByName(START_MARK).pop();
|
230
|
+
if (startMark) {
|
231
|
+
return floor(sinceNavigationStart - startMark.startTime);
|
232
|
+
}
|
233
|
+
return sinceNavigationStart;
|
234
|
+
}
|
235
|
+
|
236
|
+
function padStart(str, length, char) {
|
237
|
+
while (str.length < length) {
|
238
|
+
str = char + str;
|
239
|
+
}
|
240
|
+
return str;
|
241
|
+
}
|
242
|
+
|
243
|
+
var VERSION = "4.0.20";
|
244
|
+
/**
|
245
|
+
* Returns the version of the script as a float to be stored in legacy systems that do not support
|
246
|
+
* string versions.
|
247
|
+
*/
|
248
|
+
function versionAsFloat(ver) {
|
249
|
+
if (ver === void 0) { ver = VERSION; }
|
250
|
+
var parts = ver.split(".");
|
251
|
+
return parseFloat(parts[0] + "." + padStart(parts[1], 2, "0") + padStart(parts[2], 2, "0"));
|
252
|
+
}
|
253
|
+
|
254
|
+
var sendBeaconFallback = function (url, data) {
|
255
|
+
var xhr = new XMLHttpRequest();
|
256
|
+
xhr.open("POST", url, true);
|
257
|
+
xhr.setRequestHeader("content-type", "application/json");
|
258
|
+
xhr.send(String(data));
|
259
|
+
};
|
260
|
+
var sendBeacon = "sendBeacon" in navigator ? navigator.sendBeacon.bind(navigator) : sendBeaconFallback;
|
25
261
|
/**
|
26
262
|
* Fit an array of user timing delimited strings into a URL and return both the entries that fit and
|
27
263
|
* the remaining entries that didn't fit.
|
@@ -38,31 +274,162 @@
|
|
38
274
|
}
|
39
275
|
return [beaconUtValues, remainingUtValues];
|
40
276
|
}
|
277
|
+
var Beacon = /** @class */ (function () {
|
278
|
+
function Beacon(opts) {
|
279
|
+
var _this = this;
|
280
|
+
this.isRecording = true;
|
281
|
+
this.isSent = false;
|
282
|
+
this.maxMeasureTimeout = 0;
|
283
|
+
this.onBeforeSendCbs = [];
|
284
|
+
this.startTime = opts.startTime || getZeroTime();
|
285
|
+
this.config = opts.config;
|
286
|
+
this.logger = opts.logger;
|
287
|
+
this.customerId = opts.customerId;
|
288
|
+
this.sessionId = opts.sessionId;
|
289
|
+
this.pageId = opts.pageId;
|
290
|
+
this.metricData = {};
|
291
|
+
this.maxMeasureTimeout = window.setTimeout(function () {
|
292
|
+
_this.logger.logEvent(LogEvent.PostBeaconTimeoutReached);
|
293
|
+
_this.stopRecording();
|
294
|
+
_this.send();
|
295
|
+
}, this.config.maxMeasureTime);
|
296
|
+
this.logger.logEvent(LogEvent.PostBeaconInitialised);
|
297
|
+
}
|
298
|
+
Beacon.prototype.isBeingSampled = function () {
|
299
|
+
var bucket = parseInt(String(this.sessionId).slice(-2));
|
300
|
+
return bucket < this.config.samplerate;
|
301
|
+
};
|
302
|
+
Beacon.prototype.stopRecording = function () {
|
303
|
+
this.isRecording = false;
|
304
|
+
this.logger.logEvent(LogEvent.PostBeaconStopRecording);
|
305
|
+
};
|
306
|
+
Beacon.prototype.setMetricData = function (metric, data) {
|
307
|
+
if (!this.isRecording) {
|
308
|
+
this.logger.logEvent(LogEvent.PostBeaconMetricRejected, [metric]);
|
309
|
+
return;
|
310
|
+
}
|
311
|
+
this.metricData[metric] = data;
|
312
|
+
};
|
313
|
+
Beacon.prototype.hasMetricData = function () {
|
314
|
+
return Object.keys(this.metricData).length > 0;
|
315
|
+
};
|
316
|
+
Beacon.prototype.beaconUrl = function () {
|
317
|
+
return this.config.beaconUrlV2;
|
318
|
+
};
|
319
|
+
Beacon.prototype.onBeforeSend = function (cb) {
|
320
|
+
this.onBeforeSendCbs.push(cb);
|
321
|
+
};
|
322
|
+
Beacon.prototype.send = function () {
|
323
|
+
this.logger.logEvent(LogEvent.PostBeaconSendCalled);
|
324
|
+
if (!this.config.enablePostBeacon) {
|
325
|
+
this.logger.logEvent(LogEvent.PostBeaconDisabled);
|
326
|
+
return;
|
327
|
+
}
|
328
|
+
for (var _i = 0, _a = this.onBeforeSendCbs; _i < _a.length; _i++) {
|
329
|
+
var cb = _a[_i];
|
330
|
+
cb();
|
331
|
+
}
|
332
|
+
if (!this.isBeingSampled()) {
|
333
|
+
return;
|
334
|
+
}
|
335
|
+
if (!this.hasMetricData() && !this.config.allowEmptyPostBeacon) {
|
336
|
+
// TODO: This is only required while the new beacon is supplementary. Once it's the primary
|
337
|
+
// beacon, we should send it regardless of how much metric data it has.
|
338
|
+
this.logger.logEvent(LogEvent.PostBeaconCancelled);
|
339
|
+
return;
|
340
|
+
}
|
341
|
+
if (this.isSent) {
|
342
|
+
this.logger.logEvent(LogEvent.PostBeaconAlreadySent);
|
343
|
+
return;
|
344
|
+
}
|
345
|
+
// Only clear the max measure timeout if there's data to send.
|
346
|
+
clearTimeout(this.maxMeasureTimeout);
|
347
|
+
var beaconUrl = this.beaconUrl();
|
348
|
+
var payload = Object.assign({
|
349
|
+
customerId: this.customerId,
|
350
|
+
measureDuration: msSincePageInit(),
|
351
|
+
pageId: this.pageId,
|
352
|
+
scriptVersion: VERSION,
|
353
|
+
sessionId: this.sessionId,
|
354
|
+
startTime: this.startTime,
|
355
|
+
}, this.metricData);
|
356
|
+
try {
|
357
|
+
sendBeacon(beaconUrl, JSON.stringify(payload));
|
358
|
+
this.isSent = true;
|
359
|
+
this.logger.logEvent(LogEvent.PostBeaconSent, [beaconUrl, payload]);
|
360
|
+
}
|
361
|
+
catch (e) {
|
362
|
+
this.logger.logEvent(LogEvent.PostBeaconSendFailed, [e]);
|
363
|
+
}
|
364
|
+
};
|
365
|
+
return Beacon;
|
366
|
+
}());
|
367
|
+
|
368
|
+
// Wrapper to support older browsers (<= IE8)
|
369
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
370
|
+
function addListener(type, callback, useCapture) {
|
371
|
+
if (useCapture === void 0) { useCapture = false; }
|
372
|
+
if (addEventListener) {
|
373
|
+
addEventListener(type, callback, useCapture);
|
374
|
+
}
|
375
|
+
else if (window.attachEvent && true) {
|
376
|
+
window.attachEvent("on" + type, callback);
|
377
|
+
}
|
378
|
+
}
|
379
|
+
// Wrapper to support older browsers (<= IE8)
|
380
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
381
|
+
function removeListener(type, callback, useCapture) {
|
382
|
+
if (useCapture === void 0) { useCapture = false; }
|
383
|
+
if (removeEventListener) {
|
384
|
+
removeEventListener(type, callback, useCapture);
|
385
|
+
}
|
386
|
+
else if (window.detachEvent && true) {
|
387
|
+
window.detachEvent("on" + type, callback);
|
388
|
+
}
|
389
|
+
}
|
390
|
+
|
391
|
+
function onPageLoad(callback) {
|
392
|
+
if (document.readyState === "complete") {
|
393
|
+
// The onload event has already fired
|
394
|
+
callback();
|
395
|
+
}
|
396
|
+
else {
|
397
|
+
// Listen for the onload event and run the callback after a short delay
|
398
|
+
addListener("load", function () {
|
399
|
+
setTimeout(callback, 200);
|
400
|
+
});
|
401
|
+
}
|
402
|
+
}
|
41
403
|
|
42
404
|
var luxOrigin = "https://lux.speedcurve.com";
|
43
405
|
function fromObject(obj) {
|
44
406
|
var autoMode = getProperty(obj, "auto", true);
|
45
407
|
return {
|
408
|
+
allowEmptyPostBeacon: getProperty(obj, "allowEmptyPostBeacon", false),
|
46
409
|
auto: autoMode,
|
47
410
|
beaconUrl: getProperty(obj, "beaconUrl", luxOrigin + "/lux/"),
|
411
|
+
beaconUrlV2: getProperty(obj, "beaconUrlV2", "https://beacon.speedcurve.com/store"),
|
48
412
|
conversions: getProperty(obj, "conversions"),
|
49
413
|
cookieDomain: getProperty(obj, "cookieDomain"),
|
50
414
|
customerid: getProperty(obj, "customerid"),
|
415
|
+
enablePostBeacon: getProperty(obj, "enablePostBeacon", true),
|
51
416
|
errorBeaconUrl: getProperty(obj, "errorBeaconUrl", luxOrigin + "/error/"),
|
417
|
+
interactionBeaconDelay: getProperty(obj, "interactionBeaconDelay", 200),
|
52
418
|
jspagelabel: getProperty(obj, "jspagelabel"),
|
53
419
|
label: getProperty(obj, "label"),
|
54
420
|
maxBeaconUrlLength: getProperty(obj, "maxBeaconUrlLength", 8190),
|
55
421
|
maxBeaconUTEntries: getProperty(obj, "maxBeaconUTEntries", 20),
|
56
422
|
maxErrors: getProperty(obj, "maxErrors", 5),
|
57
423
|
maxMeasureTime: getProperty(obj, "maxMeasureTime", 60000),
|
424
|
+
measureUntil: getProperty(obj, "measureUntil", "onload"),
|
58
425
|
minMeasureTime: getProperty(obj, "minMeasureTime", 0),
|
59
426
|
newBeaconOnPageShow: getProperty(obj, "newBeaconOnPageShow", false),
|
427
|
+
pagegroups: getProperty(obj, "pagegroups"),
|
60
428
|
samplerate: getProperty(obj, "samplerate", 100),
|
61
429
|
sendBeaconOnPageHidden: getProperty(obj, "sendBeaconOnPageHidden", autoMode),
|
62
430
|
serverTiming: getProperty(obj, "serverTiming"),
|
63
431
|
trackErrors: getProperty(obj, "trackErrors", true),
|
64
432
|
trackHiddenPages: getProperty(obj, "trackHiddenPages", false),
|
65
|
-
pagegroups: getProperty(obj, "pagegroups"),
|
66
433
|
};
|
67
434
|
}
|
68
435
|
function getProperty(obj, key, defaultValue) {
|
@@ -72,9 +439,7 @@
|
|
72
439
|
return defaultValue;
|
73
440
|
}
|
74
441
|
|
75
|
-
var
|
76
|
-
var END_MARK = "LUX_end";
|
77
|
-
var BOOLEAN_TRUE = "true";
|
442
|
+
var SESSION_COOKIE_NAME = "lux_uid";
|
78
443
|
|
79
444
|
var customDataValues = {};
|
80
445
|
var updatedCustomData = {};
|
@@ -117,93 +482,6 @@
|
|
117
482
|
return encodeURIComponent(strings.join(","));
|
118
483
|
}
|
119
484
|
|
120
|
-
function floor(x) {
|
121
|
-
return Math.floor(x);
|
122
|
-
}
|
123
|
-
var max = Math.max;
|
124
|
-
var round = Math.round;
|
125
|
-
/**
|
126
|
-
* Clamp a number so that it is never less than 0
|
127
|
-
*/
|
128
|
-
function clamp(x) {
|
129
|
-
return max(0, x);
|
130
|
-
}
|
131
|
-
function sortNumeric(a, b) {
|
132
|
-
return a - b;
|
133
|
-
}
|
134
|
-
|
135
|
-
function now() {
|
136
|
-
return Date.now ? Date.now() : +new Date();
|
137
|
-
}
|
138
|
-
|
139
|
-
var scriptStartTime = now();
|
140
|
-
|
141
|
-
var _a;
|
142
|
-
// If the various performance APIs aren't available, we export an empty object to
|
143
|
-
// prevent having to make regular typeof checks.
|
144
|
-
var performance = window.performance || {};
|
145
|
-
var timing = performance.timing || {
|
146
|
-
// If performance.timing isn't available, we attempt to polyfill the navigationStart value.
|
147
|
-
// Our first attempt is from LUX.ns, which is the time that the snippet execution began. If this
|
148
|
-
// is not available, we fall back to the time that the current script execution began.
|
149
|
-
navigationStart: ((_a = window.LUX) === null || _a === void 0 ? void 0 : _a.ns) || scriptStartTime,
|
150
|
-
};
|
151
|
-
function msSinceNavigationStart() {
|
152
|
-
if (performance.now) {
|
153
|
-
return floor(performance.now());
|
154
|
-
}
|
155
|
-
return now() - timing.navigationStart;
|
156
|
-
}
|
157
|
-
function navigationType() {
|
158
|
-
if (performance.navigation && typeof performance.navigation.type !== "undefined") {
|
159
|
-
return performance.navigation.type;
|
160
|
-
}
|
161
|
-
return "";
|
162
|
-
}
|
163
|
-
function getNavigationEntry() {
|
164
|
-
var navEntries = getEntriesByType("navigation");
|
165
|
-
if (navEntries.length) {
|
166
|
-
var nativeEntry = navEntries[0];
|
167
|
-
var entry_1 = {
|
168
|
-
navigationStart: 0,
|
169
|
-
activationStart: 0,
|
170
|
-
};
|
171
|
-
for (var key in nativeEntry) {
|
172
|
-
entry_1[key] = nativeEntry[key];
|
173
|
-
}
|
174
|
-
return entry_1;
|
175
|
-
}
|
176
|
-
var navType = navigationType();
|
177
|
-
var entry = {
|
178
|
-
navigationStart: 0,
|
179
|
-
activationStart: 0,
|
180
|
-
startTime: 0,
|
181
|
-
type: navType == 2 ? "back_forward" : navType === 1 ? "reload" : "navigate",
|
182
|
-
};
|
183
|
-
if (true) {
|
184
|
-
for (var key in timing) {
|
185
|
-
if (typeof timing[key] === "number" && key !== "navigationStart") {
|
186
|
-
entry[key] = floor(timing[key] - timing.navigationStart);
|
187
|
-
}
|
188
|
-
}
|
189
|
-
}
|
190
|
-
return entry;
|
191
|
-
}
|
192
|
-
/**
|
193
|
-
* Simple wrapper around performance.getEntriesByType to provide fallbacks for
|
194
|
-
* legacy browsers, and work around edge cases where undefined is returned instead
|
195
|
-
* of an empty PerformanceEntryList.
|
196
|
-
*/
|
197
|
-
function getEntriesByType(type) {
|
198
|
-
if (typeof performance.getEntriesByType === "function") {
|
199
|
-
var entries = performance.getEntriesByType(type);
|
200
|
-
if (entries && entries.length) {
|
201
|
-
return entries;
|
202
|
-
}
|
203
|
-
}
|
204
|
-
return [];
|
205
|
-
}
|
206
|
-
|
207
485
|
function isVisible() {
|
208
486
|
if (document.visibilityState) {
|
209
487
|
return document.visibilityState === "visible";
|
@@ -264,37 +542,40 @@
|
|
264
542
|
}
|
265
543
|
var MAX_SELECTOR_LENGTH = 100;
|
266
544
|
function getNodeSelector(node, selector) {
|
267
|
-
if (selector === void 0) {
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
var selectorWithParent = getNodeSelector(el.parentNode, currentSelector);
|
292
|
-
if (selectorWithParent.length < MAX_SELECTOR_LENGTH) {
|
293
|
-
return selectorWithParent;
|
545
|
+
if (selector === void 0) { selector = ""; }
|
546
|
+
try {
|
547
|
+
if (selector &&
|
548
|
+
(node.nodeType === 9 || selector.length > MAX_SELECTOR_LENGTH || !node.parentNode)) {
|
549
|
+
// Final selector.
|
550
|
+
return selector;
|
551
|
+
}
|
552
|
+
var el = node;
|
553
|
+
// Our first preference is to use the data-sctrack attribute from anywhere in the tree
|
554
|
+
var trackId = getClosestScTrackAttribute(el);
|
555
|
+
if (trackId) {
|
556
|
+
return trackId;
|
557
|
+
}
|
558
|
+
if (el.id) {
|
559
|
+
// Once we've found an element with ID we return the selector.
|
560
|
+
return "#" + el.id + (selector ? ">" + selector : "");
|
561
|
+
}
|
562
|
+
else if (el) {
|
563
|
+
// Otherwise attempt to get parent elements recursively
|
564
|
+
var name_1 = el.nodeType === 1 ? el.nodeName.toLowerCase() : el.nodeName.toUpperCase();
|
565
|
+
var classes = el.className ? "." + el.className.replace(/\s+/g, ".") : "";
|
566
|
+
// Remove classes until the selector is short enough
|
567
|
+
while ((name_1 + classes).length > MAX_SELECTOR_LENGTH) {
|
568
|
+
classes = classes.split(".").slice(0, -1).join(".");
|
294
569
|
}
|
570
|
+
var currentSelector = name_1 + classes + (selector ? ">" + selector : "");
|
571
|
+
if (el.parentNode) {
|
572
|
+
var selectorWithParent = getNodeSelector(el.parentNode, currentSelector);
|
573
|
+
if (selectorWithParent.length < MAX_SELECTOR_LENGTH) {
|
574
|
+
return selectorWithParent;
|
575
|
+
}
|
576
|
+
}
|
577
|
+
return currentSelector;
|
295
578
|
}
|
296
|
-
return currentSelector;
|
297
|
-
}
|
298
579
|
}
|
299
580
|
catch (error) {
|
300
581
|
// Do nothing.
|
@@ -320,72 +601,40 @@
|
|
320
601
|
return flags | flag;
|
321
602
|
}
|
322
603
|
|
323
|
-
var LogEvent = {
|
324
|
-
// Internal events
|
325
|
-
EvaluationStart: 1,
|
326
|
-
EvaluationEnd: 2,
|
327
|
-
InitCalled: 3,
|
328
|
-
MarkCalled: 4,
|
329
|
-
MeasureCalled: 5,
|
330
|
-
AddDataCalled: 6,
|
331
|
-
SendCalled: 7,
|
332
|
-
ForceSampleCalled: 8,
|
333
|
-
DataCollectionStart: 9,
|
334
|
-
UnloadHandlerTriggered: 10,
|
335
|
-
OnloadHandlerTriggered: 11,
|
336
|
-
MarkLoadTimeCalled: 12,
|
337
|
-
SendCancelledPageHidden: 13,
|
338
|
-
// Data collection events
|
339
|
-
SessionIsSampled: 21,
|
340
|
-
SessionIsNotSampled: 22,
|
341
|
-
MainBeaconSent: 23,
|
342
|
-
UserTimingBeaconSent: 24,
|
343
|
-
InteractionBeaconSent: 25,
|
344
|
-
CustomDataBeaconSent: 26,
|
345
|
-
// Metric information
|
346
|
-
NavigationStart: 41,
|
347
|
-
PerformanceEntryReceived: 42,
|
348
|
-
PerformanceEntryProcessed: 43,
|
349
|
-
// Errors
|
350
|
-
PerformanceObserverError: 51,
|
351
|
-
InputEventPermissionError: 52,
|
352
|
-
InnerHtmlAccessError: 53,
|
353
|
-
EventTargetAccessError: 54,
|
354
|
-
CookieReadError: 55,
|
355
|
-
CookieSetError: 56,
|
356
|
-
PageLabelEvaluationError: 57,
|
357
|
-
// Browser support messages
|
358
|
-
NavTimingNotSupported: 71,
|
359
|
-
PaintTimingNotSupported: 72,
|
360
|
-
};
|
361
|
-
var Logger = /** @class */ (function () {
|
362
|
-
function Logger() {
|
363
|
-
this.events = [];
|
364
|
-
}
|
365
|
-
Logger.prototype.logEvent = function (event, args) {
|
366
|
-
if (args === void 0) { args = []; }
|
367
|
-
this.events.push([now(), event, args]);
|
368
|
-
};
|
369
|
-
Logger.prototype.getEvents = function () {
|
370
|
-
return this.events;
|
371
|
-
};
|
372
|
-
return Logger;
|
373
|
-
})();
|
374
|
-
|
375
604
|
var sessionValue = 0;
|
376
605
|
var sessionEntries = [];
|
606
|
+
var sessionAttributions = [];
|
607
|
+
var largestEntry;
|
377
608
|
var maximumSessionValue = 0;
|
378
|
-
function
|
609
|
+
function processEntry$2(entry) {
|
379
610
|
if (!entry.hadRecentInput) {
|
380
611
|
var firstEntry = sessionEntries[0];
|
381
612
|
var latestEntry = sessionEntries[sessionEntries.length - 1];
|
382
|
-
|
383
|
-
|
384
|
-
|
613
|
+
var sources = entry.sources
|
614
|
+
? entry.sources
|
615
|
+
.filter(function (source) { return source.node; })
|
616
|
+
.map(function (source) { return ({
|
617
|
+
value: entry.value,
|
618
|
+
startTime: processTimeMetric(entry.startTime),
|
619
|
+
elementSelector: getNodeSelector(source.node),
|
620
|
+
elementType: source.node.nodeName,
|
621
|
+
}); })
|
622
|
+
: [];
|
623
|
+
if (sessionEntries.length &&
|
624
|
+
(entry.startTime - latestEntry.startTime >= 1000 ||
|
625
|
+
entry.startTime - firstEntry.startTime >= 5000)) {
|
626
|
+
sessionValue = entry.value;
|
627
|
+
sessionEntries = [entry];
|
628
|
+
sessionAttributions = sources;
|
629
|
+
largestEntry = entry;
|
385
630
|
}
|
386
631
|
else {
|
387
632
|
sessionValue += entry.value;
|
388
633
|
sessionEntries.push(entry);
|
634
|
+
sessionAttributions = sessionAttributions.concat(sources);
|
635
|
+
if (!largestEntry || entry.value > largestEntry.value) {
|
636
|
+
largestEntry = entry;
|
637
|
+
}
|
389
638
|
}
|
390
639
|
maximumSessionValue = max(maximumSessionValue, sessionValue);
|
391
640
|
}
|
@@ -394,9 +643,20 @@
|
|
394
643
|
sessionValue = 0;
|
395
644
|
sessionEntries = [];
|
396
645
|
maximumSessionValue = 0;
|
646
|
+
largestEntry = undefined;
|
397
647
|
}
|
398
|
-
function
|
399
|
-
return
|
648
|
+
function getData$2() {
|
649
|
+
return {
|
650
|
+
value: maximumSessionValue,
|
651
|
+
startTime: sessionEntries[0] ? processTimeMetric(sessionEntries[0].startTime) : null,
|
652
|
+
largestEntry: largestEntry
|
653
|
+
? {
|
654
|
+
value: largestEntry.value,
|
655
|
+
startTime: processTimeMetric(largestEntry.startTime),
|
656
|
+
}
|
657
|
+
: null,
|
658
|
+
sources: sessionAttributions.length ? sessionAttributions : null,
|
659
|
+
};
|
400
660
|
}
|
401
661
|
|
402
662
|
/**
|
@@ -416,23 +676,27 @@
|
|
416
676
|
slowestEntries = [];
|
417
677
|
slowestEntriesMap = {};
|
418
678
|
}
|
419
|
-
function
|
679
|
+
function processEntry$1(entry) {
|
420
680
|
if (entry.interactionId || (entry.entryType === "first-input" && !entryExists(entry))) {
|
421
|
-
var duration = entry.duration,
|
422
|
-
|
423
|
-
interactionId = entry.interactionId,
|
424
|
-
processingStart = entry.processingStart,
|
425
|
-
processingEnd = entry.processingEnd,
|
426
|
-
target = entry.target;
|
681
|
+
var duration = entry.duration, startTime = entry.startTime, interactionId = entry.interactionId, name_1 = entry.name, processingStart = entry.processingStart, processingEnd = entry.processingEnd, target = entry.target;
|
682
|
+
var processingTime = processingEnd - processingStart;
|
427
683
|
var existingEntry = slowestEntriesMap[interactionId];
|
428
684
|
var selector = target ? getNodeSelector(target) : null;
|
429
685
|
if (existingEntry) {
|
430
|
-
|
686
|
+
var longerDuration = duration > existingEntry.duration;
|
687
|
+
var sameWithLongerProcessingTime = duration === existingEntry.duration && processingTime > existingEntry.processingTime;
|
688
|
+
if (longerDuration || sameWithLongerProcessingTime) {
|
689
|
+
// Only replace an existing interation if the duration is longer, or if the duration is the
|
690
|
+
// same but the processing time is longer. The logic around this is that the interaction with
|
691
|
+
// longer processing time is likely to be the event that actually had a handler.
|
431
692
|
existingEntry.duration = duration;
|
432
|
-
existingEntry.
|
433
|
-
existingEntry.processingStart = processingStart;
|
693
|
+
existingEntry.name = name_1;
|
434
694
|
existingEntry.processingEnd = processingEnd;
|
695
|
+
existingEntry.processingStart = processingStart;
|
696
|
+
existingEntry.processingTime = processingTime;
|
435
697
|
existingEntry.selector = selector;
|
698
|
+
existingEntry.startTime = startTime;
|
699
|
+
existingEntry.target = target;
|
436
700
|
}
|
437
701
|
}
|
438
702
|
else {
|
@@ -440,10 +704,13 @@
|
|
440
704
|
slowestEntriesMap[interactionId] = {
|
441
705
|
duration: duration,
|
442
706
|
interactionId: interactionId,
|
443
|
-
|
444
|
-
processingStart: processingStart,
|
707
|
+
name: name_1,
|
445
708
|
processingEnd: processingEnd,
|
709
|
+
processingStart: processingStart,
|
710
|
+
processingTime: processingTime,
|
446
711
|
selector: selector,
|
712
|
+
startTime: startTime,
|
713
|
+
target: target,
|
447
714
|
};
|
448
715
|
slowestEntries.push(slowestEntriesMap[interactionId]);
|
449
716
|
}
|
@@ -465,6 +732,29 @@
|
|
465
732
|
var index = Math.min(slowestEntries.length - 1, Math.floor(getInteractionCount() / 50));
|
466
733
|
return slowestEntries[index];
|
467
734
|
}
|
735
|
+
function getData$1() {
|
736
|
+
var _a;
|
737
|
+
var interaction = getHighPercentileInteraction();
|
738
|
+
if (!interaction) {
|
739
|
+
return undefined;
|
740
|
+
}
|
741
|
+
return {
|
742
|
+
value: interaction.duration,
|
743
|
+
startTime: processTimeMetric(interaction.startTime),
|
744
|
+
subParts: {
|
745
|
+
inputDelay: clamp(floor(interaction.processingStart - interaction.startTime)),
|
746
|
+
processingTime: clamp(floor(interaction.processingTime)),
|
747
|
+
presentationDelay: clamp(floor(interaction.startTime + interaction.duration - interaction.processingEnd)),
|
748
|
+
},
|
749
|
+
attribution: interaction.selector
|
750
|
+
? {
|
751
|
+
elementSelector: interaction.selector,
|
752
|
+
elementType: ((_a = interaction.target) === null || _a === void 0 ? void 0 : _a.nodeName) || "",
|
753
|
+
eventType: interaction.name,
|
754
|
+
}
|
755
|
+
: null,
|
756
|
+
};
|
757
|
+
}
|
468
758
|
function getInteractionCount() {
|
469
759
|
if ("interactionCount" in performance) {
|
470
760
|
return performance.interactionCount;
|
@@ -472,6 +762,47 @@
|
|
472
762
|
return interactionCountEstimate;
|
473
763
|
}
|
474
764
|
|
765
|
+
var lcpEntry;
|
766
|
+
function processEntry(entry) {
|
767
|
+
if (!lcpEntry || entry.startTime > lcpEntry.startTime) {
|
768
|
+
lcpEntry = entry;
|
769
|
+
}
|
770
|
+
}
|
771
|
+
function getData() {
|
772
|
+
if (!lcpEntry) {
|
773
|
+
return undefined;
|
774
|
+
}
|
775
|
+
var subParts = null;
|
776
|
+
if (lcpEntry.url) {
|
777
|
+
var lcpResource = getEntriesByType("resource").find(function (resource) { return resource.name === lcpEntry.url; });
|
778
|
+
if (lcpResource) {
|
779
|
+
var navEntry = getNavigationEntry();
|
780
|
+
var responseStart = navEntry.responseStart || timing.responseStart;
|
781
|
+
var activationStart = navEntry.activationStart;
|
782
|
+
var ttfb = max(0, responseStart - activationStart);
|
783
|
+
var lcpStartTime = lcpResource.startTime;
|
784
|
+
var lcpRequestStart = (lcpResource.requestStart || lcpStartTime) - activationStart;
|
785
|
+
var lcpResponseEnd = max(lcpRequestStart, lcpResource.responseEnd - activationStart);
|
786
|
+
var lcpRenderTime = max(lcpResponseEnd, lcpStartTime - activationStart);
|
787
|
+
subParts = {
|
788
|
+
resourceLoadDelay: clamp(floor(lcpRequestStart - ttfb)),
|
789
|
+
resourceLoadTime: clamp(floor(lcpResponseEnd - lcpRequestStart)),
|
790
|
+
elementRenderDelay: clamp(floor(lcpRenderTime - lcpResponseEnd)),
|
791
|
+
};
|
792
|
+
}
|
793
|
+
}
|
794
|
+
return {
|
795
|
+
value: processTimeMetric(lcpEntry.startTime),
|
796
|
+
subParts: subParts,
|
797
|
+
attribution: lcpEntry.element
|
798
|
+
? {
|
799
|
+
elementSelector: getNodeSelector(lcpEntry.element),
|
800
|
+
elementType: lcpEntry.element.nodeName,
|
801
|
+
}
|
802
|
+
: null,
|
803
|
+
};
|
804
|
+
}
|
805
|
+
|
475
806
|
var ALL_ENTRIES = [];
|
476
807
|
function observe(type, callback, options) {
|
477
808
|
if (typeof PerformanceObserver === "function" &&
|
@@ -577,10 +908,9 @@
|
|
577
908
|
// -------------------------------------------------------------------------
|
578
909
|
/// End
|
579
910
|
// -------------------------------------------------------------------------
|
580
|
-
var SCRIPT_VERSION = "314";
|
581
911
|
var logger = new Logger();
|
582
912
|
var globalConfig = fromObject(LUX);
|
583
|
-
logger.logEvent(LogEvent.EvaluationStart, [
|
913
|
+
logger.logEvent(LogEvent.EvaluationStart, [VERSION, JSON.stringify(globalConfig)]);
|
584
914
|
// Variable aliases that allow the minifier to reduce file size.
|
585
915
|
var document = window.document;
|
586
916
|
var addEventListener = window.addEventListener;
|
@@ -605,7 +935,7 @@
|
|
605
935
|
new Image().src =
|
606
936
|
globalConfig.errorBeaconUrl +
|
607
937
|
"?v=" +
|
608
|
-
|
938
|
+
versionAsFloat() +
|
609
939
|
"&id=" +
|
610
940
|
getCustomerId() +
|
611
941
|
"&fn=" +
|
@@ -627,6 +957,30 @@
|
|
627
957
|
}
|
628
958
|
}
|
629
959
|
addEventListener("error", errorHandler);
|
960
|
+
// Bitmask of flags for this session & page
|
961
|
+
var gFlags = 0;
|
962
|
+
var gaMarks = [];
|
963
|
+
var gaMeasures = [];
|
964
|
+
var ghIx = {}; // hash for Interaction Metrics (scroll, click, keyboard)
|
965
|
+
var gbLuxSent = 0; // have we sent the LUX data? (avoid sending twice in unload)
|
966
|
+
var gbNavSent = 0; // have we sent the Nav Timing beacon yet? (avoid sending twice for SPA)
|
967
|
+
var gbIxSent = 0; // have we sent the IX data? (avoid sending twice for SPA)
|
968
|
+
var gbFirstPV = 1; // this is the first page view (vs. a SPA "soft nav")
|
969
|
+
var gSessionTimeout = 30 * 60; // number of seconds after which we consider a session to have "timed out" (used for calculating bouncerate)
|
970
|
+
var gSyncId = createSyncId(); // if we send multiple beacons, use this to sync them (eg, LUX & IX) (also called "luxid")
|
971
|
+
var gUid = refreshUniqueId(gSyncId); // cookie for this session ("Unique ID")
|
972
|
+
var gCustomDataTimeout; // setTimeout timer for sending a Custom data beacon after onload
|
973
|
+
var gMaxMeasureTimeout; // setTimeout timer for sending the beacon after a maximum measurement time
|
974
|
+
var initPostBeacon = function () {
|
975
|
+
return new Beacon({
|
976
|
+
config: globalConfig,
|
977
|
+
logger: logger,
|
978
|
+
customerId: getCustomerId(),
|
979
|
+
sessionId: gUid,
|
980
|
+
pageId: gSyncId,
|
981
|
+
});
|
982
|
+
};
|
983
|
+
var beacon = initPostBeacon();
|
630
984
|
var logEntry = function (entry) {
|
631
985
|
logger.logEvent(LogEvent.PerformanceEntryReceived, [entry]);
|
632
986
|
};
|
@@ -635,74 +989,67 @@
|
|
635
989
|
addEntry(entry);
|
636
990
|
logEntry(entry);
|
637
991
|
};
|
638
|
-
// Before long tasks were buffered, we added a PerformanceObserver to the lux.js snippet to capture
|
639
|
-
// any long tasks that occurred before the full script was loaded. To deal with this, we process
|
640
|
-
// all of the snippet long tasks, and we check for double-ups in the new PerformanceObserver.
|
641
|
-
var snippetLongTasks = typeof window.LUX_al === "object" ? window.LUX_al : [];
|
642
|
-
snippetLongTasks.forEach(processAndLogEntry);
|
643
992
|
try {
|
644
|
-
observe("longtask",
|
645
|
-
if (ALL_ENTRIES.indexOf(entry) === -1) {
|
646
|
-
processAndLogEntry(entry);
|
647
|
-
}
|
648
|
-
});
|
993
|
+
observe("longtask", processAndLogEntry);
|
649
994
|
observe("largest-contentful-paint", processAndLogEntry);
|
650
995
|
observe("element", processAndLogEntry);
|
651
996
|
observe("paint", processAndLogEntry);
|
997
|
+
observe("largest-contentful-paint", function (entry) {
|
998
|
+
// Process the LCP entry for the legacy beacon
|
999
|
+
processAndLogEntry(entry);
|
1000
|
+
// Process the LCP entry for the new beacon
|
1001
|
+
processEntry(entry);
|
1002
|
+
beacon.setMetricData("lcp", getData());
|
1003
|
+
});
|
652
1004
|
observe("layout-shift", function (entry) {
|
653
|
-
|
1005
|
+
processEntry$2(entry);
|
1006
|
+
beacon.setMetricData("cls", getData$2());
|
654
1007
|
logEntry(entry);
|
655
1008
|
});
|
1009
|
+
var handleINPEntry_1 = function (entry) {
|
1010
|
+
processEntry$1(entry);
|
1011
|
+
var data = getData$1();
|
1012
|
+
if (data) {
|
1013
|
+
beacon.setMetricData("inp", data);
|
1014
|
+
}
|
1015
|
+
};
|
656
1016
|
observe("first-input", function (entry) {
|
1017
|
+
logEntry(entry);
|
657
1018
|
var entryTime = entry.processingStart - entry.startTime;
|
658
1019
|
if (!gFirstInputDelay || gFirstInputDelay < entryTime) {
|
659
1020
|
gFirstInputDelay = floor(entryTime);
|
660
1021
|
}
|
661
1022
|
// Allow first-input events to be considered for INP
|
662
|
-
|
1023
|
+
handleINPEntry_1(entry);
|
663
1024
|
});
|
664
|
-
// TODO:
|
1025
|
+
// TODO: Set durationThreshold to 40 once performance.interactionCount is widely supported.
|
665
1026
|
// Right now we have to count every event to get the total interaction count so that we can
|
666
1027
|
// estimate a high percentile value for INP.
|
667
|
-
observe("event",
|
1028
|
+
observe("event", function (entry) {
|
1029
|
+
handleINPEntry_1(entry);
|
1030
|
+
// It's useful to log the interactionId, but it is not serialised by default. Annoyingly, we
|
1031
|
+
// need to manually serialize our own object with the keys we want.
|
1032
|
+
logEntry({
|
1033
|
+
interactionId: entry.interactionId,
|
1034
|
+
name: entry.name,
|
1035
|
+
entryType: entry.entryType,
|
1036
|
+
startTime: entry.startTime,
|
1037
|
+
duration: entry.duration,
|
1038
|
+
processingStart: entry.processingStart,
|
1039
|
+
processingEnd: entry.processingEnd,
|
1040
|
+
});
|
1041
|
+
}, { durationThreshold: 0 });
|
668
1042
|
}
|
669
1043
|
catch (e) {
|
670
1044
|
logger.logEvent(LogEvent.PerformanceObserverError, [e]);
|
671
1045
|
}
|
672
|
-
// Bitmask of flags for this session & page
|
673
|
-
var gFlags = 0;
|
674
|
-
var gaMarks = [];
|
675
|
-
var gaMeasures = [];
|
676
|
-
var ghIx = {}; // hash for Interaction Metrics (scroll, click, keyboard)
|
677
|
-
var gbLuxSent = 0; // have we sent the LUX data? (avoid sending twice in unload)
|
678
|
-
var gbNavSent = 0; // have we sent the Nav Timing beacon yet? (avoid sending twice for SPA)
|
679
|
-
var gbIxSent = 0; // have we sent the IX data? (avoid sending twice for SPA)
|
680
|
-
var gbFirstPV = 1; // this is the first page view (vs. a SPA "soft nav")
|
681
|
-
var gSessionTimeout = 30 * 60; // number of seconds after which we consider a session to have "timed out" (used for calculating bouncerate)
|
682
|
-
var gSyncId = createSyncId(); // if we send multiple beacons, use this to sync them (eg, LUX & IX) (also called "luxid")
|
683
|
-
var gUid = refreshUniqueId(gSyncId); // cookie for this session ("Unique ID")
|
684
|
-
var gCustomDataTimeout; // setTimeout timer for sending a Custom data beacon after onload
|
685
|
-
var gMaxMeasureTimeout; // setTimeout timer for sending the beacon after a maximum measurement time
|
686
|
-
var pageRestoreTime; // ms since navigationStart representing when the page was restored from the bfcache
|
687
|
-
/**
|
688
|
-
* To measure the way a user experienced a metric, we measure metrics relative to the time the user
|
689
|
-
* started viewing the page. On prerendered pages, this is activationStart. On bfcache restores, this
|
690
|
-
* is the page restore time. On all other pages this value will be zero.
|
691
|
-
*/
|
692
|
-
var getZeroTime = function () {
|
693
|
-
var _a;
|
694
|
-
return max(pageRestoreTime || 0, getNavigationEntry().activationStart, ((_a = _getMark(START_MARK)) === null || _a === void 0 ? void 0 : _a.startTime) || 0);
|
695
|
-
};
|
696
|
-
/**
|
697
|
-
* Most time-based metrics that LUX reports should be relative to the "zero" marker, rounded down
|
698
|
-
* to the nearest unit so as not to report times in the future, and clamped to zero.
|
699
|
-
*/
|
700
|
-
var processTimeMetric = function (value) { return clamp(floor(value - getZeroTime())); };
|
701
1046
|
/**
|
702
1047
|
* Some values should only be reported if they are non-zero. The exception to this is when the page
|
703
1048
|
* was prerendered or restored from BF cache
|
704
1049
|
*/
|
705
|
-
var shouldReportValue = function (value) {
|
1050
|
+
var shouldReportValue = function (value) {
|
1051
|
+
return value > 0 || getPageRestoreTime() || wasPrerendered();
|
1052
|
+
};
|
706
1053
|
if (_sample()) {
|
707
1054
|
logger.logEvent(LogEvent.SessionIsSampled, [globalConfig.samplerate]);
|
708
1055
|
}
|
@@ -764,7 +1111,7 @@
|
|
764
1111
|
return;
|
765
1112
|
}
|
766
1113
|
if (bCancelable) {
|
767
|
-
var now_1 =
|
1114
|
+
var now_1 = msSinceNavigationStart();
|
768
1115
|
var eventTimeStamp = evt.timeStamp;
|
769
1116
|
if (eventTimeStamp > 1520000000) {
|
770
1117
|
// If the event timeStamp is an epoch time instead of a time relative to NavigationStart,
|
@@ -791,23 +1138,6 @@
|
|
791
1138
|
addEventListener(eventType, onInput, ghListenerOptions);
|
792
1139
|
});
|
793
1140
|
////////////////////// FID END
|
794
|
-
/**
|
795
|
-
* Returns the time elapsed (in ms) since navigationStart. For SPAs, returns
|
796
|
-
* the time elapsed since the last LUX.init call.
|
797
|
-
*
|
798
|
-
* When `absolute = true` the time is always relative to navigationStart, even
|
799
|
-
* in SPAs.
|
800
|
-
*/
|
801
|
-
function _now(absolute) {
|
802
|
-
var sinceNavigationStart = msSinceNavigationStart();
|
803
|
-
var startMark = _getMark(START_MARK);
|
804
|
-
// For SPA page views, we use our internal mark as a reference point
|
805
|
-
if (startMark && !absolute) {
|
806
|
-
return floor(sinceNavigationStart - startMark.startTime);
|
807
|
-
}
|
808
|
-
// For "regular" page views, we can use performance.now() if it's available...
|
809
|
-
return sinceNavigationStart;
|
810
|
-
}
|
811
1141
|
// This is a wrapper around performance.mark that falls back to a polyfill when the User Timing
|
812
1142
|
// API isn't supported.
|
813
1143
|
function _mark() {
|
@@ -825,7 +1155,7 @@
|
|
825
1155
|
if (true) {
|
826
1156
|
var name_1 = args[0];
|
827
1157
|
var detail = ((_a = args[1]) === null || _a === void 0 ? void 0 : _a.detail) || null;
|
828
|
-
var startTime = ((_b = args[1]) === null || _b === void 0 ? void 0 : _b.startTime) ||
|
1158
|
+
var startTime = ((_b = args[1]) === null || _b === void 0 ? void 0 : _b.startTime) || msSincePageInit();
|
829
1159
|
var entry = {
|
830
1160
|
entryType: "mark",
|
831
1161
|
duration: 0,
|
@@ -888,7 +1218,7 @@
|
|
888
1218
|
if (true) {
|
889
1219
|
var navEntry = getNavigationEntry();
|
890
1220
|
var startTime = typeof startMarkName === "number" ? startMarkName : 0;
|
891
|
-
var endTime = typeof endMarkName === "number" ? endMarkName :
|
1221
|
+
var endTime = typeof endMarkName === "number" ? endMarkName : msSincePageInit();
|
892
1222
|
var throwError = function (missingMark) {
|
893
1223
|
throw new DOMException("Failed to execute 'measure' on 'Performance': The mark '" +
|
894
1224
|
missingMark +
|
@@ -1132,13 +1462,12 @@
|
|
1132
1462
|
var median = arrayMedian(aValues);
|
1133
1463
|
return { count: count, median: median, max: max, fci: fci };
|
1134
1464
|
}
|
1135
|
-
function getCLS
|
1465
|
+
function getCLS() {
|
1136
1466
|
if (!("LayoutShift" in self)) {
|
1137
1467
|
return undefined;
|
1138
1468
|
}
|
1139
|
-
|
1140
|
-
|
1141
|
-
return getCLS().toFixed(6);
|
1469
|
+
var clsData = getData$2();
|
1470
|
+
return clsData.value.toFixed(6);
|
1142
1471
|
}
|
1143
1472
|
// Return the median value from an array of integers.
|
1144
1473
|
function arrayMedian(aValues) {
|
@@ -1276,6 +1605,7 @@
|
|
1276
1605
|
reset();
|
1277
1606
|
nErrors = 0;
|
1278
1607
|
gFirstInputDelay = undefined;
|
1608
|
+
beacon = initPostBeacon();
|
1279
1609
|
// Clear flags then set the flag that init was called (ie, this is a SPA).
|
1280
1610
|
if (clearFlags) {
|
1281
1611
|
gFlags = 0;
|
@@ -1298,10 +1628,13 @@
|
|
1298
1628
|
var num = 0;
|
1299
1629
|
for (var i = 0, len = aElems.length; i < len; i++) {
|
1300
1630
|
var e = aElems[i];
|
1301
|
-
if (e.src &&
|
1302
|
-
|
1303
|
-
|
1304
|
-
|
1631
|
+
if (e.src &&
|
1632
|
+
!e.async &&
|
1633
|
+
!e.defer &&
|
1634
|
+
0 !== (e.compareDocumentPosition(lastViewportElem) & 4)) {
|
1635
|
+
// If the script has a SRC and async is false and it occurs BEFORE the last viewport element,
|
1636
|
+
// then increment the counter.
|
1637
|
+
num++;
|
1305
1638
|
}
|
1306
1639
|
}
|
1307
1640
|
return num;
|
@@ -1318,7 +1651,7 @@
|
|
1318
1651
|
e.onloadcssdefined ||
|
1319
1652
|
"print" === e.media ||
|
1320
1653
|
"style" === e.as ||
|
1321
|
-
(typeof e.onload === "function" && e.media === "all"));
|
1654
|
+
(typeof e.onload === "function" && e.media === "all")) ;
|
1322
1655
|
else {
|
1323
1656
|
nBlocking++;
|
1324
1657
|
}
|
@@ -1384,9 +1717,9 @@
|
|
1384
1717
|
var ns = timing.navigationStart;
|
1385
1718
|
var startMark = _getMark(START_MARK);
|
1386
1719
|
var endMark = _getMark(END_MARK);
|
1387
|
-
if (startMark && endMark && !
|
1720
|
+
if (startMark && endMark && !getPageRestoreTime()) {
|
1388
1721
|
// This is a SPA page view, so send the SPA marks & measures instead of Nav Timing.
|
1389
|
-
// Note:
|
1722
|
+
// Note: getPageRestoreTime() indicates this was a bfcache restore, which we don't want to treat as a SPA.
|
1390
1723
|
var start = floor(startMark.startTime); // the start mark is "zero"
|
1391
1724
|
ns += start; // "navigationStart" for a SPA is the real navigationStart plus the start mark
|
1392
1725
|
var end = floor(endMark.startTime) - start; // delta from start mark
|
@@ -1423,7 +1756,7 @@
|
|
1423
1756
|
};
|
1424
1757
|
var loadEventStartStr = prefixNTValue("loadEventStart", "ls", true);
|
1425
1758
|
var loadEventEndStr = prefixNTValue("loadEventEnd", "le", true);
|
1426
|
-
if (
|
1759
|
+
if (getPageRestoreTime() && startMark && endMark) {
|
1427
1760
|
// For bfcache restores, we set the load time to the time it took for the page to be restored.
|
1428
1761
|
var loadTime = floor(endMark.startTime - startMark.startTime);
|
1429
1762
|
loadEventStartStr = "ls" + loadTime;
|
@@ -1434,8 +1767,8 @@
|
|
1434
1767
|
s = [
|
1435
1768
|
ns,
|
1436
1769
|
"as" + clamp(navEntry_1.activationStart),
|
1437
|
-
redirect && !
|
1438
|
-
redirect && !
|
1770
|
+
redirect && !getPageRestoreTime() ? prefixNTValue("redirectStart", "rs") : "",
|
1771
|
+
redirect && !getPageRestoreTime() ? prefixNTValue("redirectEnd", "re") : "",
|
1439
1772
|
prefixNTValue("fetchStart", "fs"),
|
1440
1773
|
prefixNTValue("domainLookupStart", "ds"),
|
1441
1774
|
prefixNTValue("domainLookupEnd", "de"),
|
@@ -1542,9 +1875,9 @@
|
|
1542
1875
|
return [
|
1543
1876
|
"&INP=" + details.duration,
|
1544
1877
|
details.selector ? "&INPs=" + encodeURIComponent(details.selector) : "",
|
1545
|
-
"&INPt=" +
|
1878
|
+
"&INPt=" + floor(details.startTime),
|
1546
1879
|
"&INPi=" + clamp(floor(details.processingStart - details.startTime)),
|
1547
|
-
"&INPp=" + clamp(floor(details.
|
1880
|
+
"&INPp=" + clamp(floor(details.processingTime)),
|
1548
1881
|
"&INPd=" + clamp(floor(details.startTime + details.duration - details.processingEnd)),
|
1549
1882
|
].join("");
|
1550
1883
|
}
|
@@ -1657,7 +1990,12 @@
|
|
1657
1990
|
var vw = document.documentElement.clientWidth;
|
1658
1991
|
// Return true if the top-left corner is in the viewport and it has width & height.
|
1659
1992
|
var lt = findPos(e);
|
1660
|
-
return lt[0] >= 0 &&
|
1993
|
+
return (lt[0] >= 0 &&
|
1994
|
+
lt[1] >= 0 &&
|
1995
|
+
lt[0] < vw &&
|
1996
|
+
lt[1] < vh &&
|
1997
|
+
e.offsetWidth > 0 &&
|
1998
|
+
e.offsetHeight > 0);
|
1661
1999
|
}
|
1662
2000
|
// Return an array containing the top & left coordinates of the element.
|
1663
2001
|
// from http://www.quirksmode.org/js/findpos.html
|
@@ -1687,7 +2025,7 @@
|
|
1687
2025
|
gMaxMeasureTimeout = setTimeout(function () {
|
1688
2026
|
gFlags = addFlag(gFlags, Flags.BeaconSentAfterTimeout);
|
1689
2027
|
_sendLux();
|
1690
|
-
}, globalConfig.maxMeasureTime -
|
2028
|
+
}, globalConfig.maxMeasureTime - msSincePageInit());
|
1691
2029
|
}
|
1692
2030
|
function clearMaxMeasureTimeout() {
|
1693
2031
|
if (gMaxMeasureTimeout) {
|
@@ -1696,7 +2034,7 @@
|
|
1696
2034
|
}
|
1697
2035
|
function _getBeaconUrl(customData) {
|
1698
2036
|
var queryParams = [
|
1699
|
-
"v=" +
|
2037
|
+
"v=" + versionAsFloat(),
|
1700
2038
|
"id=" + getCustomerId(),
|
1701
2039
|
"sid=" + gSyncId,
|
1702
2040
|
"uid=" + gUid,
|
@@ -1727,7 +2065,7 @@
|
|
1727
2065
|
!gSyncId ||
|
1728
2066
|
!_sample() || // OUTSIDE the sampled range
|
1729
2067
|
gbLuxSent // LUX data already sent
|
1730
|
-
|
2068
|
+
) {
|
1731
2069
|
return;
|
1732
2070
|
}
|
1733
2071
|
logger.logEvent(LogEvent.DataCollectionStart);
|
@@ -1752,7 +2090,7 @@
|
|
1752
2090
|
}
|
1753
2091
|
var sET = elementTimingValues(); // Element Timing data
|
1754
2092
|
var sCPU = cpuTimes();
|
1755
|
-
var CLS = getCLS
|
2093
|
+
var CLS = getCLS();
|
1756
2094
|
var sLuxjs = selfLoading();
|
1757
2095
|
if (!isVisible()) {
|
1758
2096
|
gFlags = addFlag(gFlags, Flags.VisibilityStateNotVisible);
|
@@ -1839,7 +2177,7 @@
|
|
1839
2177
|
gbIxSent = sIx ? 1 : 0;
|
1840
2178
|
// Send other beacons for JUST User Timing.
|
1841
2179
|
while (remainingUtValues.length) {
|
1842
|
-
|
2180
|
+
_a = fitUserTimingEntries(remainingUtValues, globalConfig, baseUrl), beaconUtValues = _a[0], remainingUtValues = _a[1];
|
1843
2181
|
var utBeaconUrl = baseUrl + "&UT=" + beaconUtValues.join(",");
|
1844
2182
|
logger.logEvent(LogEvent.UserTimingBeaconSent, [utBeaconUrl]);
|
1845
2183
|
_sendBeacon(utBeaconUrl);
|
@@ -1848,7 +2186,7 @@
|
|
1848
2186
|
var ixTimerId;
|
1849
2187
|
function _sendIxAfterDelay() {
|
1850
2188
|
clearTimeout(ixTimerId);
|
1851
|
-
ixTimerId = setTimeout(_sendIx,
|
2189
|
+
ixTimerId = setTimeout(_sendIx, globalConfig.interactionBeaconDelay);
|
1852
2190
|
}
|
1853
2191
|
// Beacon back the IX data separately (need to sync with LUX beacon on the backend).
|
1854
2192
|
function _sendIx() {
|
@@ -1906,7 +2244,7 @@
|
|
1906
2244
|
// Note for scroll input we don't remove the handlers or send the IX beacon because we want to
|
1907
2245
|
// capture click and key events as well, since these are typically more important than scrolls.
|
1908
2246
|
if (typeof ghIx["s"] === "undefined") {
|
1909
|
-
ghIx["s"] =
|
2247
|
+
ghIx["s"] = msSincePageInit();
|
1910
2248
|
}
|
1911
2249
|
}
|
1912
2250
|
function _keyHandler(e) {
|
@@ -1924,7 +2262,7 @@
|
|
1924
2262
|
return;
|
1925
2263
|
}
|
1926
2264
|
if (typeof ghIx["k"] === "undefined") {
|
1927
|
-
ghIx["k"] =
|
2265
|
+
ghIx["k"] = msSincePageInit();
|
1928
2266
|
if (e && e.target instanceof Element) {
|
1929
2267
|
var trackId = getNodeSelector(e.target);
|
1930
2268
|
if (trackId) {
|
@@ -1940,7 +2278,7 @@
|
|
1940
2278
|
}
|
1941
2279
|
function _clickHandler(e) {
|
1942
2280
|
if (typeof ghIx["c"] === "undefined") {
|
1943
|
-
ghIx["c"] =
|
2281
|
+
ghIx["c"] = msSincePageInit();
|
1944
2282
|
// Only one interaction type is recorded. Scrolls are considered less important, so delete
|
1945
2283
|
// any scroll times if they exist.
|
1946
2284
|
delete ghIx["s"];
|
@@ -1969,34 +2307,13 @@
|
|
1969
2307
|
}
|
1970
2308
|
_removeIxHandlers();
|
1971
2309
|
}
|
1972
|
-
// Wrapper to support older browsers (<= IE8)
|
1973
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
1974
|
-
function addListener(type, callback, useCapture) {
|
1975
|
-
if (useCapture === void 0) { useCapture = false; }
|
1976
|
-
if (addEventListener) {
|
1977
|
-
addEventListener(type, callback, useCapture);
|
1978
|
-
}
|
1979
|
-
else if (window.attachEvent && true) {
|
1980
|
-
window.attachEvent("on" + type, callback);
|
1981
|
-
}
|
1982
|
-
}
|
1983
|
-
// Wrapper to support older browsers (<= IE8)
|
1984
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
1985
|
-
function removeListener(type, callback, useCapture) {
|
1986
|
-
if (useCapture === void 0) { useCapture = false; }
|
1987
|
-
if (removeEventListener) {
|
1988
|
-
removeEventListener(type, callback, useCapture);
|
1989
|
-
}
|
1990
|
-
else if (window.detachEvent && true) {
|
1991
|
-
window.detachEvent("on" + type, callback);
|
1992
|
-
}
|
1993
|
-
}
|
1994
2310
|
function _addUnloadHandlers() {
|
1995
2311
|
var onunload = function () {
|
1996
2312
|
gFlags = addFlag(gFlags, Flags.BeaconSentFromUnloadHandler);
|
1997
2313
|
logger.logEvent(LogEvent.UnloadHandlerTriggered);
|
1998
2314
|
_sendLux();
|
1999
2315
|
_sendIx();
|
2316
|
+
beacon.send();
|
2000
2317
|
};
|
2001
2318
|
// As well as visibilitychange, we also listen for pagehide. This is really only for browsers
|
2002
2319
|
// with buggy visibilitychange implementations. For much older browsers that don't support
|
@@ -2033,14 +2350,14 @@
|
|
2033
2350
|
// "00" matches all sample rates
|
2034
2351
|
return Number(new Date()) + "00000";
|
2035
2352
|
}
|
2036
|
-
return Number(new Date()) +
|
2353
|
+
return Number(new Date()) + padStart(String(round(100000 * Math.random())), 5, "0");
|
2037
2354
|
}
|
2038
2355
|
// Unique ID (also known as Session ID)
|
2039
2356
|
// We use this to track all the page views in a single user session.
|
2040
2357
|
// If there is NOT a UID then set it to the new value (which is the same as the "sync ID" for this page).
|
2041
2358
|
// Refresh its expiration date and return its value.
|
2042
2359
|
function refreshUniqueId(newValue) {
|
2043
|
-
var uid = _getCookie(
|
2360
|
+
var uid = _getCookie(SESSION_COOKIE_NAME);
|
2044
2361
|
if (!uid || uid.length < 11) {
|
2045
2362
|
uid = newValue;
|
2046
2363
|
}
|
@@ -2058,7 +2375,7 @@
|
|
2058
2375
|
return uid;
|
2059
2376
|
}
|
2060
2377
|
function setUniqueId(uid) {
|
2061
|
-
_setCookie(
|
2378
|
+
_setCookie(SESSION_COOKIE_NAME, uid, gSessionTimeout);
|
2062
2379
|
return uid;
|
2063
2380
|
}
|
2064
2381
|
// We use gUid (session ID) to do sampling. We make this available to customers so
|
@@ -2120,18 +2437,13 @@
|
|
2120
2437
|
"=" +
|
2121
2438
|
escape(value) +
|
2122
2439
|
(seconds ? "; max-age=" + seconds : "") +
|
2123
|
-
(globalConfig.cookieDomain ? "; domain=" +
|
2124
|
-
globalConfig.cookieDomain : "") +
|
2440
|
+
(globalConfig.cookieDomain ? "; domain=" + globalConfig.cookieDomain : "") +
|
2125
2441
|
"; path=/; SameSite=Lax";
|
2126
2442
|
}
|
2127
2443
|
catch (e) {
|
2128
2444
|
logger.logEvent(LogEvent.CookieSetError);
|
2129
2445
|
}
|
2130
2446
|
}
|
2131
|
-
// "padding" MUST be the length of the resulting string, eg, "0000" if you want a result of length 4.
|
2132
|
-
function _padLeft(str, padding) {
|
2133
|
-
return (padding + str).slice(-padding.length);
|
2134
|
-
}
|
2135
2447
|
// Set "LUX.auto=false" to disable send results automatically and
|
2136
2448
|
// instead you must call LUX.send() explicitly.
|
2137
2449
|
if (globalConfig.auto) {
|
@@ -2144,22 +2456,15 @@
|
|
2144
2456
|
}
|
2145
2457
|
};
|
2146
2458
|
var sendBeaconAfterMinimumMeasureTime_1 = function () {
|
2147
|
-
var elapsedTime =
|
2459
|
+
var elapsedTime = msSincePageInit();
|
2148
2460
|
var timeRemaining = globalConfig.minMeasureTime - elapsedTime;
|
2149
2461
|
if (timeRemaining <= 0) {
|
2150
2462
|
logger.logEvent(LogEvent.OnloadHandlerTriggered, [
|
2151
2463
|
elapsedTime,
|
2152
2464
|
globalConfig.minMeasureTime,
|
2153
2465
|
]);
|
2154
|
-
if (
|
2155
|
-
|
2156
|
-
sendBeaconWhenVisible_1();
|
2157
|
-
}
|
2158
|
-
else {
|
2159
|
-
// Ow, send the beacon slightly after window.onload.
|
2160
|
-
addListener("load", function () {
|
2161
|
-
setTimeout(sendBeaconWhenVisible_1, 200);
|
2162
|
-
});
|
2466
|
+
if (globalConfig.measureUntil === "onload") {
|
2467
|
+
onPageLoad(sendBeaconWhenVisible_1);
|
2163
2468
|
}
|
2164
2469
|
}
|
2165
2470
|
else {
|
@@ -2176,7 +2481,7 @@
|
|
2176
2481
|
addEventListener("pageshow", function (event) {
|
2177
2482
|
if (event.persisted) {
|
2178
2483
|
// Record the timestamp of the bfcache restore
|
2179
|
-
|
2484
|
+
setPageRestoreTime(event.timeStamp);
|
2180
2485
|
// In Chromium, document.visibilityState is still "hidden" when pageshow fires after a bfcache
|
2181
2486
|
// restore. Wrapping this in a setTimeout ensures the browser has enough time to update the
|
2182
2487
|
// visibility.
|
@@ -2185,7 +2490,7 @@
|
|
2185
2490
|
if (gbLuxSent) {
|
2186
2491
|
// If the beacon was already sent for this page, we start a new page view and mark the
|
2187
2492
|
// load time as the time it took to restore the page.
|
2188
|
-
_init(
|
2493
|
+
_init(getPageRestoreTime(), false);
|
2189
2494
|
_markLoadTime();
|
2190
2495
|
}
|
2191
2496
|
// Flag the current page as a bfcache restore
|
@@ -2214,11 +2519,15 @@
|
|
2214
2519
|
globalLux.markLoadTime = _markLoadTime;
|
2215
2520
|
globalLux.send = function () {
|
2216
2521
|
logger.logEvent(LogEvent.SendCalled);
|
2522
|
+
beacon.send();
|
2217
2523
|
_sendLux();
|
2218
2524
|
};
|
2219
2525
|
globalLux.addData = _addData;
|
2220
2526
|
globalLux.getSessionId = _getUniqueId; // so customers can do their own sampling
|
2221
|
-
globalLux.getDebug = function () {
|
2527
|
+
globalLux.getDebug = function () {
|
2528
|
+
console.log("SpeedCurve RUM debugging documentation: https://support.speedcurve.com/docs/rum-js-api#luxgetdebug");
|
2529
|
+
return logger.getEvents();
|
2530
|
+
};
|
2222
2531
|
globalLux.forceSample = function () {
|
2223
2532
|
logger.logEvent(LogEvent.ForceSampleCalled);
|
2224
2533
|
setUniqueId(createSyncId(true));
|
@@ -2228,7 +2537,7 @@
|
|
2228
2537
|
};
|
2229
2538
|
globalLux.cmd = _runCommand;
|
2230
2539
|
// Public properties
|
2231
|
-
globalLux.version =
|
2540
|
+
globalLux.version = VERSION;
|
2232
2541
|
/**
|
2233
2542
|
* Run a command from the command queue
|
2234
2543
|
*/
|