Merge pull request #413 from apache/test
Test
diff --git a/.gitignore b/.gitignore
index 140e2df..8d50f9a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,5 @@
/.git/*
npm-debug.log
DS_store
-cypress
\ No newline at end of file
+cypress
+/build/*
\ No newline at end of file
diff --git a/build/UserALEWebExtension/background.js b/build/UserALEWebExtension/background.js
deleted file mode 100644
index 126c474..0000000
--- a/build/UserALEWebExtension/background.js
+++ /dev/null
@@ -1,610 +0,0 @@
-/*
-* 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.4.0.min.js';
-var toolUser = 'nobody';
-var toolName = 'test_app';
-var toolVersion = '2.4.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
- */
diff --git a/build/UserALEWebExtension/content.js b/build/UserALEWebExtension/content.js
deleted file mode 100644
index 2ec7e1f..0000000
--- a/build/UserALEWebExtension/content.js
+++ /dev/null
@@ -1,1136 +0,0 @@
-/*
-* 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.4.0.min.js';
-var toolUser = 'nobody';
-var toolName = 'test_app';
-var toolVersion = '2.4.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';
-
-var version = "2.4.0";
-
-/*
- * 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 sessionId = null;
-
-/**
- * Extracts the initial configuration settings from the
- * currently executing script tag.
- * @return {Object} The extracted configuration object
- */
-function getInitialSettings() {
- var settings = {};
- if (sessionId === null) {
- sessionId = getSessionId('userAleSessionId', 'session_' + String(Date.now()));
- }
- var script = document.currentScript || function () {
- var scripts = document.getElementsByTagName('script');
- return scripts[scripts.length - 1];
- }();
- var get = script ? script.getAttribute.bind(script) : function () {
- return null;
- };
- settings.autostart = get('data-autostart') === 'false' ? false : true;
- settings.url = get('data-url') || 'http://localhost:8000';
- settings.transmitInterval = +get('data-interval') || 5000;
- settings.logCountThreshold = +get('data-threshold') || 5;
- settings.userId = get('data-user') || null;
- settings.version = get('data-version') || null;
- settings.logDetails = get('data-log-details') === 'true' ? true : false;
- settings.resolution = +get('data-resolution') || 500;
- settings.toolName = get('data-tool') || null;
- settings.userFromParams = get('data-user-from-params') || null;
- settings.time = timeStampScale(document.createEvent('CustomEvent'));
- settings.sessionID = get('data-session') || sessionId;
- settings.authHeader = get('data-auth') || null;
- settings.custIndex = get('data-index') || null;
- return settings;
-}
-
-/**
- * defines sessionId, stores it in sessionStorage, checks to see if there is a sessionId in
- * storage when script is started. This prevents events like 'submit', which refresh page data
- * from refreshing the current user session
- *
- */
-function getSessionId(sessionKey, value) {
- if (window.sessionStorage.getItem(sessionKey) === null) {
- window.sessionStorage.setItem(sessionKey, JSON.stringify(value));
- return value;
- }
- return JSON.parse(window.sessionStorage.getItem(sessionKey));
-}
-
-/**
- * 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;
-}
-
-/*
- * 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.
- */
-
-/**
- * Shallow merges the first argument with the second.
- * Retrieves/updates the userid if userFromParams is provided.
- * @param {Object} config Current configuration object to be merged into.
- * @param {Object} newConfig Configuration object to merge into the current config.
- */
-function configure(config, newConfig) {
- var configAutostart = config['autostart'];
- var newConfigAutostart = newConfig['autostart'];
- Object.keys(newConfig).forEach(function (option) {
- if (option === 'userFromParams') {
- var userId = getUserIdFromParams(newConfig[option]);
- if (userId) {
- config.userId = userId;
- }
- }
- config[option] = newConfig[option];
- });
- if (configAutostart === false || newConfigAutostart === false) {
- config['autostart'] = false;
- }
-}
-
-/**
- * Attempts to extract the userid from the query parameters of the URL.
- * @param {string} param The name of the query parameter containing the userid.
- * @return {string|null} The extracted/decoded userid, or null if none is found.
- */
-function getUserIdFromParams(param) {
- var userField = param;
- var regex = new RegExp('[?&]' + userField + '(=([^&#]*)|&|#|$)');
- var results = window.location.href.match(regex);
- if (results && results[2]) {
- return decodeURIComponent(results[2].replace(/\+/g, ' '));
- } else {
- return null;
- }
-}
-
-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.
- */
-
-var browser$1 = detect();
-var logs$1;
-var config$1;
-
-// Interval Logging Globals
-var intervalID;
-var intervalType;
-var intervalPath;
-var intervalTimer;
-var intervalCounter;
-var intervalLog;
-var cbHandlers = {};
-
-/**
- * Adds named callbacks to be executed when logging.
- * @param {Object } newCallbacks An object containing named callback functions.
- */
-function addCallbacks() {
- for (var _len = arguments.length, newCallbacks = new Array(_len), _key = 0; _key < _len; _key++) {
- newCallbacks[_key] = arguments[_key];
- }
- newCallbacks.forEach(function (source) {
- var descriptors = Object.keys(source).reduce(function (descriptors, key) {
- descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
- return descriptors;
- }, {});
- Object.getOwnPropertySymbols(source).forEach(function (sym) {
- var descriptor = Object.getOwnPropertyDescriptor(source, sym);
- if (descriptor.enumerable) {
- descriptors[sym] = descriptor;
- }
- });
- Object.defineProperties(cbHandlers, descriptors);
- });
- return cbHandlers;
-}
-
-/**
- * Assigns the config and log container to be used by the logging functions.
- * @param {Array} newLogs Log container.
- * @param {Object} newConfig Configuration to use while logging.
- */
-function initPackager(newLogs, newConfig) {
- logs$1 = newLogs;
- config$1 = newConfig;
- cbHandlers = [];
- intervalID = null;
- intervalType = null;
- intervalPath = null;
- intervalTimer = null;
- intervalCounter = 0;
- intervalLog = null;
-}
-
-/**
- * Transforms the provided HTML event into a log and appends it to the log queue.
- * @param {Object} e The event to be logged.
- * @param {Function} detailFcn The function to extract additional log parameters from the event.
- * @return {boolean} Whether the event was logged.
- */
-function packageLog(e, detailFcn) {
- if (!config$1.on) {
- return false;
- }
- var details = null;
- if (detailFcn) {
- details = detailFcn(e);
- }
- var timeFields = extractTimeFields(e.timeStamp && e.timeStamp > 0 ? config$1.time(e.timeStamp) : Date.now());
- var log = {
- 'target': getSelector(e.target),
- 'path': buildPath(e),
- 'pageUrl': window.location.href,
- 'pageTitle': document.title,
- 'pageReferrer': document.referrer,
- 'browser': detectBrowser(),
- 'clientTime': timeFields.milli,
- 'microTime': timeFields.micro,
- 'location': getLocation(e),
- 'scrnRes': getSreenRes(),
- 'type': e.type,
- 'logType': 'raw',
- 'userAction': true,
- 'details': details,
- 'userId': config$1.userId,
- 'toolVersion': config$1.version,
- 'toolName': config$1.toolName,
- 'useraleVersion': config$1.useraleVersion,
- 'sessionID': config$1.sessionID
- };
- for (var _i = 0, _Object$values = Object.values(cbHandlers); _i < _Object$values.length; _i++) {
- var func = _Object$values[_i];
- if (typeof func === 'function') {
- log = func(log, e);
- if (!log) {
- return false;
- }
- }
- }
- logs$1.push(log);
- return true;
-}
-
-/**
- * Packages the provided customLog to include standard meta data and appends it to the log queue.
- * @param {Object} customLog The behavior to be logged.
- * @param {Function} detailFcn The function to extract additional log parameters from the event.
- * @param {boolean} userAction Indicates user behavior (true) or system behavior (false)
- * @return {boolean} Whether the event was logged.
- */
-function packageCustomLog(customLog, detailFcn, userAction) {
- if (!config$1.on) {
- return false;
- }
- var details = null;
- if (detailFcn) {
- details = detailFcn();
- }
- var metaData = {
- 'pageUrl': window.location.href,
- 'pageTitle': document.title,
- 'pageReferrer': document.referrer,
- 'browser': detectBrowser(),
- 'clientTime': Date.now(),
- 'scrnRes': getSreenRes(),
- 'logType': 'custom',
- 'userAction': userAction,
- 'details': details,
- 'userId': config$1.userId,
- 'toolVersion': config$1.version,
- 'toolName': config$1.toolName,
- 'useraleVersion': config$1.useraleVersion,
- 'sessionID': config$1.sessionID
- };
- var log = Object.assign(metaData, customLog);
- for (var _i2 = 0, _Object$values2 = Object.values(cbHandlers); _i2 < _Object$values2.length; _i2++) {
- var func = _Object$values2[_i2];
- if (typeof func === 'function') {
- log = func(log, null);
- if (!log) {
- return false;
- }
- }
- }
- logs$1.push(log);
- return true;
-}
-
-/**
- * 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))
- };
-}
-
-/**
- * Track intervals and gather details about it.
- * @param {Object} e
- * @return boolean
- */
-function packageIntervalLog(e) {
- var target = getSelector(e.target);
- var path = buildPath(e);
- var type = e.type;
- var timestamp = Math.floor(e.timeStamp && e.timeStamp > 0 ? config$1.time(e.timeStamp) : Date.now());
-
- // Init - this should only happen once on initialization
- if (intervalID == null) {
- intervalID = target;
- intervalType = type;
- intervalPath = path;
- intervalTimer = timestamp;
- intervalCounter = 0;
- }
- if (intervalID !== target || intervalType !== type) {
- // When to create log? On transition end
- // @todo Possible for intervalLog to not be pushed in the event the interval never ends...
-
- intervalLog = {
- 'target': intervalID,
- 'path': intervalPath,
- 'pageUrl': window.location.href,
- 'pageTitle': document.title,
- 'pageReferrer': document.referrer,
- 'browser': detectBrowser(),
- 'count': intervalCounter,
- 'duration': timestamp - intervalTimer,
- // microseconds
- 'startTime': intervalTimer,
- 'endTime': timestamp,
- 'type': intervalType,
- 'logType': 'interval',
- 'targetChange': intervalID !== target,
- 'typeChange': intervalType !== type,
- 'userAction': false,
- 'userId': config$1.userId,
- 'toolVersion': config$1.version,
- 'toolName': config$1.toolName,
- 'useraleVersion': config$1.useraleVersion,
- 'sessionID': config$1.sessionID
- };
- for (var _i3 = 0, _Object$values3 = Object.values(cbHandlers); _i3 < _Object$values3.length; _i3++) {
- var func = _Object$values3[_i3];
- if (typeof func === 'function') {
- intervalLog = func(intervalLog, null);
- if (!intervalLog) {
- return false;
- }
- }
- }
- logs$1.push(intervalLog);
-
- // Reset
- intervalID = target;
- intervalType = type;
- intervalPath = path;
- intervalTimer = timestamp;
- intervalCounter = 0;
- }
-
- // Interval is still occuring, just update counter
- if (intervalID == target && intervalType == type) {
- intervalCounter = intervalCounter + 1;
- }
- return true;
-}
-
-/**
- * Extracts coordinate information from the event
- * depending on a few browser quirks.
- * @param {Object} e The event to extract coordinate information from.
- * @return {Object} An object containing nullable x and y coordinates for the event.
- */
-function getLocation(e) {
- if (e.pageX != null) {
- return {
- 'x': e.pageX,
- 'y': e.pageY
- };
- } else if (e.clientX != null) {
- return {
- 'x': document.documentElement.scrollLeft + e.clientX,
- 'y': document.documentElement.scrollTop + e.clientY
- };
- } else {
- return {
- 'x': null,
- 'y': null
- };
- }
-}
-
-/**
- * Extracts innerWidth and innerHeight to provide estimates of screen resolution
- * @return {Object} An object containing the innerWidth and InnerHeight
- */
-function getSreenRes() {
- return {
- 'width': window.innerWidth,
- 'height': window.innerHeight
- };
-}
-
-/**
- * Builds a string CSS selector from the provided element
- * @param {HTMLElement} ele The element from which the selector is built.
- * @return {string} The CSS selector for the element, or Unknown if it can't be determined.
- */
-function getSelector(ele) {
- if (ele.localName) {
- return ele.localName + (ele.id ? '#' + ele.id : '') + (ele.className ? '.' + ele.className : '');
- } else if (ele.nodeName) {
- return ele.nodeName + (ele.id ? '#' + ele.id : '') + (ele.className ? '.' + ele.className : '');
- } else if (ele && ele.document && ele.location && ele.alert && ele.setInterval) {
- return "Window";
- } else {
- return "Unknown";
- }
-}
-
-/**
- * Builds an array of elements from the provided event target, to the root element.
- * @param {Object} e Event from which the path should be built.
- * @return {HTMLElement[]} Array of elements, starting at the event target, ending at the root element.
- */
-function buildPath(e) {
- if (e instanceof window.Event) {
- var path = e.composedPath();
- return selectorizePath(path);
- }
-}
-
-/**
- * Builds a CSS selector path from the provided list of elements.
- * @param {HTMLElement[]} path Array of HTMLElements from which the path should be built.
- * @return {string[]} Array of string CSS selectors.
- */
-function selectorizePath(path) {
- var i = 0;
- var pathEle;
- var pathSelectors = [];
- while (pathEle = path[i]) {
- pathSelectors.push(getSelector(pathEle));
- ++i;
- }
- return pathSelectors;
-}
-function detectBrowser() {
- return {
- 'browser': browser$1 ? browser$1.name : '',
- 'version': browser$1 ? browser$1.version : ''
- };
-}
-
-/*
- * 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 events;
-var bufferBools;
-var bufferedEvents;
-//@todo: Investigate drag events and their behavior
-var intervalEvents = ['click', 'focus', 'blur', 'input', 'change', 'mouseover', 'submit'];
-var refreshEvents;
-var windowEvents = ['load', 'blur', 'focus'];
-
-/**
- * Maps an event to an object containing useful information.
- * @param {Object} e Event to extract data from
- */
-function extractMouseEvent(e) {
- return {
- 'clicks': e.detail,
- 'ctrl': e.ctrlKey,
- 'alt': e.altKey,
- 'shift': e.shiftKey,
- 'meta': e.metaKey
- // 'text' : e.target.innerHTML
- };
-}
-
-/**
- * Defines the way information is extracted from various events.
- * Also defines which events we will listen to.
- * @param {Object} config Configuration object to read from.
- */
-function defineDetails(config) {
- // Events list
- // Keys are event types
- // Values are functions that return details object if applicable
- events = {
- 'click': extractMouseEvent,
- 'dblclick': extractMouseEvent,
- 'mousedown': extractMouseEvent,
- 'mouseup': extractMouseEvent,
- 'focus': null,
- 'blur': null,
- 'input': config.logDetails ? function (e) {
- return {
- 'value': e.target.value
- };
- } : null,
- 'change': config.logDetails ? function (e) {
- return {
- 'value': e.target.value
- };
- } : null,
- 'dragstart': null,
- 'dragend': null,
- 'drag': null,
- 'drop': null,
- 'keydown': config.logDetails ? function (e) {
- return {
- 'key': e.keyCode,
- 'ctrl': e.ctrlKey,
- 'alt': e.altKey,
- 'shift': e.shiftKey,
- 'meta': e.metaKey
- };
- } : null,
- 'mouseover': null
- };
- bufferBools = {};
- bufferedEvents = {
- 'wheel': function wheel(e) {
- return {
- 'x': e.deltaX,
- 'y': e.deltaY,
- 'z': e.deltaZ
- };
- },
- 'scroll': function scroll() {
- return {
- 'x': window.scrollX,
- 'y': window.scrollY
- };
- },
- 'resize': function resize() {
- return {
- 'width': window.outerWidth,
- 'height': window.outerHeight
- };
- }
- };
- refreshEvents = {
- 'submit': null
- };
-}
-
-/**
- * Hooks the event handlers for each event type of interest.
- * @param {Object} config Configuration object to use.
- * @return {boolean} Whether the operation succeeded
- */
-function attachHandlers(config) {
- defineDetails(config);
- Object.keys(events).forEach(function (ev) {
- document.addEventListener(ev, function (e) {
- packageLog(e, events[ev]);
- }, true);
- });
- intervalEvents.forEach(function (ev) {
- document.addEventListener(ev, function (e) {
- packageIntervalLog(e);
- }, true);
- });
- Object.keys(bufferedEvents).forEach(function (ev) {
- bufferBools[ev] = true;
- window.addEventListener(ev, function (e) {
- if (bufferBools[ev]) {
- bufferBools[ev] = false;
- packageLog(e, bufferedEvents[ev]);
- setTimeout(function () {
- bufferBools[ev] = true;
- }, config.resolution);
- }
- }, true);
- });
- Object.keys(refreshEvents).forEach(function (ev) {
- document.addEventListener(ev, function (e) {
- packageLog(e, events[ev]);
- }, true);
- });
- windowEvents.forEach(function (ev) {
- window.addEventListener(ev, function (e) {
- packageLog(e, function () {
- return {
- 'window': true
- };
- });
- }, true);
- });
- return true;
-}
-
-/*
- * 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);
-}
-
-var config = {};
-var logs = [];
-var startLoadTimestamp = Date.now();
-var endLoadTimestamp;
-window.onload = function () {
- endLoadTimestamp = Date.now();
-};
-var started = false;
-
-// Start up Userale
-config.on = false;
-config.useraleVersion = version;
-configure(config, getInitialSettings());
-initPackager(logs, config);
-if (config.autostart) {
- setup(config);
-}
-
-/**
- * Hooks the global event listener, and starts up the
- * logging interval.
- * @param {Object} config Configuration settings for the logger
- */
-function setup(config) {
- if (!started) {
- setTimeout(function () {
- var state = document.readyState;
- if (config.autostart && (state === 'interactive' || state === 'complete')) {
- attachHandlers(config);
- initSender(logs, config);
- started = config.on = true;
- packageCustomLog({
- type: 'load',
- logType: 'raw',
- details: {
- pageLoadTime: endLoadTimestamp - startLoadTimestamp
- }
- }, function () {}, false);
- } else {
- setup(config);
- }
- }, 100);
- }
-}
-
-/**
- * Updates the current configuration
- * object with the provided values.
- * @param {Object} newConfig The configuration options to use.
- * @return {Object} Returns the updated configuration.
- */
-function options(newConfig) {
- if (newConfig !== undefined) {
- configure(config, newConfig);
- }
- return config;
-}
-
-/*
- * 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.
- */
-
-
-// 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;
-
-// creates a Future for retrieval of the named keys
-// the value specified is the default value if one doesn't exist in the storage
-browser.storage.local.get({
- sessionId: null,
- userAleHost: userAleHost,
- userAleScript: userAleScript,
- toolUser: toolUser,
- toolName: toolName,
- toolVersion: toolVersion
-}, storeCallback);
-function storeCallback(item) {
- injectScript({
- url: item.userAleHost,
- userId: item.toolUser,
- sessionID: item.sessionId,
- toolName: item.toolName,
- toolVersion: item.toolVersion
- });
-}
-function queueLog(log) {
- browser.runtime.sendMessage({
- type: ADD_LOG,
- payload: log
- });
-}
-function injectScript(config) {
- options(config);
- // start(); not necessary given that autostart in place, and option is masked from WebExt users
- addCallbacks({
- "function": function _function(log) {
- queueLog(Object.assign({}, log, {
- pageUrl: document.location.href
- }));
- console.log(log);
- return false;
- }
- });
-}
-browser.runtime.onMessage.addListener(function (message) {
- if (message.type === CONFIG_CHANGE) {
- options({
- url: message.payload.userAleHost,
- userId: message.payload.toolUser,
- toolName: message.payload.toolName,
- toolVersion: message.payload.toolVersion
- });
- }
-});
-
-/*
- eslint-enable
- */
diff --git a/build/UserALEWebExtension/icons/border-48.png b/build/UserALEWebExtension/icons/border-48.png
deleted file mode 100644
index 90687de..0000000
--- a/build/UserALEWebExtension/icons/border-48.png
+++ /dev/null
Binary files differ
diff --git a/build/UserALEWebExtension/manifest.json b/build/UserALEWebExtension/manifest.json
deleted file mode 100644
index ca26fdc..0000000
--- a/build/UserALEWebExtension/manifest.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "manifest_version": 2,
- "name": "User ALE Extension",
- "version": "2.4.0",
- "description": "Injects UserALE.js into every page for testing & user research purposes",
- "icons": {
- "48": "icons/border-48.png"
- },
- "permissions": [
- "activeTab",
- "storage",
- "tabs",
- "<all_urls>"
- ],
- "background": {
- "scripts": ["background.js"]
- },
- "content_scripts": [
- {
- "matches": [
- "<all_urls>"
- ],
- "js": ["content.js"],
- "all_frames": true
- }
- ],
- "options_ui": {
- "page": "optionsPage.html"
- }
-}
\ No newline at end of file
diff --git a/build/UserALEWebExtension/options.js b/build/UserALEWebExtension/options.js
deleted file mode 100644
index 1495455..0000000
--- a/build/UserALEWebExtension/options.js
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
-* 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.4.0.min.js';
-var toolUser = 'nobody';
-var toolName = 'test_app';
-var toolVersion = '2.4.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';
-
-/*
-* 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.
-*/
-
-if (chrome) {
- browser = chrome;
-}
-
-// creates a Future for retrieval of the named keys
-// the value specified is the default value if one doesn't exist in the storage
-browser.storage.local.get({
- userAleHost: userAleHost,
- userAleScript: userAleScript,
- toolUser: toolUser,
- toolName: toolName,
- toolVersion: toolVersion
-}, storeCallback);
-function storeCallback(item) {
- document.getElementById("host").value = item.userAleHost;
- document.getElementById("clientScript").value = item.userAleScript;
- document.getElementById("toolUser").value = item.toolUser;
- document.getElementById("toolName").value = item.toolName;
- document.getElementById("toolVersion").value = item.toolVersion;
-}
-function saveOptions(e) {
- var updatedConfig = {
- userAleHost: document.getElementById("host").value,
- userAleScript: document.getElementById("clientScript").value,
- toolUser: document.getElementById("toolUser").value,
- toolName: document.getElementById("toolName").value,
- toolVersion: document.getElementById("toolVersion").value
- };
- browser.storage.local.set(updatedConfig);
- browser.runtime.sendMessage({
- type: CONFIG_CHANGE,
- payload: updatedConfig
- });
-}
-document.addEventListener("submit", function () {
- saveOptions();
-});
-
-/* eslint-enable */
diff --git a/build/UserALEWebExtension/optionsPage.html b/build/UserALEWebExtension/optionsPage.html
deleted file mode 100644
index b746439..0000000
--- a/build/UserALEWebExtension/optionsPage.html
+++ /dev/null
@@ -1,54 +0,0 @@
-<!--
-* 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.
--->
-
-<!DOCTYPE html>
-<html>
-<head>
- <title>User ALE Web Extension - Options</title>
- <script src="globals.js"></script>
- <script src="options.js"></script>
- <meta charset="utf-8">
-</head>
-<body>
-<h1>Options</h1>
-<form>
- <label>User ALE Server Host:</label>
- <input id="host"/>
- <br/>
-
- <label>User ALE Client Script:</label>
- <input id="clientScript"/>
- <br/>
-
- <label>User:</label>
- <input id="toolUser"/>
- <br/>
-
- <label>Tool Name:</label>
- <input id="toolName"/>
- <br/>
-
- <label>Tool Version:</label>
- <input id="toolVersion"/>
- <br/>
-
- <div align="right">
- <button type="submit">Save</button>
- </div>
-</form>
-</body>
-</html>
\ No newline at end of file
diff --git a/build/userale-2.4.0.js b/build/userale-2.4.0.js
deleted file mode 100644
index 6b89cc4..0000000
--- a/build/userale-2.4.0.js
+++ /dev/null
@@ -1,1172 +0,0 @@
-/**
- * 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.
- * @preserved
- */
-
-(function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
- typeof define === 'function' && define.amd ? define(['exports'], factory) :
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.userale = {}));
-})(this, (function (exports) { 'use strict';
-
- function _typeof(o) {
- "@babel/helpers - typeof";
-
- return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
- return typeof o;
- } : function (o) {
- return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
- }, _typeof(o);
- }
-
- var version$1 = "2.4.0";
-
- /*
- * 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 sessionId = null;
-
- /**
- * Extracts the initial configuration settings from the
- * currently executing script tag.
- * @return {Object} The extracted configuration object
- */
- function getInitialSettings() {
- var settings = {};
- if (sessionId === null) {
- sessionId = getSessionId('userAleSessionId', 'session_' + String(Date.now()));
- }
- var script = document.currentScript || function () {
- var scripts = document.getElementsByTagName('script');
- return scripts[scripts.length - 1];
- }();
- var get = script ? script.getAttribute.bind(script) : function () {
- return null;
- };
- settings.autostart = get('data-autostart') === 'false' ? false : true;
- settings.url = get('data-url') || 'http://localhost:8000';
- settings.transmitInterval = +get('data-interval') || 5000;
- settings.logCountThreshold = +get('data-threshold') || 5;
- settings.userId = get('data-user') || null;
- settings.version = get('data-version') || null;
- settings.logDetails = get('data-log-details') === 'true' ? true : false;
- settings.resolution = +get('data-resolution') || 500;
- settings.toolName = get('data-tool') || null;
- settings.userFromParams = get('data-user-from-params') || null;
- settings.time = timeStampScale(document.createEvent('CustomEvent'));
- settings.sessionID = get('data-session') || sessionId;
- settings.authHeader = get('data-auth') || null;
- settings.custIndex = get('data-index') || null;
- return settings;
- }
-
- /**
- * defines sessionId, stores it in sessionStorage, checks to see if there is a sessionId in
- * storage when script is started. This prevents events like 'submit', which refresh page data
- * from refreshing the current user session
- *
- */
- function getSessionId(sessionKey, value) {
- if (window.sessionStorage.getItem(sessionKey) === null) {
- window.sessionStorage.setItem(sessionKey, JSON.stringify(value));
- return value;
- }
- return JSON.parse(window.sessionStorage.getItem(sessionKey));
- }
-
- /**
- * 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;
- }
-
- /*
- * 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.
- */
-
- /**
- * Shallow merges the first argument with the second.
- * Retrieves/updates the userid if userFromParams is provided.
- * @param {Object} config Current configuration object to be merged into.
- * @param {Object} newConfig Configuration object to merge into the current config.
- */
- function configure(config, newConfig) {
- var configAutostart = config['autostart'];
- var newConfigAutostart = newConfig['autostart'];
- Object.keys(newConfig).forEach(function (option) {
- if (option === 'userFromParams') {
- var userId = getUserIdFromParams(newConfig[option]);
- if (userId) {
- config.userId = userId;
- }
- }
- config[option] = newConfig[option];
- });
- if (configAutostart === false || newConfigAutostart === false) {
- config['autostart'] = false;
- }
- }
-
- /**
- * Attempts to extract the userid from the query parameters of the URL.
- * @param {string} param The name of the query parameter containing the userid.
- * @return {string|null} The extracted/decoded userid, or null if none is found.
- */
- function getUserIdFromParams(param) {
- var userField = param;
- var regex = new RegExp('[?&]' + userField + '(=([^&#]*)|&|#|$)');
- var results = window.location.href.match(regex);
- if (results && results[2]) {
- return decodeURIComponent(results[2].replace(/\+/g, ' '));
- } else {
- return null;
- }
- }
-
- 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.
- */
-
- var browser = detect();
- var logs$1;
- var config$1;
-
- // Interval Logging Globals
- var intervalID;
- var intervalType;
- var intervalPath;
- var intervalTimer;
- var intervalCounter;
- var intervalLog;
- var cbHandlers = {};
-
- /**
- * Adds named callbacks to be executed when logging.
- * @param {Object } newCallbacks An object containing named callback functions.
- */
- function addCallbacks() {
- for (var _len = arguments.length, newCallbacks = new Array(_len), _key = 0; _key < _len; _key++) {
- newCallbacks[_key] = arguments[_key];
- }
- newCallbacks.forEach(function (source) {
- var descriptors = Object.keys(source).reduce(function (descriptors, key) {
- descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
- return descriptors;
- }, {});
- Object.getOwnPropertySymbols(source).forEach(function (sym) {
- var descriptor = Object.getOwnPropertyDescriptor(source, sym);
- if (descriptor.enumerable) {
- descriptors[sym] = descriptor;
- }
- });
- Object.defineProperties(cbHandlers, descriptors);
- });
- return cbHandlers;
- }
-
- /**
- * Removes callbacks by name.
- * @param {String[]} targetKeys A list of names of functions to remove.
- */
- function removeCallbacks(targetKeys) {
- targetKeys.forEach(function (key) {
- if (Object.hasOwn(cbHandlers, key)) {
- delete cbHandlers[key];
- }
- });
- }
-
- /**
- * Assigns the config and log container to be used by the logging functions.
- * @param {Array} newLogs Log container.
- * @param {Object} newConfig Configuration to use while logging.
- */
- function initPackager(newLogs, newConfig) {
- logs$1 = newLogs;
- config$1 = newConfig;
- cbHandlers = [];
- intervalID = null;
- intervalType = null;
- intervalPath = null;
- intervalTimer = null;
- intervalCounter = 0;
- intervalLog = null;
- }
-
- /**
- * Transforms the provided HTML event into a log and appends it to the log queue.
- * @param {Object} e The event to be logged.
- * @param {Function} detailFcn The function to extract additional log parameters from the event.
- * @return {boolean} Whether the event was logged.
- */
- function packageLog(e, detailFcn) {
- if (!config$1.on) {
- return false;
- }
- var details = null;
- if (detailFcn) {
- details = detailFcn(e);
- }
- var timeFields = extractTimeFields(e.timeStamp && e.timeStamp > 0 ? config$1.time(e.timeStamp) : Date.now());
- var log = {
- 'target': getSelector(e.target),
- 'path': buildPath(e),
- 'pageUrl': window.location.href,
- 'pageTitle': document.title,
- 'pageReferrer': document.referrer,
- 'browser': detectBrowser(),
- 'clientTime': timeFields.milli,
- 'microTime': timeFields.micro,
- 'location': getLocation(e),
- 'scrnRes': getSreenRes(),
- 'type': e.type,
- 'logType': 'raw',
- 'userAction': true,
- 'details': details,
- 'userId': config$1.userId,
- 'toolVersion': config$1.version,
- 'toolName': config$1.toolName,
- 'useraleVersion': config$1.useraleVersion,
- 'sessionID': config$1.sessionID
- };
- for (var _i = 0, _Object$values = Object.values(cbHandlers); _i < _Object$values.length; _i++) {
- var func = _Object$values[_i];
- if (typeof func === 'function') {
- log = func(log, e);
- if (!log) {
- return false;
- }
- }
- }
- logs$1.push(log);
- return true;
- }
-
- /**
- * Packages the provided customLog to include standard meta data and appends it to the log queue.
- * @param {Object} customLog The behavior to be logged.
- * @param {Function} detailFcn The function to extract additional log parameters from the event.
- * @param {boolean} userAction Indicates user behavior (true) or system behavior (false)
- * @return {boolean} Whether the event was logged.
- */
- function packageCustomLog(customLog, detailFcn, userAction) {
- if (!config$1.on) {
- return false;
- }
- var details = null;
- if (detailFcn) {
- details = detailFcn();
- }
- var metaData = {
- 'pageUrl': window.location.href,
- 'pageTitle': document.title,
- 'pageReferrer': document.referrer,
- 'browser': detectBrowser(),
- 'clientTime': Date.now(),
- 'scrnRes': getSreenRes(),
- 'logType': 'custom',
- 'userAction': userAction,
- 'details': details,
- 'userId': config$1.userId,
- 'toolVersion': config$1.version,
- 'toolName': config$1.toolName,
- 'useraleVersion': config$1.useraleVersion,
- 'sessionID': config$1.sessionID
- };
- var log = Object.assign(metaData, customLog);
- for (var _i2 = 0, _Object$values2 = Object.values(cbHandlers); _i2 < _Object$values2.length; _i2++) {
- var func = _Object$values2[_i2];
- if (typeof func === 'function') {
- log = func(log, null);
- if (!log) {
- return false;
- }
- }
- }
- logs$1.push(log);
- return true;
- }
-
- /**
- * 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))
- };
- }
-
- /**
- * Track intervals and gather details about it.
- * @param {Object} e
- * @return boolean
- */
- function packageIntervalLog(e) {
- var target = getSelector(e.target);
- var path = buildPath(e);
- var type = e.type;
- var timestamp = Math.floor(e.timeStamp && e.timeStamp > 0 ? config$1.time(e.timeStamp) : Date.now());
-
- // Init - this should only happen once on initialization
- if (intervalID == null) {
- intervalID = target;
- intervalType = type;
- intervalPath = path;
- intervalTimer = timestamp;
- intervalCounter = 0;
- }
- if (intervalID !== target || intervalType !== type) {
- // When to create log? On transition end
- // @todo Possible for intervalLog to not be pushed in the event the interval never ends...
-
- intervalLog = {
- 'target': intervalID,
- 'path': intervalPath,
- 'pageUrl': window.location.href,
- 'pageTitle': document.title,
- 'pageReferrer': document.referrer,
- 'browser': detectBrowser(),
- 'count': intervalCounter,
- 'duration': timestamp - intervalTimer,
- // microseconds
- 'startTime': intervalTimer,
- 'endTime': timestamp,
- 'type': intervalType,
- 'logType': 'interval',
- 'targetChange': intervalID !== target,
- 'typeChange': intervalType !== type,
- 'userAction': false,
- 'userId': config$1.userId,
- 'toolVersion': config$1.version,
- 'toolName': config$1.toolName,
- 'useraleVersion': config$1.useraleVersion,
- 'sessionID': config$1.sessionID
- };
- for (var _i3 = 0, _Object$values3 = Object.values(cbHandlers); _i3 < _Object$values3.length; _i3++) {
- var func = _Object$values3[_i3];
- if (typeof func === 'function') {
- intervalLog = func(intervalLog, null);
- if (!intervalLog) {
- return false;
- }
- }
- }
- logs$1.push(intervalLog);
-
- // Reset
- intervalID = target;
- intervalType = type;
- intervalPath = path;
- intervalTimer = timestamp;
- intervalCounter = 0;
- }
-
- // Interval is still occuring, just update counter
- if (intervalID == target && intervalType == type) {
- intervalCounter = intervalCounter + 1;
- }
- return true;
- }
-
- /**
- * Extracts coordinate information from the event
- * depending on a few browser quirks.
- * @param {Object} e The event to extract coordinate information from.
- * @return {Object} An object containing nullable x and y coordinates for the event.
- */
- function getLocation(e) {
- if (e.pageX != null) {
- return {
- 'x': e.pageX,
- 'y': e.pageY
- };
- } else if (e.clientX != null) {
- return {
- 'x': document.documentElement.scrollLeft + e.clientX,
- 'y': document.documentElement.scrollTop + e.clientY
- };
- } else {
- return {
- 'x': null,
- 'y': null
- };
- }
- }
-
- /**
- * Extracts innerWidth and innerHeight to provide estimates of screen resolution
- * @return {Object} An object containing the innerWidth and InnerHeight
- */
- function getSreenRes() {
- return {
- 'width': window.innerWidth,
- 'height': window.innerHeight
- };
- }
-
- /**
- * Builds a string CSS selector from the provided element
- * @param {HTMLElement} ele The element from which the selector is built.
- * @return {string} The CSS selector for the element, or Unknown if it can't be determined.
- */
- function getSelector(ele) {
- if (ele.localName) {
- return ele.localName + (ele.id ? '#' + ele.id : '') + (ele.className ? '.' + ele.className : '');
- } else if (ele.nodeName) {
- return ele.nodeName + (ele.id ? '#' + ele.id : '') + (ele.className ? '.' + ele.className : '');
- } else if (ele && ele.document && ele.location && ele.alert && ele.setInterval) {
- return "Window";
- } else {
- return "Unknown";
- }
- }
-
- /**
- * Builds an array of elements from the provided event target, to the root element.
- * @param {Object} e Event from which the path should be built.
- * @return {HTMLElement[]} Array of elements, starting at the event target, ending at the root element.
- */
- function buildPath(e) {
- if (e instanceof window.Event) {
- var path = e.composedPath();
- return selectorizePath(path);
- }
- }
-
- /**
- * Builds a CSS selector path from the provided list of elements.
- * @param {HTMLElement[]} path Array of HTMLElements from which the path should be built.
- * @return {string[]} Array of string CSS selectors.
- */
- function selectorizePath(path) {
- var i = 0;
- var pathEle;
- var pathSelectors = [];
- while (pathEle = path[i]) {
- pathSelectors.push(getSelector(pathEle));
- ++i;
- }
- return pathSelectors;
- }
- function detectBrowser() {
- return {
- 'browser': browser ? browser.name : '',
- 'version': browser ? browser.version : ''
- };
- }
-
- /*
- * 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 events;
- var bufferBools;
- var bufferedEvents;
- //@todo: Investigate drag events and their behavior
- var intervalEvents = ['click', 'focus', 'blur', 'input', 'change', 'mouseover', 'submit'];
- var refreshEvents;
- var windowEvents = ['load', 'blur', 'focus'];
-
- /**
- * Maps an event to an object containing useful information.
- * @param {Object} e Event to extract data from
- */
- function extractMouseEvent(e) {
- return {
- 'clicks': e.detail,
- 'ctrl': e.ctrlKey,
- 'alt': e.altKey,
- 'shift': e.shiftKey,
- 'meta': e.metaKey
- // 'text' : e.target.innerHTML
- };
- }
-
- /**
- * Defines the way information is extracted from various events.
- * Also defines which events we will listen to.
- * @param {Object} config Configuration object to read from.
- */
- function defineDetails(config) {
- // Events list
- // Keys are event types
- // Values are functions that return details object if applicable
- events = {
- 'click': extractMouseEvent,
- 'dblclick': extractMouseEvent,
- 'mousedown': extractMouseEvent,
- 'mouseup': extractMouseEvent,
- 'focus': null,
- 'blur': null,
- 'input': config.logDetails ? function (e) {
- return {
- 'value': e.target.value
- };
- } : null,
- 'change': config.logDetails ? function (e) {
- return {
- 'value': e.target.value
- };
- } : null,
- 'dragstart': null,
- 'dragend': null,
- 'drag': null,
- 'drop': null,
- 'keydown': config.logDetails ? function (e) {
- return {
- 'key': e.keyCode,
- 'ctrl': e.ctrlKey,
- 'alt': e.altKey,
- 'shift': e.shiftKey,
- 'meta': e.metaKey
- };
- } : null,
- 'mouseover': null
- };
- bufferBools = {};
- bufferedEvents = {
- 'wheel': function wheel(e) {
- return {
- 'x': e.deltaX,
- 'y': e.deltaY,
- 'z': e.deltaZ
- };
- },
- 'scroll': function scroll() {
- return {
- 'x': window.scrollX,
- 'y': window.scrollY
- };
- },
- 'resize': function resize() {
- return {
- 'width': window.outerWidth,
- 'height': window.outerHeight
- };
- }
- };
- refreshEvents = {
- 'submit': null
- };
- }
-
- /**
- * Defines the way information is extracted from various events.
- * Also defines which events we will listen to.
- * @param {Object} options UserALE.js Configuration object to read from.
- * @param {string} type of html event (e.g., 'click', 'mouseover', etc.), such as passed to addEventListener methods.
- */
- function defineCustomDetails(options, type) {
- // Events list
- // Keys are event types
- // Values are functions that return details object if applicable
- var eventType = {
- 'click': extractMouseEvent,
- 'dblclick': extractMouseEvent,
- 'mousedown': extractMouseEvent,
- 'mouseup': extractMouseEvent,
- 'focus': null,
- 'blur': null,
- 'input': options.logDetails ? function (e) {
- return {
- 'value': e.target.value
- };
- } : null,
- 'change': options.logDetails ? function (e) {
- return {
- 'value': e.target.value
- };
- } : null,
- 'dragstart': null,
- 'dragend': null,
- 'drag': null,
- 'drop': null,
- 'keydown': options.logDetails ? function (e) {
- return {
- 'key': e.keyCode,
- 'ctrl': e.ctrlKey,
- 'alt': e.altKey,
- 'shift': e.shiftKey,
- 'meta': e.metaKey
- };
- } : null,
- 'mouseover': null,
- 'wheel': function wheel(e) {
- return {
- 'x': e.deltaX,
- 'y': e.deltaY,
- 'z': e.deltaZ
- };
- },
- 'scroll': function scroll() {
- return {
- 'x': window.scrollX,
- 'y': window.scrollY
- };
- },
- 'resize': function resize() {
- return {
- 'width': window.outerWidth,
- 'height': window.outerHeight
- };
- },
- 'submit': null
- };
- return eventType[type];
- }
-
- /**
- * Hooks the event handlers for each event type of interest.
- * @param {Object} config Configuration object to use.
- * @return {boolean} Whether the operation succeeded
- */
- function attachHandlers(config) {
- defineDetails(config);
- Object.keys(events).forEach(function (ev) {
- document.addEventListener(ev, function (e) {
- packageLog(e, events[ev]);
- }, true);
- });
- intervalEvents.forEach(function (ev) {
- document.addEventListener(ev, function (e) {
- packageIntervalLog(e);
- }, true);
- });
- Object.keys(bufferedEvents).forEach(function (ev) {
- bufferBools[ev] = true;
- window.addEventListener(ev, function (e) {
- if (bufferBools[ev]) {
- bufferBools[ev] = false;
- packageLog(e, bufferedEvents[ev]);
- setTimeout(function () {
- bufferBools[ev] = true;
- }, config.resolution);
- }
- }, true);
- });
- Object.keys(refreshEvents).forEach(function (ev) {
- document.addEventListener(ev, function (e) {
- packageLog(e, events[ev]);
- }, true);
- });
- windowEvents.forEach(function (ev) {
- window.addEventListener(ev, function (e) {
- packageLog(e, function () {
- return {
- 'window': true
- };
- });
- }, true);
- });
- return true;
- }
-
- /*
- * 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);
- }
-
- var config = {};
- var logs = [];
- var startLoadTimestamp = Date.now();
- var endLoadTimestamp;
- window.onload = function () {
- endLoadTimestamp = Date.now();
- };
- exports.started = false;
-
- // Start up Userale
- config.on = false;
- config.useraleVersion = version$1;
- configure(config, getInitialSettings());
- initPackager(logs, config);
- if (config.autostart) {
- setup(config);
- }
-
- /**
- * Hooks the global event listener, and starts up the
- * logging interval.
- * @param {Object} config Configuration settings for the logger
- */
- function setup(config) {
- if (!exports.started) {
- setTimeout(function () {
- var state = document.readyState;
- if (config.autostart && (state === 'interactive' || state === 'complete')) {
- attachHandlers(config);
- initSender(logs, config);
- exports.started = config.on = true;
- packageCustomLog({
- type: 'load',
- logType: 'raw',
- details: {
- pageLoadTime: endLoadTimestamp - startLoadTimestamp
- }
- }, function () {}, false);
- } else {
- setup(config);
- }
- }, 100);
- }
- }
-
- // Export the Userale API
- var version = version$1;
-
- /**
- * Used to start the logging process if the
- * autostart configuration option is set to false.
- */
- function start() {
- if (!exports.started || config.autostart === false) {
- exports.started = config.on = true;
- config.autostart = true;
- }
- }
-
- /**
- * Halts the logging process. Logs will no longer be sent.
- */
- function stop() {
- exports.started = config.on = false;
- config.autostart = false;
- }
-
- /**
- * Updates the current configuration
- * object with the provided values.
- * @param {Object} newConfig The configuration options to use.
- * @return {Object} Returns the updated configuration.
- */
- function options(newConfig) {
- if (newConfig !== undefined) {
- configure(config, newConfig);
- }
- return config;
- }
-
- /**
- * Appends a log to the log queue.
- * @param {Object} customLog The log to append.
- * @return {boolean} Whether the operation succeeded.
- */
- function log(customLog) {
- if (customLog !== null && _typeof(customLog) === 'object') {
- logs.push(customLog);
- return true;
- } else {
- return false;
- }
- }
-
- exports.addCallbacks = addCallbacks;
- exports.buildPath = buildPath;
- exports.details = defineCustomDetails;
- exports.getSelector = getSelector;
- exports.log = log;
- exports.options = options;
- exports.packageCustomLog = packageCustomLog;
- exports.packageLog = packageLog;
- exports.removeCallbacks = removeCallbacks;
- exports.start = start;
- exports.stop = stop;
- exports.version = version;
-
-}));
diff --git a/build/userale-2.4.0.min.js b/build/userale-2.4.0.min.js
deleted file mode 100644
index 81d0c30..0000000
--- a/build/userale-2.4.0.min.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * 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.
- * @preserved
- */
-!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).userale={})}(this,(function(e){"use strict";function t(e){return t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t(e)}var n="2.4.0",o=null;function r(e,t){var n=e.autostart,o=t.autostart;Object.keys(t).forEach((function(n){if("userFromParams"===n){var o=(r=t[n],i=new RegExp("[?&]"+r+"(=([^&#]*)|&|#|$)"),(a=window.location.href.match(i))&&a[2]?decodeURIComponent(a[2].replace(/\+/g," ")):null);o&&(e.userId=o)}var r,i,a;e[n]=t[n]})),!1!==n&&!1!==o||(e.autostart=!1)}var i=function(e,t,n){if(n||2===arguments.length)for(var o,r=0,i=t.length;r<i;r++)!o&&r in t||(o||(o=Array.prototype.slice.call(t,0,r)),o[r]=t[r]);return e.concat(o||Array.prototype.slice.call(t))},a=function(e,t,n){this.name=e,this.version=t,this.os=n,this.type="browser"},s=function(e){this.version=e,this.type="node",this.name="node",this.os=process.platform},l=function(e,t,n,o){this.name=e,this.version=t,this.os=n,this.bot=o,this.type="bot-device"},u=function(){this.type="bot",this.bot=!0,this.name="bot",this.version=null,this.os=null},c=function(){this.type="react-native",this.name="react-native",this.version=null,this.os=null},d=/(nuhk|curl|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask\ Jeeves\/Teoma|ia_archiver)/,f=3,m=[["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",/alexa|bot|crawl(er|ing)|facebookexternalhit|feedburner|google web preview|nagios|postrank|pingdom|slurp|spider|yahoo!|yandex/]],p=[["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 w(e){var t=function(e){return""!==e&&m.reduce((function(t,n){var o=n[0],r=n[1];if(t)return t;var i=r.exec(e);return!!i&&[o,i]}),!1)}(e);if(!t)return null;var n=t[0],o=t[1];if("searchbot"===n)return new u;var r=o[1]&&o[1].split(".").join("_").split("_").slice(0,3);r?r.length<f&&(r=i(i([],r,!0),function(e){for(var t=[],n=0;n<e;n++)t.push("0");return t}(f-r.length),!0)):r=[];var s=r.join("."),c=function(e){for(var t=0,n=p.length;t<n;t++){var o=p[t],r=o[0];if(o[1].exec(e))return r}return null}(e),w=d.exec(e);return w&&w[1]?new l(n,s,c,w[1]):new a(n,s,c)}var h,g,v,y,b,S,O,k,W,E,T,I,N=h?w(h):"undefined"==typeof document&&"undefined"!=typeof navigator&&"ReactNative"===navigator.product?new c:"undefined"!=typeof navigator?w(navigator.userAgent):"undefined"!=typeof process&&process.version?new s(process.version.slice(1)):null,x={};function D(e,t){if(!v.on)return!1;var n=null;t&&(n=t(e));for(var o,r=(o=e.timeStamp&&e.timeStamp>0?v.time(e.timeStamp):Date.now(),{milli:Math.floor(o),micro:Number((o%1).toFixed(3))}),i={target:B(e.target),path:P(e),pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:j(),clientTime:r.milli,microTime:r.micro,location:A(e),scrnRes:M(),type:e.type,logType:"raw",userAction:!0,details:n,userId:v.userId,toolVersion:v.version,toolName:v.toolName,useraleVersion:v.useraleVersion,sessionID:v.sessionID},a=0,s=Object.values(x);a<s.length;a++){var l=s[a];if("function"==typeof l&&!(i=l(i,e)))return!1}return g.push(i),!0}function C(e,t,n){if(!v.on)return!1;var o=null;t&&(o=t());for(var r={pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:j(),clientTime:Date.now(),scrnRes:M(),logType:"custom",userAction:n,details:o,userId:v.userId,toolVersion:v.version,toolName:v.toolName,useraleVersion:v.useraleVersion,sessionID:v.sessionID},i=Object.assign(r,e),a=0,s=Object.values(x);a<s.length;a++){var l=s[a];if("function"==typeof l&&!(i=l(i,null)))return!1}return g.push(i),!0}function A(e){return null!=e.pageX?{x:e.pageX,y:e.pageY}:null!=e.clientX?{x:document.documentElement.scrollLeft+e.clientX,y:document.documentElement.scrollTop+e.clientY}:{x:null,y:null}}function M(){return{width:window.innerWidth,height:window.innerHeight}}function B(e){return e.localName?e.localName+(e.id?"#"+e.id:"")+(e.className?"."+e.className:""):e.nodeName?e.nodeName+(e.id?"#"+e.id:"")+(e.className?"."+e.className:""):e&&e.document&&e.location&&e.alert&&e.setInterval?"Window":"Unknown"}function P(e){if(e instanceof window.Event)return function(e){var t,n=0,o=[];for(;t=e[n];)o.push(B(t)),++n;return o}(e.composedPath())}function j(){return{browser:N?N.name:"",version:N?N.version:""}}var K,V=["click","focus","blur","input","change","mouseover","submit"],L=["load","blur","focus"];function R(e){return{clicks:e.detail,ctrl:e.ctrlKey,alt:e.altKey,shift:e.shiftKey,meta:e.metaKey}}function X(e){return function(e){E={click:R,dblclick:R,mousedown:R,mouseup:R,focus:null,blur:null,input:e.logDetails?function(e){return{value:e.target.value}}:null,change:e.logDetails?function(e){return{value:e.target.value}}:null,dragstart:null,dragend:null,drag:null,drop:null,keydown:e.logDetails?function(e){return{key:e.keyCode,ctrl:e.ctrlKey,alt:e.altKey,shift:e.shiftKey,meta:e.metaKey}}:null,mouseover:null},T={},I={wheel:function(e){return{x:e.deltaX,y:e.deltaY,z:e.deltaZ}},scroll:function(){return{x:window.scrollX,y:window.scrollY}},resize:function(){return{width:window.outerWidth,height:window.outerHeight}}},K={submit:null}}(e),Object.keys(E).forEach((function(e){document.addEventListener(e,(function(t){D(t,E[e])}),!0)})),V.forEach((function(e){document.addEventListener(e,(function(e){!function(e){var t=B(e.target),n=P(e),o=e.type,r=Math.floor(e.timeStamp&&e.timeStamp>0?v.time(e.timeStamp):Date.now());if(null==y&&(y=t,b=o,S=n,O=r,k=0),y!==t||b!==o){W={target:y,path:S,pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:j(),count:k,duration:r-O,startTime:O,endTime:r,type:b,logType:"interval",targetChange:y!==t,typeChange:b!==o,userAction:!1,userId:v.userId,toolVersion:v.version,toolName:v.toolName,useraleVersion:v.useraleVersion,sessionID:v.sessionID};for(var i=0,a=Object.values(x);i<a.length;i++){var s=a[i];if("function"==typeof s&&!(W=s(W,null)))return!1}g.push(W),y=t,b=o,S=n,O=r,k=0}y==t&&b==o&&(k+=1)}(e)}),!0)})),Object.keys(I).forEach((function(t){T[t]=!0,window.addEventListener(t,(function(n){T[t]&&(T[t]=!1,D(n,I[t]),setTimeout((function(){T[t]=!0}),e.resolution))}),!0)})),Object.keys(K).forEach((function(e){document.addEventListener(e,(function(t){D(t,E[e])}),!0)})),L.forEach((function(e){window.addEventListener(e,(function(e){D(e,(function(){return{window:!0}}))}),!0)})),!0}var $=null;function _(e,t){null!==$&&clearInterval($),$=function(e,t){return setInterval((function(){t.on&&e.length>=t.logCountThreshold&&(H(e.slice(0),t,0),e.splice(0))}),t.transmitInterval)}(e,t),function(e,t){window.addEventListener("pagehide",(function(){t.on&&e.length>0&&(navigator.sendBeacon(t.url,JSON.stringify(e)),e.splice(0))}))}(e,t)}function H(e,t,n){var o=new XMLHttpRequest,r=JSON.stringify(e);o.open("POST",t.url),t.authHeader&&o.setRequestHeader("Authorization",t.authHeader),o.setRequestHeader("Content-type","application/json;charset=UTF-8"),o.onreadystatechange=function(){4===o.readyState&&200!==o.status&&n>0&&H(e,t,n--)},o.send(r)}var z,F={},Y=[],J=Date.now();window.onload=function(){z=Date.now()},e.started=!1,F.on=!1,F.useraleVersion=n,r(F,function(){var e={};null===o&&(o=function(e,t){if(null===window.sessionStorage.getItem(e))return window.sessionStorage.setItem(e,JSON.stringify(t)),t;return JSON.parse(window.sessionStorage.getItem(e))}("userAleSessionId","session_"+String(Date.now())));var t,n=document.currentScript||(t=document.getElementsByTagName("script"))[t.length-1],r=n?n.getAttribute.bind(n):function(){return null};return e.autostart="false"!==r("data-autostart"),e.url=r("data-url")||"http://localhost:8000",e.transmitInterval=+r("data-interval")||5e3,e.logCountThreshold=+r("data-threshold")||5,e.userId=r("data-user")||null,e.version=r("data-version")||null,e.logDetails="true"===r("data-log-details"),e.resolution=+r("data-resolution")||500,e.toolName=r("data-tool")||null,e.userFromParams=r("data-user-from-params")||null,e.time=function(e){var t;if(e.timeStamp&&e.timeStamp>0){var n=Date.now()-e.timeStamp;if(n<0)t=function(){return e.timeStamp/1e3};else if(n>e.timeStamp){var o=performance.timing.navigationStart;t=function(e){return e+o}}else t=function(e){return e}}else t=function(){return Date.now()};return t}(document.createEvent("CustomEvent")),e.sessionID=r("data-session")||o,e.authHeader=r("data-auth")||null,e.custIndex=r("data-index")||null,e}()),g=Y,v=F,x=[],y=null,b=null,S=null,O=null,k=0,W=null,F.autostart&&function t(n){e.started||setTimeout((function(){var o=document.readyState;!n.autostart||"interactive"!==o&&"complete"!==o?t(n):(X(n),_(Y,n),e.started=n.on=!0,C({type:"load",logType:"raw",details:{pageLoadTime:z-J}},(function(){}),!1))}),100)}(F);var U=n;e.addCallbacks=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return t.forEach((function(e){var t=Object.keys(e).reduce((function(t,n){return t[n]=Object.getOwnPropertyDescriptor(e,n),t}),{});Object.getOwnPropertySymbols(e).forEach((function(n){var o=Object.getOwnPropertyDescriptor(e,n);o.enumerable&&(t[n]=o)})),Object.defineProperties(x,t)})),x},e.buildPath=P,e.details=function(e,t){return{click:R,dblclick:R,mousedown:R,mouseup:R,focus:null,blur:null,input:e.logDetails?function(e){return{value:e.target.value}}:null,change:e.logDetails?function(e){return{value:e.target.value}}:null,dragstart:null,dragend:null,drag:null,drop:null,keydown:e.logDetails?function(e){return{key:e.keyCode,ctrl:e.ctrlKey,alt:e.altKey,shift:e.shiftKey,meta:e.metaKey}}:null,mouseover:null,wheel:function(e){return{x:e.deltaX,y:e.deltaY,z:e.deltaZ}},scroll:function(){return{x:window.scrollX,y:window.scrollY}},resize:function(){return{width:window.outerWidth,height:window.outerHeight}},submit:null}[t]},e.getSelector=B,e.log=function(e){return null!==e&&"object"===t(e)&&(Y.push(e),!0)},e.options=function(e){return void 0!==e&&r(F,e),F},e.packageCustomLog=C,e.packageLog=D,e.removeCallbacks=function(e){e.forEach((function(e){Object.hasOwn(x,e)&&delete x[e]}))},e.start=function(){e.started&&!1!==F.autostart||(e.started=F.on=!0,F.autostart=!0)},e.stop=function(){e.started=F.on=!1,F.autostart=!1},e.version=U}));
diff --git a/example/log-label-example/README.md b/example/log-label-example/README.md
index 1b6b159..a17a89e 100644
--- a/example/log-label-example/README.md
+++ b/example/log-label-example/README.md
@@ -6,6 +6,9 @@
## Adding Custom Labels to Logs
+#### Important Note
+Please be aware that when adding custom logs without disabling raw logging for those specific logs, duplicate log entries will be generated: one for raw logs and another for custom logs.
+
### Example 1
Consider the following HTML:
@@ -59,4 +62,4 @@
Note that we only advise adding custom event listeners in the following scenarios:
1. For events not captured natively by UserALE.js (as seen above)
-2. For sending custom logs *only*
\ No newline at end of file
+2. For sending custom logs *only*
diff --git a/example/log.schema.json b/example/log.schema.json
new file mode 100644
index 0000000..e138c7f
--- /dev/null
+++ b/example/log.schema.json
@@ -0,0 +1,125 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://flagon.incubator.apache.org/log.schema.json",
+ "title": "Log",
+ "description": "A raw or custom log produced by userale",
+ "type": "object",
+ "properties": {
+ "target": {
+ "type": "string"
+ },
+ "path": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minItems": 1
+ },
+ "pageUrl": {
+ "type": "string"
+ },
+ "pageTitle": {
+ "type": "string"
+ },
+ "browser": {
+ "type": "object",
+ "properties": {
+ "browser": {
+ "type": "string"
+ },
+ "version": {
+ "type": "string"
+ }
+ },
+ "required" : ["browser", "version"]
+ },
+ "clientTime": {
+ "type": "integer"
+ },
+ "microTime": {
+ "type": "number",
+ "minimum": 0,
+ "maxmaximumi": 1
+ },
+ "location": {
+ "type": "object",
+ "properties": {
+ "x": {
+ "type": ["integer", "null"]
+ },
+ "y": {
+ "type": ["integer", "null"]
+ }
+ },
+ "required" : ["x", "y"]
+ },
+ "scrnRes": {
+ "type": "object",
+ "properties": {
+ "height": {
+ "type": "integer"
+ },
+ "width": {
+ "type": "integer"
+ }
+ },
+ "required" : ["height", "width"]
+ },
+ "type": {
+ "type": "string"
+ },
+ "logType": {
+ "type": "string",
+ "enum": ["raw", "custom"]
+ },
+ "userAction": {
+ "type": "boolean"
+ },
+ "details": {
+ "type": "object"
+ },
+ "userId": {
+ "type": "string"
+ },
+ "toolVersion": {
+ "type": "string"
+ },
+ "toolName": {
+ "type": "string"
+ },
+ "useraleVersion": {
+ "type": "string"
+ },
+ "sessionID": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "pageUrl",
+ "pageTitle",
+ "pageReferrer",
+ "browser",
+ "clientTime",
+ "scrnRes",
+ "logType",
+ "userAction",
+ "details",
+ "userId",
+ "toolVersion",
+ "toolName",
+ "useraleVersion",
+ "sessionID"
+ ],
+ "if": {
+ "properties": { "logType": { "const": "raw" } }
+ },
+ "then": {
+ "required": [
+ "target",
+ "path",
+ "microTime",
+ "location",
+ "type"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/journey/userale.journey.cy.js b/journey/userale.journey.cy.js
index 1a73d39..cb8d28d 100644
--- a/journey/userale.journey.cy.js
+++ b/journey/userale.journey.cy.js
@@ -14,6 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import { validate } from 'jsonschema';
+
describe('Userale logging', () => {
beforeEach(() => {
cy.intercept('POST', 'http://localhost:8000/').as('backend')
@@ -26,7 +28,7 @@
const pageLoadLog = body[0]
expect(pageLoadLog['details']['pageLoadTime']).to.be.greaterThan(0)
expect(pageLoadLog).to.contain({
- logType: 'raw',
+ logType: 'custom',
type: 'load'
})
})
@@ -62,4 +64,24 @@
expect(actualValue).to.equal(expectedValue)
})
});
+
+ it('produces valid logs', () => {
+ cy.visit('http://localhost:8000');
+ cy.wait('@backend').then(xhr => {
+ var schema = require('../example/log.schema.json');
+ for(const log of xhr.request.body) {
+ const result = validate(log, schema);
+ expect(result.valid, result.errors).to.equal(true);
+ }
+ })
+ cy.contains(/click me/i).click();
+ cy.wait('@backend').then(xhr => {
+ var schema = require('../example/log.schema.json');
+ for(const log of xhr.request.body) {
+ const result = validate(log, schema);
+ expect(result.valid, result.errors).to.equal(true);
+ }
+ })
+ });
+
});
diff --git a/package-lock.json b/package-lock.json
index d4e249c..8361101 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,13 +21,15 @@
"@rollup/plugin-terser": "^0.4.4",
"body-parser": "^1.20.2",
"chai": "^4.3.10",
+ "chai-subset": "^1.6.0",
"cypress": "^13.6.0",
"detect-browser": "^5.3.0",
"dom-storage": "^2.1.0",
"eslint": "^8.55.0",
"express": "^4.18.2",
"global-jsdom": "^9.1.0",
- "jsdom": "^22.1.0",
+ "jsdom": "^23.0.1",
+ "jsonschema": "^1.4.1",
"mocha": "^10.2.0",
"nodemon": "^3.0.2",
"rollup": "^4.6.1",
@@ -1404,16 +1406,16 @@
}
},
"node_modules/@babel/plugin-transform-runtime": {
- "version": "7.23.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.4.tgz",
- "integrity": "sha512-ITwqpb6V4btwUG0YJR82o2QvmWrLgDnx/p2A3CTPYGaRgULkDiC0DRA2C4jlRB9uXGUEfaSS/IGHfVW+ohzYDw==",
+ "version": "7.23.9",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.9.tgz",
+ "integrity": "sha512-A7clW3a0aSjm3ONU9o2HAILSegJCYlEZmOhmBRReVtIpY/Z/p7yIZ+wR41Z+UipwdGuqwtID/V/dOdZXjwi9gQ==",
"dev": true,
"dependencies": {
"@babel/helper-module-imports": "^7.22.15",
"@babel/helper-plugin-utils": "^7.22.5",
- "babel-plugin-polyfill-corejs2": "^0.4.6",
- "babel-plugin-polyfill-corejs3": "^0.8.5",
- "babel-plugin-polyfill-regenerator": "^0.5.3",
+ "babel-plugin-polyfill-corejs2": "^0.4.8",
+ "babel-plugin-polyfill-corejs3": "^0.9.0",
+ "babel-plugin-polyfill-regenerator": "^0.5.5",
"semver": "^6.3.1"
},
"engines": {
@@ -1423,6 +1425,35 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/plugin-transform-runtime/node_modules/@babel/helper-define-polyfill-provider": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz",
+ "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-compilation-targets": "^7.22.6",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "debug": "^4.1.1",
+ "lodash.debounce": "^4.0.8",
+ "resolve": "^1.14.2"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-runtime/node_modules/babel-plugin-polyfill-corejs3": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz",
+ "integrity": "sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-define-polyfill-provider": "^0.5.0",
+ "core-js-compat": "^3.34.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
"node_modules/@babel/plugin-transform-shorthand-properties": {
"version": "7.23.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz",
@@ -1671,15 +1702,15 @@
}
},
"node_modules/@babel/register": {
- "version": "7.22.15",
- "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.22.15.tgz",
- "integrity": "sha512-V3Q3EqoQdn65RCgTLwauZaTfd1ShhwPmbBv+1dkZV/HpCGMKVyn6oFcRlI7RaKqiDQjX2Qd3AuoEguBgdjIKlg==",
+ "version": "7.23.7",
+ "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.23.7.tgz",
+ "integrity": "sha512-EjJeB6+kvpk+Y5DAkEAmbOBEFkh9OASx0huoEkqYTFxAZHzOAX2Oh5uwAUuL2rUddqfM0SA+KPXV2TbzoZ2kvQ==",
"dev": true,
"dependencies": {
"clone-deep": "^4.0.1",
"find-cache-dir": "^2.0.0",
"make-dir": "^2.1.0",
- "pirates": "^4.0.5",
+ "pirates": "^4.0.6",
"source-map-support": "^0.5.16"
},
"engines": {
@@ -2105,12 +2136,12 @@
}
},
"node_modules/@rollup/plugin-json": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.0.1.tgz",
- "integrity": "sha512-RgVfl5hWMkxN1h/uZj8FVESvPuBJ/uf6ly6GTj0GONnkfoBN5KC0MSz+PN2OLDgYXMhtG0mWpTrkiOjoxAIevw==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz",
+ "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==",
"dev": true,
"dependencies": {
- "@rollup/pluginutils": "^5.0.1"
+ "@rollup/pluginutils": "^5.1.0"
},
"engines": {
"node": ">=14.0.0"
@@ -2414,15 +2445,6 @@
"integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==",
"dev": true
},
- "node_modules/@tootallnate/once": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
- "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
- "dev": true,
- "engines": {
- "node": ">= 10"
- }
- },
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
@@ -2497,13 +2519,6 @@
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true
},
- "node_modules/abab": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
- "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
- "deprecated": "Use your platform's native atob() and btoa() methods instead",
- "dev": true
- },
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -2545,15 +2560,15 @@
}
},
"node_modules/agent-base": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
- "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
+ "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
"dev": true,
"dependencies": {
- "debug": "4"
+ "debug": "^4.3.4"
},
"engines": {
- "node": ">= 6.0.0"
+ "node": ">= 14"
}
},
"node_modules/aggregate-error": {
@@ -2803,19 +2818,35 @@
"dev": true
},
"node_modules/babel-plugin-polyfill-corejs2": {
- "version": "0.4.6",
- "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz",
- "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==",
+ "version": "0.4.8",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz",
+ "integrity": "sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg==",
"dev": true,
"dependencies": {
"@babel/compat-data": "^7.22.6",
- "@babel/helper-define-polyfill-provider": "^0.4.3",
+ "@babel/helper-define-polyfill-provider": "^0.5.0",
"semver": "^6.3.1"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
+ "node_modules/babel-plugin-polyfill-corejs2/node_modules/@babel/helper-define-polyfill-provider": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz",
+ "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-compilation-targets": "^7.22.6",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "debug": "^4.1.1",
+ "lodash.debounce": "^4.0.8",
+ "resolve": "^1.14.2"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
"node_modules/babel-plugin-polyfill-corejs3": {
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.6.tgz",
@@ -2830,12 +2861,28 @@
}
},
"node_modules/babel-plugin-polyfill-regenerator": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz",
- "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==",
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz",
+ "integrity": "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==",
"dev": true,
"dependencies": {
- "@babel/helper-define-polyfill-provider": "^0.4.3"
+ "@babel/helper-define-polyfill-provider": "^0.5.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/babel-plugin-polyfill-regenerator/node_modules/@babel/helper-define-polyfill-provider": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz",
+ "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-compilation-targets": "^7.22.6",
+ "@babel/helper-plugin-utils": "^7.22.5",
+ "debug": "^4.1.1",
+ "lodash.debounce": "^4.0.8",
+ "resolve": "^1.14.2"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
@@ -3144,6 +3191,15 @@
"node": ">=4"
}
},
+ "node_modules/chai-subset": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/chai-subset/-/chai-subset-1.6.0.tgz",
+ "integrity": "sha512-K3d+KmqdS5XKW5DWPd5sgNffL3uxdDe+6GdnJh3AYPhwnBGRY5urfvfcbRtWIvvpz+KxkL9FeBB6MZewLUNwug==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -3422,12 +3478,12 @@
"dev": true
},
"node_modules/core-js-compat": {
- "version": "3.33.3",
- "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.3.tgz",
- "integrity": "sha512-cNzGqFsh3Ot+529GIXacjTJ7kegdt5fPXxCBVS1G0iaZpuo/tBz399ymceLJveQhFFZ8qThHiP3fzuoQjKN2ow==",
+ "version": "3.35.1",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.1.tgz",
+ "integrity": "sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw==",
"dev": true,
"dependencies": {
- "browserslist": "^4.22.1"
+ "browserslist": "^4.22.2"
},
"funding": {
"type": "opencollective",
@@ -3467,15 +3523,14 @@
}
},
"node_modules/cypress": {
- "version": "13.6.0",
- "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.0.tgz",
- "integrity": "sha512-quIsnFmtj4dBUEJYU4OH0H12bABJpSujvWexC24Ju1gTlKMJbeT6tTO0vh7WNfiBPPjoIXLN+OUqVtiKFs6SGw==",
+ "version": "13.6.4",
+ "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.4.tgz",
+ "integrity": "sha512-pYJjCfDYB+hoOoZuhysbbYhEmNW7DEDsqn+ToCLwuVowxUXppIWRr7qk4TVRIU471ksfzyZcH+mkoF0CQUKnpw==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"@cypress/request": "^3.0.0",
"@cypress/xvfb": "^1.2.4",
- "@types/node": "^18.17.5",
"@types/sinonjs__fake-timers": "8.1.1",
"@types/sizzle": "^2.3.2",
"arch": "^2.2.0",
@@ -3655,17 +3710,16 @@
}
},
"node_modules/data-urls": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz",
- "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
+ "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
"dev": true,
"dependencies": {
- "abab": "^2.0.6",
- "whatwg-mimetype": "^3.0.0",
- "whatwg-url": "^12.0.0"
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.0.0"
},
"engines": {
- "node": ">=14"
+ "node": ">=18"
}
},
"node_modules/dayjs": {
@@ -3826,19 +3880,6 @@
"node": "*"
}
},
- "node_modules/domexception": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
- "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==",
- "deprecated": "Use your platform's native DOMException instead",
- "dev": true,
- "dependencies": {
- "webidl-conversions": "^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/duplexer": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
@@ -4842,15 +4883,15 @@
}
},
"node_modules/global-jsdom": {
- "version": "9.1.0",
- "resolved": "https://registry.npmjs.org/global-jsdom/-/global-jsdom-9.1.0.tgz",
- "integrity": "sha512-mNjpiQBgCTApFeRZxU3L01pjzulHIbwtk+5rGDdqUDACTupvhHywpgaZtXNMn/97HofZzHEoXdNR5qFNgjZCSg==",
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/global-jsdom/-/global-jsdom-9.2.0.tgz",
+ "integrity": "sha512-mspbiuYwcl5nbl2VRvHNaF4SzSmSMCTJGXXatzHYZAf6u3rO/aLXQICbVNTVI+vN8jaq1s4QcvKYnWhVepSbFQ==",
"dev": true,
"engines": {
"node": ">=16"
},
"peerDependencies": {
- "jsdom": ">=22 <23"
+ "jsdom": ">=23 <24"
}
},
"node_modules/globals": {
@@ -4992,15 +5033,15 @@
}
},
"node_modules/html-encoding-sniffer": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
- "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
"dev": true,
"dependencies": {
- "whatwg-encoding": "^2.0.0"
+ "whatwg-encoding": "^3.1.1"
},
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/http-errors": {
@@ -5020,17 +5061,16 @@
}
},
"node_modules/http-proxy-agent": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
- "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz",
+ "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==",
"dev": true,
"dependencies": {
- "@tootallnate/once": "2",
- "agent-base": "6",
- "debug": "4"
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
},
"engines": {
- "node": ">= 6"
+ "node": ">= 14"
}
},
"node_modules/http-signature": {
@@ -5048,16 +5088,16 @@
}
},
"node_modules/https-proxy-agent": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
- "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==",
"dev": true,
"dependencies": {
- "agent-base": "6",
+ "agent-base": "^7.0.2",
"debug": "4"
},
"engines": {
- "node": ">= 6"
+ "node": ">= 14"
}
},
"node_modules/human-signals": {
@@ -5436,40 +5476,38 @@
"dev": true
},
"node_modules/jsdom": {
- "version": "22.1.0",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz",
- "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==",
+ "version": "23.0.1",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-23.0.1.tgz",
+ "integrity": "sha512-2i27vgvlUsGEBO9+/kJQRbtqtm+191b5zAZrU/UezVmnC2dlDAFLgDYJvAEi94T4kjsRKkezEtLQTgsNEsW2lQ==",
"dev": true,
"dependencies": {
- "abab": "^2.0.6",
"cssstyle": "^3.0.0",
- "data-urls": "^4.0.0",
+ "data-urls": "^5.0.0",
"decimal.js": "^10.4.3",
- "domexception": "^4.0.0",
"form-data": "^4.0.0",
- "html-encoding-sniffer": "^3.0.0",
- "http-proxy-agent": "^5.0.0",
- "https-proxy-agent": "^5.0.1",
+ "html-encoding-sniffer": "^4.0.0",
+ "http-proxy-agent": "^7.0.0",
+ "https-proxy-agent": "^7.0.2",
"is-potential-custom-element-name": "^1.0.1",
- "nwsapi": "^2.2.4",
+ "nwsapi": "^2.2.7",
"parse5": "^7.1.2",
"rrweb-cssom": "^0.6.0",
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
- "tough-cookie": "^4.1.2",
- "w3c-xmlserializer": "^4.0.0",
+ "tough-cookie": "^4.1.3",
+ "w3c-xmlserializer": "^5.0.0",
"webidl-conversions": "^7.0.0",
- "whatwg-encoding": "^2.0.0",
- "whatwg-mimetype": "^3.0.0",
- "whatwg-url": "^12.0.1",
- "ws": "^8.13.0",
- "xml-name-validator": "^4.0.0"
+ "whatwg-encoding": "^3.1.1",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.0.0",
+ "ws": "^8.14.2",
+ "xml-name-validator": "^5.0.0"
},
"engines": {
- "node": ">=16"
+ "node": ">=18"
},
"peerDependencies": {
- "canvas": "^2.5.0"
+ "canvas": "^2.11.2"
},
"peerDependenciesMeta": {
"canvas": {
@@ -5557,6 +5595,15 @@
"graceful-fs": "^4.1.6"
}
},
+ "node_modules/jsonschema": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz",
+ "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/jsprim": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz",
@@ -8047,15 +8094,15 @@
}
},
"node_modules/tr46": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
- "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz",
+ "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==",
"dev": true,
"dependencies": {
- "punycode": "^2.3.0"
+ "punycode": "^2.3.1"
},
"engines": {
- "node": ">=14"
+ "node": ">=18"
}
},
"node_modules/tslib": {
@@ -8298,15 +8345,15 @@
}
},
"node_modules/w3c-xmlserializer": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
- "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
"dev": true,
"dependencies": {
- "xml-name-validator": "^4.0.0"
+ "xml-name-validator": "^5.0.0"
},
"engines": {
- "node": ">=14"
+ "node": ">=18"
}
},
"node_modules/wait-on": {
@@ -8338,15 +8385,15 @@
}
},
"node_modules/whatwg-encoding": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
- "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
"dev": true,
"dependencies": {
"iconv-lite": "0.6.3"
},
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/whatwg-encoding/node_modules/iconv-lite": {
@@ -8362,25 +8409,25 @@
}
},
"node_modules/whatwg-mimetype": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
- "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
"dev": true,
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/whatwg-url": {
- "version": "12.0.1",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz",
- "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==",
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz",
+ "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==",
"dev": true,
"dependencies": {
- "tr46": "^4.1.1",
+ "tr46": "^5.0.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
- "node": ">=14"
+ "node": ">=18"
}
},
"node_modules/which": {
@@ -8461,9 +8508,9 @@
"dev": true
},
"node_modules/ws": {
- "version": "8.14.2",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz",
- "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==",
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
+ "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
"dev": true,
"engines": {
"node": ">=10.0.0"
@@ -8482,12 +8529,12 @@
}
},
"node_modules/xml-name-validator": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
- "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
"dev": true,
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/xmlchars": {
diff --git a/package.json b/package.json
index 04a6ff4..251b6ae 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,7 @@
"pretest": "npm run lint && npm run clean && npm run build",
"test": "mocha --require @babel/register && npm run journey:ci",
"build": "rollup -c --bundleConfigAsCjs rollup.config.js",
- "clean": "rm -r ./build",
+ "clean": "rm -rf ./build",
"journey": "cypress run",
"journey:debug": "cypress open",
"journey:ci": "start-server-and-test example:run 8000 journey",
@@ -61,13 +61,15 @@
"@rollup/plugin-terser": "^0.4.4",
"body-parser": "^1.20.2",
"chai": "^4.3.10",
+ "chai-subset": "^1.6.0",
"cypress": "^13.6.0",
"detect-browser": "^5.3.0",
"dom-storage": "^2.1.0",
"eslint": "^8.55.0",
"express": "^4.18.2",
"global-jsdom": "^9.1.0",
- "jsdom": "^22.1.0",
+ "jsdom": "^23.0.1",
+ "jsonschema": "^1.4.1",
"mocha": "^10.2.0",
"nodemon": "^3.0.2",
"rollup": "^4.6.1",
diff --git a/src/UserALEWebExtension/background.js b/src/UserALEWebExtension/background.js
index 60ebf2e..0516727 100644
--- a/src/UserALEWebExtension/background.js
+++ b/src/UserALEWebExtension/background.js
@@ -19,60 +19,46 @@
eslint-disable
*/
-import * as globals from './globals';
import * as MessageTypes from './messageTypes.js';
-import { timeStampScale } from '../getInitialSettings.js';
-import { extractTimeFields, initPackager, packageLog } from '../packageLogs.js';
-import { initSender } from '../sendLogs.js';
+import * as userale from '../main.js';
+import { browser } from './globals.js';
-// 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,
+// Initalize userale plugin options
+const defaultConfig = {
+ useraleConfig: {
+ url: 'http://localhost:8000',
+ userId: 'pluginUser',
+ authHeader: null,
+ toolName: 'useralePlugin',
+ version: userale.version,
+ },
+ pluginConfig: {
+ // Default to a regex that will match no string
+ urlWhitelist: '(?!x)x'
+ }
};
-var sessionId = 'session_' + Date.now();
-var getTimestamp = ((typeof performance !== 'undefined') && (typeof performance.now !== 'undefined'))
- ? function () { return performance.now() + performance.timing.navigationStart; }
- : Date.now;
+var urlWhitelist;
+var tabToHttpSession = {};
+var browserSessionId = null;
-browser.storage.local.set({ sessionId: sessionId });
-
-var store = browser.storage.local.get({
- userAleHost: globals.userAleHost,
- userAleScript: globals.userAleScript,
- toolUser: globals.toolUser,
- toolName: globals.toolName,
- toolVersion: globals.toolVersion,
-}, storeCallback);
-
-function storeCallback(item) {
- config = Object.assign({}, config, {
- url: item.userAleHost,
- userId: item.toolUser,
- sessionID: sessionId,
- toolName: item.toolName,
- toolVersion: item.toolVersion
- });
-
- initPackager(logs, config);
- initSender(logs, config);
+/**
+ * Apply the extension config to both the background and content instances of userale
+ * @param {Object} config The extension config to apply
+ * @return {undefined}
+ */
+function updateConfig(config) {
+ urlWhitelist = new RegExp(config.pluginConfig.urlWhitelist);
+ userale.options(config.useraleConfig);
+ // TODO: tabs need a page load to apply this config change.
+ dispatchTabMessage(config.useraleConfig);
}
+/**
+ * Send a message to all tabs
+ * @param {Object} message The message to send
+ * @return {undefined}
+ */
function dispatchTabMessage(message) {
browser.tabs.query({}, function (tabs) {
tabs.forEach(function (tab) {
@@ -81,47 +67,62 @@
});
}
-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' : globals.toolUser,
- 'toolVersion': null,
- 'toolName': null,
- 'useraleVersion': null,
- 'sessionID': sessionId,
- });
+/**
+ * Callback for filtering out logs with urls that do not match the regex defined in extension options.
+ * @param {Object} log The candidate log
+ * @return {Object} The transformed log
+ */
+function filterUrl(log) {
+ if(urlWhitelist.test(log.pageUrl)) {
+ return log
+ }
+ return false;
}
-browser.runtime.onMessage.addListener(function (message) {
+/**
+ * Callback for setting the session id's of tab logs to that of the target tab
+ * @param {Object} log The candidate log
+ * @return {Object} The transformed log
+ */
+function injectSessions(log) {
+ let id = log.details.id;
+ if(id in tabToHttpSession) {
+ log.httpSessionId = tabToHttpSession[id];
+ } else {
+ log.httpSessionId = null
+ }
+ log.browserSessionId = browserSessionId;
+ return log;
+}
+
+browser.storage.local.get(defaultConfig, (res) => {
+ // Apply url filter to logs generated by the background page.
+ userale.addCallbacks({filterUrl, injectSessions});
+ updateConfig(res);
+ browserSessionId = JSON.parse(window.sessionStorage.getItem('userAleHttpSessionId'));
+});
+
+browser.runtime.onMessage.addListener(function (message, sender, sendResponse) {
switch (message.type) {
- case MessageTypes.CONFIG_CHANGE:
- (function () {
- var updatedConfig = Object.assign({}, config, {
- url: message.payload.userAleHost,
- userId: message.payload.toolUser,
- toolName: message.payload.toolName,
- toolVersion: message.payload.toolVersion
- });
- initPackager(logs, updatedConfig);
- initSender(logs, updatedConfig);
- dispatchTabMessage(message);
- })();
+ // Handles logs rerouted from content and option scripts.
+ case MessageTypes.ADD_LOG:
+ let log = message.payload;
+ log.browserSessionId = browserSessionId;
+ // Apply url filter to logs generated outside the background page.
+ log = filterUrl(log);
+ if(log) {
+ userale.log(log);
+ }
break;
- case MessageTypes.ADD_LOG:
- (function () {
- logs.push(message.payload);
- })();
+ case MessageTypes.HTTP_SESSION:
+ if("tab" in sender && "id" in sender.tab) {
+ tabToHttpSession[sender.tab.id] = message.payload;
+ }
+ break;
+
+ case MessageTypes.CONFIG_CHANGE:
+ updateConfig(message.payload);
break;
default:
@@ -129,69 +130,63 @@
}
});
-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,
- });
+/**
+ * Extract tab details then log a tab event
+ * @param {integer} tabId The id of the target tab
+ * @param {Object} data The data of the tab event
+ * @param {String} type The type of tab event
+ * @return {undefined}
+ */
+function packageTabLog(tabId, data, type) {
+ browser.tabs.get(tabId, (tab) => {
+ packageDetailedTabLog(tab, data, type);
});
}
-browser.tabs.onActivated.addListener(function (e) {
- getTabDetailById(e.tabId, function (detail) {
- packageBrowserLog('tabs.onActivated', detail);
- });
+/**
+ * Log a tab event with tab details
+ * @param {Object} tab The target tab object
+ * @param {Object} data The data of the tab event
+ * @param {String} type The type of tab event
+ * @return {undefined}
+ */
+function packageDetailedTabLog(tab, data, type) {
+ Object.assign(data, {type});
+ userale.packageCustomLog(data, ()=>{return tab}, true);
+}
+
+// Attach Handlers for tab events
+// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs
+browser.tabs.onActivated.addListener((activeInfo) => {
+ packageTabLog(activeInfo.tabId, activeInfo, "tabs.onActivated");
});
-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.onAttached.addListener((tabId, attachInfo) => {
+ packageTabLog(tabId, attachInfo, "tabs.onAttached");
});
-browser.tabs.onDetached.addListener(function (tabId) {
- getTabDetailById(tabId, function (detail) {
- packageBrowserLog('tabs.onDetached', detail);
- });
+browser.tabs.onCreated.addListener((tab) => {
+ packageDetailedTabLog(tab, {}, "tabs.onCreated");
});
-browser.tabs.onMoved.addListener(function (tabId) {
- getTabDetailById(tabId, function (detail) {
- packageBrowserLog('tabs.onMoved', detail);
- });
+browser.tabs.onDetached.addListener((tabId, detachInfo) => {
+ packageTabLog(tabId, detachInfo, "tabs.onDetached");
});
-browser.tabs.onRemoved.addListener(function (tabId) {
- packageBrowserLog('tabs.onRemoved', { tabId: tabId });
+browser.tabs.onMoved.addListener((tabId, moveInfo) => {
+ packageTabLog(tabId, moveInfo, "tabs.onMoved");
});
-browser.tabs.onZoomChange.addListener(function (e) {
- getTabDetailById(e.tabId, function (detail) {
- packageBrowserLog('tabs.onZoomChange', Object.assign({}, {
- oldZoomFactor: e.oldZoomFactor,
- newZoomFactor: e.newZoomFactor,
- }, detail));
- });
+browser.tabs.onRemoved.addListener((tabId, removeInfo) => {
+ packageDetailedTabLog({id: tabId}, removeInfo, "tabs.onRemoved");
+});
+
+browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
+ packageDetailedTabLog(tab, changeInfo, "tabs.onUpdated");
+});
+
+browser.tabs.onZoomChange.addListener((ZoomChangeInfo) => {
+ packageTabLog(ZoomChangeInfo.tabId, ZoomChangeInfo, "tabs.onZoomChange");
});
/*
diff --git a/src/UserALEWebExtension/content.js b/src/UserALEWebExtension/content.js
index 7ede7a6..b0fe393 100644
--- a/src/UserALEWebExtension/content.js
+++ b/src/UserALEWebExtension/content.js
@@ -17,61 +17,22 @@
/* eslint-disable */
-import * as globals from './globals';
import * as MessageTypes from './messageTypes.js';
-import { addCallbacks, options, start } from '../main.js';
+import * as userale from '../main.js';
+import { rerouteLog, browser } from './globals.js';
-// 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;
-
-// creates a Future for retrieval of the named keys
-// the value specified is the default value if one doesn't exist in the storage
-let store = browser.storage.local.get({
- sessionId: null,
- userAleHost: globals.userAleHost,
- userAleScript: globals.userAleScript,
- toolUser: globals.toolUser,
- toolName: globals.toolName,
- toolVersion: globals.toolVersion,
-}, storeCallback);
-
-function storeCallback(item) {
- injectScript({
- url: item.userAleHost,
- userId: item.toolUser,
- sessionID: item.sessionId,
- toolName: item.toolName,
- toolVersion: item.toolVersion
- });
-}
-
-function queueLog(log) {
- browser.runtime.sendMessage({ type: MessageTypes.ADD_LOG, payload: log });
-}
-
-function injectScript(config) {
- options(config);
-// start(); not necessary given that autostart in place, and option is masked from WebExt users
- addCallbacks({function (log) {
- queueLog(Object.assign({}, log, {
- pageUrl: document.location.href,
- }));
- console.log(log);
- return false;
- }});
-}
+browser.storage.local.get("useraleConfig", (res) => {
+ userale.options(res.useraleConfig);
+ userale.addCallbacks({rerouteLog});
+
+ // Send httpSession to background scirpt to inject into tab events.
+ let payload = JSON.parse(window.sessionStorage.getItem('userAleHttpSessionId'));
+ browser.runtime.sendMessage({type: MessageTypes.HTTP_SESSION, payload});
+});
browser.runtime.onMessage.addListener(function (message) {
if (message.type === MessageTypes.CONFIG_CHANGE) {
- options({
- url: message.payload.userAleHost,
- userId: message.payload.toolUser,
- toolName: message.payload.toolName,
- toolVersion: message.payload.toolVersion
- });
+ userale.options(message.payload);
}
});
diff --git a/src/UserALEWebExtension/globals.js b/src/UserALEWebExtension/globals.js
index bda0d7f..1f5d844 100644
--- a/src/UserALEWebExtension/globals.js
+++ b/src/UserALEWebExtension/globals.js
@@ -16,12 +16,14 @@
*/
/* eslint-disable */
+import * as MessageTypes from './messageTypes.js';
-// these are default values, which can be overridden by the user on the options page
-export var userAleHost = 'http://localhost:8000';
-export var userAleScript = 'userale-2.4.0.min.js';
-export var toolUser = 'nobody';
-export var toolName = 'test_app';
-export var toolVersion = '2.4.0';
+// browser is defined in firefox, but chrome uses the 'chrome' global.
+export var browser = browser || chrome;
+
+export function rerouteLog(log) {
+ browser.runtime.sendMessage({ type: MessageTypes.ADD_LOG, payload: log });
+ return false;
+}
/* eslint-enable */
diff --git a/src/UserALEWebExtension/messageTypes.js b/src/UserALEWebExtension/messageTypes.js
index 8eaedbc..6d553c9 100644
--- a/src/UserALEWebExtension/messageTypes.js
+++ b/src/UserALEWebExtension/messageTypes.js
@@ -15,7 +15,8 @@
* limitations under the License.
*/
-var prefix = 'USERALE_';
+const prefix = 'USERALE_';
-export var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE';
-export var ADD_LOG = prefix + 'ADD_LOG';
+export const CONFIG_CHANGE = prefix + 'CONFIG_CHANGE';
+export const ADD_LOG = prefix + 'ADD_LOG';
+export const HTTP_SESSION = prefix + 'HTTP_SESSION';
diff --git a/src/UserALEWebExtension/options.js b/src/UserALEWebExtension/options.js
index 8ef7867..caa442a 100644
--- a/src/UserALEWebExtension/options.js
+++ b/src/UserALEWebExtension/options.js
@@ -16,51 +16,56 @@
*/
/* eslint-disable */
-import * as globals from './globals.js';
import * as MessageTypes from './messageTypes.js';
+import * as userale from '../main.js'
+import { rerouteLog, browser } from './globals.js';
-if (chrome) {
- browser = chrome;
-}
+userale.addCallbacks({rerouteLog});
-// creates a Future for retrieval of the named keys
-// the value specified is the default value if one doesn't exist in the storage
-let store = browser.storage.local.get({
- userAleHost: globals.userAleHost,
- userAleScript: globals.userAleScript,
- toolUser: globals.toolUser,
- toolName: globals.toolName,
- toolVersion: globals.toolVersion,
-}, storeCallback);
+// TODO: Warn users when setting credentials with unsecured connection.
+const mitmWarning = "Setting credentials with http will expose you to a MITM attack. Are you sure you want to continue?";
-function storeCallback(item) {
- document.getElementById("host").value = item.userAleHost;
- document.getElementById("clientScript").value = item.userAleScript;
- document.getElementById("toolUser").value = item.toolUser;
- document.getElementById("toolName").value = item.toolName;
- document.getElementById("toolVersion").value = item.toolVersion;
-}
-
-function onError(error) {
- console.log(error);
-}
-
-function saveOptions(e) {
- const updatedConfig = {
- userAleHost: document.getElementById("host").value,
- userAleScript: document.getElementById("clientScript").value,
- toolUser: document.getElementById("toolUser").value,
- toolName: document.getElementById("toolName").value,
- toolVersion: document.getElementById("toolVersion").value,
+function setConfig() {
+ let config = {
+ url: document.getElementById("url").value,
+ userId: document.getElementById("user").value,
+ toolName: document.getElementById("tool").value,
+ version: document.getElementById("version").value
};
- browser.storage.local.set(updatedConfig);
+ // Set a basic auth header if given credentials.
+ const password = document.getElementById("password").value;
+ if(config.userId && password) {
+ config.authHeader = "Basic " + btoa(`${config.userId}:${password}`);
+ }
- browser.runtime.sendMessage({ type: MessageTypes.CONFIG_CHANGE, payload: updatedConfig });
+ let payload = {
+ useraleConfig: config,
+ pluginConfig: {urlWhitelist: document.getElementById("filter").value}
+ };
+
+ browser.storage.local.set(payload, () => {
+ userale.options(config);
+ browser.runtime.sendMessage({ type: MessageTypes.CONFIG_CHANGE, payload });
+ });
}
-document.addEventListener("submit", function() {
- saveOptions();
-});
+function getConfig() {
+ browser.storage.local.get("useraleConfig", (res) => {
+ let config = res.useraleConfig;
+
+ userale.options(config);
+ document.getElementById("url").value = config.url;
+ document.getElementById("user").value = config.userId;
+ document.getElementById("tool").value = config.toolName;
+ document.getElementById("version").value = config.version;
+ });
+ browser.storage.local.get("pluginConfig", (res) => {
+ document.getElementById("filter").value = res.pluginConfig.urlWhitelist;
+ });
+}
-/* eslint-enable */
+document.addEventListener("DOMContentLoaded", getConfig);
+document.addEventListener("submit", setConfig);
+
+/* eslint-enable */
\ No newline at end of file
diff --git a/src/UserALEWebExtension/optionsPage.html b/src/UserALEWebExtension/optionsPage.html
index b746439..9a3f363 100644
--- a/src/UserALEWebExtension/optionsPage.html
+++ b/src/UserALEWebExtension/optionsPage.html
@@ -19,31 +19,34 @@
<html>
<head>
<title>User ALE Web Extension - Options</title>
- <script src="globals.js"></script>
<script src="options.js"></script>
<meta charset="utf-8">
</head>
<body>
<h1>Options</h1>
<form>
- <label>User ALE Server Host:</label>
- <input id="host"/>
- <br/>
-
- <label>User ALE Client Script:</label>
- <input id="clientScript"/>
+ <label>Logging Endpoint URL:</label>
+ <input id="url"/>
<br/>
<label>User:</label>
- <input id="toolUser"/>
+ <input id="user"/>
+ <br/>
+
+ <label>Password:</label>
+ <input type="password" id="password"/>
<br/>
<label>Tool Name:</label>
- <input id="toolName"/>
+ <input id="tool"/>
<br/>
<label>Tool Version:</label>
- <input id="toolVersion"/>
+ <input id="version"/>
+ <br/>
+
+ <label>URL whitelist regex:</label>
+ <input id="filter"/>
<br/>
<div align="right">
diff --git a/src/getInitialSettings.js b/src/getInitialSettings.js
index 174c642..99f35f0 100644
--- a/src/getInitialSettings.js
+++ b/src/getInitialSettings.js
@@ -16,6 +16,7 @@
*/
let sessionId = null;
+let httpSessionId = null;
/**
* Extracts the initial configuration settings from the
@@ -29,6 +30,10 @@
sessionId = getSessionId('userAleSessionId', 'session_' + String(Date.now()));
}
+ if (httpSessionId === null) {
+ httpSessionId = getSessionId('userAleHttpSessionId', generateHttpSessionId());
+ }
+
const script = document.currentScript || (function () {
const scripts = document.getElementsByTagName('script');
return scripts[scripts.length - 1];
@@ -49,8 +54,11 @@
settings.userFromParams = get('data-user-from-params') || null;
settings.time = timeStampScale(document.createEvent('CustomEvent'));
settings.sessionID = get('data-session') || sessionId;
+ settings.httpSessionId = httpSessionId;
+ settings.browserSessionId = null;
settings.authHeader = get('data-auth') || null;
settings.custIndex = get('data-index') || null;
+ settings.headers = get('data-headers') || null;
return settings;
}
@@ -69,7 +77,6 @@
return JSON.parse(window.sessionStorage.getItem(sessionKey));
}
-
/**
* Creates a function to normalize the timestamp of the provided event.
* @param {Object} e An event containing a timeStamp property.
@@ -107,3 +114,19 @@
return tsScaler;
}
+
+/**
+ * Creates a cryptographiclly random string to represent this http session.
+ * @return {String} A random 32 digit hex string
+ */
+function generateHttpSessionId() {
+ // 32 digit hex -> 128 bits of info -> 2^64 ~= 10^19 sessions needed for 50% chance of collison
+ let len = 32;
+ var arr = new Uint8Array(len / 2);
+ window.crypto.getRandomValues(arr);
+ return Array.from(arr,
+ (dec) => {
+ return dec.toString(16).padStart(2, "0");
+ }
+ ).join('');
+}
diff --git a/src/main.js b/src/main.js
index 6d09c71..297fab5 100644
--- a/src/main.js
+++ b/src/main.js
@@ -32,6 +32,7 @@
export let started = false;
export {defineCustomDetails as details} from './attachHandlers.js';
+export {registerAuthCallback as registerAuthCallback} from './utils';
export {
addCallbacks as addCallbacks,
removeCallbacks as removeCallbacks,
@@ -69,7 +70,6 @@
started = config.on = true;
packageCustomLog({
type: 'load',
- logType: 'raw',
details: {pageLoadTime: endLoadTimestamp - startLoadTimestamp}
}, () => {},false)
} else {
diff --git a/src/packageLogs.js b/src/packageLogs.js
index db287d1..0a23935 100644
--- a/src/packageLogs.js
+++ b/src/packageLogs.js
@@ -16,7 +16,7 @@
*/
import { detect } from 'detect-browser';
-const browser = detect();
+const browserInfo = detect();
export let logs;
let config;
@@ -144,6 +144,8 @@
'toolName' : config.toolName,
'useraleVersion': config.useraleVersion,
'sessionID': config.sessionID,
+ 'httpSessionId': config.httpSessionId,
+ 'browserSessionId': config.browserSessionId,
};
if ((typeof filterHandler === 'function') && !filterHandler(log)) {
@@ -198,7 +200,9 @@
'toolVersion' : config.version,
'toolName' : config.toolName,
'useraleVersion': config.useraleVersion,
- 'sessionID': config.sessionID
+ 'sessionID': config.sessionID,
+ 'httpSessionId': config.httpSessionId,
+ 'browserSessionId': config.browserSessionId,
};
let log = Object.assign(metaData, customLog);
@@ -282,7 +286,9 @@
'toolVersion': config.version,
'toolName': config.toolName,
'useraleVersion': config.useraleVersion,
- 'sessionID': config.sessionID
+ 'sessionID': config.sessionID,
+ 'httpSessionId': config.httpSessionId,
+ 'browserSessionId': config.browserSessionId,
};
if (typeof filterHandler === 'function' && !filterHandler(intervalLog)) {
@@ -391,7 +397,7 @@
export function detectBrowser() {
return {
- 'browser': browser ? browser.name : '',
- 'version': browser ? browser.version : ''
+ 'browser': browserInfo ? browserInfo.name : '',
+ 'version': browserInfo ? browserInfo.version : ''
};
}
\ No newline at end of file
diff --git a/src/sendLogs.js b/src/sendLogs.js
index c2da246..5985644 100644
--- a/src/sendLogs.js
+++ b/src/sendLogs.js
@@ -5,9 +5,9 @@
* 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.
@@ -15,6 +15,9 @@
* limitations under the License.
*/
+import { updateAuthHeader } from "./utils";
+import { updateCustomHeaders } from "./utils/headers";
+
let sendIntervalId = null;
/**
@@ -39,7 +42,7 @@
* @return {Number} The newly created interval id.
*/
export function sendOnInterval(logs, config) {
- return setInterval(function() {
+ return setInterval(function () {
if (!config.on) {
return;
}
@@ -57,8 +60,13 @@
* @param {Object} config Configuration object to be read from.
*/
export function sendOnClose(logs, config) {
- window.addEventListener('pagehide', function () {
+ window.addEventListener("pagehide", function () {
if (config.on && logs.length > 0) {
+ // NOTE: sendBeacon does not support auth headers,
+ // so this will fail if auth is required.
+ // The alternative is to use fetch() with keepalive: true
+ // https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon#description
+ // https://stackoverflow.com/a/73062712/9263449
navigator.sendBeacon(config.url, JSON.stringify(logs));
logs.splice(0); // clear log queue
}
@@ -76,18 +84,27 @@
// @todo expose config object to sendLogs replate url with config.url
export function sendLogs(logs, config, retries) {
const req = new XMLHttpRequest();
-
- // @todo setRequestHeader for Auth
const data = JSON.stringify(logs);
- req.open('POST', config.url);
+ req.open("POST", config.url);
+
+ // Update headers
+ updateAuthHeader(config);
if (config.authHeader) {
- req.setRequestHeader('Authorization', config.authHeader)
+ req.setRequestHeader("Authorization", config.authHeader);
+ }
+ req.setRequestHeader("Content-type", "application/json;charset=UTF-8");
+
+ // Update custom headers last to allow them to over-write the defaults. This assumes
+ // the user knows what they are doing and may want to over-write the defaults.
+ updateCustomHeaders(config);
+ if (config.headers) {
+ Object.entries(config.headers).forEach(([header, value]) => {
+ req.setRequestHeader(header, value);
+ });
}
- req.setRequestHeader('Content-type', 'application/json;charset=UTF-8');
-
- req.onreadystatechange = function() {
+ req.onreadystatechange = function () {
if (req.readyState === 4 && req.status !== 200) {
if (retries > 0) {
sendLogs(logs, config, retries--);
diff --git a/src/utils/auth/index.js b/src/utils/auth/index.js
new file mode 100644
index 0000000..cb8099a
--- /dev/null
+++ b/src/utils/auth/index.js
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+export let authCallback = null;
+
+/**
+ * Fetches the most up-to-date auth header string from the auth callback
+ * and updates the config object with the new value.
+ * @param {Object} config Configuration object to be updated.
+ * @param {Function} authCallback Callback used to fetch the newest header.
+ * @returns {void}
+ */
+export function updateAuthHeader(config) {
+ if (authCallback) {
+ try {
+ config.authHeader = authCallback();
+ } catch (e) {
+ // We should emit the error, but otherwise continue as this could be a temporary issue
+ // due to network connectivity or some logic inside the authCallback which is the user's
+ // responsibility.
+ console.error(`Error encountered while setting the auth header: ${e}`);
+ }
+ }
+}
+
+/**
+ * Registers the provided callback to be used when updating the auth header.
+ * @param {Function} callback Callback used to fetch the newest header. Should return a string.
+ * @returns {boolean} Whether the operation succeeded.
+ */
+export function registerAuthCallback(callback) {
+ try {
+ verifyCallback(callback);
+ authCallback = callback;
+ return true;
+ } catch (e) {
+ return false;
+ }
+}
+
+/**
+ * Verify that the provided callback is a function which returns a string
+ * @param {Function} callback Callback used to fetch the newest header. Should return a string.
+ * @throws {Error} If the callback is not a function or does not return a string.
+ * @returns {void}
+ */
+export function verifyCallback(callback) {
+ if (typeof callback !== "function") {
+ throw new Error("Userale auth callback must be a function");
+ }
+ const result = callback();
+ if (typeof result !== "string") {
+ throw new Error("Userale auth callback must return a string");
+ }
+}
+
+/**
+ * Resets the authCallback to null. Used for primarily for testing, but could be used
+ * to remove the callback in production.
+ * @returns {void}
+ */
+export function resetAuthCallback() {
+ authCallback = null;
+}
\ No newline at end of file
diff --git a/src/utils/headers/index.js b/src/utils/headers/index.js
new file mode 100644
index 0000000..e0c599f
--- /dev/null
+++ b/src/utils/headers/index.js
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+export let headersCallback = null;
+
+/**
+ * Fetches the most up-to-date custom headers object from the headers callback
+ * and updates the config object with the new value.
+ * @param {Object} config Configuration object to be updated.
+ * @param {Function} headersCallback Callback used to fetch the newest headers.
+ * @returns {void}
+ */
+export function updateCustomHeaders(config) {
+ if (headersCallback) {
+ try {
+ config.headers = headersCallback();
+ } catch (e) {
+ // We should emit the error, but otherwise continue as this could be a temporary issue
+ // due to network connectivity or some logic inside the headersCallback which is the user's
+ // responsibility.
+ console.error(`Error encountered while setting the headers: ${e}`);
+ }
+ }
+}
+
+/**
+ * Registers the provided callback to be used when updating the auth header.
+ * @param {Function} callback Callback used to fetch the newest headers. Should return an object.
+ * @returns {boolean} Whether the operation succeeded.
+ */
+export function registerHeadersCallback(callback) {
+ try {
+ verifyCallback(callback);
+ headersCallback = callback;
+ return true;
+ } catch (e) {
+ return false;
+ }
+}
+
+/**
+ * Verify that the provided callback is a function which returns a string
+ * @param {Function} callback Callback used to fetch the newest header. Should return an object.
+ * @throws {Error} If the callback is not a function or does not return a string.
+ * @returns {void}
+ */
+export function verifyCallback(callback) {
+ if (typeof callback !== "function") {
+ throw new Error("Userale headers callback must be a function");
+ }
+ const result = callback();
+ if (typeof result !== "object") {
+ throw new Error("Userale headers callback must return an object");
+ }
+ for (const [key, value] of Object.entries(result)) {
+ if (typeof key !== "string" || typeof value !== "string") {
+ throw new Error("Userale header callback must return an object with string keys and values");
+ }
+ }
+}
+
+/**
+ * Resets the authCallback to null. Used for primarily for testing, but could be used
+ * to remove the callback in production.
+ * @returns {void}
+ */
+export function resetHeadersCallback() {
+ headersCallback = null;
+}
\ No newline at end of file
diff --git a/src/utils/index.js b/src/utils/index.js
new file mode 100644
index 0000000..243cbbe
--- /dev/null
+++ b/src/utils/index.js
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+export {
+ authCallback,
+ updateAuthHeader,
+ registerAuthCallback,
+ resetAuthCallback,
+ verifyCallback as verifyAuthCallback
+} from "./auth";
+export {
+ headersCallback,
+ updateCustomHeaders,
+ registerHeadersCallback,
+ resetHeadersCallback,
+ verifyCallback as verifyHeadersCallback
+} from "./headers";
\ No newline at end of file
diff --git a/test/auth_spec.js b/test/auth_spec.js
new file mode 100644
index 0000000..db9b7c7
--- /dev/null
+++ b/test/auth_spec.js
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+import {expect} from 'chai';
+import sinon from 'sinon';
+import {
+ authCallback,
+ registerAuthCallback,
+ resetAuthCallback,
+ updateAuthHeader,
+ verifyAuthCallback
+} from '../src/utils';
+
+describe('verifyCallback', () => {
+ it('should not throw error for valid callback', () => {
+ const validCallback = sinon.stub().returns('someString');
+ expect(() => verifyAuthCallback(validCallback)).to.not.throw();
+ });
+
+ it('should throw error for non-function callback', () => {
+ const nonFunctionCallback = 'notAFunction';
+ expect(() => verifyAuthCallback(nonFunctionCallback)).to.throw('Userale auth callback must be a function');
+ });
+
+ it('should throw error for non-string callback return', () => {
+ const invalidReturnCallback = sinon.stub().returns(123);
+ expect(() => verifyAuthCallback(invalidReturnCallback)).to.throw('Userale auth callback must return a string');
+ });
+
+ it('should not throw error for valid callback with empty string return', () => {
+ const validCallback = sinon.stub().returns('');
+ expect(() => verifyAuthCallback(validCallback)).to.not.throw();
+ });
+});
+
+describe('registerAuthCallback', () => {
+ afterEach(() => {
+ resetAuthCallback();
+ });
+
+ it('should register a valid callback', () => {
+ const validCallback = sinon.stub().returns('someString');
+ expect(registerAuthCallback(validCallback)).to.be.true;
+ expect(authCallback).to.equal(validCallback);
+ });
+
+ it('should not register a non-function callback', () => {
+ const nonFunctionCallback = 'notAFunction';
+ expect(registerAuthCallback(nonFunctionCallback)).to.be.false;
+ expect(authCallback).to.be.null;
+ });
+
+ it('should not register a callback with invalid return type', () => {
+ const invalidReturnCallback = sinon.stub().returns(123);
+ expect(registerAuthCallback(invalidReturnCallback)).to.be.false;
+ expect(authCallback).to.be.null;
+ });
+
+ it('should register a callback with empty string return', () => {
+ const validCallback = sinon.stub().returns('');
+ expect(registerAuthCallback(validCallback)).to.be.true;
+ expect(authCallback).to.equal(validCallback);
+ });
+});
+
+describe('updateAuthHeader', () => {
+ let config;
+
+ beforeEach(() => {
+ // Initialize config object before each test
+ config = { authHeader: null };
+ });
+
+ afterEach(() => {
+ resetAuthCallback();
+ });
+
+ it('should update auth header when authCallback is provided', () => {
+ const validCallback = sinon.stub().returns('someString');
+ registerAuthCallback(validCallback);
+ updateAuthHeader(config, authCallback);
+ expect(config.authHeader).to.equal('someString');
+ });
+
+ it('should not update auth header when authCallback is not provided', () => {
+ updateAuthHeader(config, authCallback);
+ expect(config.authHeader).to.be.null;
+ });
+
+ it('should not update auth header when authCallback returns non-string', () => {
+ const invalidReturnCallback = sinon.stub().returns(123);
+ registerAuthCallback(invalidReturnCallback);
+ updateAuthHeader(config, authCallback);
+ expect(config.authHeader).to.be.null;
+ });
+
+ it('should update auth header with empty string return from authCallback', () => {
+ const validCallback = sinon.stub().returns('');
+ registerAuthCallback(validCallback);
+ updateAuthHeader(config, authCallback);
+ expect(config.authHeader).to.equal('');
+ });
+
+ it('should handle errors thrown during authCallback execution', () => {
+ const errorThrowingCallback = sinon.stub().throws(new Error('Callback execution failed'));
+ registerAuthCallback(errorThrowingCallback);
+ updateAuthHeader(config, authCallback);
+ expect(config.authHeader).to.be.null;
+ });
+
+ it('should not update auth header after unregistering authCallback', () => {
+ const validCallback = sinon.stub().returns('someString');
+ registerAuthCallback(validCallback);
+ updateAuthHeader(config, authCallback);
+ expect(config.authHeader).to.equal('someString');
+
+ // Unregister authCallback
+ updateAuthHeader(config, null);
+ expect(config.authHeader).to.equal('someString');
+ });
+ });
\ No newline at end of file
diff --git a/test/headers_spec.js b/test/headers_spec.js
new file mode 100644
index 0000000..a8bba1d
--- /dev/null
+++ b/test/headers_spec.js
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+import {expect} from 'chai';
+import sinon from 'sinon';
+import {
+ headersCallback,
+ registerHeadersCallback,
+ resetHeadersCallback,
+ updateCustomHeaders,
+ verifyHeadersCallback
+} from '../src/utils';
+
+describe('verifyCallback', () => {
+ it('should not throw error for valid callback', () => {
+ const validCallback = sinon.stub().returns({'x-api-token': 'someString', 'x-abc-def': 'someOtherString'});
+ expect(() => verifyHeadersCallback(validCallback)).to.not.throw();
+ });
+
+ it('should throw error for non-function callback', () => {
+ const nonFunctionCallback = 'notAFunction';
+ expect(() => verifyHeadersCallback(nonFunctionCallback)).to.throw('Userale headers callback must be a function');
+ });
+
+ it('should throw error for non-object callback return', () => {
+ const invalidReturnCallback = sinon.stub().returns(123);
+ expect(() => verifyHeadersCallback(invalidReturnCallback)).to.throw('Userale headers callback must return an object');
+ });
+
+ it('should throw error for incorrect headers object return', () => {
+ const invalidReturnCallback = sinon.stub().returns({'x-not-a-proper-value': 123});
+ expect(() => verifyHeadersCallback(invalidReturnCallback)).to.throw('Userale header callback must return an object with string keys and values');
+ });
+
+ it('should not throw error for valid callback with empty object return', () => {
+ const validCallback = sinon.stub().returns({});
+ expect(() => verifyHeadersCallback(validCallback)).to.not.throw();
+ });
+});
+
+describe('registerHeadersCallback', () => {
+ afterEach(() => {
+ resetHeadersCallback();
+ });
+
+ it('should register a valid callback', () => {
+ const validCallback = sinon.stub().returns({'x-api-token': 'someString', 'x-abc-def': 'someOtherString'});
+ expect(registerHeadersCallback(validCallback)).to.be.true;
+ expect(headersCallback).to.equal(validCallback);
+ });
+
+ it('should not register a non-function callback', () => {
+ const nonFunctionCallback = 'notAFunction';
+ expect(registerHeadersCallback(nonFunctionCallback)).to.be.false;
+ expect(headersCallback).to.be.null;
+ });
+
+ it('should not register a callback with invalid return type', () => {
+ const invalidReturnCallback = sinon.stub().returns(123);
+ expect(registerHeadersCallback(invalidReturnCallback)).to.be.false;
+ expect(headersCallback).to.be.null;
+ });
+
+ it('should register a callback with empty object return', () => {
+ const validCallback = sinon.stub().returns({});
+ expect(registerHeadersCallback(validCallback)).to.be.true;
+ expect(headersCallback).to.equal(validCallback);
+ });
+});
+
+describe('updateCustomHeader', () => {
+ let config;
+
+ beforeEach(() => {
+ // Initialize config object before each test
+ config = { headers: null };
+ });
+
+ afterEach(() => {
+ resetHeadersCallback();
+ });
+
+ it('should update custom headers when headersCallback is provided', () => {
+ const customHeaders = {'x-api-token': 'someString', 'x-abc-def': 'someOtherString'}
+ const validCallback = sinon.stub().returns(customHeaders);
+ registerHeadersCallback(validCallback);
+ updateCustomHeaders(config, headersCallback);
+ expect(config.headers).to.equal(customHeaders);
+ });
+
+ it('should not update custom headers when headersCallback is not provided', () => {
+ updateCustomHeaders(config, headersCallback);
+ expect(config.headers).to.be.null;
+ });
+
+ it('should not update custom headers when headersCallback returns non-object', () => {
+ const invalidReturnCallback = sinon.stub().returns(123);
+ registerHeadersCallback(invalidReturnCallback);
+ updateCustomHeaders(config, headersCallback);
+ expect(config.headers).to.be.null;
+ });
+
+ it('should update custom headers with empty string return from headersCallback', () => {
+ const validCallback = sinon.stub().returns({});
+ registerHeadersCallback(validCallback);
+ updateCustomHeaders(config, headersCallback);
+ expect(config.headers).to.deep.equal({});
+ });
+
+ it('should handle errors thrown during headersCallback execution', () => {
+ const errorThrowingCallback = sinon.stub().throws(new Error('Callback execution failed'));
+ registerHeadersCallback(errorThrowingCallback);
+ updateCustomHeaders(config, headersCallback);
+ expect(config.headers).to.be.null;
+ });
+
+ it('should not update custom headers after unregistering headersCallback', () => {
+ const customHeaders = {'x-api-token': 'someString', 'x-abc-def': 'someOtherString'}
+ const validCallback = sinon.stub().returns(customHeaders);
+ registerHeadersCallback(validCallback);
+ updateCustomHeaders(config, headersCallback);
+ expect(config.headers).to.equal(customHeaders);
+
+ // Unregister headersCallback
+ updateCustomHeaders(config, null);
+ expect(config.headers).to.equal(customHeaders);
+ });
+ });
\ No newline at end of file
diff --git a/test/main_spec.js b/test/main_spec.js
index 8f66d1e..084561a 100644
--- a/test/main_spec.js
+++ b/test/main_spec.js
@@ -32,6 +32,8 @@
'logCountThreshold',
'userId',
'sessionID',
+ 'httpSessionId',
+ 'browserSessionId',
'version',
'logDetails',
'resolution',
@@ -39,6 +41,7 @@
'userFromParams',
'time',
'authHeader',
+ 'headers',
'custIndex'
]);
dom.window.close();
diff --git a/test/sendLogs_spec.js b/test/sendLogs_spec.js
index 35686e6..7b50802 100644
--- a/test/sendLogs_spec.js
+++ b/test/sendLogs_spec.js
@@ -14,12 +14,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {expect} from 'chai';
+import chai, {expect} from 'chai';
+import chaiSubset from 'chai-subset';
import {JSDOM} from 'jsdom';
import sinon from 'sinon';
-import {sendOnInterval, sendOnClose} from '../src/sendLogs';
+import {initSender, sendOnInterval, sendOnClose} from '../src/sendLogs';
+import {registerAuthCallback, registerHeadersCallback} from '../src/utils';
import 'global-jsdom/register'
+chai.use(chaiSubset);
+
describe('sendLogs', () => {
it('sends logs on an interval', (done) => {
let requests = 0;
@@ -110,4 +114,81 @@
global.window.dispatchEvent(new window.CustomEvent('pagehide'))
sinon.assert.notCalled(sendBeaconSpy)
});
+
+ it('sends logs with proper auth header when using registerAuthCallback', (done) => {
+ let requests = []
+ const originalXMLHttpRequest = global.XMLHttpRequest;
+ const conf = { on: true, transmitInterval: 500, url: 'test', logCountThreshold: 1 };
+ const logs = [];
+ const clock = sinon.useFakeTimers();
+ const xhr = sinon.useFakeXMLHttpRequest();
+ global.XMLHttpRequest = xhr;
+ xhr.onCreate = (xhr) => {
+ requests.push(xhr);
+ };
+
+ // Mock the authCallback function
+ const authCallback = sinon.stub().returns('fakeAuthToken');
+
+ // Register the authCallback
+ registerAuthCallback(authCallback);
+
+ // Initialize sender with logs and config
+ initSender(logs, conf);
+
+ // Simulate log entry
+ logs.push({ foo: 'bar' });
+
+ // Trigger interval to send logs
+ clock.tick(conf.transmitInterval);
+
+ // Verify that the request has the proper auth header
+ expect(requests.length).to.equal(1);
+ expect(requests[0].requestHeaders.Authorization).to.equal('fakeAuthToken');
+
+ // Restore XMLHttpRequest and clock
+ xhr.restore();
+ clock.restore();
+ global.XMLHttpRequest = originalXMLHttpRequest;
+ done()
+ });
+
+ it('sends logs with proper custom headers when using registerHeadersCallback', (done) => {
+ let requests = []
+ const originalXMLHttpRequest = global.XMLHttpRequest;
+ const conf = { on: true, transmitInterval: 500, url: 'test', logCountThreshold: 1 };
+ const logs = [];
+ const clock = sinon.useFakeTimers();
+ const xhr = sinon.useFakeXMLHttpRequest();
+ global.XMLHttpRequest = xhr;
+ xhr.onCreate = (xhr) => {
+ requests.push(xhr);
+ };
+
+ // Mock the authCallback function
+ const customHeaders = {'x-api-token': 'someString', 'x-abc-def': 'someOtherString'}
+ const headersCallback = sinon.stub().returns(customHeaders);
+
+ // Register the authCallback
+ registerHeadersCallback(headersCallback);
+
+ // Initialize sender with logs and config
+ initSender(logs, conf);
+
+ // Simulate log entry
+ logs.push({ foo: 'bar' });
+
+ // Trigger interval to send logs
+ clock.tick(conf.transmitInterval);
+
+ // Verify that the request has the proper auth header
+ expect(requests.length).to.equal(1);
+ expect(requests[0].requestHeaders).to.containSubset(customHeaders);
+
+ // Restore XMLHttpRequest and clock
+ xhr.restore();
+ clock.restore();
+ global.XMLHttpRequest = originalXMLHttpRequest;
+ done()
+ });
});