forked from googleanalytics/autotrack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathautotrack.js
More file actions
4868 lines (4114 loc) · 153 KB
/
autotrack.js
File metadata and controls
4868 lines (4114 loc) · 153 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
var proto = window.Element.prototype;
var nativeMatches = proto.matches || proto.matchesSelector || proto.webkitMatchesSelector || proto.mozMatchesSelector || proto.msMatchesSelector || proto.oMatchesSelector;
/**
* Tests if a DOM elements matches any of the test DOM elements or selectors.
* @param {Element} element The DOM element to test.
* @param {Element|string|Array<Element|string>} test A DOM element, a CSS
* selector, or an array of DOM elements or CSS selectors to match against.
* @return {boolean} True of any part of the test matches.
*/
function matches(element, test) {
// Validate input.
if (element && element.nodeType == 1 && test) {
// if test is a string or DOM element test it.
if (typeof test == 'string' || test.nodeType == 1) {
return element == test || matchesSelector(element, /** @type {string} */test);
} else if ('length' in test) {
// if it has a length property iterate over the items
// and return true if any match.
for (var i = 0, item; item = test[i]; i++) {
if (element == item || matchesSelector(element, item)) return true;
}
}
}
// Still here? Return false
return false;
}
/**
* Tests whether a DOM element matches a selector. This polyfills the native
* Element.prototype.matches method across browsers.
* @param {!Element} element The DOM element to test.
* @param {string} selector The CSS selector to test element against.
* @return {boolean} True if the selector matches.
*/
function matchesSelector(element, selector) {
if (typeof selector != 'string') return false;
if (nativeMatches) return nativeMatches.call(element, selector);
var nodes = element.parentNode.querySelectorAll(selector);
for (var i = 0, node; node = nodes[i]; i++) {
if (node == element) return true;
}
return false;
}
/**
* Returns an array of a DOM element's parent elements.
* @param {!Element} element The DOM element whose parents to get.
* @return {!Array} An array of all parent elemets, or an empty array if no
* parent elements are found.
*/
function parents(element) {
var list = [];
while (element && element.parentNode && element.parentNode.nodeType == 1) {
element = /** @type {!Element} */element.parentNode;
list.push(element);
}
return list;
}
/**
* Gets the closest parent element that matches the passed selector.
* @param {Element} element The element whose parents to check.
* @param {string} selector The CSS selector to match against.
* @param {boolean=} shouldCheckSelf True if the selector should test against
* the passed element itself.
* @return {Element|undefined} The matching element or undefined.
*/
function closest(element, selector) {
var shouldCheckSelf = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
if (!(element && element.nodeType == 1 && selector)) return;
var parentElements = (shouldCheckSelf ? [element] : []).concat(parents(element));
for (var i = 0, parent; parent = parentElements[i]; i++) {
if (matches(parent, selector)) return parent;
}
}
/**
* Delegates the handling of events for an element matching a selector to an
* ancestor of the matching element.
* @param {!Node} ancestor The ancestor element to add the listener to.
* @param {string} eventType The event type to listen to.
* @param {string} selector A CSS selector to match against child elements.
* @param {!Function} callback A function to run any time the event happens.
* @param {Object=} opts A configuration options object. The available options:
* - useCapture<boolean>: If true, bind to the event capture phase.
* - deep<boolean>: If true, delegate into shadow trees.
* @return {Object} The delegate object. It contains a destroy method.
*/
function delegate(ancestor, eventType, selector, callback) {
var opts = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
// Defines the event listener.
var listener = function listener(event) {
var delegateTarget = void 0;
// If opts.composed is true and the event originated from inside a Shadow
// tree, check the composed path nodes.
if (opts.composed && typeof event.composedPath == 'function') {
var composedPath = event.composedPath();
for (var i = 0, node; node = composedPath[i]; i++) {
if (node.nodeType == 1 && matches(node, selector)) {
delegateTarget = node;
}
}
} else {
// Otherwise check the parents.
delegateTarget = closest(event.target, selector, true);
}
if (delegateTarget) {
callback.call(delegateTarget, event, delegateTarget);
}
};
ancestor.addEventListener(eventType, listener, opts.useCapture);
return {
destroy: function destroy() {
ancestor.removeEventListener(eventType, listener, opts.useCapture);
}
};
}
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var defineProperty = function (obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
};
var get = function get(object, property, receiver) {
if (object === null) object = Function.prototype;
var desc = Object.getOwnPropertyDescriptor(object, property);
if (desc === undefined) {
var parent = Object.getPrototypeOf(object);
if (parent === null) {
return undefined;
} else {
return get(parent, property, receiver);
}
} else if ("value" in desc) {
return desc.value;
} else {
var getter = desc.get;
if (getter === undefined) {
return undefined;
}
return getter.call(receiver);
}
};
var inherits = function (subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};
var possibleConstructorReturn = function (self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
};
var slicedToArray = function () {
function sliceIterator(arr, i) {
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"]) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
return function (arr, i) {
if (Array.isArray(arr)) {
return arr;
} else if (Symbol.iterator in Object(arr)) {
return sliceIterator(arr, i);
} else {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
};
}();
/**
* Dispatches an event on the passed element.
* @param {!Element} element The DOM element to dispatch the event on.
* @param {string} eventType The type of event to dispatch.
* @param {Object|string=} eventName A string name of the event constructor
* to use. Defaults to 'Event' if nothing is passed or 'CustomEvent' if
* a value is set on `initDict.detail`. If eventName is given an object
* it is assumed to be initDict and thus reassigned.
* @param {Object=} initDict The initialization attributes for the
* event. A `detail` property can be used here to pass custom data.
* @return {boolean} The return value of `element.dispatchEvent`, which will
* be false if any of the event listeners called `preventDefault`.
*/
/**
* Gets all attributes of an element as a plain JavaScriot object.
* @param {Element} element The element whose attributes to get.
* @return {!Object} An object whose keys are the attribute keys and whose
* values are the attribute values. If no attributes exist, an empty
* object is returned.
*/
function getAttributes(element) {
var attrs = {};
// Validate input.
if (!(element && element.nodeType == 1)) return attrs;
// Return an empty object if there are no attributes.
var map = element.attributes;
if (map.length === 0) return {};
for (var i = 0, attr; attr = map[i]; i++) {
attrs[attr.name] = attr.value;
}
return attrs;
}
var HTTP_PORT = '80';
var HTTPS_PORT = '443';
var DEFAULT_PORT = RegExp(':(' + HTTP_PORT + '|' + HTTPS_PORT + ')$');
var a = document.createElement('a');
var cache = {};
/**
* Parses the given url and returns an object mimicing a `Location` object.
* @param {string} url The url to parse.
* @return {!Object} An object with the same properties as a `Location`.
*/
function parseUrl(url) {
// All falsy values (as well as ".") should map to the current URL.
url = !url || url == '.' ? location.href : url;
if (cache[url]) return cache[url];
a.href = url;
// When parsing file relative paths (e.g. `../index.html`), IE will correctly
// resolve the `href` property but will keep the `..` in the `path` property.
// It will also not include the `host` or `hostname` properties. Furthermore,
// IE will sometimes return no protocol or just a colon, especially for things
// like relative protocol URLs (e.g. "//google.com").
// To workaround all of these issues, we reparse with the full URL from the
// `href` property.
if (url.charAt(0) == '.' || url.charAt(0) == '/') return parseUrl(a.href);
// Don't include default ports.
var port = a.port == HTTP_PORT || a.port == HTTPS_PORT ? '' : a.port;
// PhantomJS sets the port to "0" when using the file: protocol.
port = port == '0' ? '' : port;
// Sometimes IE incorrectly includes a port for default ports
// (e.g. `:80` or `:443`) even when no port is specified in the URL.
// http://bit.ly/1rQNoMg
var host = a.host.replace(DEFAULT_PORT, '');
// Not all browser support `origin` so we have to build it.
var origin = a.origin ? a.origin : a.protocol + '//' + host;
// Sometimes IE doesn't include the leading slash for pathname.
// http://bit.ly/1rQNoMg
var pathname = a.pathname.charAt(0) == '/' ? a.pathname : '/' + a.pathname;
return cache[url] = {
hash: a.hash,
host: host,
hostname: a.hostname,
href: a.href,
origin: origin,
pathname: pathname,
port: port,
protocol: a.protocol,
search: a.search
};
}
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var VERSION = '2.4.1';
var DEV_ID = 'i5iSjo';
var VERSION_PARAM = '_av';
var USAGE_PARAM = '_au';
var NULL_DIMENSION = '(not set)';
/**
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview
* The functions exported by this module make it easier (and safer) to override
* foreign object methods (in a modular way) and respond to or modify their
* invocation. The primary feature is the ability to override a method without
* worrying if it's already been overridden somewhere else in the codebase. It
* also allows for safe restoring of an overridden method by only fully
* restoring a method once all overrides have been removed.
*/
var instances = [];
/**
* A class that wraps a foreign object method and emit events before and
* after the original method is called.
*/
var MethodChain = function () {
createClass(MethodChain, null, [{
key: "add",
/**
* Adds the passed override method to the list of method chain overrides.
* @param {!Object} context The object containing the method to chain.
* @param {string} methodName The name of the method on the object.
* @param {!Function} methodOverride The override method to add.
*/
value: function add(context, methodName, methodOverride) {
getOrCreateMethodChain(context, methodName).add(methodOverride);
}
/**
* Removes a method chain added via `add()`. If the override is the
* only override added, the original method is restored. If the method
* chain does not exist, nothing happens.
* @param {!Object} context The object containing the method to unchain.
* @param {string} methodName The name of the method on the object.
* @param {!Function} methodOverride The override method to remove.
*/
}, {
key: "remove",
value: function remove(context, methodName, methodOverride) {
var methodChain = getMethodChain(context, methodName);
if (methodChain) {
methodChain.remove(methodOverride);
}
}
/**
* Wraps a foreign object method and overrides it. Also stores a reference
* to the original method so it can be restored later.
* @param {!Object} context The object containing the method.
* @param {string} methodName The name of the method on the object.
*/
}]);
function MethodChain(context, methodName) {
var _this = this;
classCallCheck(this, MethodChain);
this.context = context;
this.methodName = methodName;
this.isTask = /Task$/.test(methodName);
this.originalMethodReference = this.isTask ? context.get(methodName) : context[methodName];
this.methodChain = [];
this.boundMethodChain = [];
// Wraps the original method.
this.wrappedMethod = function () {
var lastBoundMethod = _this.boundMethodChain[_this.boundMethodChain.length - 1];
return lastBoundMethod.apply(undefined, arguments);
};
// Override original method with the wrapped one.
if (this.isTask) {
context.set(methodName, this.wrappedMethod);
} else {
context[methodName] = this.wrappedMethod;
}
}
/**
* Adds a method to the method chain.
* @param {!Function} overrideMethod The override method to add.
*/
createClass(MethodChain, [{
key: "add",
value: function add(overrideMethod) {
this.methodChain.push(overrideMethod);
this.rebindMethodChain();
}
/**
* Removes a method from the method chain and restores the prior order.
* @param {!Function} overrideMethod The override method to remove.
*/
}, {
key: "remove",
value: function remove(overrideMethod) {
var index = this.methodChain.indexOf(overrideMethod);
if (index > -1) {
this.methodChain.splice(index, 1);
if (this.methodChain.length > 0) {
this.rebindMethodChain();
} else {
this.destroy();
}
}
}
/**
* Loops through the method chain array and recreates the bound method
* chain array. This is necessary any time a method is added or removed
* to ensure proper original method context and order.
*/
}, {
key: "rebindMethodChain",
value: function rebindMethodChain() {
this.boundMethodChain = [];
for (var method, i = 0; method = this.methodChain[i]; i++) {
var previousMethod = this.boundMethodChain[i - 1] || this.originalMethodReference.bind(this.context);
this.boundMethodChain.push(method(previousMethod));
}
}
/**
* Calls super and destroys the instance if no registered handlers remain.
*/
}, {
key: "destroy",
value: function destroy() {
var index = instances.indexOf(this);
if (index > -1) {
instances.splice(index, 1);
if (this.isTask) {
this.context.set(this.methodName, this.originalMethodReference);
} else {
this.context[this.methodName] = this.originalMethodReference;
}
}
}
}]);
return MethodChain;
}();
function getMethodChain(context, methodName) {
return instances.filter(function (h) {
return h.context == context && h.methodName == methodName;
})[0];
}
/**
* Gets a MethodChain instance for the passed object and method. If the method
* has already been wrapped via an existing MethodChain instance, that
* instance is returned.
* @param {!Object} context The object containing the method.
* @param {string} methodName The name of the method on the object.
* @return {!MethodChain}
*/
function getOrCreateMethodChain(context, methodName) {
var methodChain = getMethodChain(context, methodName);
if (!methodChain) {
methodChain = new MethodChain(context, methodName);
instances.push(methodChain);
}
return methodChain;
}
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Accepts default and user override fields and an optional tracker, hit
* filter, and target element and returns a single object that can be used in
* `ga('send', ...)` commands.
* @param {FieldsObj} defaultFields The default fields to return.
* @param {FieldsObj} userFields Fields set by the user to override the
* defaults.
* @param {Tracker=} tracker The tracker object to apply the hit filter to.
* @param {Function=} hitFilter A filter function that gets
* called with the tracker model right before the `buildHitTask`. It can
* be used to modify the model for the current hit only.
* @param {Element=} target If the hit originated from an interaction
* with a DOM element, hitFilter is invoked with that element as the
* second argument.
* @param {(Event|TwttrEvent)=} event If the hit originated via a DOM event,
* hitFilter is invoked with that event as the third argument.
* @return {!FieldsObj} The final fields object.
*/
function createFieldsObj(defaultFields, userFields) {
var tracker = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : undefined;
var hitFilter = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : undefined;
var target = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : undefined;
var event = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : undefined;
if (typeof hitFilter == 'function') {
var originalBuildHitTask = tracker.get('buildHitTask');
return {
buildHitTask: function buildHitTask( /** @type {!Model} */model) {
model.set(defaultFields, null, true);
model.set(userFields, null, true);
hitFilter(model, target, event);
originalBuildHitTask(model);
}
};
} else {
return assign({}, defaultFields, userFields);
}
}
/**
* Retrieves the attributes from an DOM element and returns a fields object
* for all attributes matching the passed prefix string.
* @param {Element} element The DOM element to get attributes from.
* @param {string} prefix An attribute prefix. Only the attributes matching
* the prefix will be returned on the fields object.
* @return {FieldsObj} An object of analytics.js fields and values
*/
function getAttributeFields(element, prefix) {
var attributes = getAttributes(element);
var attributeFields = {};
Object.keys(attributes).forEach(function (attribute) {
// The `on` prefix is used for event handling but isn't a field.
if (attribute.indexOf(prefix) === 0 && attribute != prefix + 'on') {
var value = attributes[attribute];
// Detects Boolean value strings.
if (value == 'true') value = true;
if (value == 'false') value = false;
var field = camelCase(attribute.slice(prefix.length));
attributeFields[field] = value;
}
});
return attributeFields;
}
/**
* Accepts a function to be invoked once the DOM is ready. If the DOM is
* already ready, the callback is invoked immediately.
* @param {!Function} callback The ready callback.
*/
function domReady(callback) {
if (document.readyState == 'loading') {
document.addEventListener('DOMContentLoaded', function fn() {
document.removeEventListener('DOMContentLoaded', fn);
callback();
});
} else {
callback();
}
}
/**
* Returns a function, that, as long as it continues to be called, will not
* actually run. The function will only run after it stops being called for
* `wait` milliseconds.
* @param {!Function} fn The function to debounce.
* @param {number} wait The debounce wait timeout in ms.
* @return {!Function} The debounced function.
*/
function debounce(fn, wait) {
var timeout = void 0;
return function () {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
clearTimeout(timeout);
timeout = setTimeout(function () {
return fn.apply(undefined, args);
}, wait);
};
}
/**
* Accepts a function and returns a wrapped version of the function that is
* expected to be called elsewhere in the system. If it's not called
* elsewhere after the timeout period, it's called regardless. The wrapper
* function also prevents the callback from being called more than once.
* @param {!Function} callback The function to call.
* @param {number=} wait How many milliseconds to wait before invoking
* the callback.
* @return {!Function} The wrapped version of the passed function.
*/
function withTimeout(callback) {
var wait = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 2000;
var called = false;
var fn = function fn() {
if (!called) {
called = true;
callback();
}
};
setTimeout(fn, wait);
return fn;
}
/**
* A small shim of Object.assign that aims for brevity over spec-compliant
* handling all the edge cases.
* @param {!Object} target The target object to assign to.
* @param {...?Object} sources Additional objects who properties should be
* assigned to target. Non-objects are converted to objects.
* @return {!Object} The modified target object.
*/
var assign = Object.assign || function (target) {
for (var _len2 = arguments.length, sources = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
sources[_key2 - 1] = arguments[_key2];
}
for (var i = 0, len = sources.length; i < len; i++) {
var source = Object(sources[i]);
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
/**
* Accepts a string containing hyphen or underscore word separators and
* converts it to camelCase.
* @param {string} str The string to camelCase.
* @return {string} The camelCased version of the string.
*/
function camelCase(str) {
return str.replace(/[-_]+(\w?)/g, function (match, p1) {
return p1.toUpperCase();
});
}
/**
* Capitalizes the first letter of a string.
* @param {string} str The input string.
* @return {string} The capitalized string
*/
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
/**
* Indicates whether the passed variable is a JavaScript object.
* @param {*} value The input variable to test.
* @return {boolean} Whether or not the test is an object.
*/
function isObject(value) {
return (typeof value === 'undefined' ? 'undefined' : _typeof(value)) == 'object' && value !== null;
}
/**
* Accepts a value that may or may not be an array. If it is not an array,
* it is returned as the first item in a single-item array.
* @param {*} value The value to convert to an array if it is not.
* @return {!Array} The array-ified value.
*/
function toArray$1(value) {
return Array.isArray(value) ? value : [value];
}
/**
* @return {number} The current date timestamp
*/
function now() {
return +new Date();
}
/*eslint-disable */
// https://gist.github.com/jed/982883
/** @param {?=} a */
var uuid = function b(a) {
return a ? (a ^ Math.random() * 16 >> a / 4).toString(16) : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, b);
};
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Provides a plugin for use with analytics.js, accounting for the possibility
* that the global command queue has been renamed or not yet defined.
* @param {string} pluginName The plugin name identifier.
* @param {Function} pluginConstructor The plugin constructor function.
*/
function provide(pluginName, pluginConstructor) {
var gaAlias = window.GoogleAnalyticsObject || 'ga';
window[gaAlias] = window[gaAlias] || function () {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
(window[gaAlias].q = window[gaAlias].q || []).push(args);
};
// Adds the autotrack dev ID if not already included.
window.gaDevIds = window.gaDevIds || [];
if (window.gaDevIds.indexOf(DEV_ID) < 0) {
window.gaDevIds.push(DEV_ID);
}
// Formally provides the plugin for use with analytics.js.
window[gaAlias]('provide', pluginName, pluginConstructor);
// Registers the plugin on the global gaplugins object.
window.gaplugins = window.gaplugins || {};
window.gaplugins[capitalize(pluginName)] = pluginConstructor;
}
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var plugins = {
CLEAN_URL_TRACKER: 1,
EVENT_TRACKER: 2,
IMPRESSION_TRACKER: 3,
MEDIA_QUERY_TRACKER: 4,
OUTBOUND_FORM_TRACKER: 5,
OUTBOUND_LINK_TRACKER: 6,
PAGE_VISIBILITY_TRACKER: 7,
SOCIAL_WIDGET_TRACKER: 8,
URL_CHANGE_TRACKER: 9,
MAX_SCROLL_TRACKER: 10
};
var PLUGIN_COUNT = Object.keys(plugins).length;
/**
* Tracks the usage of the passed plugin by encoding a value into a usage
* string sent with all hits for the passed tracker.
* @param {!Tracker} tracker The analytics.js tracker object.
* @param {number} plugin The plugin enum.
*/
function trackUsage(tracker, plugin) {
trackVersion(tracker);
trackPlugin(tracker, plugin);
}
/**
* Converts a hexadecimal string to a binary string.
* @param {string} hex A hexadecimal numeric string.
* @return {string} a binary numeric string.
*/
function convertHexToBin(hex) {
return parseInt(hex || '0', 16).toString(2);
}
/**
* Converts a binary string to a hexadecimal string.
* @param {string} bin A binary numeric string.
* @return {string} a hexadecimal numeric string.
*/
function convertBinToHex(bin) {
return parseInt(bin || '0', 2).toString(16);
}
/**
* Adds leading zeros to a string if it's less than a minimum length.
* @param {string} str A string to pad.
* @param {number} len The minimum length of the string
* @return {string} The padded string.
*/
function padZeros(str, len) {
if (str.length < len) {
var toAdd = len - str.length;
while (toAdd) {
str = '0' + str;
toAdd--;
}
}
return str;
}
/**
* Accepts a binary numeric string and flips the digit from 0 to 1 at the
* specified index.
* @param {string} str The binary numeric string.
* @param {number} index The index to flip the bit.
* @return {string} The new binary string with the bit flipped on
*/
function flipBitOn(str, index) {
return str.substr(0, index) + 1 + str.substr(index + 1);
}
/**
* Accepts a tracker and a plugin index and flips the bit at the specified
* index on the tracker's usage parameter.
* @param {Object} tracker An analytics.js tracker.
* @param {number} pluginIndex The index of the plugin in the global list.
*/
function trackPlugin(tracker, pluginIndex) {
var usageHex = tracker.get('&' + USAGE_PARAM);
var usageBin = padZeros(convertHexToBin(usageHex), PLUGIN_COUNT);
// Flip the bit of the plugin being tracked.
usageBin = flipBitOn(usageBin, PLUGIN_COUNT - pluginIndex);
// Stores the modified usage string back on the tracker.
tracker.set('&' + USAGE_PARAM, convertBinToHex(usageBin));
}
/**
* Accepts a tracker and adds the current version to the version param.
* @param {Object} tracker An analytics.js tracker.
*/
function trackVersion(tracker) {
tracker.set('&' + VERSION_PARAM, VERSION);
}
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Class for the `cleanUrlTracker` analytics.js plugin.
* @implements {CleanUrlTrackerPublicInterface}
*/
var CleanUrlTracker = function () {
/**
* Registers clean URL tracking on a tracker object. The clean URL tracker
* removes query parameters from the page value reported to Google Analytics.
* It also helps to prevent tracking similar URLs, e.g. sometimes ending a
* URL with a slash and sometimes not.
* @param {!Tracker} tracker Passed internally by analytics.js
* @param {?CleanUrlTrackerOpts} opts Passed by the require command.
*/
function CleanUrlTracker(tracker, opts) {
classCallCheck(this, CleanUrlTracker);
trackUsage(tracker, plugins.CLEAN_URL_TRACKER);
/** @type {CleanUrlTrackerOpts} */
var defaultOpts = {
// stripQuery: undefined,
// queryParamsWhitelist: undefined,
// queryDimensionIndex: undefined,
// indexFilename: undefined,
// trailingSlash: undefined,
// urlFilter: undefined,