govuk_publishing_components 39.2.0 → 39.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
*/
|