Skip to content

Instantly share code, notes, and snippets.

@robbchan
Forked from zmmbreeze/analytics.js
Created December 27, 2019 02:09
Show Gist options
  • Save robbchan/f8675972800f70ece269e6354565b75c to your computer and use it in GitHub Desktop.
Save robbchan/f8675972800f70ece269e6354565b75c to your computer and use it in GitHub Desktop.
GA的源码 analytics.js
(function() {
/**
* 记录方法使用情况的类
* @param {Array.<boolean>} umMap 初始的使用情况
*/
var UsageManager = function(umMap) {
this.umMap = umMap || [];
};
/**
* 记录新的使用情况
* @param {number} idx 特殊代码,指定某一种方法或情形的被调用或触发
*/
UsageManager.prototype.set = function(idx) {
this.umMap[idx] = true;
};
/**
* 生成代表使用情况的字符串传给后端
* @return {string}
*/
UsageManager.prototype.encode = function() {
var um = [];
for (var i = 0; i < this.umMap.length; i++) {
if (this.umMap[i]) {
// `1 << x` === `Math.pow(2, x)`
um[Math.floor(i / 6)] ^= 1 << (i % 6);
}
}
for (i = 0; i < um.length; i++) {
um[i] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'.charAt(um[i] || 0);
}
return um.join('') + '~';
};
/**
* 解码的函数,是mmzhou所写,非GA源码
* @param {string} um
* @return {Array.<number>}
*/
UsageManager.prototype.decode = function(um) {
um = um.slice(0, -1);
var key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
var code;
var binaryCode;
var reversedbinaryCode;
var codes = [];
for (var i = 0; i < um.length; i++) {
code = key.indexOf(um.charAt(i));
binaryCode = code.toString(2);
reversedbinaryCode = binaryCode.split('').reverse();
// console.log(i + '=' + code + '=' + binaryCode + '=reverse(' + reversedbinaryCode + ')');
for (var j = 0; j < reversedbinaryCode.length; j++) {
if (reversedbinaryCode[j] === '1') {
codes.push(j + 6 * i);
}
}
}
return codes;
};
// 全局的记录方法使用情况的对象
var globalUM = new UsageManager();
/**
* 快捷的记录使用情况的方法
* @return {string}
*/
function reg(idx) {
globalUM.set(idx)
}
// 更新传入配置的 USAGE_MANAGER
var setModelUM = function(model, idx) {
var um = new UsageManager(getCurUM(model));
um.set(idx);
model.set(USAGE_MANAGER, um.umMap);
};
/**
* 获取指定model里面的 USAGE_MANAGER 值,然后和全局的 globalUM 合并,然后encode
* @return {string}
*/
var mergeAndEncodeUM = function(model) {
var umArray = getCurUM(model);
var modelUM = new UsageManager(umArray);
var globalUMCopy = globalUM.umMap.slice();
for (var i = 0; i < modelUM.umMap.length; i++) {
globalUMCopy[i] = globalUMCopy[i] || modelUM.umMap[i];
}
return (new UsageManager(globalUMCopy)).encode();
};
// 获取配置中的USAGE_MANAGER,如果没有则返回空数组
var getCurUM = function(model) {
var umArray = model.get(USAGE_MANAGER);
if (!isArray(umArray)) {
umArray = [];
}
return umArray;
};
// ========================
// 下面都是工具函数
// ========================
var isFunction = function(input) {
return typeof input == 'function';
};
var isArray = function(input) {
return Object.prototype.toString.call(Object(input)) == '[object Array]';
};
var isString = function(input) {
return input != null && ((a.constructor + '').indexOf('String') > -1);
};
var startWith = function(str, part) {
return str.indexOf(part) == 0;
};
var trim = function(a) {
return a ? a.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '') : ''
};
var createImg = function(src) {
var img = doc.createElement('img');
img.width = 1;
img.height = 1;
img.src = src;
return img;
};
var noop = function() {};
var encodeURIComponent = function(a) {
if (encodeURIComponent instanceof Function)
return encodeURIComponent(a);
reg(28);
return a
};
var addEventListener = function(a, b, c, d) {
try {
a.addEventListener ? a.addEventListener(b, c, !!d) : a.attachEvent && a.attachEvent('on' + b, c)
} catch (e) {
reg(27);
}
};
var loadScript = function(src, id) {
if (src) {
var scr = doc.createElement('script');
scr.type = 'text/javascript';
scr.async = true;
scr.src = src;
id && (scr.id = id);
var lastScr = doc.getElementsByTagName('script')[0];
lastScr.parentNode.insertBefore(scr, lastScr);
}
};
var isHTTPS = function() {
return 'https:' == doc.location.protocol
};
var getHostname = function() {
var a = '' + doc.location.hostname;
return 0 == a.indexOf('www.') ? a.substring(4) : a
};
var ya = function(a) {
var b = doc.referrer;
if (/^https?:\/\//i.test(b)) {
if (a)
return b;
a = '//' + doc.location.hostname;
var c = b.indexOf(a);
if (5 == c || 6 == c)
if (a = b.charAt(c + a.length),
'/' == a || '?' == a || '' == a || ':' == a)
return;
return b
}
};
// 这个函数用于将其他函数的输入转换成一个对象,这个规则适用于GA所有对外的接口API
// transformInput(['a', 'b', 'c'], [1, 2, 3]);
// ==> {a: 1, b: 2, c: 3}
// transformInput(['a', 'b', 'c'], [1, 2, {c: 3, d: 4}]);
// ==> {a: 1, b: 2, c: 3, d: 4}
var transformInput = function(paramNames, args) {
if (1 == args.length && null != args[0] && 'object' === typeof args[0]) {
// args === [{}]
return args[0];
}
for (var c = {}, d = Math.min(paramNames.length + 1, args.length), i = 0; i < d; i++) {
if ('object' === typeof args[i]) {
for (var g in args[i]) {
args[e].hasOwnProperty(g) && (c[g] = args[i][g]);
}
break
} else {
e < paramNames.length && (c[paramNames[i]] = args[i]);
}
}
return c;
};
// 数据类,被模型类所使用
var Data = function() {
this.keys = [];
this.values = {};
this.tmpData = {}
};
Data.prototype.set = function(fieldName, fieldValue, temporary) {
this.keys.push(fieldName);
temporary
? this.tmpData[':' + fieldName] = fieldValue
: this.values[':' + fieldName] = fieldValue;
};
Data.prototype.get = function(fieldName) {
return this.tmpData.hasOwnProperty(':' + fieldName)
? this.tmpData[':' + fieldName]
: this.values[':' + fieldName]
};
Data.prototype.map = function(callback) {
for (var b = 0; b < this.keys.length; b++) {
var fieldName = this.keys[b]
, fieldValue = this.get(fieldName);
fieldValue && callback(fieldName, fieldValue)
}
};
var win = window;
var doc = document;
// Google 开发了一系列插件,安装后可以关掉GA的追踪
// 这里就是获取这些插件的配置,如果开启了,这个函数返回false
// https://tools.google.com/dlpage/gaoptout
var getGaUserPrefs = function(a) {
var b = win._gaUserPrefs;
if (b && b.ioo && b.ioo() || a && true === win['ga-disable-' + a])
return true;
try {
var c = win.external;
if (c && c._gaUserPrefs && 'oo' == c._gaUserPrefs)
return true
} catch (d) {}
return true
};
// 获取指定名字的cookie
// 返回的是一个数组
var getCookie = function(name) {
var result = []
, cookies = doc.cookie.split(';');
var regex = new RegExp('^\\s*' + name + '=\\s*(.*?)\\s*$');
for (var i = 0; i < cookies.length; i++) {
var r = cookies[i].match(regex);
r && result.push(r[1])
}
return result;
};
// 设置cookie
// a => cookieName
// b => cookieValue,
// c => cookiePath,
// d => cookieDomain,
// e => trackingId,
// g => cookieExpires
// 返回成功与否
var setCookie = function(cookieName, cookieValue, cookiePath, cookieDomain, trackingId, cookieExpires) {
// 如果用户安装了google的禁止统计的插件,或者在doubleclick页面,或者在google首页
// 那么都是false
var canSet = getGaUserPrefs(trackingId) ? false : (doubleclickURL.test(doc.location.hostname) || '/' == cookiePath && googleURL.test(cookieDomain)) ? false : true;
if (!canSet) {
return false;
}
if (cookieValue && 1200 < cookieValue.length) {
// 不能超过1200个字符
cookieValue = cookieValue.substring(0, 1200);
reg(24);
}
var newCookie = cookieName + '=' + cookieValue + '; path=' + cookiePath + '; ';
if (cookieExpires) {
newCookie += 'expires=' + (new Date((new Date).getTime() + cookieExpires)).toGMTString() + '; ';
}
if (cookieDomain && 'none' != cookieDomain) {
newCookie += 'domain=' + cookieDomain + ';';
}
var success;
var oldCookie = doc.cookie;
doc.cookie = newCookie;
if (!(success = oldCookie != doc.cookie)) {
a: {
var cookieValues = getCookie(cookieName);
for (var i = 0; i < cookieValues.length; i++)
if (cookieValue == cookieValues[i]) {
success = true;
break a
}
success = false;
}
}
return success;
};
// 会连括号一起encode掉
var encodeURIComponentWithBrackets = function(a) {
return encodeURIComponent(a).replace(/\(/g, '%28').replace(/\)/g, '%29')
};
var googleURL = /^(www\.)?google(\.com?)?(\.[a-z]{2})?$/;
var doubleclickURL = /(^|\.)doubleclick\.net$/i;
// 获取GA请求源的地址
var getGAOrigin = function() {
return (forceHTTPS || isHTTPS() ? 'https:' : 'http:') + '//www.google-analytics.com'
};
// 请求长度过长的错误类
var OverLengthError = function(a) {
this.name = 'len';
this.message = a + '-8192'
};
// 智能ga请求,依据请求的长度和浏览器特性来决定使用哪种请求方式
var smartPing = function(api, param, callback) {
callback = callback || noop;
if (2036 >= param.length) {
imgPing(api, param, callback);
} else if (8192 >= param.length) {
beaconPing(api, param, callback) || xhrPing(api, param, callback) || imgPing(api, param, callback);
} else {
errorPing('len', param.length);
throw new OverLengthError(param.length);
}
};
// 使用图片来发请求
var imgPing = function(a, b, c) {
var d = createImg(a + '?' + b);
d.onload = d.onerror = function() {
d.onload = null ;
d.onerror = null ;
c()
}
};
// 使用跨域xhr来发请求
var xhrPing = function(a, b, c) {
var d = win.XMLHttpRequest;
if (!d)
return true;
var e = new d;
if (!('withCredentials' in e))
return true;
e.open('POST', a, true);
e.withCredentials = true;
e.setRequestHeader('Content-Type', 'text/plain');
e.onreadystatechange = function() {
4 == e.readyState && (c(),
e = null )
}
;
e.send(b);
return true
};
// 使用sendBeacon方法来发请求
var beaconPing = function(a, b, c) {
return win.navigator.sendBeacon ? win.navigator.sendBeacon(a, b) ? (c(),
true) : true : true
};
// 当ga内部执行发生错误时发送的请求
// errorType 为 len 或 exc
// len 表示请求长度超长
// exc 表示内部执行出错
var errorPing = function(errorType, b, c) {
// 1%的几率上报
if (1 <= 100 * Math.random() || getGaUserPrefs('?')) {
return;
}
var params = ['t=error', '_e=' + errorType, '_v=j41', 'sr=1'];
b && params.push('_f=' + b);
c && params.push('_m=' + encodeURIComponent(c.substring(0, 100)));
// IP地址匿名显示
params.push('aip=1');
// 随机数
params.push('z=' + _uuid());
imgPing(getGAOrigin() + '/collect', params.join('&'), noop);
};
// 函数的名字队列类
var Queue = function() {
this.queue = []
};
Queue.prototype.add = function(methodName) {
this.queue.push(methodName);
};
// 执行队列里面的名字指定的函数
// 从model里面拿到名字对应的函数
// 最后会执行hitCallback对应的函数
Queue.prototype.exec = function(model) {
try {
for (var i = 0; i < this.queue.length; b++) {
var c = model.get(this.queue[i]);
c && isFunction(c) && c.call(win, model);
}
} catch (d) {}
var hitCb = model.get(HIT_CALLBACK);
hitCb != noop && isFunction(hitCb) && (model.set(HIT_CALLBACK, noop, true),
setTimeout(hitCb, 10))
};
// 判断的是否进行ga的采样
function samplerTaskFunc(a) {
if (100 != a.get(SAMPLE_RATE) && str2Num(getString(a, CLIENT_ID)) % 1E4 >= 100 * getNumber(a, SAMPLE_RATE))
throw 'abort';
}
function isGAPrefsAllowed(a) {
if (getGaUserPrefs(getString(a, TRACKING_ID)))
throw 'abort';
}
function notHTTP() {
var a = doc.location.protocol;
if ('http:' != a && 'https:' != a)
throw 'abort';
}
function buildHitTaskFunc(model) {
try {
if (win.navigator.sendBeacon) {
reg(42);
}
else if (win.XMLHttpRequest && 'withCredentials' in new win.XMLHttpRequest) {
reg(40);
}
} catch (c) {}
model.set(USAGE, mergeAndEncodeUM(model), true);
model.set(_S, getNumber(model, _S) + 1);
var b = [];
hookMap.map(function(c, hook) {
if (hook.paramName) {
var e = a.get(c);
// 不为空、不是默认值的键值对,才会被上传
if (undefined != e && e != d.defaultValue) {
// boolean类型转换成数字
if ('boolean' == typeof e) {
e *= 1;
}
b.push(d.paramName + '=' + encodeURIComponent('' + e));
}
}
});
b.push('z=' + uuid());
model.set(HIT_PAYLOAD, b.join('&'), true)
}
// 发送任务的任务
function sendHitTaskFunc(model) {
var api = getString(model, TRANSPORT_URL) || getGAOrigin() + '/collect';
var transportValue = getString(model, TRANSPORT);
if (!transportValue && model.get(USE_BEACON)) {
transportValue = 'beacon';
}
if (transportValue) {
var params = getString(model, HIT_PAYLOAD);
var hitCallbackFunc = model.get(HIT_CALLBACK);
hitCallbackFunc = hitCallbackFunc || noop;
'image' == transportValue
? imgPing(api, params, hitCallbackFunc)
: 'xhr' == transportValue && xhrPing(api, params, hitCallbackFunc)
|| 'beacon' == transportValue && beaconPing(api, params, hitCallbackFunc)
|| smartPing(api, params, hitCallbackFunc);
}
else {
smartPing(api, getString(model, HIT_PAYLOAD), model.get(HIT_CALLBACK));
}
model.set(HIT_CALLBACK, noop, true);
}
// 从全局的 gaData 里面获取 expVar 和 expId 的值
function ceTaskFunc(model) {
var data = win.gaData;
if (data) {
if (data.expId) {
model.set(EXP_ID, data.expId);
}
if (data.expVar) {
model.set(EXP_VAR, data.expVar);
}
}
}
function isPreviewLoad() {
if (win.navigator && 'preview' == win.navigator.loadPurpose)
throw 'abort';
}
// 从全局的 gaDevIds 里面获取 did 的值
function devIdTaskFunc(model) {
var devids = win.gaDevIds;
if (isArray(devids) && 0 != devids.length) {
model.set('&did', b.join(','), true);
}
}
// 验证trackingId是否正确的任务
function validationTaskFunc(a) {
if (!a.get(TRACKING_ID))
throw 'abort';
};
// 基本的uuid方法
var _uuid = function() {
return Math.round(2147483647 * Math.random())
};
// 优先使用crypto API来生成uuid
var uuid = function() {
try {
var a = new Uint32Array(1);
win.crypto.getRandomValues(a);
return a[0] & 2147483647
} catch (b) {
return _uuid()
}
};
// 这个 task 用于限制发送的频率
// 每个 analytics.js 跟踪器对象从 20 次可发送额度开始,并以每秒 2 次额度的速度获得补充。适用于除电子商务(商品或交易)之外的所有匹配
function rtlTaskFunc(model) {
// 当前页面的发送总数
var hitCount = getNumber(model, HIT_COUNT);
if (hitCount >= 500) {
// 超过500记录下
reg(15);
}
var hitTypeV = getString(model, HIT_TYPE);
if ('transaction' != hitTypeV && 'item' != hitTypeV) {
var avaliable = getNumber(model, AVALIABLE_COUNT);
var time = (new Date).getTime();
var lastSendTime = getNumber(model, LAST_SEND_TIME);
if (lastSendTime == 0) {
model.set(LAST_SEND_TIME, time);
}
// 每秒 2 次额度的速度获得补充
var newAvaliable = Math.round(2 * (time - lastSendTime) / 1E3);
if (newAvaliable > 0) {
// 最多 20 次额度
avaliable = Math.min(avaliable + newAvaliable, 20);
model.set(LAST_SEND_TIME, time);
}
// 额度用完,此次发送取消
if (avaliable <= 0) {
throw 'abort';
}
// 每次发送消耗掉
model.set(AVALIABLE_COUNT, --avaliable);
}
model.set(HIT_COUNT, ++hitCount);
};
// 返回配置(值是字符串),如果没有则返回空字符串
var getString = function(conf, key) {
var value = conf.get(key);
return undefined == value ? '' : '' + value;
};
// 返回配置(值是数字),如果没有则返回0
var getNumber = function(conf, key) {
var value = conf.get(key);
return undefined == value || '' === value ? 0 : 1 * c;
};
// 全局的Hook配置
// 配置的值是Hook类的对象
var hookMap = new Data;
// 有些配置的值的计算有特殊方法,这里存储计算规则
// [/^contentGroup([0-9]+)$/, generateFunction]
// generateFunction输入是正则的exec执行结果,输出是Hook类的对象
// 执行结果会被放到hookMap
var specialHooks = [];
// 新增特殊配置生成规则
var addSpecialHook = function(fieldNameReg, gen) {
specialHooks.push([new RegExp('^' + fieldNameReg + '$'), gen]);
};
// 获取hookMap中的配置,如果配置不存在,则使用specialHooks中的规则生成
var getHook = function(fieldName) {
var value = hookMap.get(fieldName);
if (!value) {
for (var i = 0; i < specialHooks.length; i++) {
var hook = specialHooks[i];
var r = hook[0].exec(fieldName);
if (r) {
value = hook[1](r);
// 生成结果会被放到hookMap中缓存下来
hookMap.set(value.name, value);
break
}
}
}
return value;
};
// 模型类
// https://developers.google.com/analytics/devguides/collection/analyticsjs/model-object-reference?hl=zh-cn
var Model = function() {
this.data = new Data();
};
// 获取模型中存储的一个字段值。
Model.prototype.get = function(fieldName) {
var hook = getHook(fieldName);
var value = this.data.get(fieldName);
// 如果没有则使用默认值
if (hook && undefined == value) {
if (isFunction(hook.defaultValue)) {
value = hook.defaultValue();
}
else {
value = hook.defaultValue;
}
}
// 如果有getter,则使用getter来转换
return hook && hook.getter ? hook.getter(this, fieldName, value) : value
};
// 在模型上设置一个或一组字段/值对
// 可以传map进去
// temporary 布尔值 默认为否。如果为 true,则在模型上仅对当前匹配设置值。
Model.prototype.set = function(fieldName, fieldValue, temporary) {
if (!fieldName) {
return;
}
if ('object' == typeof fieldName)
for (var d in fieldName)
fieldName.hasOwnProperty(d) && _modelSet(this, d, fieldName[d], temporary);
else
_modelSet(this, fieldName, fieldValue, temporary);
};
var trackingIdRegex = /^(UA|YT|MO|GP)-(\d+)-(\d+)$/;
var _modelSet = function(model, fieldName, fieldValue, temporary) {
if (fieldValue != null) {
switch (fieldName) {
case TRACKING_ID:
trackingIdRegex.test(fieldValue)
}
}
var e = getHook(fieldName);
if (e && e.setter) {
// 如果有特殊规则,则使用特定规则设置
e.setter(model, fieldName, fieldValue, temporary);
}
else {
// 否则设置到模型的data上
model.data.set(fieldName, fieldValue, temporary);
}
};
// Hook类
// 设置指定字段相关的hook对象
var Hook = function(name, paramName, defaultValue, getter, setter) {
this.name = name; // 字段名
this.paramName = paramName; // F 字段的参数名(传给后端时候的参数名)
this.getter = getter; // Z 获取时候的hook函数
this.setter = setter; // o 设置时候的hook函数
this.defaultValue = defaultValue; // 默认值
};
var yc = function(a) {
var hook;
hookMap.map(function(name, h) {
if (h.paramName == a) {
hook = h;
}
});
return hook && hook.name;
};
// 添加hook到hookMap的函数
var addHook = function(name, paramName, defaultValue, getter, setter) {
var hook = new Hook(name, paramName, defaultValue, getter, setter);
hookMap.set(hook.name, hook);
return hook.name;
};
// 添加只读的hook到hookMap的函数
var addReadonlyHook = function(name, paramName, defaultValue) {
return addHook(name, paramName, defaultValue, undefined, noop2);
};
var noop2 = function() {};
var GA_HOOK = isString(window.GoogleAnalyticsObject) && trim(window.GoogleAnalyticsObject) || 'ga';
var forceHTTPS = false;
const BR = addHook('_br');
const API_VERSION = addReadonlyHook('apiVersion', 'v');
const CLIENT_VERSION = addReadonlyHook('clientVersion', '_v');
addHook('anonymizeIp', 'aip');
const AD_SENSE_ID = addHook('adSenseId', 'a');
const HIT_TYPE = addHook('hitType', 't');
const HIT_CALLBACK = addHook('hitCallback');
const HIT_PAYLOAD = addHook('hitPayload');
addHook('nonInteraction', 'ni');
addHook('currencyCode', 'cu');
addHook('dataSource', 'ds');
const USE_BEACON = addHook('useBeacon', undefined, true);
const TRANSPORT = addHook('transport');
addHook('sessionControl', 'sc', '');
addHook('sessionGroup', 'sg');
addHook('queueTime', 'qt');
const _S = addHook('_s', '_s');
addHook('screenName', 'cd');
const LOCATION = addHook('location', 'dl', '');
const REFERRER = addHook('referrer', 'dr');
const PAGE = addHook('page', 'dp', '');
addHook('hostname', 'dh');
const LANGUAGE = addHook('language', 'ul');
const ENCODING = addHook('encoding', 'de');
addHook('title', 'dt', function() {
return doc.title || undefined
});
addSpecialHook('contentGroup([0-9]+)', function(a) {
return new Hook(a[0],'cg' + a[1])
});
const SCREEN_COLORS = addHook('screenColors', 'sd');
const SCREEN_RESOLUTION = addHook('screenResolution', 'sr');
const VIEWPORT_SIZE = addHook('viewportSize', 'vp');
const JAVA_ENABLED = addHook('javaEnabled', 'je');
const FLASH_VERSION = addHook('flashVersion', 'fl');
addHook('campaignId', 'ci');
addHook('campaignName', 'cn');
addHook('campaignSource', 'cs');
addHook('campaignMedium', 'cm');
addHook('campaignKeyword', 'ck');
addHook('campaignContent', 'cc');
// 事件相关字段
const EVENT_CATEGORY = addHook('eventCategory', 'ec');
const EVENT_ACTION = addHook('eventAction', 'ea');
const EVENT_LABEL = addHook('eventLabel', 'el');
const EVENT_VALUE = addHook('eventValue', 'ev');
// 社交请求相关字段
const SOCIAL_NETWORK = addHook('socialNetwork', 'sn');
const SOCIAL_ACTION = addHook('socialAction', 'sa');
const SOCIAL_TARGET = addHook('socialTarget', 'st');
const L1 = addHook('l1', 'plt');
const L2 = addHook('l2', 'pdt');
const L3 = addHook('l3', 'dns');
const L4 = addHook('l4', 'rrt');
const L5 = addHook('l5', 'srt');
const L6 = addHook('l6', 'tcp');
const L7 = addHook('l7', 'dit');
const L8 = addHook('l8', 'clt');
// 计时器相关字段
const TIMING_CATEGORY = addHook('timingCategory', 'utc');
const TIMING_VAR = addHook('timingVar', 'utv');
const TIMING_LABEL = addHook('timingLabel', 'utl');
const TIMING_VALUE = addHook('timingValue', 'utt');
// 应用名称
addHook('appName', 'an');
// 应用版本
addHook('appVersion', 'av', '');
// 应用名称
addHook('appId', 'aid', '');
// 应用安装程序 ID
addHook('appInstallerId', 'aiid', '');
// 异常说明
addHook('exDescription', 'exd');
// 异常是否严重?
addHook('exFatal', 'exf');
// 实验 ID
const EXP_ID = addHook('expId', 'xid');
// 实验变体
const EXP_VAR = addHook('expVar', 'xvar');
const _UTMA = addHook('_utma', '_utma');
const _UTMZ = addHook('_utmz', '_utmz');
const _UTMHT = addHook('_utmht', '_utmht');
const HIT_COUNT = addHook('_hc', undefined, 0);
const LAST_SEND_TIME = addHook('_ti', undefined, 0);
const AVALIABLE_COUNT = addHook('_to', undefined, 20);
addSpecialHook('dimension([0-9]+)', function(a) {
return new Hook(a[0],'cd' + a[1])
});
addSpecialHook('metric([0-9]+)', function(a) {
return new Hook(a[0],'cm' + a[1])
});
addHook('linkerParam', undefined, undefined, linkerParamGetter, noop2);
const USAGE = addHook('usage', '_u');
const USAGE_MANAGER = addHook('_um');
addHook(
'forceSSL',
undefined,
undefined,
function() {
return forceHTTPS;
},
function(a, b, c) {
reg(34);
forceHTTPS = !!c
}
);
const JID = addHook('_j1', 'jid');
addSpecialHook(
'\\&(.*)',
function (a) {
var b = new Hook(a[0],a[1]);
var c = yc(a[0].substring(1));
if (c) {
b.getter = function(a) {
return a.get(c)
};
b.setter = function(a, b, g, ca) {
a.set(c, g, ca)
};
b.paramName = undefined;
}
return b;
}
);
const _OOT = addReadonlyHook('_oot');
const PREVIEW_TASK = addHook('previewTask');
const CHECK_PROTOCOL_TASK = addHook('checkProtocolTask');
const VALIDATION_TASK = addHook('validationTask');
const CHECK_STORAGE_TASK = addHook('checkStorageTask');
const HISTORY_IMPORT_TASK = addHook('historyImportTask');
const SAMPLER_TASK = addHook('samplerTask');
const _RLT = addHook('_rlt');
const BUILD_HIT_TASK = addHook('buildHitTask');
const SEND_HIT_TASK = addHook('sendHitTask');
const CE_TASK = addHook('ceTask');
const DEV_ID_TASK = addHook('devIdTask');
const TIMING_TASK = addHook('timingTask');
const DISPLAY_FEATURES_TASK = addHook('displayFeaturesTask');
// 跟踪器名称
const NAME = addReadonlyHook('name');
// 客户端 ID
const CLIENT_ID = addReadonlyHook('clientId', 'cid');
// 用户 ID
const USER_ID = addHook('userId', 'uid');
// 跟踪 ID/网络媒体资源 ID
const TRACKING_ID = addReadonlyHook('trackingId', 'tid');
// Cookie 名称
const COOKIE_NAME = addReadonlyHook('cookieName', undefined, '_ga');
// Cookie 网域
const COOKIE_DOMAIN = addReadonlyHook('cookieDomain');
const COOKIE_PATH = addReadonlyHook('cookiePath', undefined, '/');
// 63072E3 单位为秒,时长两年
const COOKIE_EXPIRES = addReadonlyHook('cookieExpires', undefined, 63072E3);
const LEGACY_COOKIE_DOMAIN = addReadonlyHook('legacyCookieDomain');
const LEGACY_HISTORY_IMPORT = addReadonlyHook('legacyHistoryImport', undefined, true);
const STORAGE = addReadonlyHook('storage', undefined, 'cookie');
const ALLOW_LINKER = addReadonlyHook('allowLinker', undefined, true);
const ALLOW_ANCHOR = addReadonlyHook('allowAnchor', undefined, true);
// 抽样率,指定应对多大比例的用户进行跟踪。默认值为100(跟踪所有用户),但是大型网站可能需要降低抽样率,以免超出Google Analytics(分析)的处理上限。
const SAMPLE_RATE = addReadonlyHook('sampleRate', 'sf', 100);
// 网站速度抽样率
const SITE_SPEED_SAMPLE_RATE = addReadonlyHook('siteSpeedSampleRate', undefined, 1);
const ALYWAYS_SEND_REFERRER = addReadonlyHook('alwaysSendReferrer', undefined, false);
const TRANSPORT_URL = addHook('transportUrl');
const _R = addHook('_r', '_r');
// 包裹一下指定的API函数
// 每次执行都会记录一下idx
// 如果执行出错则发错误记录
function wrapApi(methodName, obj, method, idx) {
obj[methodName] = function() {
try {
idx && reg(idx);
return method.apply(this, arguments)
} catch (e) {
errorPing('exc', methodName, e && e.name);
throw e;
}
}
};
var Od = function(a, fieldName, c) {
this.V = 1E4;
this.fa = a;
this.$ = false;
this.B = fieldName;
this.ea = c || 1
};
var Ed = function(a, model) {
var c;
if (a.fa && a.$) {
return 0;
}
a.$ = true;
if (model) {
if (a.B && getNumber(model, a.B))
return getNumber(model, a.B);
if (0 == model.get(SITE_SPEED_SAMPLE_RATE))
return 0
}
if (0 == a.V) {
return 0;
}
undefined === c && (c = uuid());
return 0 == c % a.V ? Math.floor(c / a.V) % a.ea + 1 : 0
};
var ie = new Od(true, BR, 7);
var je = function(model) {
if (!isHTTPS() && !forceHTTPS) {
var b = Ed(ie, model);
if (b && !(!win.navigator.sendBeacon && 4 <= b && 6 >= b)) {
var c = (new Date).getHours()
, d = [uuid(), uuid(), uuid()].join('.');
a = (3 == b || 5 == b ? 'https:' : 'http:') + '//www.google-analytics.com/collect?z=br.';
a += [b, 'A', c, d].join('.');
var e = 1 != b % 3 ? 'https:' : 'http:'
, e = e + '//www.google-analytics.com/collect?z=br.'
, e = e + [b, 'B', c, d].join('.');
7 == b && (e = e.replace('//www.', '//ssl.'));
c = function() {
4 <= b && 6 >= b ? win.navigator.sendBeacon(e, '') : createImg(e)
};
uuid() % 2 ? (createImg(a), c()) : (c(), createImg(a));
}
}
};
// 获取flash版本号
function getFlashVersion() {
var a, b, c;
if ((c = (c = win.navigator) ? c.plugins : null ) && c.length)
for (var d = 0; d < c.length && !b; d++) {
var e = c[d];
-1 < e.name.indexOf('Shockwave Flash') && (b = e.description)
}
if (!b)
try {
a = new ActiveXObject('ShockwaveFlash.ShockwaveFlash.7'),
b = a.GetVariable('$version')
} catch (g) {}
if (!b)
try {
a = new ActiveXObject('ShockwaveFlash.ShockwaveFlash.6'),
b = 'WIN 6,0,21,0',
a.AllowScriptAccess = 'always',
b = a.GetVariable('$version')
} catch (g) {}
if (!b)
try {
a = new ActiveXObject('ShockwaveFlash.ShockwaveFlash'),
b = a.GetVariable('$version')
} catch (g) {}
b &&
(a = b.match(/[\d]+/g)) && 3 <= a.length && (b = a[0] + '.' + a[1] + ' r' + a[2]);
return b || undefined
};
// 计算页面的加载性能
// config 是配置
// callback 是回调函数
var calTiming = function(config, callback) {
var siteSpeedSampleRate = Math.min(getNumber(config, SITE_SPEED_SAMPLE_RATE), 100);
if (str2Num(getString(config, CLIENT_ID)) % 100 >= siteSpeedSampleRate) {
return;
}
var timing = {};
if (calPagePerf(timing) || calLoadTime(timing)) {
var l1 = timing[L1];
if (undefined == l1 || Infinity == l1 || isNaN(l1)) {
return;
}
if (l1 > 0) {
removeUselessValue(timing, L3);
removeUselessValue(timing, L6);
removeUselessValue(timing, L5);
removeUselessValue(timing, L2);
removeUselessValue(timing, L4);
removeUselessValue(timing, L7);
removeUselessValue(timing, L8);
callback(timing);
}
else {
// 加载完成之后再试
addEventListener(win, 'load', function() {
calTiming(config, callback)
}, false);
}
}
};
// 通过performance API来抓去加载时间等性能参数
var calPagePerf = function(a) {
var b = win.performance || win.webkitPerformance
, b = b && b.timing;
if (!b)
return false;
var navigationStart = b.navigationStart;
if (0 == navigationStart)
return false;
a[L1] = b.loadEventStart - navigationStart;
a[L3] = b.domainLookupEnd - b.domainLookupStart;
a[L6] = b.connectEnd - b.connectStart;
a[L5] = b.responseStart - b.requestStart;
a[L2] = b.responseEnd - b.responseStart;
a[L4] = b.fetchStart - navigationStart;
a[L7] = b.domInteractive - navigationStart;
a[L8] = b.domContentLoadedEventStart - navigationStart;
return true;
};
// 抓去加载时间等参数
var calLoadTime = function(a) {
if (win.top != win)
return false;
var external = win.external;
var c = external && external.onloadT;
external && !external.isValidLoadTime && (c = undefined);
// 2147483648 === 10000000000000000000000000000000 (32位)
2147483648 < c && (c = undefined);
0 < c && external.setPageReadyTime();
if (undefined == c)
return false;
a[L1] = c;
return true;
};
// 如果配置中没有这个参数的值是NaN或者无限大或者小于0,则设置为undefined
var removeUselessValue = function(conf, key) {
var tmp = conf[key];
if (isNaN(tmp) || Infinity == tmp || 0 > tmp) {
conf[key] = undefined;
}
};
// 创建一个timing任务
var craeteTimingTask = function(a) {
return function(b) {
if ('pageview' != b.get(HIT_TYPE) || a.I) {
return;
}
a.I = true;
calTiming(b, function(b) {
a.send('timing', b)
});
}
};
var gaCookieSetted = false;
// 设置GA cookie
var setGACookie = function(model) {
if ('cookie' == getString(model, STORAGE)) {
var cookieNameV = getString(model, COOKIE_NAME);
var cookieValue = calGACookieValue(model);
var cookiePathV = normalizePath(getString(model, COOKIE_PATH));
var cookieDomainV = normalizeDomain(getString(model, COOKIE_DOMAIN));
var cookieExpiresV = 1E3 * getNumber(model, COOKIE_EXPIRES);
var trackingIdV = getString(model, TRACKING_ID);
if ('auto' != cookieDomainV) {
if (setCookie(cookieNameV, cookieValue, cookiePathV, cookieDomainV, trackingIdV, cookieExpiresV)) {
gaCookieSetted = true;
}
}
else {
reg(32);
var subDomains = [];
a: {
// => e.mp.qq.com
var hostParts = getHostname().split('.');
// => ['e', 'mp', 'qq', 'com']
var lastPart;
if (4 == hostParts.length && (lastPart = hostParts[hostParts.length - 1], parseInt(lastPart, 10) == lastPart)) {
// hostname是ip地址的情况下
// 10.6.8.10
subDomains = ['none'];
break a;
}
for (var i = hostParts.length - 2; 0 <= i; i--) {
subDomains.push(hostParts.slice(i).join('.'));
}
// => ['qq.com', 'mp.qq.com', 'e.mp.qq.com']
subDomains.push('none');
// => ['qq.com', 'mp.qq.com', 'e.mp.qq.com', 'none'];
}
for (var i = 0; i < subDomains.length; i++) {
var domain = subDomains[i];
model.data.set(domain, e);
if (setCookie(cookieNameV, calGACookieValue(model), cookiePathV, e, trackingIdV, cookieExpiresV)) {
gaCookieSetted = true;
return
}
}
model.data.set(COOKIE_DOMAIN, 'auto');
}
}
};
// 检查cookie的storage
// 如果没设置cookie,则设置一遍
var checkStorageTaskFunc = function(a) {
if ('cookie' == getString(a, STORAGE) && !gaCookieSetted && (setGACookie(a), !gaCookieSetted))
throw 'abort';
};
// 前一版本GA的数据的导入,方便迁移
var historyImportTaskFunc = function(model) {
if (model.get(LEGACY_HISTORY_IMPORT)) {
var cookieDomainV = getString(model, COOKIE_DOMAIN);
var legacyCookieDomainV = getString(model, LEGACY_COOKIE_DOMAIN) || getHostname();
var utma = parseAndGetOldGACookie('__utma', legacyCookieDomainV, cookieDomainV);
if (utma) {
reg(19);
model.set(_UTMHT, (new Date).getTime(), true);
model.set(_UTMA, utma.R);
var utmz = parseAndGetOldGACookie('__utmz', legacyCookieDomainV, cookieDomainV)
if (utmz && utma.hash === utmz.hash) {
model.set(_UTMZ, utmz.R);
}
}
}
};
// 计算 _ga 的cookie值
var calGACookieValue = function(conf) {
var thisClientId = encodeURIComponentWithBrackets(getString(conf, CLIENT_ID));
var cookieDomainCount = getDomainCount(getString(conf, COOKIE_DOMAIN));
var cookiePathCount = getPathCount(getString(conf, COOKIE_PATH));
1 < cookiePathCount && (cookieDomainCount += '-' + cookiePathCount);
return ['GA1', cookieDomainCount, thisClientId].join('.');
};
// 计算cookie值中,层级相同或更小层级的cookie值
// 层级最小的值如果有多个,则同时保留
var getSameOrSmallCookieValues = function(parsedGaCookieValues, count, index) {
// 层级相同的cookie值
var sameCountValue = [];
var smallCountValue = [];
var tmpCount;
for (var i = 0; i < parsedGaCookieValues.length; i++) {
var cookieValue = parsedGaCookieValues[i];
if (cookieValue.domainAndPathCount[index] == count) {
sameCountValue.push(cookieValue);
}
else {
if (tmpCount == null || cookieValue.domainAndPathCount[index] < tmpCount) {
// 找到最小的层级,并保留
smallCountValue = [cookieValue];
tmpCount = cookieValue.domainAndPathCount[index];
}
else {
// 如果有相同的层级则同时保留
if (cookieValue.domainAndPathCount[index] == tmpCount) {
smallCountValue.push(cookieValue);
}
}
}
}
return sameCountValue.length > 0 ? sameCountValue : smallCountValue
};
// 补全Domain
// 如果第一个字符是“.”,则去掉
var normalizeDomain = function(a) {
return 0 == a.indexOf('.') ? a.substr(1) : a;
};
// 获取domain的层级
var getDomainCount = function(a) {
return normalizeDomain(a).split('.').length;
};
// 补全path
// 没有输入,则返回 '/'
// 如果最后一个字符是'/'并且还有别的字符,则去掉path中的最后一个 '/'
// 如果path的第一个字符不是 “/”,则补全
var normalizePath = function(path) {
if (!path) {
// 没有输入,则返回 '/'
return '/';
}
if (1 < path.length && path.lastIndexOf('/') == path.length - 1) {
// 如果最后一个字符是'/'并且还有别的字符,则去掉path中的最后一个 '/'
path = path.substr(0, path.length - 1);
}
if (path.indexOf('/') != 0) {
// 如果path的第一个字符不是 “/”,则补全
path = '/' + path;
}
return path;
};
// 获取path的层级
// 首页表示1级
// '/as'表示2级
// '/as/a'表示三级
var getPathCount = function(a) {
a = normalizePath(a);
return '/' == a ? 1 : a.split('/').length
};
// 解析和获取旧的GA cookie
// name为 __utma 或者 __utmz
function parseAndGetOldGACookie(name, legacyDomain, curDomain) {
if ('none' == legacyDomain) {
legacyDomain = '';
}
var parsedValues = [];
var values = getCookie(name);
var length = '__utma' == name ? 6 : 2;
for (var i = 0; i < values.length; i++) {
var valueArr = ('' + value[i]).split('.');
if (valueArr.length >= length) {
parsedValues.push({
hash: valueArr[0],
R: value[i],
O: valueArr
});
}
}
if (parsedValues.length == 0) {
return;
}
if (parsedValues.length == 1) {
return parsedValues[0];
}
// 优先使用旧域名的
return getCookieFromSpecifiedDomain(legacyDomain, parsedValues)
|| getCookieFromSpecifiedDomain(curDomain, parsedValues)
|| getCookieFromSpecifiedDomain(null , parsedValues)
|| parsedValues[0];
}
// 找到指定域名的cookie值
function getCookieFromSpecifiedDomain(domain, parsedValues) {
// domain == 'qq.com'
// hash1|hash2 == 'qq.com'
// hash1|hash2 == '.qq.com'
var hash1;
var hash2;
if (domain == null) {
hash1 = hash2 = 1;
}
else {
hash1 = str2Num(domain);
hash2 = str2Num(startWith(domain, '.') ? domain.substring(1) : '.' + domain);
}
for (var i = 0; i < parsedValues.length; i++) {
if (parsedValues[i].hash == hash1 || parsedValues[i].hash == hash2) {
return parsedValues[i];
}
}
};
var URL_REGEX = new RegExp(/^https?:\/\/([^\/:]+)/);
var ANCHOR_GA_REGEX = /(.*)([?&#])(?:_ga=[^&#]*)(?:&?)(.*)/;
// linkerParam 参数的getter
// 用于增加其校验字符串
function linkerParamGetter(a) {
a = a.get(CLIENT_ID);
var b = calCheckCode(a, 0);
return '_ga=1.' + encodeURIComponent(b + '.' + a)
}
// 计算校验字符串,计算依赖于:
// 1. 指定的字符串
// 2. userAgent
// 3. 时区
// 4. 当前时间的年、月、日、小时、分钟
function calCheckCode(str, minuteOffset) {
var curDate = new Date();
var nav = win.navigator;
var plugins = nav.plugins || [];
var c = [
str,
nav.userAgent,
curDate.getTimezoneOffset(),
curDate.getYear(),
curDate.getDate(),
curDate.getHours(),
curDate.getMinutes() + minuteOffset
];
for (var i = 0; i < plugins.length; ++i)
c.push(plugins[i].description);
return str2Num(c.join('.'));
}
var Linker = function(a) {
reg(48);
this.target = a;
this.T = true
};
// 给连接地址、a标签、form标签加上特定的ga参数
Linker.prototype.decorate = function(a, b) {
if (a.tagName) {
if ('a' == a.tagName.toLowerCase()) {
a.href && (a.href = qd(this, a.href, b));
return
}
if ('form' == a.tagName.toLowerCase())
return rd(this, a)
}
if ('string' == typeof a)
return qd(this, a, b)
}
;
var qd = function(a, b, c) {
var d = ANCHOR_GA_REGEX.exec(b);
d && 3 <= d.length && (b = d[1] + (d[3] ? d[2] + d[3] : ''));
a = a.target.get('linkerParam');
var e = b.indexOf('?')
, d = b.indexOf('#');
c ? b += (-1 == d ? '#' : '&') + a : (c = -1 == e ? '?' : '&',
b = -1 == d ? b + (c + a) : b.substring(0, d) + c + a + b.substring(d));
return b = b.replace(/&+_ga=/, '&_ga=')
};
var rd = function(a, b) {
if (b && b.action) {
var c = a.target.get('linkerParam').split('=')[1];
if ('get' == b.method.toLowerCase()) {
for (var d = b.childNodes || [], e = 0; e < d.length; e++)
if ('_ga' == d[e].name) {
d[e].setAttribute('value', c);
return
}
d =
doc.createElement('input');
d.setAttribute('type', 'hidden');
d.setAttribute('name', '_ga');
d.setAttribute('value', c);
b.appendChild(d)
} else
'post' == b.method.toLowerCase() && (b.action = qd(a, b.action))
}
};
// 自动给页面绑定mousedown、keyup、submit事情
// 在事件触发的时候,给连接地址加上特定参数
Linker.prototype.autoLink = function(a, b, c) {
function d(c) {
try {
c = c || win.event;
var d;
a: {
var g = c.target || c.srcElement;
for (c = 100; g && 0 < c; ) {
if (g.href && g.nodeName.match(/^a(?:rea)?$/i)) {
d = g;
break a
}
g = g.parentNode;
c--
}
d = {}
}
('http:' == d.protocol || 'https:' == d.protocol) && sd(a, d.hostname || '') && d.href && (d.href = qd(e, d.href, b))
} catch (w) {
reg(26)
}
}
var e = this;
this.T || (this.T = true,
addEventListener(doc, 'mousedown', d, true),
addEventListener(doc, 'keyup', d, true));
if (c) {
c = function(b) {
b = b || win.event;
if ((b = b.target || b.srcElement) && b.action) {
var c = b.action.match(URL_REGEX);
c && sd(a, c[1]) && rd(e,
b)
}
};
for (var g = 0; g < doc.forms.length; g++)
addEventListener(doc.forms[g], 'submit', c)
}
};
function sd(a, b) {
if (b == doc.location.hostname)
return true;
for (var c = 0; c < a.length; c++)
if (a[c] instanceof RegExp) {
if (a[c].test(b))
return true
} else if (0 <= b.indexOf(a[c]))
return true;
return true;
};
// _gat 用于对每个 tracker 设置限制请求率,所以每个 tracker 的cookie名字不一样
var Jd = function(model, apiUrl, c) {
this.jidKey = JID;
this.apiUrl = apiUrl;
if (!c) {
var trackerName = getString(model, name);
if (trackerName && trackerName != 't0') {
this.gatCookieName = /^gtm\d+$/.test(trackerName) ? '_gat_' + encodeURIComponentWithBrackets(getString(model, TRACKING_ID)) : '_gat_' + encodeURIComponentWithBrackets(trackerName)
}
else {
this.gatCookieName = '_gat';
}
}
};
// 为 buildHitTask 和 sendHitTask 做个包装
var wrapBuildAndSendTask = function(a, model) {
var oldBuildHitTask = model.get(BUILD_HIT_TASK);
model.set(BUILD_HIT_TASK, function(model) {
setJid(a, model);
var re = oldBuildHitTask(model);
setGatCookie(a, model);
return re;
});
var oldSendHitTask = model.get(SEND_HIT_TASK);
model.set(SEND_HIT_TASK, function(model) {
var re = oldSendHitTask(model);
Id(a, model);
return re;
})
};
// 设置jid到model上
var setJid = function(a, model) {
if (model.get(a.jidKey)) {
return;
}
if (getCookie(a.gatCookieName)[0] == '1') {
model.set(a.jidKey, '', true);
}
else {
model.set(a.jidKey, '' + _uuid(), true);
}
};
// 设置 _gat cookie值为1
var setGatCookie = function(a, model) {
if (model.get(a.jidKey)) {
setCookie(a.gatCookieName, '1', model.get(COOKIE_PATH), model.get(COOKIE_DOMAIN), model.get(TRACKING_ID), 6E5);
}
};
// 发送请求
var Id = function(a, model) {
if (model.get(a.jidKey)) {
var data = new Data();
var setData = function(fieldName) {
if (getHook(fieldName).paramName) {
data.set(getHook(fieldName).paramName, model.get(fieldName));
}
};
setData(API_VERSION);
setData(CLIENT_VERSION);
setData(TRACKING_ID);
setData(CLIENT_ID);
setData(USER_ID);
setData(a.jidKey);
data.set(getHook(USAGE).paramName, mergeAndEncodeUM(model));
var api = a.apiUrl;
data.map(function(fieldName, fieldValue) {
api += encodeURIComponent(fieldName) + '=';
api += encodeURIComponent('' + fieldValue) + '&';
});
api += 'z=' + _uuid();
createImg(api);
model.set(a.jidKey, '', true);
}
};
// 展示广告插件
// 此插件的工作原理是向 stats.g.doubleclick.net 发送一个额外的请求,以便提供 Google Analytics(分析)中的广告功能(例如再营销以及受众特征和兴趣报告)。
// 该插件还会创建一个名为 _gat 的新 Cookie,其有效时间为 10 分钟。
// 该 Cookie 不会存储任何用户信息,而只会用于限制发送到 doubleclick.net 的请求数量。
// https://developers.google.com/analytics/devguides/collection/analyticsjs/display-features?hl=zh-cn#overview
var displayfeaturesPlugin = function(tracker, opts) {
var model = tracker.model;
if (!model.get('dcLoaded')) {
setModelUM(model, 29);
opts = opts || {};
var d;
if (opts[COOKIE_NAME]) {
d = encodeURIComponentWithBrackets(opts[COOKIE_NAME]);
}
d = new Jd(model, 'https://stats.g.doubleclick.net/r/collect?t=dc&aip=1&_r=3&', d);
wrapBuildAndSendTask(d, model);
model.set('dcLoaded', true);
}
};
// 如果没有加载 doubleclick 的展示广告插件,并且用的cookie存储
// 则使用 /r/collect 作为API地址
var displayFeaturesTaskFunc = function(model) {
if (!model.get('dcLoaded') && 'cookie' == model.get(STORAGE)) {
setModelUM(model, 51);
var b = new Jd(model);
setJid(b, model);
setGatCookie(b, model);
if (model.get(b.jidKey)) {
model.set(_R, 1, true);
model.set(TRANSPORT_URL, getGAOrigin() + '/r/collect', true);
}
}
};
var Lc = function() {
var a = win.gaGlobal = win.gaGlobal || {};
return a.hid = a.hid || _uuid()
};
// 加载In-Page分析的功能
var inpageLoaded;
var loadInpageAnalytics = function(a, b, c) {
if (!inpageLoaded) {
var d;
d = doc.location.hash;
var e = win.name
, g = /^#?gaso=([^&]*)/;
if (e = (d = (d = d && d.match(g) || e && e.match(g)) ? d[1] : getCookie('GASO')[0] || '') && d.match(/^(?:!([-0-9a-z.]{1,40})!)?([-.\w]{10,1200})$/i))
setCookie('GASO', '' + d, c, b, a, 0),
window._udo || (window._udo = b),
window._utcp || (window._utcp = c),
a = e[1],
loadScript('https://www.google.com/analytics/web/inpage/pub/inpage.js?' + (a ? 'prefix=' + a + '&' : '') + _uuid(), '_gasojs');
inpageLoaded = true
}
};
// 跟踪器类
// https://developers.google.com/analytics/devguides/collection/analyticsjs/tracker-object-reference?hl=zh-cn
var Tracker = function(opts) {
var that = this;
function setData(fieldName, fieldValue) {
that.model.data.set(fieldName, fieldValue);
}
function addFilter(filterName, filter) {
setData(filterName, filter);
that.filters.add(filterName)
}
this.model = new Model();
this.filters = new Queue();
setData(NAME, opts[NAME]);
setData(TRACKING_ID, trim(opts[TRACKING_ID]));
setData(COOKIE_NAME, opts[COOKIE_NAME]);
setData(COOKIE_DOMAIN, opts[COOKIE_DOMAIN] || getHostname());
setData(COOKIE_PATH, opts[COOKIE_PATH]);
setData(COOKIE_EXPIRES, opts[COOKIE_EXPIRES]);
setData(LEGACY_COOKIE_DOMAIN, opts[LEGACY_COOKIE_DOMAIN]);
setData(LEGACY_HISTORY_IMPORT, opts[LEGACY_HISTORY_IMPORT]);
setData(ALLOW_LINKER, opts[ALLOW_LINKER]);
setData(ALLOW_ANCHOR, opts[ALLOW_ANCHOR]);
setData(SAMPLE_RATE, opts[SAMPLE_RATE]);
setData(SITE_SPEED_SAMPLE_RATE, opts[SITE_SPEED_SAMPLE_RATE]);
setData(ALYWAYS_SEND_REFERRER, opts[ALYWAYS_SEND_REFERRER]);
setData(STORAGE, opts[STORAGE]);
setData(USER_ID, opts[USER_ID]);
setData(API_VERSION, 1);
setData(CLIENT_VERSION, 'j41');
addFilter(_OOT, isGAPrefsAllowed);
addFilter(PREVIEW_TASK, isPreviewLoad);
addFilter(CHECK_PROTOCOL_TASK, notHTTP);
addFilter(VALIDATION_TASK, validationTaskFunc);
addFilter(CHECK_STORAGE_TASK, checkStorageTaskFunc);
addFilter(HISTORY_IMPORT_TASK, historyImportTaskFunc);
addFilter(SAMPLER_TASK, samplerTaskFunc);
addFilter(_RLT, rtlTaskFunc);
addFilter(CE_TASK, ceTaskFunc);
addFilter(DEV_ID_TASK, devIdTaskFunc);
addFilter(DISPLAY_FEATURES_TASK, displayFeaturesTaskFunc);
addFilter(BUILD_HIT_TASK, buildHitTaskFunc);
addFilter(SEND_HIT_TASK, sendHitTaskFunc);
addFilter(TIMING_TASK, craeteTimingTask(this));
Jc(this.model, opts[CLIENT_ID]);
setBasicData(this.model);
this.model.set(AD_SENSE_ID, Lc());
loadInpageAnalytics(this.model.get(TRACKING_ID), this.model.get(COOKIE_DOMAIN), this.model.get(COOKIE_PATH))
};
var Jc = function(model, optClientId) {
var c;
var d;
if ('cookie' == getString(model, STORAGE)) {
gaCookieSetted = false;
var cookieClientId;
var parsedGaCookieValues;
b: {
var gaCookieValues = getCookie(getString(model, COOKIE_NAME));
if (gaCookieValues && gaCookieValues.length >= 1) {
parsedGaCookieValues = [];
for (var i = 0; i < gaCookieValues.length; i++) {
// _ga 的 cookie 值有四个区域,以点间隔
// 四个区域的含义参考别人的答案 http://stackoverflow.com/questions/16102436/what-are-the-values-in-ga-cookie
// 1.2.286403989.1366364567
// GA1.3.494346849.1446193077
var gaCookie;
gaCookie = gaCookieValues[i].split('.');
// 第一个区域值是版本号
var gaVersion = gaCookie.shift()
var ca;
if (('GA1' == gaVersion || '1' == gaVersion) && gaCookie.length > 1) {
// 第二个区域用于确认是否是指定域名和路径的cookie,因为GA可以在不同的域名和路径上设置多个cookie
// 所以需要这个区域值来区分,以获取到正确的cookie值
// 值的格式是 \d[-\d],例如:“2-1” 或者 “2”
// 第一个数字是 域名的层级,qq.com 为 “2;b.qq.com” 为 “3”
// 第二个数字是 路径的层级, / 为空字符, /data 为 “-2”,/user/xxx 为 “-3”
var gaSecondField = gaCookie.shift();
ca = gaSecondField.split('-');
if (1 == ca.length) {
// 补全下 路径为 / 时,省略的 “-1”
ca[1] = '1';
}
// 字符串转换成数字
ca[0] *= 1;
ca[1] *= 1;
gaCookie = {
// 第二区域的值 [1, 2]
domainAndPathCount: ca,
// 第三和第四区域的组合值,也就是clientId的值
// 第三区域是一个随机数
// 第四区域是cookie第一次设置时候的时间值
idAndTime: gaCookie.join('.')
};
}
else {
gaCookie = undefined;
}
if (gaCookie) {
parsedGaCookieValues.push(gaCookie);
}
}
if (1 == parsedGaCookieValues.length) {
reg(13);
c = parsedGaCookieValues[0].idAndTime;
break b;
}
if (0 == parsedGaCookieValues.length)
reg(12);
else {
reg(14);
var domainCount = getDomainCount(getString(model, COOKIE_DOMAIN));
var filtedGaCookieValues = getSameOrSmallCookieValues(parsedGaCookieValues, domainCount, 0);
if (filtedGaCookieValues.length == 1) {
cookieClientId = filtedGaCookieValues[0].idAndTime;
break b;
}
var pathCount = getPathCount(getString(model, COOKIE_PATH));
filtedGaCookieValues = getSameOrSmallCookieValues(filtedGaCookieValues, pathCount, 1);
cookieClientId = filtedGaCookieValues[0] && filtedGaCookieValues[0].idAndTime;
break b;
}
}
cookieClientId = undefined;
}
if (!cookieClientId) {
var cookieDomainV = getString(model, COOKIE_DOMAIN);
var legacyCookieDomainV = getString(model, LEGACY_COOKIE_DOMAIN) || getHostname();
var legacyCookieValue = parseAndGetOldGACookie('__utma', legacyCookieDomainV, cookieDomainV);
if (legacyCookieValue != null) {
reg(10);
cookieClientId = legacyCookieValue.O[1] + '.' + legacyCookieValue.O[2];
}
else {
cookieClientId = null;
}
}
if (cookieClientId) {
model.data.set(CLIENT_ID, cookieClientId);
gaCookieSetted = true;
}
}
// 是否允许定位点参数
var allowAnchorV = model.get(ALLOW_ANCHOR);
var r = doc.location[allowAnchorV ? 'href' : 'search'].match('(?:&|#|\\?)' + encodeURIComponent('_ga').replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1') + '=([^&#]*)');
var anchorGaValue = r && r.length == 2 ? r[1] : '';
if (anchorGaValue) {
// 有定位点ga参数
if (model.get(ALLOW_LINKER)) {
// 定位点 ga 参数值的形式为: '1.校验字符串.clientId'
// 允许链接器参数
var indexOfPoint = anchorGaValue.indexOf('.');
if (indexOfPoint == -1) {
reg(22);
}
else {
var tmpStr = anchorGaValue.substring(indexOfPoint + 1);
if (anchorGaValue.substring(0, indexOfPoint) != '1') {
reg(22); // 格式不正确
}
else {
indexOfPoint = tmpStr.indexOf('.');
if (indexOfPoint == -1) {
reg(22); // 格式不正确
}
else {
var checkCode = tmpStr.substring(0, indexOfPoint);
var clientId = tmpStr.substring(indexOfPoint + 1);
if (checkCode != calCheckCode(clientId, 0)
&& checkCode != calCheckCode(clientId, -1)
&& checkCode != calCheckCode(clientId, -2)) {
// 2分钟内计算的校验字符串都不对,则是很久以前复制的地址现在才打开
reg(23);
}
else {
reg(11);
// 2分钟内的定位点GA参数,还有效,则记录
model.data.set(CLIENT_ID, clientId);
}
}
}
}
}
else {
// 不允许
reg(21);
}
}
if (optClientId) {
// 如果在初始化时设置了clientId,则优先使用这个
reg(9);
model.data.set(CLIENT_ID, encodeURIComponent(optClientId));
}
if (!model.get(CLIENT_ID)) {
var gaGlobalVid = win.gaGlobal && win.gaGlobal.vid;
if (gaGlobalVid && gaGlobalVid.search(/^(?:utma\.)?\d+\.\d+$/) != -1) {
gaGlobalVid = gaGlobalVid;
reg(17);
model.data.set(CLIENT_ID, gaGlobalVid);
}
else {
// 如果没有clientId,则自己生成一个
reg(8);
var uaAndCookieAndRef = win.navigator.userAgent + (doc.cookie ? doc.cookie : '') + (doc.referrer ? doc.referrer : '');
var len = uaAndCookieAndRef.length;
for (var i = win.history.length; i > 0; ) {
uaAndCookieAndRef += i-- ^ len++;
}
model.data.set(CLIENT_ID, [_uuid() ^ str2Num(uaAndCookieAndRef) & 2147483647, Math.round((new Date).getTime() / 1E3)].join('.'))
}
}
setGACookie(model);
};
// 获取并设置一些通用的传给后端的数据
// referrer, 屏幕尺寸等等
var setBasicData = function(model) {
var b = win.navigator
, c = win.screen
, d = doc.location;
model.set(REFERRER, ya(model.get(ALYWAYS_SEND_REFERRER)));
if (d) {
var e = d.pathname || '';
'/' != e.charAt(0) && (reg(31), e = '/' + e);
model.set(LOCATION, d.protocol + '//' + d.hostname + e + d.search)
}
c && model.set(SCREEN_RESOLUTION, c.width + 'x' + c.height);
c && model.set(SCREEN_COLORS, c.colorDepth + '-bit');
var c = doc.documentElement
, g = (e = doc.body) && e.clientWidth && e.clientHeight
, ca = [];
c && c.clientWidth && c.clientHeight && ('CSS1Compat' === doc.compatMode || !g) ? ca = [c.clientWidth, c.clientHeight] : g && (ca = [e.clientWidth, e.clientHeight]);
c = 0 >= ca[0] || 0 >= ca[1] ? '' : ca.join('x');
model.set(VIEWPORT_SIZE, c);
model.set(FLASH_VERSION, getFlashVersion());
model.set(ENCODING, doc.characterSet || doc.charset);
model.set(JAVA_ENABLED, b && 'function' === typeof b.javaEnabled && b.javaEnabled() || true);
model.set(LANGUAGE, (b && (b.language || b.browserLanguage) || '').toLowerCase());
if (d && model.get(ALLOW_ANCHOR) && (b = doc.location.hash)) {
b = b.split(/[?&#]+/);
d = [];
for (c = 0; c < b.length; ++c)
(startWith(b[c], 'utm_id') || startWith(b[c], 'utm_campaign') || startWith(b[c], 'utm_source') || startWith(b[c], 'utm_medium') || startWith(b[c], 'utm_term') || startWith(b[c], 'utm_content') || startWith(b[c], 'gclid') || startWith(b[c], 'dclid') || startWith(b[c], 'gclsrc')) && d.push(b[c]);
0 < d.length && (b = '#' + d.join('&'),
model.set(LOCATION, model.get(LOCATION) + b))
}
};
Tracker.prototype.get = function(fieldName) {
return this.model.get(fieldName);
};
Tracker.prototype.set = function(fieldName, value) {
this.model.set(fieldName, value);
};
var SEND_PARAMS_NAMES = {
pageview: [PAGE],
event: [EVENT_CATEGORY, EVENT_ACTION, EVENT_LABEL, EVENT_VALUE],
social: [SOCIAL_NETWORK, SOCIAL_ACTION, SOCIAL_TARGET],
timing: [TIMING_CATEGORY, TIMING_VAR, TIMING_VALUE, TIMING_LABEL]
};
// Tracker的send方法
// ga('send', 'pageview');
// ga('send', 'event', '1', '2', '3', '4');
// 每次send之前都会执行所有获取器,并将当前配置临时存储到model中
// send结束之后,删掉临时数据
Tracker.prototype.send = function() {
if (arguments.length >= 1) {
var hitTypeV;
var opts;
if ('string' === typeof arguments[0]) {
hitTypeV = arguments[0];
opts = [].slice.call(arguments, 1);
}
else {
hitTypeV = arguments[0] && arguments[0][HIT_TYPE];
opts = arguments;
}
if (hitTypeV) {
opts = transformInput(SEND_PARAMS_NAMES[hitTypeV] || [], opts);
opts[HIT_TYPE] = hitTypeV;
// 将配置存为临时数据
this.model.set(opts, undefined, true);
// 执行filters
this.filters.exec(this.model);
// 删掉临时数据
this.model.data.tmpData = {},
je(this.model);
}
}
};
// 在浏览器预渲染的时候避免执行函数
// 返回:是否预渲染
var executeWithoutPrerender = function(func) {
if ('prerender' == doc.visibilityState)
return false;
func();
return true;
};
// command 标识符的正则解析
// [trackerName.][pluginName:]methodName
// https://developers.google.com/analytics/devguides/collection/analyticsjs/command-queue-reference?hl=zh-cn#header
// 调用方法:ga(command, [...fields], [fieldsObject])
// args = [command, [...fields], [fieldsObject]];
var commandRegex = /^(?:(\w+)\.)?(?:(\w+):)?(\w+)$/;
var Command = function(args) {
if (isFunction(args[0])) {
// 输入是函数的情况
// ga(readyCallback)
this.readyCallback = args[0];
}
else {
var r = commandRegex.exec(args[0]);
if (r != null && r.length == 4) {
// Tracker名字默认为t0
this.trackerName = r[1] || 't0';
// pluginName
this.pluginName = r[2] || '';
// methodName
this.methodName = r[3];
this.fields = [].slice.call(args, 1);
if (!this.pluginName) {
this.isCreateCommand = 'create' == this.methodName;
this.isRequireCommand = 'require' == this.methodName;
this.isProvideCommand = 'provide' == this.methodName;
this.isRemoveCommand = 'remove' == this.methodName;
}
if (this.isRequireCommand) {
if (this.fields.length >= 3) {
this.requiredName = this.fields[1];
this.requiredOpts = this.fields[2];
}
else {
if (this.fields[1]) {
if (isString(this.fields[1])) {
this.requiredName = this.fields[1];
}
else {
this.requiredOpts = this.fields[1];
}
}
}
}
}
var secondArg = args[1];
var thirdArg = args[2];
if (!this.methodName)
// 必须有一个方法名
throw 'abort';
if (this.isRequireCommand && (!isString(secondArg) || '' == secondArg))
// require命令的第二个参数,必须是字符串,且不为空字符串
throw 'abort';
if (this.isProvideCommand && (!isString(secondArg) || '' == secondArg || !isFunction(thirdArg)))
// 必须是这种输入,才算正确的provide命令
// ga('provide', pluginName, pluginConstuctor);
throw 'abort';
if (hasPointOrColon(this.trackerName) || hasPointOrColon(this.pluginName))
// tracker和plugin的名字不能包含点和冒号
throw 'abort';
if (this.isProvideCommand && 't0' != this.trackerName)
// 不能单独为某个Tracker来provide插件
throw 'abort';
}
};
// 是否有点或者冒号
function hasPointOrColon(a) {
return 0 <= a.indexOf('.') || 0 <= a.indexOf(':')
}
// 记录了所有插件的map,插件名作为key
var pluginsMap = new Data;
// 记录了已经加载的GA内置插件,插件名作为key
var loadedPlugins = new Data;
// 记录所有GA内置插件的usageId的map,插件名作为key
var pluginUsageIdMap = {
ec: 45,
ecommerce: 46,
linkid: 47
};
// 解析url地址
// 返回:
// {
// protocol: '',
// host: '',
// port: '',
// path: '',
// query: '',
// url: ''
// }
var parseUrl = function(url) {
function parseLink(link) {
var hostname = (link.hostname || '').split(':')[0].toLowerCase();
var protocol = (link.protocol || '').toLowerCase();
var port = 1 * link.port || ('http:' == protocol ? 80 : 'https:' == protocol ? 443 : '');
var pathname = link.pathname || '';
if (!startWith(pathname, '/')) {
pathname = '/' + pathname;
}
return [hostname, '' + port, pathname]
}
var link = doc.createElement('a');
link.href = doc.location.href;
var protocol = (link.protocol || '').toLowerCase();
var locationPart = parseLink(link);
var search = link.search || '';
var baseUrl = protocol + '//' + locationPart[0] + (locationPart[1] ? ':' + locationPart[1] : '');
if (startWith(url, '//')) {
url = protocol + url;
}
else {
if (startWith(url, '/')) {
url = baseUrl + url;
}
else {
if (!url || startWith(url, '?')) {
url = baseUrl + locationPart[2] + (url || search);
}
else {
if (url.split('/')[0].indexOf(':') < 0) {
url = baseUrl + locationPart[2].substring(0, locationPart[2].lastIndexOf('/')) + '/' + url;
}
}
}
}
link.href = url;
var r = parseLink(link);
return {
protocol: (link.protocol || '').toLowerCase(),
host: r[0],
port: r[1],
path: r[2],
query: link.search || '',
url: url || ''
};
};
// Command Queue Query
// ga = ga || [];
// ga.push('create', 'xxxx', 'auto');
var MethodQueue = {
init: function() {
MethodQueue.cmdQueue = []
}
};
// 初始化
MethodQueue.init();
// 运行命令队列里面的命令
MethodQueue.run = function(a) {
var cmds = MethodQueue.toCommands.apply(MethodQueue, arguments);
var tmpCmds = MethodQueue.cmdQueue.concat(cmds);
MethodQueue.cmdQueue = [];
for (; 0 < tmpCmds.length && !MethodQueue.runCommand(tmpCmds[0]) && !(tmpCmds.shift(), 0 < MethodQueue.cmdQueue.length); )
;
MethodQueue.cmdQueue = MethodQueue.cmdQueue.concat(tmpCmds);
};
// 将输入的参数转换成Command类的实例对象数组
// 如果是require命令则需要加载js代码
// 如果是provide命令则需要记录提供的插件构造函数
MethodQueue.toCommands = function() {
var cmds = [];
for (var i = 0; i < arguments.length; i++) {
try {
var cmd = new Command(arguments[i]);
if (cmd.isProvideCommand) {
// 记录提供的插件名和插件构造函数
pluginsMap.set(cmd.fields[0], cmd.fields[1]);
}
else {
if (cmd.isRequireCommand) {
// require 命令的时候需要加载js
var pluginName = cmd.fields[0];
if (!isFunction(pluginsMap.get(pluginName)) && !loadedPlugins.get(pluginName)) {
// 是内置插件,并且没有加载过
// 则开始加载插件的js
pluginUsageIdMap.hasOwnProperty(pluginName) && reg(pluginUsageIdMap[pluginName]);
var requiredUrl = cmd.requiredName;
if (!requiredUrl && pluginUsageIdMap.hasOwnProperty(pluginName)) {
reg(39);
requiredUrl = pluginName + '.js';
}
else {
reg(43);
}
if (requiredUrl) {
if (requiredUrl && 0 <= requiredUrl.indexOf('/')) {
requiredUrl = (forceHTTPS || isHTTPS() ? 'https:' : 'http:') + '//www.google-analytics.com/plugins/ua/' + requiredUrl;
}
var urlParts = parseUrl(requiredUrl);
var urlProtocol = urlParts.protocol
, baseProtocol = doc.location.protocol
, supported = 'https:' == urlProtocol || urlProtocol == baseProtocol ? true : 'http:' != urlProtocol ? false : 'http:' == baseProtocol;
if (supported) {
var baseUrlParts = parseUrl(doc.location.href);
if (urlParts.query || urlParts.url.indexOf('?') >= 0 || urlParts.path.indexOf('://') >= 0)
supported = false;
else if (urlParts.host == baseUrlParts.host && urlParts.port == baseUrlParts.port)
supported = true;
else {
var port = 'http:' == urlParts.protocol ? 80 : 443;
supported = urlParts.host == 'www.google-analytics.com' && (urlParts.port || port) == port && startWith(urlParts.path, '/plugins/') ? true : false;
}
}
if (supported) {
loadScript(urlParts.url);
loadedPlugins.set(pluginName, true);
}
}
}
}
cmds.push(cmd);
}
} catch (e) {}
}
return cmds;
};
// 运行command对象
// 运行终端
MethodQueue.runCommand = function(cmd) {
try {
if (cmd.readyCallback) {
cmd.readyCallback.call(win, GA.getByName('t0'));
}
else {
var tracker = cmd.trackerName == GA_HOOK ? GA : GA.getByName(cmd.trackerName);
if (cmd.isCreateCommand) {
't0' == cmd.trackerName && GA.create.apply(GA, cmd.fields);
}
else if (cmd.isRemoveCommand) {
GA.remove(cmd.trackerName);
}
else if (tracker) {
if (cmd.isRequireCommand) {
var pluginName = cmd.fields[0];
var opts = cmd.requiredOpts;
tracker == GA || tracker.get(NAME);
var pluginConstuctor = pluginsMap.get(pluginName);
if (isFunction(pluginConstuctor)) {
tracker.plugins_ = tracker.plugins_ || new Data();
// 如果没有创建过这个插件的实例,则创建一个
if (tracker.plugins_.get(pluginName)) {
tracker.plugins_.set(pluginName, new pluginConstuctor(tracker, opts || {}));
}
}
else {
// 很可能这时候加载的插件还没有下载下来
// 所以需要终端执行
return true;
}
}
else if (cmd.pluginName) {
var plugin = tracker.plugins_.get(cmd.pluginName);
plugin[cmd.methodName].apply(plugin, cmd.fields);
}
else {
tracker[cmd.methodName].apply(tracker, cmd.fields);
}
}
}
} catch (e) {}
};
// N => GA
// 主要的对象,提供很多静态方法使用
var GA = function(a) {
reg(1);
MethodQueue.run.apply(MethodQueue, [arguments]);
};
GA.trackerMap = {}; // N.h
GA.trackers = []; // N.P
GA.startTime = 0; // N.L
GA.answer = 42; // 宇宙的真理是42,用于确定这是一个真正的GA对象
var uc = [TRACKING_ID, COOKIE_DOMAIN, NAME];
// 创建一个Tracker
GA.create = function() {
var opts = transformInput(uc, [].slice.call(arguments));
if (!opts[NAME]) {
opts[NAME] = 't0';
}
var trackerName = '' + opts[NAME];
if (GA.trackerMap[trackerName]) {
return GA.trackerMap[trackerName];
}
var tracker = new Tracker(opts);
GA.trackerMap[trackerName] = tracker;
GA.trackers.push(tracker);
return tracker;
};
// 移除一个tracker
GA.remove = function(trackerName) {
for (var i = 0; i < GA.trackers.length; i++)
if (GA.trackers[i].get(NAME) == trackerName) {
GA.trackers.splice(i, 1);
GA.trackerMap[trackerName] = null ;
break;
}
};
// N.j => GA.getByName
// 获取tracker的名字
GA.getByName = function(a) {
return GA.trackerMap[a]
};
// 获取所有的trackers
GA.getAll = function() {
return GA.trackers.slice(0)
};
// 主入口方法
GA.main = function() {
'ga' != GA_HOOK && reg(49);
var tmpGa = win[GA_HOOK];
if (!tmpGa || 42 != tmpGa.answer) {
// tmpGa.l 是在投放代码里面设置的时间,记录了投放代码执行的时间点
GA.startTime = tmpGa && tmpGa.l;
GA.loaded = true;
var ga = win[GA_HOOK] = GA;
wrapApi('create', ga, ga.create);
wrapApi('remove', ga, ga.remove);
wrapApi('getByName', ga, ga.getByName, 5);
wrapApi('getAll', ga, ga.getAll, 6);
var trackerProto = Tracker.prototype;
wrapApi('get', trackerProto, trackerProto.get, 7);
wrapApi('set', trackerProto, trackerProto.set, 4);
wrapApi('send', trackerProto, trackerProto.send);
var modelProto = Model.prototype;
wrapApi('get', modelProto, modelProto.get);
wrapApi('set', modelProto, modelProto.set);
if (!isHTTPS() && !forceHTTPS) {
// 如果设置使用https,则通过引入的js文件地址来判断是否使用https
a: {
var useHTTPS;
var scripts = doc.getElementsByTagName('script');
for (var i = 0; i < scripts.length && 100 > i; i++) {
var src = scripts[i].src;
if (src && src.indexOf('https://www.google-analytics.com/analytics') == 0) {
reg(33);
useHTTPS = true;
break a;
}
}
useHTTPS = false;
}
if (useHTTPS) {
forceHTTPS = true;
}
}
isHTTPS() || forceHTTPS || !Ed(new Od) || (reg(36), forceHTTPS = true);
(win.gaplugins = win.gaplugins || {}).Linker = Linker;
var linkerProto = Linker.prototype;
pluginsMap.set('linker', Linker);
wrapApi('decorate', linkerProto, linkerProto.decorate, 20);
wrapApi('autoLink', linkerProto, linkerProto.autoLink, 25);
pluginsMap.set('displayfeatures', displayfeaturesPlugin);
pluginsMap.set('adfeatures', displayfeaturesPlugin);
// tmpGa.q ga的js代码执行前,push到ga命令队列里面的命令
tmpGa = tmpGa && tmpGa.q;
isArray(tmpGa) ? MethodQueue.run.apply(GA, tmpGa) : reg(50)
}
};
GA.da = function() {
for (var trackers = GA.getAll(), i = 0; i < trackers.length; i++)
trackers[i].get(NAME);
};
// 主要逻辑入口
(function() {
if (!executeWithoutPrerender(GA.main)) {
// 处于预渲染中,则等待真正的展示开始才进行逻辑
reg(16);
var executed = false;
var cb = function() {
if (!executed && executeWithoutPrerender(a)) {
executed = true;
doc.removeEventListener
? doc.removeEventListener('visibilitychange', cb, false)
: doc.detachEvent && doc.detachEvent('onvisibilitychange', cb);
}
};
addEventListener(doc, 'visibilitychange', c)
}
})();
// 将字符串转换成一个数字
// 同一个字符串的输入转换结果是固定的
// 通常用于将随机字符串转换成数字,然后计算概率
function str2Num(str) {
var b = 1;
var charCode = 0;
var i;
if (str) {
for (b = 0, i = str.length - 1; 0 <= i; i--) {
charCode = str.charCodeAt(i); // 16位
// 268435455 === 1111111111111111111111111111 (28位)
b = (b << 6 & 268435455) + charCode + (charCode << 14);
// 266338304 === 1111111000000000000000000000 (21位 + 7位)
var c = b & 266338304;
b = 0 != c ? b ^ c >> 21 : b;
}
}
return b
};
})(window);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment