blob: ad860f7360399750adaf709cd1af4236d8123fd0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/* eslint-disable */
// these are default values, which can be overridden by the user on the options page
var userAleHost = 'http://localhost:8000';
var userAleScript = 'userale-2.3.0.min.js';
var toolUser = 'nobody';
var toolName = 'test_app';
var toolVersion = '2.3.0';
/* eslint-enable */
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 prefix = 'USERALE_';
var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE';
var ADD_LOG = prefix + 'ADD_LOG';
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/**
* Creates a function to normalize the timestamp of the provided event.
* @param {Object} e An event containing a timeStamp property.
* @return {timeStampScale~tsScaler} The timestamp normalizing function.
*/
function timeStampScale(e) {
var tsScaler;
if (e.timeStamp && e.timeStamp > 0) {
var delta = Date.now() - e.timeStamp;
/**
* Returns a timestamp depending on various browser quirks.
* @param {?Number} ts A timestamp to use for normalization.
* @return {Number} A normalized timestamp.
*/
if (delta < 0) {
tsScaler = function tsScaler() {
return e.timeStamp / 1000;
};
} else if (delta > e.timeStamp) {
var navStart = performance.timing.navigationStart;
tsScaler = function tsScaler(ts) {
return ts + navStart;
};
} else {
tsScaler = function tsScaler(ts) {
return ts;
};
}
} else {
tsScaler = function tsScaler() {
return Date.now();
};
}
return tsScaler;
}
var __spreadArray = (undefined && undefined.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
var BrowserInfo = /** @class */ (function () {
function BrowserInfo(name, version, os) {
this.name = name;
this.version = version;
this.os = os;
this.type = 'browser';
}
return BrowserInfo;
}());
var NodeInfo = /** @class */ (function () {
function NodeInfo(version) {
this.version = version;
this.type = 'node';
this.name = 'node';
this.os = process.platform;
}
return NodeInfo;
}());
var SearchBotDeviceInfo = /** @class */ (function () {
function SearchBotDeviceInfo(name, version, os, bot) {
this.name = name;
this.version = version;
this.os = os;
this.bot = bot;
this.type = 'bot-device';
}
return SearchBotDeviceInfo;
}());
var BotInfo = /** @class */ (function () {
function BotInfo() {
this.type = 'bot';
this.bot = true; // NOTE: deprecated test name instead
this.name = 'bot';
this.version = null;
this.os = null;
}
return BotInfo;
}());
var ReactNativeInfo = /** @class */ (function () {
function ReactNativeInfo() {
this.type = 'react-native';
this.name = 'react-native';
this.version = null;
this.os = null;
}
return ReactNativeInfo;
}());
// tslint:disable-next-line:max-line-length
var SEARCHBOX_UA_REGEX = /alexa|bot|crawl(er|ing)|facebookexternalhit|feedburner|google web preview|nagios|postrank|pingdom|slurp|spider|yahoo!|yandex/;
var SEARCHBOT_OS_REGEX = /(nuhk|curl|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask\ Jeeves\/Teoma|ia_archiver)/;
var REQUIRED_VERSION_PARTS = 3;
var userAgentRules = [
['aol', /AOLShield\/([0-9\._]+)/],
['edge', /Edge\/([0-9\._]+)/],
['edge-ios', /EdgiOS\/([0-9\._]+)/],
['yandexbrowser', /YaBrowser\/([0-9\._]+)/],
['kakaotalk', /KAKAOTALK\s([0-9\.]+)/],
['samsung', /SamsungBrowser\/([0-9\.]+)/],
['silk', /\bSilk\/([0-9._-]+)\b/],
['miui', /MiuiBrowser\/([0-9\.]+)$/],
['beaker', /BeakerBrowser\/([0-9\.]+)/],
['edge-chromium', /EdgA?\/([0-9\.]+)/],
[
'chromium-webview',
/(?!Chrom.*OPR)wv\).*Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/,
],
['chrome', /(?!Chrom.*OPR)Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/],
['phantomjs', /PhantomJS\/([0-9\.]+)(:?\s|$)/],
['crios', /CriOS\/([0-9\.]+)(:?\s|$)/],
['firefox', /Firefox\/([0-9\.]+)(?:\s|$)/],
['fxios', /FxiOS\/([0-9\.]+)/],
['opera-mini', /Opera Mini.*Version\/([0-9\.]+)/],
['opera', /Opera\/([0-9\.]+)(?:\s|$)/],
['opera', /OPR\/([0-9\.]+)(:?\s|$)/],
['pie', /^Microsoft Pocket Internet Explorer\/(\d+\.\d+)$/],
['pie', /^Mozilla\/\d\.\d+\s\(compatible;\s(?:MSP?IE|MSInternet Explorer) (\d+\.\d+);.*Windows CE.*\)$/],
['netfront', /^Mozilla\/\d\.\d+.*NetFront\/(\d.\d)/],
['ie', /Trident\/7\.0.*rv\:([0-9\.]+).*\).*Gecko$/],
['ie', /MSIE\s([0-9\.]+);.*Trident\/[4-7].0/],
['ie', /MSIE\s(7\.0)/],
['bb10', /BB10;\sTouch.*Version\/([0-9\.]+)/],
['android', /Android\s([0-9\.]+)/],
['ios', /Version\/([0-9\._]+).*Mobile.*Safari.*/],
['safari', /Version\/([0-9\._]+).*Safari/],
['facebook', /FB[AS]V\/([0-9\.]+)/],
['instagram', /Instagram\s([0-9\.]+)/],
['ios-webview', /AppleWebKit\/([0-9\.]+).*Mobile/],
['ios-webview', /AppleWebKit\/([0-9\.]+).*Gecko\)$/],
['curl', /^curl\/([0-9\.]+)$/],
['searchbot', SEARCHBOX_UA_REGEX],
];
var operatingSystemRules = [
['iOS', /iP(hone|od|ad)/],
['Android OS', /Android/],
['BlackBerry OS', /BlackBerry|BB10/],
['Windows Mobile', /IEMobile/],
['Amazon OS', /Kindle/],
['Windows 3.11', /Win16/],
['Windows 95', /(Windows 95)|(Win95)|(Windows_95)/],
['Windows 98', /(Windows 98)|(Win98)/],
['Windows 2000', /(Windows NT 5.0)|(Windows 2000)/],
['Windows XP', /(Windows NT 5.1)|(Windows XP)/],
['Windows Server 2003', /(Windows NT 5.2)/],
['Windows Vista', /(Windows NT 6.0)/],
['Windows 7', /(Windows NT 6.1)/],
['Windows 8', /(Windows NT 6.2)/],
['Windows 8.1', /(Windows NT 6.3)/],
['Windows 10', /(Windows NT 10.0)/],
['Windows ME', /Windows ME/],
['Windows CE', /Windows CE|WinCE|Microsoft Pocket Internet Explorer/],
['Open BSD', /OpenBSD/],
['Sun OS', /SunOS/],
['Chrome OS', /CrOS/],
['Linux', /(Linux)|(X11)/],
['Mac OS', /(Mac_PowerPC)|(Macintosh)/],
['QNX', /QNX/],
['BeOS', /BeOS/],
['OS/2', /OS\/2/],
];
function detect(userAgent) {
if (!!userAgent) {
return parseUserAgent(userAgent);
}
if (typeof document === 'undefined' &&
typeof navigator !== 'undefined' &&
navigator.product === 'ReactNative') {
return new ReactNativeInfo();
}
if (typeof navigator !== 'undefined') {
return parseUserAgent(navigator.userAgent);
}
return getNodeVersion();
}
function matchUserAgent(ua) {
// opted for using reduce here rather than Array#first with a regex.test call
// this is primarily because using the reduce we only perform the regex
// execution once rather than once for the test and for the exec again below
// probably something that needs to be benchmarked though
return (ua !== '' &&
userAgentRules.reduce(function (matched, _a) {
var browser = _a[0], regex = _a[1];
if (matched) {
return matched;
}
var uaMatch = regex.exec(ua);
return !!uaMatch && [browser, uaMatch];
}, false));
}
function parseUserAgent(ua) {
var matchedRule = matchUserAgent(ua);
if (!matchedRule) {
return null;
}
var name = matchedRule[0], match = matchedRule[1];
if (name === 'searchbot') {
return new BotInfo();
}
// Do not use RegExp for split operation as some browser do not support it (See: http://blog.stevenlevithan.com/archives/cross-browser-split)
var versionParts = match[1] && match[1].split('.').join('_').split('_').slice(0, 3);
if (versionParts) {
if (versionParts.length < REQUIRED_VERSION_PARTS) {
versionParts = __spreadArray(__spreadArray([], versionParts, true), createVersionParts(REQUIRED_VERSION_PARTS - versionParts.length), true);
}
}
else {
versionParts = [];
}
var version = versionParts.join('.');
var os = detectOS(ua);
var searchBotMatch = SEARCHBOT_OS_REGEX.exec(ua);
if (searchBotMatch && searchBotMatch[1]) {
return new SearchBotDeviceInfo(name, version, os, searchBotMatch[1]);
}
return new BrowserInfo(name, version, os);
}
function detectOS(ua) {
for (var ii = 0, count = operatingSystemRules.length; ii < count; ii++) {
var _a = operatingSystemRules[ii], os = _a[0], regex = _a[1];
var match = regex.exec(ua);
if (match) {
return os;
}
}
return null;
}
function getNodeVersion() {
var isNode = typeof process !== 'undefined' && process.version;
return isNode ? new NodeInfo(process.version.slice(1)) : null;
}
function createVersionParts(count) {
var output = [];
for (var ii = 0; ii < count; ii++) {
output.push('0');
}
return output;
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
detect();
/**
* Extract the millisecond and microsecond portions of a timestamp.
* @param {Number} timeStamp The timestamp to split into millisecond and microsecond fields.
* @return {Object} An object containing the millisecond
* and microsecond portions of the timestamp.
*/
function extractTimeFields(timeStamp) {
return {
milli: Math.floor(timeStamp),
micro: Number((timeStamp % 1).toFixed(3))
};
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 sendIntervalId = null;
/**
* Initializes the log queue processors.
* @param {Array} logs Array of logs to append to.
* @param {Object} config Configuration object to use when logging.
*/
function initSender(logs, config) {
if (sendIntervalId !== null) {
clearInterval(sendIntervalId);
}
sendIntervalId = sendOnInterval(logs, config);
sendOnClose(logs, config);
}
/**
* Checks the provided log array on an interval, flushing the logs
* if the queue has reached the threshold specified by the provided config.
* @param {Array} logs Array of logs to read from.
* @param {Object} config Configuration object to be read from.
* @return {Number} The newly created interval id.
*/
function sendOnInterval(logs, config) {
return setInterval(function () {
if (!config.on) {
return;
}
if (logs.length >= config.logCountThreshold) {
sendLogs(logs.slice(0), config, 0); // Send a copy
logs.splice(0); // Clear array reference (no reassignment)
}
}, config.transmitInterval);
}
/**
* Attempts to flush the remaining logs when the window is closed.
* @param {Array} logs Array of logs to be flushed.
* @param {Object} config Configuration object to be read from.
*/
function sendOnClose(logs, config) {
window.addEventListener('pagehide', function () {
if (config.on && logs.length > 0) {
navigator.sendBeacon(config.url, JSON.stringify(logs));
logs.splice(0); // clear log queue
}
});
}
/**
* Sends the provided array of logs to the specified url,
* retrying the request up to the specified number of retries.
* @param {Array} logs Array of logs to send.
* @param {string} config configuration parameters (e.g., to extract URL from & send the POST request to).
* @param {Number} retries Maximum number of attempts to send the logs.
*/
// @todo expose config object to sendLogs replate url with config.url
function sendLogs(logs, config, retries) {
var req = new XMLHttpRequest();
// @todo setRequestHeader for Auth
var data = JSON.stringify(logs);
req.open('POST', config.url);
if (config.authHeader) {
req.setRequestHeader('Authorization', config.authHeader);
}
req.setRequestHeader('Content-type', 'application/json;charset=UTF-8');
req.onreadystatechange = function () {
if (req.readyState === 4 && req.status !== 200) {
if (retries > 0) {
sendLogs(logs, config, retries--);
}
}
};
req.send(data);
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
// inherent dependency on globals.js, loaded by the webext
// browser is defined in firefox, but not in chrome. In chrome, they use
// the 'chrome' global instead. Let's map it to browser so we don't have
// to have if-conditions all over the place.
var browser = browser || chrome;
var logs = [];
var config = {
autostart: true,
url: 'http://localhost:8000',
transmitInterval: 5000,
logCountThreshold: 5,
userId: null,
version: null,
resolution: 500,
time: timeStampScale({}),
on: true
};
var sessionId = 'session_' + Date.now();
var getTimestamp = typeof performance !== 'undefined' && typeof performance.now !== 'undefined' ? function () {
return performance.now() + performance.timing.navigationStart;
} : Date.now;
browser.storage.local.set({
sessionId: sessionId
});
browser.storage.local.get({
userAleHost: userAleHost,
userAleScript: userAleScript,
toolUser: toolUser,
toolName: toolName,
toolVersion: toolVersion
}, storeCallback);
function storeCallback(item) {
config = Object.assign({}, config, {
url: item.userAleHost,
userId: item.toolUser,
sessionID: sessionId,
toolName: item.toolName,
toolVersion: item.toolVersion
});
initSender(logs, config);
}
function dispatchTabMessage(message) {
browser.tabs.query({}, function (tabs) {
tabs.forEach(function (tab) {
browser.tabs.sendMessage(tab.id, message);
});
});
}
function packageBrowserLog(type, logDetail) {
var timeFields = extractTimeFields(getTimestamp());
logs.push({
'target': null,
'path': null,
'clientTime': timeFields.milli,
'microTime': timeFields.micro,
'location': null,
'type': 'browser.' + type,
'logType': 'raw',
'userAction': true,
'details': logDetail,
'userId': toolUser,
'toolVersion': null,
'toolName': null,
'useraleVersion': null,
'sessionID': sessionId
});
}
browser.runtime.onMessage.addListener(function (message) {
switch (message.type) {
case CONFIG_CHANGE:
(function () {
var updatedConfig = Object.assign({}, config, {
url: message.payload.userAleHost,
userId: message.payload.toolUser,
toolName: message.payload.toolName,
toolVersion: message.payload.toolVersion
});
initSender(logs, updatedConfig);
dispatchTabMessage(message);
})();
break;
case ADD_LOG:
(function () {
logs.push(message.payload);
})();
break;
default:
console.log('got unknown message type ', message);
}
});
function getTabDetailById(tabId, onReady) {
browser.tabs.get(tabId, function (tab) {
onReady({
active: tab.active,
audible: tab.audible,
incognito: tab.incognito,
index: tab.index,
muted: tab.mutedInfo ? tab.mutedInfo.muted : null,
pinned: tab.pinned,
selected: tab.selected,
tabId: tab.id,
title: tab.title,
url: tab.url,
windowId: tab.windowId
});
});
}
browser.tabs.onActivated.addListener(function (e) {
getTabDetailById(e.tabId, function (detail) {
packageBrowserLog('tabs.onActivated', detail);
});
});
browser.tabs.onCreated.addListener(function (tab, e) {
packageBrowserLog('tabs.onCreated', {
active: tab.active,
audible: tab.audible,
incognito: tab.incognito,
index: tab.index,
muted: tab.mutedInfo ? tab.mutedInfo.muted : null,
pinned: tab.pinned,
selected: tab.selected,
tabId: tab.id,
title: tab.title,
url: tab.url,
windowId: tab.windowId
});
});
browser.tabs.onDetached.addListener(function (tabId) {
getTabDetailById(tabId, function (detail) {
packageBrowserLog('tabs.onDetached', detail);
});
});
browser.tabs.onMoved.addListener(function (tabId) {
getTabDetailById(tabId, function (detail) {
packageBrowserLog('tabs.onMoved', detail);
});
});
browser.tabs.onRemoved.addListener(function (tabId) {
packageBrowserLog('tabs.onRemoved', {
tabId: tabId
});
});
browser.tabs.onZoomChange.addListener(function (e) {
getTabDetailById(e.tabId, function (detail) {
packageBrowserLog('tabs.onZoomChange', Object.assign({}, {
oldZoomFactor: e.oldZoomFactor,
newZoomFactor: e.newZoomFactor
}, detail));
});
});
/*
eslint-enable
*/