This is an automated email from the ASF dual-hosted git repository. poorejc pushed a commit to branch FLAGON-469 in repository https://gitbox.apache.org/repos/asf/incubator-flagon-useralejs.git
commit 8ecfc002d955afa4c05ab14369678fdad2678e24 Author: poorejc <[email protected]> AuthorDate: Sun Nov 3 01:26:52 2019 -0400 [FLAGON-468] added packageLogs and supporting functions to exported functions via API --- build/UserAleWebExtension/background.js | 770 +++++++-------- build/UserAleWebExtension/content.js | 1633 ++++++++++++++++--------------- build/UserAleWebExtension/options.js | 38 +- build/userale-2.0.2.js | 5 + build/userale-2.0.2.min.js | 2 +- src/main.js | 13 +- 6 files changed, 1239 insertions(+), 1222 deletions(-) diff --git a/build/UserAleWebExtension/background.js b/build/UserAleWebExtension/background.js index f8d7bcb..ddd48e8 100644 --- a/build/UserAleWebExtension/background.js +++ b/build/UserAleWebExtension/background.js @@ -26,397 +26,397 @@ var toolVersion = '2.0.2'; /* 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. +*/ + +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) { - 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. - */ - var tsScaler; - - if (delta < 0) { - tsScaler = function () { - return e.timeStamp / 1000; - }; - } else if (delta > e.timeStamp) { - var navStart = performance.timing.navigationStart; - tsScaler = function (ts) { - return ts + navStart; - }; - } else { - tsScaler = function (ts) { - return ts; - }; - } - } else { - tsScaler = function () { 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. + */ + + +/** + * 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) { + 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. + */ + var tsScaler; + + if (delta < 0) { + tsScaler = function () { + return e.timeStamp / 1000; + }; + } else if (delta > e.timeStamp) { + var navStart = performance.timing.navigationStart; + tsScaler = function (ts) { + return ts + navStart; + }; + } else { + tsScaler = function (ts) { + return ts; + }; + } + } else { + tsScaler = function () { 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. - */ - -/** - * 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. + */ + +/** + * 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.url, 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) { - if (!config.on) { - return; - } - - if (navigator.sendBeacon) { - window.addEventListener('unload', function() { - navigator.sendBeacon(config.url, JSON.stringify(logs)); - }); - } else { - window.addEventListener('beforeunload', function() { - if (logs.length > 0) { - sendLogs(logs, config.url, 1); - } - }); - } -} - -/** - * 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} url URL to send the POST request to. - * @param {Number} retries Maximum number of attempts to send the logs. - */ -function sendLogs(logs, url, retries) { - var req = new XMLHttpRequest(); - - var data = JSON.stringify(logs); - - req.open('POST', url); - req.setRequestHeader('Content-type', 'application/json;charset=UTF-8'); - - req.onreadystatechange = function() { - if (req.readyState === 4 && req.status !== 200) { - if (retries > 0) { - sendLogs(logs, url, 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. + */ + +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); } -/* - * 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 }); - -var store = 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 +/** + * 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.url, 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) { + if (!config.on) { + return; + } + + if (navigator.sendBeacon) { + window.addEventListener('unload', function() { + navigator.sendBeacon(config.url, JSON.stringify(logs)); + }); + } else { + window.addEventListener('beforeunload', function() { + if (logs.length > 0) { + sendLogs(logs, config.url, 1); + } + }); + } +} + +/** + * 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} url URL to send the POST request to. + * @param {Number} retries Maximum number of attempts to send the logs. + */ +function sendLogs(logs, url, retries) { + var req = new XMLHttpRequest(); + + var data = JSON.stringify(logs); + + req.open('POST', url); + req.setRequestHeader('Content-type', 'application/json;charset=UTF-8'); + + req.onreadystatechange = function() { + if (req.readyState === 4 && req.status !== 200) { + if (retries > 0) { + sendLogs(logs, url, 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 }); + +var store = 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 index ae2be33..1edd011 100644 --- a/build/UserAleWebExtension/content.js +++ b/build/UserAleWebExtension/content.js @@ -26,836 +26,837 @@ var toolVersion = '2.0.2'; /* 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. +*/ + +var prefix = 'USERALE_'; + +var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE'; var ADD_LOG = prefix + 'ADD_LOG'; var version = "2.0.2"; -/* - * 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; - - 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 JSON.stringify(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) { - 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. - */ - var tsScaler; - - if (delta < 0) { - tsScaler = function () { - return e.timeStamp / 1000; - }; - } else if (delta > e.timeStamp) { - var navStart = performance.timing.navigationStart; - tsScaler = function (ts) { - return ts + navStart; - }; - } else { - tsScaler = function (ts) { - return ts; - }; - } - } else { - tsScaler = function () { 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. + */ + + 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; + + return settings; } -/* - * 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) { - Object.keys(newConfig).forEach(function(option) { - if (option === 'userFromParams') { - var userId = getUserIdFromParams(newConfig[option]); - if (userId) { - config.userId = userId; - } - } - config[option] = newConfig[option]; - }); -} - -/** - * 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; - } +/** + * 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 JSON.stringify(value); + } + + return JSON.parse(window.sessionStorage.getItem(sessionKey)); } -/* - * 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 logs; -var config; - -// Interval Logging Globals -var intervalID; -var intervalType; -var intervalPath; -var intervalTimer; -var intervalCounter; -var intervalLog; - -var filterHandler = null; -var mapHandler = null; - -/** - * Assigns a handler to filter logs out of the queue. - * @param {Function} callback The handler to invoke when logging. - */ -function setLogFilter(callback) { - filterHandler = callback; -} - - -/** - * 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 = newLogs; - config = newConfig; - filterHandler = null; - mapHandler = null; - intervalID = null; - intervalType = null; - intervalPath = null; - intervalTimer = null; - intervalCounter = 0; - intervalLog = null; -} - -/** - * Transforms the provided event into a log and appends it to the log container. - * @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.on) { - return false; - } - - var details = null; - if (detailFcn) { - details = detailFcn(e); - } - - var timeFields = extractTimeFields( - (e.timeStamp && e.timeStamp > 0) ? config.time(e.timeStamp) : Date.now() - ); - - var log = { - 'target' : getSelector(e.target), - 'path' : buildPath(e), - 'pageUrl': window.location.href, - 'pageTitle': document.title, - 'pageReferrer': document.referrer, - 'clientTime' : timeFields.milli, - 'microTime' : timeFields.micro, - 'location' : getLocation(e), - 'type' : e.type, - 'logType': 'raw', - 'userAction' : true, - 'details' : details, - 'userId' : config.userId, - 'toolVersion' : config.version, - 'toolName' : config.toolName, - 'useraleVersion': config.useraleVersion, - 'sessionID': config.sessionID - }; - - if ((typeof filterHandler === 'function') && !filterHandler(log)) { - return false; - } - - if (typeof mapHandler === 'function') { - log = mapHandler(log); - } - - logs.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.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, - 'count': intervalCounter, - 'duration': timestamp - intervalTimer, // microseconds - 'startTime': intervalTimer, - 'endTime': timestamp, - 'type': intervalType, - 'logType': 'interval', - 'targetChange': intervalID !== target, - 'typeChange': intervalType !== type, - 'userAction': false, - 'userId': config.userId, - 'toolVersion': config.version, - 'toolName': config.toolName, - 'useraleVersion': config.useraleVersion, - 'sessionID': config.sessionID - }; - - if (typeof filterHandler === 'function' && !filterHandler(intervalLog)) { - return false; - } - - if (typeof mapHandler === 'function') { - intervalLog = mapHandler(intervalLog); - } - - logs.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 }; - } -} - -/** - * 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) { - var path = []; - if (e.path) { - path = e.path; - } else { - var ele = e.target; - while(ele) { - path.push(ele); - ele = ele.parentElement; - } - } - - 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; + +/** + * 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) { + 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. + */ + var tsScaler; + + if (delta < 0) { + tsScaler = function () { + return e.timeStamp / 1000; + }; + } else if (delta > e.timeStamp) { + var navStart = performance.timing.navigationStart; + tsScaler = function (ts) { + return ts + navStart; + }; + } else { + tsScaler = function (ts) { + return ts; + }; + } + } else { + tsScaler = function () { 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. - */ - -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.url, 0); // Send a copy - logs.splice(0); // Clear array reference (no reassignment) - } - }, config.transmitInterval); -} - -/** - * Provides a simplified send function that can be called before events that would - * refresh page can resolve so that log queue ('logs) can be shipped immediately. This - * is different than sendOnClose because browser security practices prevent you from - * listening the process responsible for window navigation actions, in action (e.g., refresh; - * you can only detect, after the fact, the process responsible for the current window state. - * @param {Array} logs Array of logs to read from. - * @param {Object} config Configuration object to be read from. - */ -function sendOnRefresh(logs, config) { - if (!config.on) { - return; - } - if (logs.length > 0) { - sendLogs(logs, config.url, 1); - } -} - -/** - * 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) { - if (!config.on) { - return; - } - - if (navigator.sendBeacon) { - window.addEventListener('unload', function() { - navigator.sendBeacon(config.url, JSON.stringify(logs)); - }); - } else { - window.addEventListener('beforeunload', function() { - if (logs.length > 0) { - sendLogs(logs, config.url, 1); - } - }); - } -} - -/** - * 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} url URL to send the POST request to. - * @param {Number} retries Maximum number of attempts to send the logs. - */ -function sendLogs(logs, url, retries) { - var req = new XMLHttpRequest(); - - var data = JSON.stringify(logs); - - req.open('POST', url); - req.setRequestHeader('Content-type', 'application/json;charset=UTF-8'); - - req.onreadystatechange = function() { - if (req.readyState === 4 && req.status !== 200) { - if (retries > 0) { - sendLogs(logs, url, 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. + */ + +/** + * 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) { + Object.keys(newConfig).forEach(function(option) { + if (option === 'userFromParams') { + var userId = getUserIdFromParams(newConfig[option]); + if (userId) { + config.userId = userId; + } + } + config[option] = newConfig[option]; + }); } -/* - * 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 - }; -} - -/** - * 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(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 }; } - }; - - 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]); - sendOnRefresh(logs,config); - }, true); - }); - - windowEvents.forEach(function(ev) { - window.addEventListener(ev, function(e) { - packageLog(e, function() { return { 'window' : true }; }); - }, true); - }); - - return true; +/** + * 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; + } } -/* - * 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 config$1 = {}; -var logs$1 = []; -var started = false; - -// Start up Userale -config$1.on = false; -config$1.useraleVersion = version; - -configure(config$1, getInitialSettings()); -initPackager(logs$1, config$1); - -if (config$1.autostart) { - setup(config$1); -} - -/** - * 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 (state === 'interactive' || state === 'complete') { - attachHandlers(config); - initSender(logs$1, config); - started = config.on = true; - } 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$1, newConfig); - } - - return config$1; +/* + * 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 logs; +var config; + +// Interval Logging Globals +var intervalID; +var intervalType; +var intervalPath; +var intervalTimer; +var intervalCounter; +var intervalLog; + +var filterHandler = null; +var mapHandler = null; + +/** + * Assigns a handler to filter logs out of the queue. + * @param {Function} callback The handler to invoke when logging. + */ +function setLogFilter(callback) { + filterHandler = callback; } -/* - * 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 -let store = 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 - setLogFilter(function (log) { - queueLog(Object.assign({}, log, { - pageUrl: document.location.href, - })); - 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 + +/** + * 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 = newLogs; + config = newConfig; + filterHandler = null; + mapHandler = null; + intervalID = null; + intervalType = null; + intervalPath = null; + intervalTimer = null; + intervalCounter = 0; + intervalLog = null; +} + +/** + * Transforms the provided event into a log and appends it to the log container. + * @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.on) { + return false; + } + + var details = null; + if (detailFcn) { + details = detailFcn(e); + } + + var timeFields = extractTimeFields( + (e.timeStamp && e.timeStamp > 0) ? config.time(e.timeStamp) : Date.now() + ); + + var log = { + 'target' : getSelector(e.target), + 'path' : buildPath(e), + 'pageUrl': window.location.href, + 'pageTitle': document.title, + 'pageReferrer': document.referrer, + 'clientTime' : timeFields.milli, + 'microTime' : timeFields.micro, + 'location' : getLocation(e), + 'type' : e.type, + 'logType': 'raw', + 'userAction' : true, + 'details' : details, + 'userId' : config.userId, + 'toolVersion' : config.version, + 'toolName' : config.toolName, + 'useraleVersion': config.useraleVersion, + 'sessionID': config.sessionID + }; + + if ((typeof filterHandler === 'function') && !filterHandler(log)) { + return false; + } + + if (typeof mapHandler === 'function') { + log = mapHandler(log); + } + + logs.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.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, + 'count': intervalCounter, + 'duration': timestamp - intervalTimer, // microseconds + 'startTime': intervalTimer, + 'endTime': timestamp, + 'type': intervalType, + 'logType': 'interval', + 'targetChange': intervalID !== target, + 'typeChange': intervalType !== type, + 'userAction': false, + 'userId': config.userId, + 'toolVersion': config.version, + 'toolName': config.toolName, + 'useraleVersion': config.useraleVersion, + 'sessionID': config.sessionID + }; + + if (typeof filterHandler === 'function' && !filterHandler(intervalLog)) { + return false; + } + + if (typeof mapHandler === 'function') { + intervalLog = mapHandler(intervalLog); + } + + logs.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 }; + } +} + +/** + * 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) { + var path = []; + if (e.path) { + path = e.path; + } else { + var ele = e.target; + while(ele) { + path.push(ele); + ele = ele.parentElement; + } + } + + 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; +} + +/* + * 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.url, 0); // Send a copy + logs.splice(0); // Clear array reference (no reassignment) + } + }, config.transmitInterval); +} + +/** + * Provides a simplified send function that can be called before events that would + * refresh page can resolve so that log queue ('logs) can be shipped immediately. This + * is different than sendOnClose because browser security practices prevent you from + * listening the process responsible for window navigation actions, in action (e.g., refresh; + * you can only detect, after the fact, the process responsible for the current window state. + * @param {Array} logs Array of logs to read from. + * @param {Object} config Configuration object to be read from. + */ +function sendOnRefresh(logs, config) { + if (!config.on) { + return; + } + if (logs.length > 0) { + sendLogs(logs, config.url, 1); + } +} + +/** + * 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) { + if (!config.on) { + return; + } + + if (navigator.sendBeacon) { + window.addEventListener('unload', function() { + navigator.sendBeacon(config.url, JSON.stringify(logs)); + }); + } else { + window.addEventListener('beforeunload', function() { + if (logs.length > 0) { + sendLogs(logs, config.url, 1); + } + }); + } +} + +/** + * 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} url URL to send the POST request to. + * @param {Number} retries Maximum number of attempts to send the logs. + */ +function sendLogs(logs, url, retries) { + var req = new XMLHttpRequest(); + + var data = JSON.stringify(logs); + + req.open('POST', url); + req.setRequestHeader('Content-type', 'application/json;charset=UTF-8'); + + req.onreadystatechange = function() { + if (req.readyState === 4 && req.status !== 200) { + if (retries > 0) { + sendLogs(logs, url, 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. + */ + + +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 + }; +} + +/** + * 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(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 }; } + }; + + 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]); + sendOnRefresh(logs,config); + }, 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 config$1 = {}; +var logs$1 = []; +var started = false; + + +// Start up Userale +config$1.on = false; +config$1.useraleVersion = version; + +configure(config$1, getInitialSettings()); +initPackager(logs$1, config$1); + +if (config$1.autostart) { + setup(config$1); +} + +/** + * 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 (state === 'interactive' || state === 'complete') { + attachHandlers(config); + initSender(logs$1, config); + started = config.on = true; + } 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$1, newConfig); + } + + return config$1; +} + +/* + * 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 +let store = 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 + setLogFilter(function (log) { + queueLog(Object.assign({}, log, { + pageUrl: document.location.href, + })); + 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/options.js b/build/UserAleWebExtension/options.js index 274d183..1badba4 100644 --- a/build/UserAleWebExtension/options.js +++ b/build/UserAleWebExtension/options.js @@ -26,25 +26,25 @@ var toolVersion = '2.0.2'; /* 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_'; - +/* +* 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'; /* diff --git a/build/userale-2.0.2.js b/build/userale-2.0.2.js index 44c4c39..aa1e182 100644 --- a/build/userale-2.0.2.js +++ b/build/userale-2.0.2.js @@ -719,6 +719,7 @@ var userale = (function (exports) { var logs$1 = []; exports.started = false; + // Start up Userale config$1.on = false; config$1.useraleVersion = version; @@ -802,10 +803,14 @@ var userale = (function (exports) { } } + exports.buildPath = buildPath; + exports.details = defineDetails; exports.filter = setLogFilter; + exports.getSelector = getSelector; exports.log = log; exports.map = setLogMapper; exports.options = options; + exports.packageLog = packageLog; exports.start = start; exports.stop = stop; exports.version = version$1; diff --git a/build/userale-2.0.2.min.js b/build/userale-2.0.2.min.js index 6254ad4..8578afd 100644 --- a/build/userale-2.0.2.min.js +++ b/build/userale-2.0.2.min.js @@ -15,4 +15,4 @@ * limitations under the License. * @preserved */ -var userale=function(n){"use strict";var a,i,u,l,s,c,d,f,t="2.0.2",r=null;function e(n,o){Object.keys(o).forEach(function(t){if("userFromParams"===t){var e=function(t){var e=new RegExp("[?&]"+t+"(=([^&#]*)|&|#|$)"),n=window.location.href.match(e);return n&&n[2]?decodeURIComponent(n[2].replace(/\+/g," ")):null}(o[t]);e&&(n.userId=e)}n[t]=o[t]})}var m=null,p=null;function o(t,e){if(!i.on)return!1;var n=null;e&&(n=e(t));var o=function(t){return{milli:Math.floor(t),micro:Number((t%1).toFixed [...] \ No newline at end of file +var userale=function(n){"use strict";var a,i,u,l,s,c,d,f,t="2.0.2",r=null;function e(n,o){Object.keys(o).forEach(function(t){if("userFromParams"===t){var e=function(t){var e=new RegExp("[?&]"+t+"(=([^&#]*)|&|#|$)"),n=window.location.href.match(e);return n&&n[2]?decodeURIComponent(n[2].replace(/\+/g," ")):null}(o[t]);e&&(n.userId=e)}n[t]=o[t]})}var m=null,p=null;function o(t,e){if(!i.on)return!1;var n=null;e&&(n=e(t));var o=function(t){return{milli:Math.floor(t),micro:Number((t%1).toFixed [...] \ No newline at end of file diff --git a/src/main.js b/src/main.js index 28fe622..d6cae6c 100644 --- a/src/main.js +++ b/src/main.js @@ -25,7 +25,18 @@ import { initSender } from './sendLogs.js'; var config = {}; var logs = []; export var started = false; -export { setLogMapper as map, setLogFilter as filter } from './packageLogs.js'; +export { + setLogMapper as map, + setLogFilter as filter, + packageLog as packageLog, + getSelector as getSelector, + buildPath as buildPath, + } from './packageLogs.js'; + +export { + defineDetails as details, +} from './attachHandlers.js'; + // Start up Userale config.on = false;
