From d4cb2fb7fd56bedf71fe58fe815772922e3547ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 07:18:30 +0000 Subject: [PATCH 01/30] Bump jsdom and global-jsdom Bumps [jsdom](https://github.com/jsdom/jsdom) and [global-jsdom](https://github.com/modosc/global-jsdom). These dependencies needed to be updated together. Updates `jsdom` from 22.1.0 to 23.0.1 - [Release notes](https://github.com/jsdom/jsdom/releases) - [Changelog](https://github.com/jsdom/jsdom/blob/main/Changelog.md) - [Commits](https://github.com/jsdom/jsdom/compare/22.1.0...23.0.1) Updates `global-jsdom` from 9.1.0 to 9.2.0 - [Changelog](https://github.com/modosc/global-jsdom/blob/main/HISTORY.md) - [Commits](https://github.com/modosc/global-jsdom/commits) --- updated-dependencies: - dependency-name: jsdom dependency-type: direct:development update-type: version-update:semver-major - dependency-name: global-jsdom dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 191 +++++++++++++++++++--------------------------- package.json | 2 +- 2 files changed, 80 insertions(+), 113 deletions(-) diff --git a/package-lock.json b/package-lock.json index f96f0fea..7282b293 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "eslint": "^8.55.0", "express": "^4.18.2", "global-jsdom": "^9.1.0", - "jsdom": "^22.1.0", + "jsdom": "^23.0.1", "mocha": "^10.2.0", "nodemon": "^3.0.2", "rollup": "^4.6.1", @@ -2414,15 +2414,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 +2488,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 +2529,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": { @@ -3655,17 +3639,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 +3809,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 +4812,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 +4962,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 +4990,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 +5017,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 +5405,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": { @@ -8047,15 +8014,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 +8265,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 +8305,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 +8329,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 +8428,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 +8449,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 04a6ff4d..1bf08c9b 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "eslint": "^8.55.0", "express": "^4.18.2", "global-jsdom": "^9.1.0", - "jsdom": "^22.1.0", + "jsdom": "^23.0.1", "mocha": "^10.2.0", "nodemon": "^3.0.2", "rollup": "^4.6.1", From c0256863398d5d47a838af8d37a04ecb865553e3 Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Wed, 17 Jan 2024 15:42:52 -0500 Subject: [PATCH 02/30] #397 adds registerAuthCallback function using strategy pattern; adds tests --- src/auth.js | 78 +++++++++++++++++++++++++ src/main.js | 1 + src/sendLogs.js | 29 ++++++---- test/auth_spec.js | 128 ++++++++++++++++++++++++++++++++++++++++++ test/sendLogs_spec.js | 41 +++++++++++++- 5 files changed, 265 insertions(+), 12 deletions(-) create mode 100644 src/auth.js create mode 100644 test/auth_spec.js diff --git a/src/auth.js b/src/auth.js new file mode 100644 index 00000000..cb8099a4 --- /dev/null +++ b/src/auth.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/main.js b/src/main.js index 6d09c71b..f34c080f 100644 --- a/src/main.js +++ b/src/main.js @@ -32,6 +32,7 @@ window.onload = function () { export let started = false; export {defineCustomDetails as details} from './attachHandlers.js'; +export {registerAuthCallback as registerAuthCallback} from './auth.js'; export { addCallbacks as addCallbacks, removeCallbacks as removeCallbacks, diff --git a/src/sendLogs.js b/src/sendLogs.js index c2da2467..d420fa02 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,8 @@ * limitations under the License. */ +import { updateAuthHeader } from "./auth.js"; + let sendIntervalId = null; /** @@ -39,7 +41,7 @@ export function initSender(logs, config) { * @return {Number} The newly created interval id. */ export function sendOnInterval(logs, config) { - return setInterval(function() { + return setInterval(function () { if (!config.on) { return; } @@ -57,8 +59,13 @@ export function sendOnInterval(logs, config) { * @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 +83,18 @@ export function sendOnClose(logs, config) { // @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"); - 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/test/auth_spec.js b/test/auth_spec.js new file mode 100644 index 00000000..86029cbe --- /dev/null +++ b/test/auth_spec.js @@ -0,0 +1,128 @@ +/* + * 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, verifyCallback} from '../src/auth'; + +describe('verifyCallback', () => { + it('should not throw error for valid callback', () => { + const validCallback = sinon.stub().returns('someString'); + expect(() => verifyCallback(validCallback)).to.not.throw(); + }); + + it('should throw error for non-function callback', () => { + const nonFunctionCallback = 'notAFunction'; + expect(() => verifyCallback(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(() => verifyCallback(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(() => verifyCallback(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/sendLogs_spec.js b/test/sendLogs_spec.js index 35686e6c..60597244 100644 --- a/test/sendLogs_spec.js +++ b/test/sendLogs_spec.js @@ -17,7 +17,8 @@ import {expect} from 'chai'; import {JSDOM} from 'jsdom'; import sinon from 'sinon'; -import {sendOnInterval, sendOnClose} from '../src/sendLogs'; +import {initSender, sendOnInterval, sendOnClose} from '../src/sendLogs'; +import {registerAuthCallback} from '../src/auth'; import 'global-jsdom/register' describe('sendLogs', () => { @@ -110,4 +111,42 @@ describe('sendLogs', () => { global.window.dispatchEvent(new window.CustomEvent('pagehide')) sinon.assert.notCalled(sendBeaconSpy) }); + + it('sends logs with proper auth header when using registerCallback', (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() + }); }); From 5ea9251360125a6c5c0917178d5d20a626b49b3a Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Wed, 24 Jan 2024 14:47:51 -0500 Subject: [PATCH 03/30] add updated build --- build/UserALEWebExtension/background.js | 19 ++--- build/UserALEWebExtension/content.js | 19 ++--- build/userale-2.4.0.js | 93 ++++++++++++++++++++++--- build/userale-2.4.0.min.js | 2 +- 4 files changed, 108 insertions(+), 25 deletions(-) diff --git a/build/UserALEWebExtension/background.js b/build/UserALEWebExtension/background.js index 126c4740..845cad69 100644 --- a/build/UserALEWebExtension/background.js +++ b/build/UserALEWebExtension/background.js @@ -347,9 +347,9 @@ function extractTimeFields(timeStamp) { * 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. @@ -397,8 +397,13 @@ function sendOnInterval(logs, config) { * @param {Object} config Configuration object to be read from. */ 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 } @@ -416,14 +421,12 @@ function sendOnClose(logs, config) { // @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); + req.open("POST", config.url); if (config.authHeader) { - req.setRequestHeader('Authorization', config.authHeader); + req.setRequestHeader("Authorization", config.authHeader); } - req.setRequestHeader('Content-type', 'application/json;charset=UTF-8'); + req.setRequestHeader("Content-type", "application/json;charset=UTF-8"); req.onreadystatechange = function () { if (req.readyState === 4 && req.status !== 200) { if (retries > 0) { diff --git a/build/UserALEWebExtension/content.js b/build/UserALEWebExtension/content.js index 2ec7e1f7..d3dbf9b8 100644 --- a/build/UserALEWebExtension/content.js +++ b/build/UserALEWebExtension/content.js @@ -913,9 +913,9 @@ function attachHandlers(config) { * 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. @@ -963,8 +963,13 @@ function sendOnInterval(logs, config) { * @param {Object} config Configuration object to be read from. */ 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 } @@ -982,14 +987,12 @@ function sendOnClose(logs, config) { // @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); + req.open("POST", config.url); if (config.authHeader) { - req.setRequestHeader('Authorization', config.authHeader); + req.setRequestHeader("Authorization", config.authHeader); } - req.setRequestHeader('Content-type', 'application/json;charset=UTF-8'); + req.setRequestHeader("Content-type", "application/json;charset=UTF-8"); req.onreadystatechange = function () { if (req.readyState === 4 && req.status !== 200) { if (retries > 0) { diff --git a/build/userale-2.4.0.js b/build/userale-2.4.0.js index 6b89cc40..4876a80e 100644 --- a/build/userale-2.4.0.js +++ b/build/userale-2.4.0.js @@ -975,9 +975,79 @@ * 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 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} + */ + 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: ".concat(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. + */ + 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} + */ + function verifyCallback(callback) { + if (typeof callback !== "function") { + throw new Error("Userale auth callback must be a function"); + } + var result = callback(); + if (typeof result !== "string") { + throw new Error("Userale auth callback must return a string"); + } + } + + /* + * 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. @@ -1025,8 +1095,13 @@ * @param {Object} config Configuration object to be read from. */ 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 } @@ -1044,14 +1119,15 @@ // @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); + 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'); + req.setRequestHeader("Content-type", "application/json;charset=UTF-8"); req.onreadystatechange = function () { if (req.readyState === 4 && req.status !== 200) { if (retries > 0) { @@ -1164,6 +1240,7 @@ exports.options = options; exports.packageCustomLog = packageCustomLog; exports.packageLog = packageLog; + exports.registerAuthCallback = registerAuthCallback; exports.removeCallbacks = removeCallbacks; exports.start = start; exports.stop = stop; diff --git a/build/userale-2.4.0.min.js b/build/userale-2.4.0.min.js index 81d0c308..6d4277b9 100644 --- a/build/userale-2.4.0.min.js +++ b/build/userale-2.4.0.min.js @@ -15,4 +15,4 @@ * 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;r0?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);a0?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=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;n0?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);a0?v.time(e.timeStamp):Date.now());if(null==y&&(y=t,b=o,S=n,O=r,k=0),y!==t||b!==o){E={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=t.logCountThreshold&&(z(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 z(e,t,n){var o=new XMLHttpRequest,r=JSON.stringify(e);o.open("POST",t.url),function(e){if($)try{e.authHeader=$()}catch(e){console.error("Error encountered while setting the auth header: ".concat(e))}}(t),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&&z(e,t,n--)},o.send(r)}var F,U={},Y=[],J=Date.now();window.onload=function(){F=Date.now()},e.started=!1,U.on=!1,U.useraleVersion=n,r(U,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=U,x=[],y=null,b=null,S=null,O=null,k=0,E=null,U.autostart&&function t(n){e.started||setTimeout((function(){var o=document.readyState;!n.autostart||"interactive"!==o&&"complete"!==o?t(n):(X(n),H(Y,n),e.started=n.on=!0,C({type:"load",logType:"raw",details:{pageLoadTime:F-J}},(function(){}),!1))}),100)}(U);var q=n;e.addCallbacks=function(){for(var e=arguments.length,t=new Array(e),n=0;n Date: Wed, 24 Jan 2024 18:07:49 -0500 Subject: [PATCH 04/30] #400 follows strategy pattern to allow users to statically or dynamically define custom headers --- build/UserALEWebExtension/background.js | 78 ++++++++++--- build/UserALEWebExtension/content.js | 79 ++++++++++--- build/userale-2.4.0.js | 81 +++++++++++--- build/userale-2.4.0.min.js | 2 +- package-lock.json | 10 ++ package.json | 1 + src/getInitialSettings.js | 1 + src/main.js | 2 +- src/sendLogs.js | 12 +- src/{auth.js => utils/auth/index.js} | 0 src/utils/headers/index.js | 83 ++++++++++++++ src/utils/index.js | 14 +++ test/auth_spec.js | 16 ++- test/headers_spec.js | 141 ++++++++++++++++++++++++ test/main_spec.js | 1 + test/sendLogs_spec.js | 48 +++++++- 16 files changed, 509 insertions(+), 60 deletions(-) rename src/{auth.js => utils/auth/index.js} (100%) create mode 100644 src/utils/headers/index.js create mode 100644 src/utils/index.js create mode 100644 test/headers_spec.js diff --git a/build/UserALEWebExtension/background.js b/build/UserALEWebExtension/background.js index 845cad69..c8b4808a 100644 --- a/build/UserALEWebExtension/background.js +++ b/build/UserALEWebExtension/background.js @@ -340,22 +340,60 @@ function extractTimeFields(timeStamp) { }; } -/* - * 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. - */ +function _arrayWithHoles(arr) { + if (Array.isArray(arr)) return arr; +} + +function _iterableToArrayLimit(r, l) { + var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; + if (null != t) { + var e, + n, + i, + u, + a = [], + f = !0, + o = !1; + try { + if (i = (t = t.call(r)).next, 0 === l) { + if (Object(t) !== t) return; + f = !1; + } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); + } catch (r) { + o = !0, n = r; + } finally { + try { + if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; + } finally { + if (o) throw n; + } + } + return a; + } +} + +function _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length; + for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; + return arr2; +} + +function _unsupportedIterableToArray(o, minLen) { + if (!o) return; + if (typeof o === "string") return _arrayLikeToArray(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === "Object" && o.constructor) n = o.constructor.name; + if (n === "Map" || n === "Set") return Array.from(o); + if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); +} + +function _nonIterableRest() { + throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); +} + +function _slicedToArray(arr, i) { + return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); +} var sendIntervalId = null; @@ -427,6 +465,14 @@ function sendLogs(logs, config, retries) { req.setRequestHeader("Authorization", config.authHeader); } req.setRequestHeader("Content-type", "application/json;charset=UTF-8"); + if (config.headers) { + Object.entries(config.headers).forEach(function (_ref) { + var _ref2 = _slicedToArray(_ref, 2), + header = _ref2[0], + value = _ref2[1]; + req.setRequestHeader(header, value); + }); + } req.onreadystatechange = function () { if (req.readyState === 4 && req.status !== 200) { if (retries > 0) { diff --git a/build/UserALEWebExtension/content.js b/build/UserALEWebExtension/content.js index d3dbf9b8..72111532 100644 --- a/build/UserALEWebExtension/content.js +++ b/build/UserALEWebExtension/content.js @@ -99,6 +99,7 @@ function getInitialSettings() { settings.sessionID = get('data-session') || sessionId; settings.authHeader = get('data-auth') || null; settings.custIndex = get('data-index') || null; + settings.headers = get('data-headers') || null; return settings; } @@ -906,22 +907,60 @@ function attachHandlers(config) { 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. - */ +function _arrayWithHoles(arr) { + if (Array.isArray(arr)) return arr; +} + +function _iterableToArrayLimit(r, l) { + var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; + if (null != t) { + var e, + n, + i, + u, + a = [], + f = !0, + o = !1; + try { + if (i = (t = t.call(r)).next, 0 === l) { + if (Object(t) !== t) return; + f = !1; + } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); + } catch (r) { + o = !0, n = r; + } finally { + try { + if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; + } finally { + if (o) throw n; + } + } + return a; + } +} + +function _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length; + for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; + return arr2; +} + +function _unsupportedIterableToArray(o, minLen) { + if (!o) return; + if (typeof o === "string") return _arrayLikeToArray(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === "Object" && o.constructor) n = o.constructor.name; + if (n === "Map" || n === "Set") return Array.from(o); + if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); +} + +function _nonIterableRest() { + throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); +} + +function _slicedToArray(arr, i) { + return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); +} var sendIntervalId = null; @@ -993,6 +1032,14 @@ function sendLogs(logs, config, retries) { req.setRequestHeader("Authorization", config.authHeader); } req.setRequestHeader("Content-type", "application/json;charset=UTF-8"); + if (config.headers) { + Object.entries(config.headers).forEach(function (_ref) { + var _ref2 = _slicedToArray(_ref, 2), + header = _ref2[0], + value = _ref2[1]; + req.setRequestHeader(header, value); + }); + } req.onreadystatechange = function () { if (req.readyState === 4 && req.status !== 200) { if (retries > 0) { diff --git a/build/userale-2.4.0.js b/build/userale-2.4.0.js index 4876a80e..be9f847d 100644 --- a/build/userale-2.4.0.js +++ b/build/userale-2.4.0.js @@ -84,6 +84,7 @@ settings.sessionID = get('data-session') || sessionId; settings.authHeader = get('data-auth') || null; settings.custIndex = get('data-index') || null; + settings.headers = get('data-headers') || null; return settings; } @@ -968,6 +969,61 @@ return true; } + function _arrayWithHoles(arr) { + if (Array.isArray(arr)) return arr; + } + + function _iterableToArrayLimit(r, l) { + var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; + if (null != t) { + var e, + n, + i, + u, + a = [], + f = !0, + o = !1; + try { + if (i = (t = t.call(r)).next, 0 === l) { + if (Object(t) !== t) return; + f = !1; + } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); + } catch (r) { + o = !0, n = r; + } finally { + try { + if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; + } finally { + if (o) throw n; + } + } + return a; + } + } + + function _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length; + for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; + return arr2; + } + + function _unsupportedIterableToArray(o, minLen) { + if (!o) return; + if (typeof o === "string") return _arrayLikeToArray(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === "Object" && o.constructor) n = o.constructor.name; + if (n === "Map" || n === "Set") return Array.from(o); + if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); + } + + function _nonIterableRest() { + throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + + function _slicedToArray(arr, i) { + return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); + } + /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -1038,23 +1094,6 @@ } } - /* - * 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; /** @@ -1128,6 +1167,14 @@ req.setRequestHeader("Authorization", config.authHeader); } req.setRequestHeader("Content-type", "application/json;charset=UTF-8"); + if (config.headers) { + Object.entries(config.headers).forEach(function (_ref) { + var _ref2 = _slicedToArray(_ref, 2), + header = _ref2[0], + value = _ref2[1]; + req.setRequestHeader(header, value); + }); + } req.onreadystatechange = function () { if (req.readyState === 4 && req.status !== 200) { if (retries > 0) { diff --git a/build/userale-2.4.0.min.js b/build/userale-2.4.0.min.js index 6d4277b9..1fa96eb3 100644 --- a/build/userale-2.4.0.min.js +++ b/build/userale-2.4.0.min.js @@ -15,4 +15,4 @@ * 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;r0?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);a0?v.time(e.timeStamp):Date.now());if(null==y&&(y=t,b=o,S=n,O=r,k=0),y!==t||b!==o){E={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=t.logCountThreshold&&(z(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 z(e,t,n){var o=new XMLHttpRequest,r=JSON.stringify(e);o.open("POST",t.url),function(e){if($)try{e.authHeader=$()}catch(e){console.error("Error encountered while setting the auth header: ".concat(e))}}(t),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&&z(e,t,n--)},o.send(r)}var F,U={},Y=[],J=Date.now();window.onload=function(){F=Date.now()},e.started=!1,U.on=!1,U.useraleVersion=n,r(U,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=U,x=[],y=null,b=null,S=null,O=null,k=0,E=null,U.autostart&&function t(n){e.started||setTimeout((function(){var o=document.readyState;!n.autostart||"interactive"!==o&&"complete"!==o?t(n):(X(n),H(Y,n),e.started=n.on=!0,C({type:"load",logType:"raw",details:{pageLoadTime:F-J}},(function(){}),!1))}),100)}(U);var q=n;e.addCallbacks=function(){for(var e=arguments.length,t=new Array(e),n=0;n0?v.time(e.timeStamp):Date.now(),{milli:Math.floor(r),micro:Number((r%1).toFixed(3))}),i={target:M(e.target),path:B(e),pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:P(),clientTime:o.milli,microTime:o.micro,location:C(e),scrnRes:D(),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,l=Object.values(x);a0?v.time(e.timeStamp):Date.now());if(null==y&&(y=t,b=r,S=n,O=o,k=0),y!==t||b!==r){E={target:y,path:S,pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:P(),count:k,duration:o-O,startTime:O,endTime:o,type:b,logType:"interval",targetChange:y!==t,typeChange:b!==r,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);ie.length)&&(t=e.length);for(var n=0,r=new Array(t);n=t.logCountThreshold&&(F(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 F(e,t,n){var r=new XMLHttpRequest,o=JSON.stringify(e);r.open("POST",t.url),function(e){if(H)try{e.authHeader=H()}catch(e){console.error("Error encountered while setting the auth header: ".concat(e))}}(t),t.authHeader&&r.setRequestHeader("Authorization",t.authHeader),r.setRequestHeader("Content-type","application/json;charset=UTF-8"),t.headers&&Object.entries(t.headers).forEach((function(e){var t=_(e,2),n=t[0],o=t[1];r.setRequestHeader(n,o)})),r.onreadystatechange=function(){4===r.readyState&&200!==r.status&&n>0&&F(e,t,n--)},r.send(o)}var Y,J={},q=[],G=Date.now();window.onload=function(){Y=Date.now()},e.started=!1,J.on=!1,J.useraleVersion=n,o(J,function(){var e={};null===r&&(r=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],o=n?n.getAttribute.bind(n):function(){return null};return e.autostart="false"!==o("data-autostart"),e.url=o("data-url")||"http://localhost:8000",e.transmitInterval=+o("data-interval")||5e3,e.logCountThreshold=+o("data-threshold")||5,e.userId=o("data-user")||null,e.version=o("data-version")||null,e.logDetails="true"===o("data-log-details"),e.resolution=+o("data-resolution")||500,e.toolName=o("data-tool")||null,e.userFromParams=o("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 r=performance.timing.navigationStart;t=function(e){return e+r}}else t=function(e){return e}}else t=function(){return Date.now()};return t}(document.createEvent("CustomEvent")),e.sessionID=o("data-session")||r,e.authHeader=o("data-auth")||null,e.custIndex=o("data-index")||null,e.headers=o("data-headers")||null,e}()),g=q,v=J,x=[],y=null,b=null,S=null,O=null,k=0,E=null,J.autostart&&function t(n){e.started||setTimeout((function(){var r=document.readyState;!n.autostart||"interactive"!==r&&"complete"!==r?t(n):($(n),z(q,n),e.started=n.on=!0,j({type:"load",logType:"raw",details:{pageLoadTime:Y-G}},(function(){}),!1))}),100)}(J);var Q=n;e.addCallbacks=function(){for(var e=arguments.length,t=new Array(e),n=0;n=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", diff --git a/package.json b/package.json index 04a6ff4d..7da9de1a 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "@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", diff --git a/src/getInitialSettings.js b/src/getInitialSettings.js index 174c6424..b9624ebe 100644 --- a/src/getInitialSettings.js +++ b/src/getInitialSettings.js @@ -51,6 +51,7 @@ export function getInitialSettings() { settings.sessionID = get('data-session') || sessionId; settings.authHeader = get('data-auth') || null; settings.custIndex = get('data-index') || null; + settings.headers = get('data-headers') || null; return settings; } diff --git a/src/main.js b/src/main.js index f34c080f..01ba09b2 100644 --- a/src/main.js +++ b/src/main.js @@ -32,7 +32,7 @@ window.onload = function () { export let started = false; export {defineCustomDetails as details} from './attachHandlers.js'; -export {registerAuthCallback as registerAuthCallback} from './auth.js'; +export {registerAuthCallback as registerAuthCallback} from './utils'; export { addCallbacks as addCallbacks, removeCallbacks as removeCallbacks, diff --git a/src/sendLogs.js b/src/sendLogs.js index d420fa02..5985644d 100644 --- a/src/sendLogs.js +++ b/src/sendLogs.js @@ -15,7 +15,8 @@ * limitations under the License. */ -import { updateAuthHeader } from "./auth.js"; +import { updateAuthHeader } from "./utils"; +import { updateCustomHeaders } from "./utils/headers"; let sendIntervalId = null; @@ -94,6 +95,15 @@ export function sendLogs(logs, config, retries) { } 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.onreadystatechange = function () { if (req.readyState === 4 && req.status !== 200) { if (retries > 0) { diff --git a/src/auth.js b/src/utils/auth/index.js similarity index 100% rename from src/auth.js rename to src/utils/auth/index.js diff --git a/src/utils/headers/index.js b/src/utils/headers/index.js new file mode 100644 index 00000000..e0c599fb --- /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 00000000..f8adf00a --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,14 @@ +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 index 86029cbe..db9b7c72 100644 --- a/test/auth_spec.js +++ b/test/auth_spec.js @@ -16,27 +16,33 @@ */ import {expect} from 'chai'; import sinon from 'sinon'; -import {authCallback, registerAuthCallback, resetAuthCallback, updateAuthHeader, verifyCallback} from '../src/auth'; +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(() => verifyCallback(validCallback)).to.not.throw(); + expect(() => verifyAuthCallback(validCallback)).to.not.throw(); }); it('should throw error for non-function callback', () => { const nonFunctionCallback = 'notAFunction'; - expect(() => verifyCallback(nonFunctionCallback)).to.throw('Userale auth callback must be a function'); + 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(() => verifyCallback(invalidReturnCallback)).to.throw('Userale auth callback must return a string'); + 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(() => verifyCallback(validCallback)).to.not.throw(); + expect(() => verifyAuthCallback(validCallback)).to.not.throw(); }); }); diff --git a/test/headers_spec.js b/test/headers_spec.js new file mode 100644 index 00000000..a8bba1d9 --- /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 8f66d1e1..5bc045ab 100644 --- a/test/main_spec.js +++ b/test/main_spec.js @@ -39,6 +39,7 @@ describe('Userale API', () => { 'userFromParams', 'time', 'authHeader', + 'headers', 'custIndex' ]); dom.window.close(); diff --git a/test/sendLogs_spec.js b/test/sendLogs_spec.js index 60597244..7b508029 100644 --- a/test/sendLogs_spec.js +++ b/test/sendLogs_spec.js @@ -14,13 +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 {initSender, sendOnInterval, sendOnClose} from '../src/sendLogs'; -import {registerAuthCallback} from '../src/auth'; +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; @@ -112,7 +115,7 @@ describe('sendLogs', () => { sinon.assert.notCalled(sendBeaconSpy) }); - it('sends logs with proper auth header when using registerCallback', (done) => { + 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 }; @@ -149,4 +152,43 @@ describe('sendLogs', () => { 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() + }); }); From 1da0f2546b78413e6edf9d097f516c1c34d17115 Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Wed, 24 Jan 2024 18:13:41 -0500 Subject: [PATCH 05/30] #400 add license header --- src/utils/index.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/utils/index.js b/src/utils/index.js index f8adf00a..243cbbe6 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,3 +1,19 @@ +/* + * 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, From 053590e2b358c004f04dfe60fced861d9f859e49 Mon Sep 17 00:00:00 2001 From: Amir Ghaemi <51731855+amirmghaemi@users.noreply.github.com> Date: Mon, 29 Jan 2024 11:18:32 -0500 Subject: [PATCH 06/30] Duplicate logging note for custom logs Added note to documentation for duplicate logging with custom logs. --- example/log-label-example/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/example/log-label-example/README.md b/example/log-label-example/README.md index 1b6b159e..a17a89eb 100644 --- a/example/log-label-example/README.md +++ b/example/log-label-example/README.md @@ -6,6 +6,9 @@ logging information about a specific feature. ## 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 @@ This event listener will now be invoked on the `customEvent` and send a custom l 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* From 99d571052383c0ba396dbdff53f2d0924e9b86d0 Mon Sep 17 00:00:00 2001 From: Jason Young Date: Thu, 3 Aug 2023 12:54:29 -0400 Subject: [PATCH 07/30] Initial json schema and tests --- example/log.schema.json | 125 ++++++++++++++++++++++++++++++++++ journey/userale.journey.cy.js | 22 ++++++ package-lock.json | 10 +++ package.json | 1 + 4 files changed, 158 insertions(+) create mode 100644 example/log.schema.json diff --git a/example/log.schema.json b/example/log.schema.json new file mode 100644 index 00000000..e138c7fb --- /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 1a73d396..52537a23 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') @@ -62,4 +64,24 @@ describe('Userale logging', () => { 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 f728a5ef..0ddfb2ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "express": "^4.18.2", "global-jsdom": "^9.1.0", "jsdom": "^22.1.0", + "jsonschema": "^1.4.1", "mocha": "^10.2.0", "nodemon": "^3.0.2", "rollup": "^4.6.1", @@ -5567,6 +5568,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", diff --git a/package.json b/package.json index 7da9de1a..4fb48934 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "express": "^4.18.2", "global-jsdom": "^9.1.0", "jsdom": "^22.1.0", + "jsonschema": "^1.4.1", "mocha": "^10.2.0", "nodemon": "^3.0.2", "rollup": "^4.6.1", From b3773a44341c763e1acbb86478e79da0c71458b9 Mon Sep 17 00:00:00 2001 From: Jason Young Date: Fri, 26 Jan 2024 10:53:42 -0500 Subject: [PATCH 08/30] Mark page load as custom --- build/UserALEWebExtension/background.js | 3 --- build/UserALEWebExtension/content.js | 4 ---- build/UserALEWebExtension/options.js | 1 - build/userale-2.4.0.js | 17 ++++++++--------- build/userale-2.4.0.min.js | 3 ++- journey/userale.journey.cy.js | 2 +- src/main.js | 1 - 7 files changed, 11 insertions(+), 20 deletions(-) diff --git a/build/UserALEWebExtension/background.js b/build/UserALEWebExtension/background.js index c8b4808a..3ea27bc2 100644 --- a/build/UserALEWebExtension/background.js +++ b/build/UserALEWebExtension/background.js @@ -64,7 +64,6 @@ var ADD_LOG = prefix + 'ADD_LOG'; * limitations under the License. */ - /** * Creates a function to normalize the timestamp of the provided event. * @param {Object} e An event containing a timeStamp property. @@ -324,7 +323,6 @@ function createVersionParts(count) { * See the License for the specific language governing permissions and * limitations under the License. */ - detect(); /** @@ -500,7 +498,6 @@ function sendLogs(logs, config, retries) { * 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 diff --git a/build/UserALEWebExtension/content.js b/build/UserALEWebExtension/content.js index 72111532..0982f801 100644 --- a/build/UserALEWebExtension/content.js +++ b/build/UserALEWebExtension/content.js @@ -432,7 +432,6 @@ function createVersionParts(count) { * See the License for the specific language governing permissions and * limitations under the License. */ - var browser$1 = detect(); var logs$1; var config$1; @@ -769,7 +768,6 @@ function detectBrowser() { * See the License for the specific language governing permissions and * limitations under the License. */ - var events; var bufferBools; var bufferedEvents; @@ -1083,7 +1081,6 @@ function setup(config) { started = config.on = true; packageCustomLog({ type: 'load', - logType: 'raw', details: { pageLoadTime: endLoadTimestamp - startLoadTimestamp } @@ -1125,7 +1122,6 @@ function options(newConfig) { * 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. diff --git a/build/UserALEWebExtension/options.js b/build/UserALEWebExtension/options.js index 14954554..8a40ae39 100644 --- a/build/UserALEWebExtension/options.js +++ b/build/UserALEWebExtension/options.js @@ -62,7 +62,6 @@ var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE'; * See the License for the specific language governing permissions and * limitations under the License. */ - if (chrome) { browser = chrome; } diff --git a/build/userale-2.4.0.js b/build/userale-2.4.0.js index be9f847d..3c20b81a 100644 --- a/build/userale-2.4.0.js +++ b/build/userale-2.4.0.js @@ -22,14 +22,14 @@ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.userale = {})); })(this, (function (exports) { 'use strict'; - function _typeof(o) { + function _typeof(obj) { "@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); + return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { + return typeof obj; + } : function (obj) { + return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }, _typeof(obj); } var version$1 = "2.4.0"; @@ -417,7 +417,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - var browser = detect(); var logs$1; var config$1; @@ -766,7 +765,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - var events; var bufferBools; var bufferedEvents; @@ -1218,7 +1216,6 @@ exports.started = config.on = true; packageCustomLog({ type: 'load', - logType: 'raw', details: { pageLoadTime: endLoadTimestamp - startLoadTimestamp } @@ -1293,4 +1290,6 @@ exports.stop = stop; exports.version = version; + Object.defineProperty(exports, '__esModule', { value: true }); + })); diff --git a/build/userale-2.4.0.min.js b/build/userale-2.4.0.min.js index 1fa96eb3..cdea5dbc 100644 --- a/build/userale-2.4.0.min.js +++ b/build/userale-2.4.0.min.js @@ -15,4 +15,5 @@ * 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",r=null;function o(e,t){var n=e.autostart,r=t.autostart;Object.keys(t).forEach((function(n){if("userFromParams"===n){var r=(o=t[n],i=new RegExp("[?&]"+o+"(=([^&#]*)|&|#|$)"),(a=window.location.href.match(i))&&a[2]?decodeURIComponent(a[2].replace(/\+/g," ")):null);r&&(e.userId=r)}var o,i,a;e[n]=t[n]})),!1!==n&&!1!==r||(e.autostart=!1)}var i=function(e,t,n){if(n||2===arguments.length)for(var r,o=0,i=t.length;o0?v.time(e.timeStamp):Date.now(),{milli:Math.floor(r),micro:Number((r%1).toFixed(3))}),i={target:M(e.target),path:B(e),pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:P(),clientTime:o.milli,microTime:o.micro,location:C(e),scrnRes:D(),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,l=Object.values(x);a0?v.time(e.timeStamp):Date.now());if(null==y&&(y=t,b=r,S=n,O=o,k=0),y!==t||b!==r){E={target:y,path:S,pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:P(),count:k,duration:o-O,startTime:O,endTime:o,type:b,logType:"interval",targetChange:y!==t,typeChange:b!==r,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);ie.length)&&(t=e.length);for(var n=0,r=new Array(t);n=t.logCountThreshold&&(F(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 F(e,t,n){var r=new XMLHttpRequest,o=JSON.stringify(e);r.open("POST",t.url),function(e){if(H)try{e.authHeader=H()}catch(e){console.error("Error encountered while setting the auth header: ".concat(e))}}(t),t.authHeader&&r.setRequestHeader("Authorization",t.authHeader),r.setRequestHeader("Content-type","application/json;charset=UTF-8"),t.headers&&Object.entries(t.headers).forEach((function(e){var t=_(e,2),n=t[0],o=t[1];r.setRequestHeader(n,o)})),r.onreadystatechange=function(){4===r.readyState&&200!==r.status&&n>0&&F(e,t,n--)},r.send(o)}var Y,J={},q=[],G=Date.now();window.onload=function(){Y=Date.now()},e.started=!1,J.on=!1,J.useraleVersion=n,o(J,function(){var e={};null===r&&(r=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],o=n?n.getAttribute.bind(n):function(){return null};return e.autostart="false"!==o("data-autostart"),e.url=o("data-url")||"http://localhost:8000",e.transmitInterval=+o("data-interval")||5e3,e.logCountThreshold=+o("data-threshold")||5,e.userId=o("data-user")||null,e.version=o("data-version")||null,e.logDetails="true"===o("data-log-details"),e.resolution=+o("data-resolution")||500,e.toolName=o("data-tool")||null,e.userFromParams=o("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 r=performance.timing.navigationStart;t=function(e){return e+r}}else t=function(e){return e}}else t=function(){return Date.now()};return t}(document.createEvent("CustomEvent")),e.sessionID=o("data-session")||r,e.authHeader=o("data-auth")||null,e.custIndex=o("data-index")||null,e.headers=o("data-headers")||null,e}()),g=q,v=J,x=[],y=null,b=null,S=null,O=null,k=0,E=null,J.autostart&&function t(n){e.started||setTimeout((function(){var r=document.readyState;!n.autostart||"interactive"!==r&&"complete"!==r?t(n):($(n),z(q,n),e.started=n.on=!0,j({type:"load",logType:"raw",details:{pageLoadTime:Y-G}},(function(){}),!1))}),100)}(J);var Q=n;e.addCallbacks=function(){for(var e=arguments.length,t=new Array(e),n=0;n0?v.time(e.timeStamp):Date.now(),{milli:Math.floor(o),micro:Number((o%1).toFixed(3))}),i={target:P(e.target),path:j(e),pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:B(),clientTime:r.milli,microTime:r.micro,location:M(e),scrnRes:A(),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);a0?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:B(),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=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):(R(n),$(Y,n),e.started=n.on=!0,C({type:"load",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 { const pageLoadLog = body[0] expect(pageLoadLog['details']['pageLoadTime']).to.be.greaterThan(0) expect(pageLoadLog).to.contain({ - logType: 'raw', + logType: 'custom', type: 'load' }) }) diff --git a/src/main.js b/src/main.js index 01ba09b2..297fab51 100644 --- a/src/main.js +++ b/src/main.js @@ -70,7 +70,6 @@ function setup(config) { started = config.on = true; packageCustomLog({ type: 'load', - logType: 'raw', details: {pageLoadTime: endLoadTimestamp - startLoadTimestamp} }, () => {},false) } else { From d4469e35bf6fe0444bdd7a1ae874196f7cb6ff63 Mon Sep 17 00:00:00 2001 From: Jason Young Date: Mon, 29 Jan 2024 14:30:18 -0500 Subject: [PATCH 09/30] Add updated build --- build/UserALEWebExtension/background.js | 3 +++ build/UserALEWebExtension/content.js | 3 +++ build/UserALEWebExtension/options.js | 1 + build/userale-2.4.0.js | 16 ++++++++-------- build/userale-2.4.0.min.js | 3 +-- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/build/UserALEWebExtension/background.js b/build/UserALEWebExtension/background.js index 3ea27bc2..c8b4808a 100644 --- a/build/UserALEWebExtension/background.js +++ b/build/UserALEWebExtension/background.js @@ -64,6 +64,7 @@ var ADD_LOG = prefix + 'ADD_LOG'; * limitations under the License. */ + /** * Creates a function to normalize the timestamp of the provided event. * @param {Object} e An event containing a timeStamp property. @@ -323,6 +324,7 @@ function createVersionParts(count) { * See the License for the specific language governing permissions and * limitations under the License. */ + detect(); /** @@ -498,6 +500,7 @@ function sendLogs(logs, config, retries) { * 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 diff --git a/build/UserALEWebExtension/content.js b/build/UserALEWebExtension/content.js index 0982f801..f6ddfa80 100644 --- a/build/UserALEWebExtension/content.js +++ b/build/UserALEWebExtension/content.js @@ -432,6 +432,7 @@ function createVersionParts(count) { * See the License for the specific language governing permissions and * limitations under the License. */ + var browser$1 = detect(); var logs$1; var config$1; @@ -768,6 +769,7 @@ function detectBrowser() { * See the License for the specific language governing permissions and * limitations under the License. */ + var events; var bufferBools; var bufferedEvents; @@ -1122,6 +1124,7 @@ function options(newConfig) { * 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. diff --git a/build/UserALEWebExtension/options.js b/build/UserALEWebExtension/options.js index 8a40ae39..14954554 100644 --- a/build/UserALEWebExtension/options.js +++ b/build/UserALEWebExtension/options.js @@ -62,6 +62,7 @@ var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE'; * See the License for the specific language governing permissions and * limitations under the License. */ + if (chrome) { browser = chrome; } diff --git a/build/userale-2.4.0.js b/build/userale-2.4.0.js index 3c20b81a..c9a78243 100644 --- a/build/userale-2.4.0.js +++ b/build/userale-2.4.0.js @@ -22,14 +22,14 @@ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.userale = {})); })(this, (function (exports) { 'use strict'; - function _typeof(obj) { + function _typeof(o) { "@babel/helpers - typeof"; - return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { - return typeof obj; - } : function (obj) { - return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; - }, _typeof(obj); + 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"; @@ -417,6 +417,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + var browser = detect(); var logs$1; var config$1; @@ -765,6 +766,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + var events; var bufferBools; var bufferedEvents; @@ -1290,6 +1292,4 @@ exports.stop = stop; exports.version = version; - Object.defineProperty(exports, '__esModule', { value: true }); - })); diff --git a/build/userale-2.4.0.min.js b/build/userale-2.4.0.min.js index cdea5dbc..e87a122b 100644 --- a/build/userale-2.4.0.min.js +++ b/build/userale-2.4.0.min.js @@ -15,5 +15,4 @@ * 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;r0?v.time(e.timeStamp):Date.now(),{milli:Math.floor(o),micro:Number((o%1).toFixed(3))}),i={target:P(e.target),path:j(e),pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:B(),clientTime:r.milli,microTime:r.micro,location:M(e),scrnRes:A(),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);a0?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:B(),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=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):(R(n),$(Y,n),e.started=n.on=!0,C({type:"load",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;n0?v.time(e.timeStamp):Date.now(),{milli:Math.floor(r),micro:Number((r%1).toFixed(3))}),i={target:M(e.target),path:B(e),pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:P(),clientTime:o.milli,microTime:o.micro,location:C(e),scrnRes:D(),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,l=Object.values(x);a0?v.time(e.timeStamp):Date.now());if(null==y&&(y=t,b=r,S=n,O=o,k=0),y!==t||b!==r){E={target:y,path:S,pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:P(),count:k,duration:o-O,startTime:O,endTime:o,type:b,logType:"interval",targetChange:y!==t,typeChange:b!==r,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);ie.length)&&(t=e.length);for(var n=0,r=new Array(t);n=t.logCountThreshold&&(F(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 F(e,t,n){var r=new XMLHttpRequest,o=JSON.stringify(e);r.open("POST",t.url),function(e){if(H)try{e.authHeader=H()}catch(e){console.error("Error encountered while setting the auth header: ".concat(e))}}(t),t.authHeader&&r.setRequestHeader("Authorization",t.authHeader),r.setRequestHeader("Content-type","application/json;charset=UTF-8"),t.headers&&Object.entries(t.headers).forEach((function(e){var t=_(e,2),n=t[0],o=t[1];r.setRequestHeader(n,o)})),r.onreadystatechange=function(){4===r.readyState&&200!==r.status&&n>0&&F(e,t,n--)},r.send(o)}var Y,J={},q=[],G=Date.now();window.onload=function(){Y=Date.now()},e.started=!1,J.on=!1,J.useraleVersion=n,o(J,function(){var e={};null===r&&(r=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],o=n?n.getAttribute.bind(n):function(){return null};return e.autostart="false"!==o("data-autostart"),e.url=o("data-url")||"http://localhost:8000",e.transmitInterval=+o("data-interval")||5e3,e.logCountThreshold=+o("data-threshold")||5,e.userId=o("data-user")||null,e.version=o("data-version")||null,e.logDetails="true"===o("data-log-details"),e.resolution=+o("data-resolution")||500,e.toolName=o("data-tool")||null,e.userFromParams=o("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 r=performance.timing.navigationStart;t=function(e){return e+r}}else t=function(e){return e}}else t=function(){return Date.now()};return t}(document.createEvent("CustomEvent")),e.sessionID=o("data-session")||r,e.authHeader=o("data-auth")||null,e.custIndex=o("data-index")||null,e.headers=o("data-headers")||null,e}()),g=q,v=J,x=[],y=null,b=null,S=null,O=null,k=0,E=null,J.autostart&&function t(n){e.started||setTimeout((function(){var r=document.readyState;!n.autostart||"interactive"!==r&&"complete"!==r?t(n):($(n),z(q,n),e.started=n.on=!0,j({type:"load",details:{pageLoadTime:Y-G}},(function(){}),!1))}),100)}(J);var Q=n;e.addCallbacks=function(){for(var e=arguments.length,t=new Array(e),n=0;n Date: Thu, 1 Feb 2024 18:11:50 -0500 Subject: [PATCH 10/30] browser extension refactoring --- build/UserALEWebExtension/background.js | 841 +++++++++-- build/UserALEWebExtension/content.js | 112 +- build/UserALEWebExtension/options.js | 1559 +++++++++++++++++++- build/UserALEWebExtension/optionsPage.html | 9 +- src/UserALEWebExtension/background.js | 173 +-- src/UserALEWebExtension/content.js | 57 +- src/UserALEWebExtension/globals.js | 14 +- src/UserALEWebExtension/options.js | 76 +- src/UserALEWebExtension/optionsPage.html | 9 +- 9 files changed, 2321 insertions(+), 529 deletions(-) diff --git a/build/UserALEWebExtension/background.js b/build/UserALEWebExtension/background.js index c8b4808a..4dc23329 100644 --- a/build/UserALEWebExtension/background.js +++ b/build/UserALEWebExtension/background.js @@ -1,31 +1,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. -*/ - -/* 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 @@ -47,6 +19,18 @@ var prefix = 'USERALE_'; var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE'; var ADD_LOG = prefix + 'ADD_LOG'; +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 = "2.4.0"; + /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -64,6 +48,56 @@ var ADD_LOG = prefix + 'ADD_LOG'; * 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; + settings.headers = get('data-headers') || 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. @@ -102,6 +136,62 @@ function timeStampScale(e) { 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)) { @@ -325,7 +415,129 @@ function createVersionParts(count) { * limitations under the License. */ -detect(); +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 = {}; + +/** + * 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. @@ -340,6 +552,319 @@ function extractTimeFields(timeStamp) { }; } +/** + * 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; +} + function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } @@ -483,6 +1008,100 @@ function 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', + 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; +} + +/** + * 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; + } +} + +/* +* 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 chrome uses the 'chrome' global. +var browser = browser || chrome; + +/* eslint-enable */ + /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -500,50 +1119,9 @@ function sendLogs(logs, config, retries) { * 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("useraleConfig", function (res) { + options(res.config); }); -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) { @@ -551,107 +1129,62 @@ function dispatchTabMessage(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); - })(); + options(message.payload); + dispatchTabMessage(message); break; case ADD_LOG: - (function () { - logs.push(message.payload); - })(); + log(message.payload); break; default: console.log('got unknown message type ', message); } }); -function getTabDetailById(tabId, onReady) { + +// Helper functions for logging tab events +function packageTabLog(tabId, data, type) { 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 - }); + packageDetailedTabLog(tab, data, type); }); } -browser.tabs.onActivated.addListener(function (e) { - getTabDetailById(e.tabId, function (detail) { - packageBrowserLog('tabs.onActivated', detail); +function packageDetailedTabLog(tab, data, type) { + Object.assign(data, { + 'type': type }); + packageCustomLog(data, function () { + 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(function (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(function (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(function (tab) { + packageDetailedTabLog(tab, {}, "tabs.onCreated"); }); -browser.tabs.onMoved.addListener(function (tabId) { - getTabDetailById(tabId, function (detail) { - packageBrowserLog('tabs.onMoved', detail); - }); +browser.tabs.onDetached.addListener(function (tabId, detachInfo) { + packageTabLog(tabId, detachInfo, "tabs.onDetached"); }); -browser.tabs.onRemoved.addListener(function (tabId) { - packageBrowserLog('tabs.onRemoved', { - tabId: tabId - }); +browser.tabs.onMoved.addListener(function (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(function (tabId, removeInfo) { + packageDetailedTabLog({ + id: tabId + }, removeInfo, "tabs.onRemoved"); +}); +browser.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) { + packageDetailedTabLog(tab, changeInfo, "tabs.onUpdated"); +}); +browser.tabs.onZoomChange.addListener(function (ZoomChangeInfo) { + packageTabLog(ZoomChangeInfo.tabId, ZoomChangeInfo, "tabs.onZoomChange"); }); /* diff --git a/build/UserALEWebExtension/content.js b/build/UserALEWebExtension/content.js index f6ddfa80..bbd5c205 100644 --- a/build/UserALEWebExtension/content.js +++ b/build/UserALEWebExtension/content.js @@ -1,31 +1,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. -*/ - -/* 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 @@ -1107,6 +1079,36 @@ function options(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 chrome uses the 'chrome' global. +var browser = browser || chrome; +function rerouteLog(log) { + browser.runtime.sendMessage({ + type: ADD_LOG, + payload: log + }); + return false; +} + +/* eslint-enable */ + /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -1124,59 +1126,15 @@ function options(newConfig) { * 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 +browser.storage.local.get("useraleConfig", function (res) { + options(res.config); addCallbacks({ - "function": function _function(log) { - queueLog(Object.assign({}, log, { - pageUrl: document.location.href - })); - console.log(log); - return false; - } + reroute: rerouteLog }); -} +}); 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 - }); + options(message.payload); } }); diff --git a/build/UserALEWebExtension/options.js b/build/UserALEWebExtension/options.js index 14954554..3e41031a 100644 --- a/build/UserALEWebExtension/options.js +++ b/build/UserALEWebExtension/options.js @@ -1,30 +1,382 @@ -/* -* 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. -*/ +function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { + try { + var info = gen[key](arg); + var value = info.value; + } catch (error) { + reject(error); + return; + } + if (info.done) { + resolve(value); + } else { + Promise.resolve(value).then(_next, _throw); + } +} +function _asyncToGenerator(fn) { + return function () { + var self = this, + args = arguments; + return new Promise(function (resolve, reject) { + var gen = fn.apply(self, args); + function _next(value) { + asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); + } + function _throw(err) { + asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); + } + _next(undefined); + }); + }; +} -/* eslint-disable */ +function getDefaultExportFromCjs (x) { + return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; +} -// 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'; +var regeneratorRuntime$1 = {exports: {}}; -/* eslint-enable */ +var _typeof = {exports: {}}; + +(function (module) { + function _typeof(o) { + "@babel/helpers - typeof"; + + return (module.exports = _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; + }, module.exports.__esModule = true, module.exports["default"] = module.exports), _typeof(o); + } + module.exports = _typeof, module.exports.__esModule = true, module.exports["default"] = module.exports; +} (_typeof)); + +var _typeofExports = _typeof.exports; + +(function (module) { + var _typeof = _typeofExports["default"]; + function _regeneratorRuntime() { + module.exports = _regeneratorRuntime = function _regeneratorRuntime() { + return e; + }, module.exports.__esModule = true, module.exports["default"] = module.exports; + var t, + e = {}, + r = Object.prototype, + n = r.hasOwnProperty, + o = Object.defineProperty || function (t, e, r) { + t[e] = r.value; + }, + i = "function" == typeof Symbol ? Symbol : {}, + a = i.iterator || "@@iterator", + c = i.asyncIterator || "@@asyncIterator", + u = i.toStringTag || "@@toStringTag"; + function define(t, e, r) { + return Object.defineProperty(t, e, { + value: r, + enumerable: !0, + configurable: !0, + writable: !0 + }), t[e]; + } + try { + define({}, ""); + } catch (t) { + define = function define(t, e, r) { + return t[e] = r; + }; + } + function wrap(t, e, r, n) { + var i = e && e.prototype instanceof Generator ? e : Generator, + a = Object.create(i.prototype), + c = new Context(n || []); + return o(a, "_invoke", { + value: makeInvokeMethod(t, r, c) + }), a; + } + function tryCatch(t, e, r) { + try { + return { + type: "normal", + arg: t.call(e, r) + }; + } catch (t) { + return { + type: "throw", + arg: t + }; + } + } + e.wrap = wrap; + var h = "suspendedStart", + l = "suspendedYield", + f = "executing", + s = "completed", + y = {}; + function Generator() {} + function GeneratorFunction() {} + function GeneratorFunctionPrototype() {} + var p = {}; + define(p, a, function () { + return this; + }); + var d = Object.getPrototypeOf, + v = d && d(d(values([]))); + v && v !== r && n.call(v, a) && (p = v); + var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); + function defineIteratorMethods(t) { + ["next", "throw", "return"].forEach(function (e) { + define(t, e, function (t) { + return this._invoke(e, t); + }); + }); + } + function AsyncIterator(t, e) { + function invoke(r, o, i, a) { + var c = tryCatch(t[r], t, o); + if ("throw" !== c.type) { + var u = c.arg, + h = u.value; + return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { + invoke("next", t, i, a); + }, function (t) { + invoke("throw", t, i, a); + }) : e.resolve(h).then(function (t) { + u.value = t, i(u); + }, function (t) { + return invoke("throw", t, i, a); + }); + } + a(c.arg); + } + var r; + o(this, "_invoke", { + value: function value(t, n) { + function callInvokeWithMethodAndArg() { + return new e(function (e, r) { + invoke(t, n, e, r); + }); + } + return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); + } + }); + } + function makeInvokeMethod(e, r, n) { + var o = h; + return function (i, a) { + if (o === f) throw new Error("Generator is already running"); + if (o === s) { + if ("throw" === i) throw a; + return { + value: t, + done: !0 + }; + } + for (n.method = i, n.arg = a;;) { + var c = n.delegate; + if (c) { + var u = maybeInvokeDelegate(c, n); + if (u) { + if (u === y) continue; + return u; + } + } + if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { + if (o === h) throw o = s, n.arg; + n.dispatchException(n.arg); + } else "return" === n.method && n.abrupt("return", n.arg); + o = f; + var p = tryCatch(e, r, n); + if ("normal" === p.type) { + if (o = n.done ? s : l, p.arg === y) continue; + return { + value: p.arg, + done: n.done + }; + } + "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); + } + }; + } + function maybeInvokeDelegate(e, r) { + var n = r.method, + o = e.iterator[n]; + if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; + var i = tryCatch(o, e.iterator, r.arg); + if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; + var a = i.arg; + return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); + } + function pushTryEntry(t) { + var e = { + tryLoc: t[0] + }; + 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); + } + function resetTryEntry(t) { + var e = t.completion || {}; + e.type = "normal", delete e.arg, t.completion = e; + } + function Context(t) { + this.tryEntries = [{ + tryLoc: "root" + }], t.forEach(pushTryEntry, this), this.reset(!0); + } + function values(e) { + if (e || "" === e) { + var r = e[a]; + if (r) return r.call(e); + if ("function" == typeof e.next) return e; + if (!isNaN(e.length)) { + var o = -1, + i = function next() { + for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; + return next.value = t, next.done = !0, next; + }; + return i.next = i; + } + } + throw new TypeError(_typeof(e) + " is not iterable"); + } + return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { + value: GeneratorFunctionPrototype, + configurable: !0 + }), o(GeneratorFunctionPrototype, "constructor", { + value: GeneratorFunction, + configurable: !0 + }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { + var e = "function" == typeof t && t.constructor; + return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); + }, e.mark = function (t) { + return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; + }, e.awrap = function (t) { + return { + __await: t + }; + }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { + return this; + }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { + void 0 === i && (i = Promise); + var a = new AsyncIterator(wrap(t, r, n, o), i); + return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { + return t.done ? t.value : a.next(); + }); + }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { + return this; + }), define(g, "toString", function () { + return "[object Generator]"; + }), e.keys = function (t) { + var e = Object(t), + r = []; + for (var n in e) r.push(n); + return r.reverse(), function next() { + for (; r.length;) { + var t = r.pop(); + if (t in e) return next.value = t, next.done = !1, next; + } + return next.done = !0, next; + }; + }, e.values = values, Context.prototype = { + constructor: Context, + reset: function reset(e) { + if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); + }, + stop: function stop() { + this.done = !0; + var t = this.tryEntries[0].completion; + if ("throw" === t.type) throw t.arg; + return this.rval; + }, + dispatchException: function dispatchException(e) { + if (this.done) throw e; + var r = this; + function handle(n, o) { + return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; + } + for (var o = this.tryEntries.length - 1; o >= 0; --o) { + var i = this.tryEntries[o], + a = i.completion; + if ("root" === i.tryLoc) return handle("end"); + if (i.tryLoc <= this.prev) { + var c = n.call(i, "catchLoc"), + u = n.call(i, "finallyLoc"); + if (c && u) { + if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); + if (this.prev < i.finallyLoc) return handle(i.finallyLoc); + } else if (c) { + if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); + } else { + if (!u) throw new Error("try statement without catch or finally"); + if (this.prev < i.finallyLoc) return handle(i.finallyLoc); + } + } + } + }, + abrupt: function abrupt(t, e) { + for (var r = this.tryEntries.length - 1; r >= 0; --r) { + var o = this.tryEntries[r]; + if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { + var i = o; + break; + } + } + i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); + var a = i ? i.completion : {}; + return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); + }, + complete: function complete(t, e) { + if ("throw" === t.type) throw t.arg; + return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; + }, + finish: function finish(t) { + for (var e = this.tryEntries.length - 1; e >= 0; --e) { + var r = this.tryEntries[e]; + if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; + } + }, + "catch": function _catch(t) { + for (var e = this.tryEntries.length - 1; e >= 0; --e) { + var r = this.tryEntries[e]; + if (r.tryLoc === t) { + var n = r.completion; + if ("throw" === n.type) { + var o = n.arg; + resetTryEntry(r); + } + return o; + } + } + throw new Error("illegal catch attempt"); + }, + delegateYield: function delegateYield(e, r, n) { + return this.delegate = { + iterator: values(e), + resultName: r, + nextLoc: n + }, "next" === this.method && (this.arg = t), y; + } + }, e; + } + module.exports = _regeneratorRuntime, module.exports.__esModule = true, module.exports["default"] = module.exports; +} (regeneratorRuntime$1)); + +var regeneratorRuntimeExports = regeneratorRuntime$1.exports; + +// TODO(Babel 8): Remove this file. + +var runtime = regeneratorRuntimeExports(); +var regenerator = runtime; + +// Copied from https://github.com/facebook/regenerator/blob/main/packages/runtime/runtime.js#L736= +try { + regeneratorRuntime = runtime; +} catch (accidentalStrictMode) { + if (typeof globalThis === "object") { + globalThis.regeneratorRuntime = runtime; + } else { + Function("r", "regeneratorRuntime = r")(runtime); + } +} + +var _regeneratorRuntime = /*@__PURE__*/getDefaultExportFromCjs(regenerator); /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -45,6 +397,1070 @@ var toolVersion = '2.4.0'; var prefix = 'USERALE_'; var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE'; +var ADD_LOG = prefix + 'ADD_LOG'; + +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; + settings.headers = get('data-headers') || 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; +} + +function _arrayWithHoles(arr) { + if (Array.isArray(arr)) return arr; +} + +function _iterableToArrayLimit(r, l) { + var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; + if (null != t) { + var e, + n, + i, + u, + a = [], + f = !0, + o = !1; + try { + if (i = (t = t.call(r)).next, 0 === l) { + if (Object(t) !== t) return; + f = !1; + } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); + } catch (r) { + o = !0, n = r; + } finally { + try { + if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; + } finally { + if (o) throw n; + } + } + return a; + } +} + +function _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length; + for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; + return arr2; +} + +function _unsupportedIterableToArray(o, minLen) { + if (!o) return; + if (typeof o === "string") return _arrayLikeToArray(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === "Object" && o.constructor) n = o.constructor.name; + if (n === "Map" || n === "Set") return Array.from(o); + if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); +} + +function _nonIterableRest() { + throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); +} + +function _slicedToArray(arr, i) { + return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); +} + +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) { + // 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 + } + }); +} + +/** + * 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(); + 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"); + if (config.headers) { + Object.entries(config.headers).forEach(function (_ref) { + var _ref2 = _slicedToArray(_ref, 2), + header = _ref2[0], + value = _ref2[1]; + req.setRequestHeader(header, value); + }); + } + 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$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 (!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', + details: { + pageLoadTime: endLoadTimestamp - startLoadTimestamp + } + }, function () {}, false); + } else { + setup(config); + } + }, 100); + } +} + +// Export the Userale API +var version = version$1; + +/** + * 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 @@ -63,42 +1479,71 @@ var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE'; * 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 is defined in firefox, but chrome uses the 'chrome' global. +var browser = browser || chrome; +function rerouteLog(log) { browser.runtime.sendMessage({ - type: CONFIG_CHANGE, - payload: updatedConfig + type: ADD_LOG, + payload: log }); + return false; } -document.addEventListener("submit", function () { - saveOptions(); + +/* eslint-enable */ + +var defaultConfig = { + useraleConfig: { + url: 'http://localhost:8000', + userId: 'nobody', + toolName: 'useralePlugin', + version: version + } +}; +addCallbacks({ + reroute: rerouteLog }); +function setConfig(_x) { + return _setConfig.apply(this, arguments); +} +function _setConfig() { + _setConfig = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(e) { + return _regeneratorRuntime.wrap(function _callee$(_context) { + while (1) switch (_context.prev = _context.next) { + case 0: + browser.storage.local.set({ + useraleConfig: { + url: document.getElementById("url").value, + userId: document.getElementById("user").value, + toolName: document.getElementById("tool").value, + version: document.getElementById("version").value + } + }, function () { + getConfig(); + }); + case 1: + case "end": + return _context.stop(); + } + }, _callee); + })); + return _setConfig.apply(this, arguments); +} +function getConfig() { + browser.storage.local.get(defaultConfig, function (res) { + var config = res.useraleConfig; + document.getElementById("url").value = config.url; + document.getElementById("user").value = config.userId; + document.getElementById("tool").value = config.toolName; + document.getElementById("version").value = config.version; + options(config); + browser.runtime.sendMessage({ + type: CONFIG_CHANGE, + payload: config + }); + }); +} +document.addEventListener('DOMContentLoaded', getConfig); +document.addEventListener("submit", setConfig); /* eslint-enable */ diff --git a/build/UserALEWebExtension/optionsPage.html b/build/UserALEWebExtension/optionsPage.html index b7464390..6a49db38 100644 --- a/build/UserALEWebExtension/optionsPage.html +++ b/build/UserALEWebExtension/optionsPage.html @@ -19,7 +19,6 @@ User ALE Web Extension - Options - @@ -27,7 +26,7 @@

Options

- +
@@ -35,15 +34,15 @@

Options


- +
- +
- +
diff --git a/src/UserALEWebExtension/background.js b/src/UserALEWebExtension/background.js index 60ebf2ea..e9f4674e 100644 --- a/src/UserALEWebExtension/background.js +++ b/src/UserALEWebExtension/background.js @@ -19,59 +19,13 @@ 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'; - -// 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: 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 - }); +import * as userale from '../main.js'; +import { browser } from './globals.js'; - initPackager(logs, config); - initSender(logs, config); -} +browser.storage.local.get("useraleConfig", (res) => { + userale.options(res.config); +}); function dispatchTabMessage(message) { browser.tabs.query({}, function (tabs) { @@ -81,47 +35,15 @@ function dispatchTabMessage(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' : globals.toolUser, - 'toolVersion': null, - 'toolName': null, - 'useraleVersion': null, - 'sessionID': sessionId, - }); -} - browser.runtime.onMessage.addListener(function (message) { 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); + userale.options(message.payload) dispatchTabMessage(message); - })(); break; case MessageTypes.ADD_LOG: - (function () { - logs.push(message.payload); - })(); + userale.log(message.payload); break; default: @@ -129,69 +51,50 @@ browser.runtime.onMessage.addListener(function (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, - }); +// Helper functions for logging tab events +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); - }); +function packageDetailedTabLog(tab, data, type) { + Object.assign(data, {'type': 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 7ede7a6c..3cbe64b3 100644 --- a/src/UserALEWebExtension/content.js +++ b/src/UserALEWebExtension/content.js @@ -17,61 +17,18 @@ /* 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.config); + userale.addCallbacks({reroute: rerouteLog}); +}); 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 bda0d7fc..1f5d8440 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/options.js b/src/UserALEWebExtension/options.js index 8ef7867d..d0d1052b 100644 --- a/src/UserALEWebExtension/options.js +++ b/src/UserALEWebExtension/options.js @@ -16,51 +16,47 @@ */ /* eslint-disable */ -import * as globals from './globals.js'; import * as MessageTypes from './messageTypes.js'; - -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 -let store = browser.storage.local.get({ - userAleHost: globals.userAleHost, - userAleScript: globals.userAleScript, - toolUser: globals.toolUser, - toolName: globals.toolName, - toolVersion: globals.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 onError(error) { - console.log(error); +import * as userale from '../main.js' +import { rerouteLog, browser } from './globals.js'; + +const defaultConfig = {useraleConfig: { + url: 'http://localhost:8000', + userId: 'nobody', + toolName: 'useralePlugin', + version: userale.version, +}}; + +userale.addCallbacks({reroute: rerouteLog}); + +async function setConfig(e) { + browser.storage.local.set( + {useraleConfig: { + url: document.getElementById("url").value, + userId: document.getElementById("user").value, + toolName: document.getElementById("tool").value, + version: document.getElementById("version").value + }}, + () => {getConfig()} + ); } -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 getConfig() { + browser.storage.local.get(defaultConfig, (res) => { + let config = res.useraleConfig; + + 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.set(updatedConfig); + userale.options(config); + browser.runtime.sendMessage({ type: MessageTypes.CONFIG_CHANGE, payload: config }); - browser.runtime.sendMessage({ type: MessageTypes.CONFIG_CHANGE, payload: updatedConfig }); + }); } -document.addEventListener("submit", function() { - saveOptions(); -}); +document.addEventListener('DOMContentLoaded', getConfig); +document.addEventListener("submit", setConfig); -/* eslint-enable */ +/* eslint-enable */ \ No newline at end of file diff --git a/src/UserALEWebExtension/optionsPage.html b/src/UserALEWebExtension/optionsPage.html index b7464390..6a49db38 100644 --- a/src/UserALEWebExtension/optionsPage.html +++ b/src/UserALEWebExtension/optionsPage.html @@ -19,7 +19,6 @@ User ALE Web Extension - Options - @@ -27,7 +26,7 @@

Options

- +
@@ -35,15 +34,15 @@

Options


- +
- +
- +
From 0c468363299f03fa971b064f6bb8397429592cc3 Mon Sep 17 00:00:00 2001 From: Jason Young Date: Fri, 2 Feb 2024 12:36:33 -0500 Subject: [PATCH 11/30] Fix option intializtion order --- build/UserALEWebExtension/background.js | 25 +- build/UserALEWebExtension/content.js | 3 +- build/UserALEWebExtension/options.js | 451 ++---------------------- src/UserALEWebExtension/background.js | 21 +- src/UserALEWebExtension/content.js | 2 +- src/UserALEWebExtension/options.js | 11 +- 6 files changed, 72 insertions(+), 441 deletions(-) diff --git a/build/UserALEWebExtension/background.js b/build/UserALEWebExtension/background.js index 4dc23329..d7d0bfc8 100644 --- a/build/UserALEWebExtension/background.js +++ b/build/UserALEWebExtension/background.js @@ -29,7 +29,7 @@ function _typeof(o) { }, _typeof(o); } -var version = "2.4.0"; +var version$1 = "2.4.0"; /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -1019,7 +1019,7 @@ var started = false; // Start up Userale config.on = false; -config.useraleVersion = version; +config.useraleVersion = version$1; configure(config, getInitialSettings()); initPackager(logs, config); if (config.autostart) { @@ -1052,6 +1052,9 @@ function setup(config) { } } +// Export the Userale API +var version = version$1; + /** * Updates the current configuration * object with the provided values. @@ -1119,8 +1122,18 @@ var browser = browser || chrome; * limitations under the License. */ -browser.storage.local.get("useraleConfig", function (res) { - options(res.config); + +// Initalize userale plugin options +var defaultConfig = { + useraleConfig: { + url: 'http://localhost:8000', + userId: 'pluginUser', + toolName: 'useralePlugin', + version: version + } +}; +browser.storage.local.get(defaultConfig, function (res) { + options(res.useraleConfig); }); function dispatchTabMessage(message) { browser.tabs.query({}, function (tabs) { @@ -1135,6 +1148,8 @@ browser.runtime.onMessage.addListener(function (message) { options(message.payload); dispatchTabMessage(message); break; + + // Handles logs rerouted from content and option scripts case ADD_LOG: log(message.payload); break; @@ -1151,7 +1166,7 @@ function packageTabLog(tabId, data, type) { } function packageDetailedTabLog(tab, data, type) { Object.assign(data, { - 'type': type + 'tabEvent': type }); packageCustomLog(data, function () { return tab; diff --git a/build/UserALEWebExtension/content.js b/build/UserALEWebExtension/content.js index bbd5c205..ee925724 100644 --- a/build/UserALEWebExtension/content.js +++ b/build/UserALEWebExtension/content.js @@ -1100,6 +1100,7 @@ function options(newConfig) { // browser is defined in firefox, but chrome uses the 'chrome' global. var browser = browser || chrome; function rerouteLog(log) { + console.log(log); browser.runtime.sendMessage({ type: ADD_LOG, payload: log @@ -1127,7 +1128,7 @@ function rerouteLog(log) { */ browser.storage.local.get("useraleConfig", function (res) { - options(res.config); + options(res.useraleConfig); addCallbacks({ reroute: rerouteLog }); diff --git a/build/UserALEWebExtension/options.js b/build/UserALEWebExtension/options.js index 3e41031a..813a934a 100644 --- a/build/UserALEWebExtension/options.js +++ b/build/UserALEWebExtension/options.js @@ -1,383 +1,3 @@ -function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { - try { - var info = gen[key](arg); - var value = info.value; - } catch (error) { - reject(error); - return; - } - if (info.done) { - resolve(value); - } else { - Promise.resolve(value).then(_next, _throw); - } -} -function _asyncToGenerator(fn) { - return function () { - var self = this, - args = arguments; - return new Promise(function (resolve, reject) { - var gen = fn.apply(self, args); - function _next(value) { - asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); - } - function _throw(err) { - asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); - } - _next(undefined); - }); - }; -} - -function getDefaultExportFromCjs (x) { - return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; -} - -var regeneratorRuntime$1 = {exports: {}}; - -var _typeof = {exports: {}}; - -(function (module) { - function _typeof(o) { - "@babel/helpers - typeof"; - - return (module.exports = _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; - }, module.exports.__esModule = true, module.exports["default"] = module.exports), _typeof(o); - } - module.exports = _typeof, module.exports.__esModule = true, module.exports["default"] = module.exports; -} (_typeof)); - -var _typeofExports = _typeof.exports; - -(function (module) { - var _typeof = _typeofExports["default"]; - function _regeneratorRuntime() { - module.exports = _regeneratorRuntime = function _regeneratorRuntime() { - return e; - }, module.exports.__esModule = true, module.exports["default"] = module.exports; - var t, - e = {}, - r = Object.prototype, - n = r.hasOwnProperty, - o = Object.defineProperty || function (t, e, r) { - t[e] = r.value; - }, - i = "function" == typeof Symbol ? Symbol : {}, - a = i.iterator || "@@iterator", - c = i.asyncIterator || "@@asyncIterator", - u = i.toStringTag || "@@toStringTag"; - function define(t, e, r) { - return Object.defineProperty(t, e, { - value: r, - enumerable: !0, - configurable: !0, - writable: !0 - }), t[e]; - } - try { - define({}, ""); - } catch (t) { - define = function define(t, e, r) { - return t[e] = r; - }; - } - function wrap(t, e, r, n) { - var i = e && e.prototype instanceof Generator ? e : Generator, - a = Object.create(i.prototype), - c = new Context(n || []); - return o(a, "_invoke", { - value: makeInvokeMethod(t, r, c) - }), a; - } - function tryCatch(t, e, r) { - try { - return { - type: "normal", - arg: t.call(e, r) - }; - } catch (t) { - return { - type: "throw", - arg: t - }; - } - } - e.wrap = wrap; - var h = "suspendedStart", - l = "suspendedYield", - f = "executing", - s = "completed", - y = {}; - function Generator() {} - function GeneratorFunction() {} - function GeneratorFunctionPrototype() {} - var p = {}; - define(p, a, function () { - return this; - }); - var d = Object.getPrototypeOf, - v = d && d(d(values([]))); - v && v !== r && n.call(v, a) && (p = v); - var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); - function defineIteratorMethods(t) { - ["next", "throw", "return"].forEach(function (e) { - define(t, e, function (t) { - return this._invoke(e, t); - }); - }); - } - function AsyncIterator(t, e) { - function invoke(r, o, i, a) { - var c = tryCatch(t[r], t, o); - if ("throw" !== c.type) { - var u = c.arg, - h = u.value; - return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { - invoke("next", t, i, a); - }, function (t) { - invoke("throw", t, i, a); - }) : e.resolve(h).then(function (t) { - u.value = t, i(u); - }, function (t) { - return invoke("throw", t, i, a); - }); - } - a(c.arg); - } - var r; - o(this, "_invoke", { - value: function value(t, n) { - function callInvokeWithMethodAndArg() { - return new e(function (e, r) { - invoke(t, n, e, r); - }); - } - return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); - } - }); - } - function makeInvokeMethod(e, r, n) { - var o = h; - return function (i, a) { - if (o === f) throw new Error("Generator is already running"); - if (o === s) { - if ("throw" === i) throw a; - return { - value: t, - done: !0 - }; - } - for (n.method = i, n.arg = a;;) { - var c = n.delegate; - if (c) { - var u = maybeInvokeDelegate(c, n); - if (u) { - if (u === y) continue; - return u; - } - } - if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { - if (o === h) throw o = s, n.arg; - n.dispatchException(n.arg); - } else "return" === n.method && n.abrupt("return", n.arg); - o = f; - var p = tryCatch(e, r, n); - if ("normal" === p.type) { - if (o = n.done ? s : l, p.arg === y) continue; - return { - value: p.arg, - done: n.done - }; - } - "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); - } - }; - } - function maybeInvokeDelegate(e, r) { - var n = r.method, - o = e.iterator[n]; - if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; - var i = tryCatch(o, e.iterator, r.arg); - if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; - var a = i.arg; - return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); - } - function pushTryEntry(t) { - var e = { - tryLoc: t[0] - }; - 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); - } - function resetTryEntry(t) { - var e = t.completion || {}; - e.type = "normal", delete e.arg, t.completion = e; - } - function Context(t) { - this.tryEntries = [{ - tryLoc: "root" - }], t.forEach(pushTryEntry, this), this.reset(!0); - } - function values(e) { - if (e || "" === e) { - var r = e[a]; - if (r) return r.call(e); - if ("function" == typeof e.next) return e; - if (!isNaN(e.length)) { - var o = -1, - i = function next() { - for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; - return next.value = t, next.done = !0, next; - }; - return i.next = i; - } - } - throw new TypeError(_typeof(e) + " is not iterable"); - } - return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { - value: GeneratorFunctionPrototype, - configurable: !0 - }), o(GeneratorFunctionPrototype, "constructor", { - value: GeneratorFunction, - configurable: !0 - }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { - var e = "function" == typeof t && t.constructor; - return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); - }, e.mark = function (t) { - return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; - }, e.awrap = function (t) { - return { - __await: t - }; - }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { - return this; - }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { - void 0 === i && (i = Promise); - var a = new AsyncIterator(wrap(t, r, n, o), i); - return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { - return t.done ? t.value : a.next(); - }); - }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { - return this; - }), define(g, "toString", function () { - return "[object Generator]"; - }), e.keys = function (t) { - var e = Object(t), - r = []; - for (var n in e) r.push(n); - return r.reverse(), function next() { - for (; r.length;) { - var t = r.pop(); - if (t in e) return next.value = t, next.done = !1, next; - } - return next.done = !0, next; - }; - }, e.values = values, Context.prototype = { - constructor: Context, - reset: function reset(e) { - if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); - }, - stop: function stop() { - this.done = !0; - var t = this.tryEntries[0].completion; - if ("throw" === t.type) throw t.arg; - return this.rval; - }, - dispatchException: function dispatchException(e) { - if (this.done) throw e; - var r = this; - function handle(n, o) { - return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; - } - for (var o = this.tryEntries.length - 1; o >= 0; --o) { - var i = this.tryEntries[o], - a = i.completion; - if ("root" === i.tryLoc) return handle("end"); - if (i.tryLoc <= this.prev) { - var c = n.call(i, "catchLoc"), - u = n.call(i, "finallyLoc"); - if (c && u) { - if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); - if (this.prev < i.finallyLoc) return handle(i.finallyLoc); - } else if (c) { - if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); - } else { - if (!u) throw new Error("try statement without catch or finally"); - if (this.prev < i.finallyLoc) return handle(i.finallyLoc); - } - } - } - }, - abrupt: function abrupt(t, e) { - for (var r = this.tryEntries.length - 1; r >= 0; --r) { - var o = this.tryEntries[r]; - if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { - var i = o; - break; - } - } - i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); - var a = i ? i.completion : {}; - return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); - }, - complete: function complete(t, e) { - if ("throw" === t.type) throw t.arg; - return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; - }, - finish: function finish(t) { - for (var e = this.tryEntries.length - 1; e >= 0; --e) { - var r = this.tryEntries[e]; - if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; - } - }, - "catch": function _catch(t) { - for (var e = this.tryEntries.length - 1; e >= 0; --e) { - var r = this.tryEntries[e]; - if (r.tryLoc === t) { - var n = r.completion; - if ("throw" === n.type) { - var o = n.arg; - resetTryEntry(r); - } - return o; - } - } - throw new Error("illegal catch attempt"); - }, - delegateYield: function delegateYield(e, r, n) { - return this.delegate = { - iterator: values(e), - resultName: r, - nextLoc: n - }, "next" === this.method && (this.arg = t), y; - } - }, e; - } - module.exports = _regeneratorRuntime, module.exports.__esModule = true, module.exports["default"] = module.exports; -} (regeneratorRuntime$1)); - -var regeneratorRuntimeExports = regeneratorRuntime$1.exports; - -// TODO(Babel 8): Remove this file. - -var runtime = regeneratorRuntimeExports(); -var regenerator = runtime; - -// Copied from https://github.com/facebook/regenerator/blob/main/packages/runtime/runtime.js#L736= -try { - regeneratorRuntime = runtime; -} catch (accidentalStrictMode) { - if (typeof globalThis === "object") { - globalThis.regeneratorRuntime = runtime; - } else { - Function("r", "regeneratorRuntime = r")(runtime); - } -} - -var _regeneratorRuntime = /*@__PURE__*/getDefaultExportFromCjs(regenerator); - /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -399,7 +19,7 @@ var prefix = 'USERALE_'; var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE'; var ADD_LOG = prefix + 'ADD_LOG'; -var version$1 = "2.4.0"; +var version = "2.4.0"; /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -1413,7 +1033,7 @@ var started = false; // Start up Userale config.on = false; -config.useraleVersion = version$1; +config.useraleVersion = version; configure(config, getInitialSettings()); initPackager(logs, config); if (config.autostart) { @@ -1446,9 +1066,6 @@ function setup(config) { } } -// Export the Userale API -var version = version$1; - /** * Updates the current configuration * object with the provided values. @@ -1483,6 +1100,7 @@ function options(newConfig) { // browser is defined in firefox, but chrome uses the 'chrome' global. var browser = browser || chrome; function rerouteLog(log) { + console.log(log); browser.runtime.sendMessage({ type: ADD_LOG, payload: log @@ -1492,45 +1110,40 @@ function rerouteLog(log) { /* eslint-enable */ -var defaultConfig = { - useraleConfig: { - url: 'http://localhost:8000', - userId: 'nobody', - toolName: 'useralePlugin', - version: 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. +*/ + addCallbacks({ reroute: rerouteLog }); -function setConfig(_x) { - return _setConfig.apply(this, arguments); -} -function _setConfig() { - _setConfig = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(e) { - return _regeneratorRuntime.wrap(function _callee$(_context) { - while (1) switch (_context.prev = _context.next) { - case 0: - browser.storage.local.set({ - useraleConfig: { - url: document.getElementById("url").value, - userId: document.getElementById("user").value, - toolName: document.getElementById("tool").value, - version: document.getElementById("version").value - } - }, function () { - getConfig(); - }); - case 1: - case "end": - return _context.stop(); - } - }, _callee); - })); - return _setConfig.apply(this, arguments); +function setConfig(e) { + browser.storage.local.set({ + useraleConfig: { + url: document.getElementById("url").value, + userId: document.getElementById("user").value, + toolName: document.getElementById("tool").value, + version: document.getElementById("version").value + } + }, function () { + getConfig(); + }); } function getConfig() { - browser.storage.local.get(defaultConfig, function (res) { + browser.storage.local.get("useraleConfig", function (res) { var config = res.useraleConfig; document.getElementById("url").value = config.url; document.getElementById("user").value = config.userId; diff --git a/src/UserALEWebExtension/background.js b/src/UserALEWebExtension/background.js index e9f4674e..d8aed77b 100644 --- a/src/UserALEWebExtension/background.js +++ b/src/UserALEWebExtension/background.js @@ -23,8 +23,16 @@ import * as MessageTypes from './messageTypes.js'; import * as userale from '../main.js'; import { browser } from './globals.js'; -browser.storage.local.get("useraleConfig", (res) => { - userale.options(res.config); +// Initalize userale plugin options +const defaultConfig = {useraleConfig: { + url: 'http://localhost:8000', + userId: 'pluginUser', + toolName: 'useralePlugin', + version: userale.version, +}}; + +browser.storage.local.get(defaultConfig, (res) => { + userale.options(res.useraleConfig); }); function dispatchTabMessage(message) { @@ -38,12 +46,13 @@ function dispatchTabMessage(message) { browser.runtime.onMessage.addListener(function (message) { switch (message.type) { case MessageTypes.CONFIG_CHANGE: - userale.options(message.payload) - dispatchTabMessage(message); + userale.options(message.payload) + dispatchTabMessage(message); break; + // Handles logs rerouted from content and option scripts case MessageTypes.ADD_LOG: - userale.log(message.payload); + userale.log(message.payload); break; default: @@ -59,7 +68,7 @@ function packageTabLog(tabId, data, type) { } function packageDetailedTabLog(tab, data, type) { - Object.assign(data, {'type': type}); + Object.assign(data, {'tabEvent': type}); userale.packageCustomLog(data, ()=>{return tab}, true); } diff --git a/src/UserALEWebExtension/content.js b/src/UserALEWebExtension/content.js index 3cbe64b3..101882b4 100644 --- a/src/UserALEWebExtension/content.js +++ b/src/UserALEWebExtension/content.js @@ -22,7 +22,7 @@ import * as userale from '../main.js'; import { rerouteLog, browser } from './globals.js'; browser.storage.local.get("useraleConfig", (res) => { - userale.options(res.config); + userale.options(res.useraleConfig); userale.addCallbacks({reroute: rerouteLog}); }); diff --git a/src/UserALEWebExtension/options.js b/src/UserALEWebExtension/options.js index d0d1052b..dbb06ead 100644 --- a/src/UserALEWebExtension/options.js +++ b/src/UserALEWebExtension/options.js @@ -20,16 +20,9 @@ import * as MessageTypes from './messageTypes.js'; import * as userale from '../main.js' import { rerouteLog, browser } from './globals.js'; -const defaultConfig = {useraleConfig: { - url: 'http://localhost:8000', - userId: 'nobody', - toolName: 'useralePlugin', - version: userale.version, -}}; - userale.addCallbacks({reroute: rerouteLog}); -async function setConfig(e) { +function setConfig(e) { browser.storage.local.set( {useraleConfig: { url: document.getElementById("url").value, @@ -42,7 +35,7 @@ async function setConfig(e) { } function getConfig() { - browser.storage.local.get(defaultConfig, (res) => { + browser.storage.local.get("useraleConfig", (res) => { let config = res.useraleConfig; document.getElementById("url").value = config.url; From b311103347c6a26d05875e6f48ad2015c03228bc Mon Sep 17 00:00:00 2001 From: Jason Young Date: Fri, 2 Feb 2024 16:44:31 -0500 Subject: [PATCH 12/30] Add basic auth to extension --- build/UserALEWebExtension/background.js | 13 +++++++++---- build/UserALEWebExtension/content.js | 1 - build/UserALEWebExtension/options.js | 12 ++++++++++-- build/UserALEWebExtension/optionsPage.html | 10 +++++----- src/UserALEWebExtension/background.js | 10 +++++++--- src/UserALEWebExtension/messageTypes.js | 1 + src/UserALEWebExtension/options.js | 11 ++++++++++- src/UserALEWebExtension/optionsPage.html | 10 +++++----- 8 files changed, 47 insertions(+), 21 deletions(-) diff --git a/build/UserALEWebExtension/background.js b/build/UserALEWebExtension/background.js index d7d0bfc8..8b470959 100644 --- a/build/UserALEWebExtension/background.js +++ b/build/UserALEWebExtension/background.js @@ -17,6 +17,7 @@ var prefix = 'USERALE_'; var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE'; +var AUTH_CHANGE = prefix + 'AUTH_CHANGE'; var ADD_LOG = prefix + 'ADD_LOG'; function _typeof(o) { @@ -1144,14 +1145,18 @@ function dispatchTabMessage(message) { } browser.runtime.onMessage.addListener(function (message) { switch (message.type) { + // Handles logs rerouted from content and option scripts + case ADD_LOG: + log(message.payload); + break; case CONFIG_CHANGE: options(message.payload); dispatchTabMessage(message); break; - - // Handles logs rerouted from content and option scripts - case ADD_LOG: - log(message.payload); + case AUTH_CHANGE: + options({ + authHeader: message.payload + }); break; default: console.log('got unknown message type ', message); diff --git a/build/UserALEWebExtension/content.js b/build/UserALEWebExtension/content.js index ee925724..53e6c599 100644 --- a/build/UserALEWebExtension/content.js +++ b/build/UserALEWebExtension/content.js @@ -1100,7 +1100,6 @@ function options(newConfig) { // browser is defined in firefox, but chrome uses the 'chrome' global. var browser = browser || chrome; function rerouteLog(log) { - console.log(log); browser.runtime.sendMessage({ type: ADD_LOG, payload: log diff --git a/build/UserALEWebExtension/options.js b/build/UserALEWebExtension/options.js index 813a934a..4add4cde 100644 --- a/build/UserALEWebExtension/options.js +++ b/build/UserALEWebExtension/options.js @@ -17,6 +17,7 @@ var prefix = 'USERALE_'; var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE'; +var AUTH_CHANGE = prefix + 'AUTH_CHANGE'; var ADD_LOG = prefix + 'ADD_LOG'; var version = "2.4.0"; @@ -1100,7 +1101,6 @@ function options(newConfig) { // browser is defined in firefox, but chrome uses the 'chrome' global. var browser = browser || chrome; function rerouteLog(log) { - console.log(log); browser.runtime.sendMessage({ type: ADD_LOG, payload: log @@ -1131,16 +1131,24 @@ addCallbacks({ reroute: rerouteLog }); function setConfig(e) { + var user = document.getElementById("user").value; + var password = document.getElementById("password").value; browser.storage.local.set({ useraleConfig: { url: document.getElementById("url").value, - userId: document.getElementById("user").value, + userId: user, toolName: document.getElementById("tool").value, version: document.getElementById("version").value } }, function () { getConfig(); }); + if (user && password) { + browser.runtime.sendMessage({ + type: AUTH_CHANGE, + payload: "Basic " + btoa("".concat(user, ":").concat(password)) + }); + } } function getConfig() { browser.storage.local.get("useraleConfig", function (res) { diff --git a/build/UserALEWebExtension/optionsPage.html b/build/UserALEWebExtension/optionsPage.html index 6a49db38..ff92cf09 100644 --- a/build/UserALEWebExtension/optionsPage.html +++ b/build/UserALEWebExtension/optionsPage.html @@ -25,18 +25,18 @@

Options

- +
- - - -

+ + +
+
diff --git a/src/UserALEWebExtension/background.js b/src/UserALEWebExtension/background.js index d8aed77b..382244ed 100644 --- a/src/UserALEWebExtension/background.js +++ b/src/UserALEWebExtension/background.js @@ -45,14 +45,18 @@ function dispatchTabMessage(message) { browser.runtime.onMessage.addListener(function (message) { switch (message.type) { + // Handles logs rerouted from content and option scripts + case MessageTypes.ADD_LOG: + userale.log(message.payload); + break; + case MessageTypes.CONFIG_CHANGE: userale.options(message.payload) dispatchTabMessage(message); break; - // Handles logs rerouted from content and option scripts - case MessageTypes.ADD_LOG: - userale.log(message.payload); + case MessageTypes.AUTH_CHANGE: + userale.options({authHeader: message.payload}); break; default: diff --git a/src/UserALEWebExtension/messageTypes.js b/src/UserALEWebExtension/messageTypes.js index 8eaedbc2..7f49c53a 100644 --- a/src/UserALEWebExtension/messageTypes.js +++ b/src/UserALEWebExtension/messageTypes.js @@ -18,4 +18,5 @@ var prefix = 'USERALE_'; export var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE'; +export var AUTH_CHANGE = prefix + 'AUTH_CHANGE'; export var ADD_LOG = prefix + 'ADD_LOG'; diff --git a/src/UserALEWebExtension/options.js b/src/UserALEWebExtension/options.js index dbb06ead..84499595 100644 --- a/src/UserALEWebExtension/options.js +++ b/src/UserALEWebExtension/options.js @@ -23,15 +23,24 @@ import { rerouteLog, browser } from './globals.js'; userale.addCallbacks({reroute: rerouteLog}); function setConfig(e) { + let user = document.getElementById("user").value; + let password = document.getElementById("password").value; + browser.storage.local.set( {useraleConfig: { url: document.getElementById("url").value, - userId: document.getElementById("user").value, + userId: user, toolName: document.getElementById("tool").value, version: document.getElementById("version").value }}, () => {getConfig()} ); + + if(user && password) { + browser.runtime.sendMessage({type: MessageTypes.AUTH_CHANGE, + payload: "Basic " + btoa(`${user}:${password}`) + }); + } } function getConfig() { diff --git a/src/UserALEWebExtension/optionsPage.html b/src/UserALEWebExtension/optionsPage.html index 6a49db38..ff92cf09 100644 --- a/src/UserALEWebExtension/optionsPage.html +++ b/src/UserALEWebExtension/optionsPage.html @@ -25,18 +25,18 @@

Options

- +
- - - -

+ + +
+
From 923be5400f84d18f58a08f3e1722f9738f4994ca Mon Sep 17 00:00:00 2001 From: Jason Young Date: Mon, 5 Feb 2024 16:36:32 -0500 Subject: [PATCH 13/30] add password to main script and persist in ext storage --- README.md | 2 ++ build/UserALEWebExtension/background.js | 12 ++++++------ build/UserALEWebExtension/content.js | 6 ++++++ build/UserALEWebExtension/options.js | 19 +++++++++---------- build/userale-2.4.0.js | 4 ++++ build/userale-2.4.0.min.js | 2 +- src/UserALEWebExtension/background.js | 6 +----- src/UserALEWebExtension/messageTypes.js | 7 +++---- src/UserALEWebExtension/options.js | 13 +++---------- src/getInitialSettings.js | 1 + src/sendLogs.js | 6 ++++++ test/main_spec.js | 1 + 12 files changed, 43 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index ae4e83ee..6d2abc12 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,7 @@ The complete list of configurable parameters that can be configured via `userale | transmitInterval | Delay between transmit checks | 5000 (ms) | | logCountThreshold | Minimum number of logs to send | 5 | | userId | User identifier | null | +| password | Used with userId for basic authentication auth header, overwritten by authHeader | null | | sessionID | Session identifier | null | | version | Application version identifier | null | | logDetails | Toggle detailed logs (keys pressed and input/change values) | false | @@ -161,6 +162,7 @@ You have access to the same parameters listed above, however, naming conventions | data-interval | Delay between transmit checks | 5000 (ms) | | data-threshold | Minimum number of logs to send | 5 | | data-user | User identifier | null | +| data-password | Used with user for basic authentication auth header, overwritten by auth | null | | data-version | Application version identifier | null | | data-log-details | Toggle detailed logs (keys pressed and input/change values) | false | | data-resolution | Delay between instances of high frequency logs (mouseover, scroll, etc.) | 500 (ms) | diff --git a/build/UserALEWebExtension/background.js b/build/UserALEWebExtension/background.js index 8b470959..874f6074 100644 --- a/build/UserALEWebExtension/background.js +++ b/build/UserALEWebExtension/background.js @@ -17,7 +17,6 @@ var prefix = 'USERALE_'; var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE'; -var AUTH_CHANGE = prefix + 'AUTH_CHANGE'; var ADD_LOG = prefix + 'ADD_LOG'; function _typeof(o) { @@ -73,6 +72,7 @@ function getInitialSettings() { settings.transmitInterval = +get('data-interval') || 5000; settings.logCountThreshold = +get('data-threshold') || 5; settings.userId = get('data-user') || null; + settings.password = get('data-password') || null; settings.version = get('data-version') || null; settings.logDetails = get('data-log-details') === 'true' ? true : false; settings.resolution = +get('data-resolution') || 500; @@ -987,6 +987,11 @@ function sendLogs(logs, config, retries) { var req = new XMLHttpRequest(); var data = JSON.stringify(logs); req.open("POST", config.url); + + // Update headers + if (config.userId && config.password) { + req.setRequestHeader("Authorization", "Basic " + btoa("".concat(config.userId, ":").concat(config.password))); + } if (config.authHeader) { req.setRequestHeader("Authorization", config.authHeader); } @@ -1153,11 +1158,6 @@ browser.runtime.onMessage.addListener(function (message) { options(message.payload); dispatchTabMessage(message); break; - case AUTH_CHANGE: - options({ - authHeader: message.payload - }); - break; default: console.log('got unknown message type ', message); } diff --git a/build/UserALEWebExtension/content.js b/build/UserALEWebExtension/content.js index 53e6c599..c79f5712 100644 --- a/build/UserALEWebExtension/content.js +++ b/build/UserALEWebExtension/content.js @@ -62,6 +62,7 @@ function getInitialSettings() { settings.transmitInterval = +get('data-interval') || 5000; settings.logCountThreshold = +get('data-threshold') || 5; settings.userId = get('data-user') || null; + settings.password = get('data-password') || null; settings.version = get('data-version') || null; settings.logDetails = get('data-log-details') === 'true' ? true : false; settings.resolution = +get('data-resolution') || 500; @@ -1000,6 +1001,11 @@ function sendLogs(logs, config, retries) { var req = new XMLHttpRequest(); var data = JSON.stringify(logs); req.open("POST", config.url); + + // Update headers + if (config.userId && config.password) { + req.setRequestHeader("Authorization", "Basic " + btoa("".concat(config.userId, ":").concat(config.password))); + } if (config.authHeader) { req.setRequestHeader("Authorization", config.authHeader); } diff --git a/build/UserALEWebExtension/options.js b/build/UserALEWebExtension/options.js index 4add4cde..5dc1f330 100644 --- a/build/UserALEWebExtension/options.js +++ b/build/UserALEWebExtension/options.js @@ -17,7 +17,6 @@ var prefix = 'USERALE_'; var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE'; -var AUTH_CHANGE = prefix + 'AUTH_CHANGE'; var ADD_LOG = prefix + 'ADD_LOG'; var version = "2.4.0"; @@ -63,6 +62,7 @@ function getInitialSettings() { settings.transmitInterval = +get('data-interval') || 5000; settings.logCountThreshold = +get('data-threshold') || 5; settings.userId = get('data-user') || null; + settings.password = get('data-password') || null; settings.version = get('data-version') || null; settings.logDetails = get('data-log-details') === 'true' ? true : false; settings.resolution = +get('data-resolution') || 500; @@ -1001,6 +1001,11 @@ function sendLogs(logs, config, retries) { var req = new XMLHttpRequest(); var data = JSON.stringify(logs); req.open("POST", config.url); + + // Update headers + if (config.userId && config.password) { + req.setRequestHeader("Authorization", "Basic " + btoa("".concat(config.userId, ":").concat(config.password))); + } if (config.authHeader) { req.setRequestHeader("Authorization", config.authHeader); } @@ -1131,30 +1136,24 @@ addCallbacks({ reroute: rerouteLog }); function setConfig(e) { - var user = document.getElementById("user").value; - var password = document.getElementById("password").value; browser.storage.local.set({ useraleConfig: { url: document.getElementById("url").value, - userId: user, + userId: document.getElementById("user").value, + password: document.getElementById("password").value, toolName: document.getElementById("tool").value, version: document.getElementById("version").value } }, function () { getConfig(); }); - if (user && password) { - browser.runtime.sendMessage({ - type: AUTH_CHANGE, - payload: "Basic " + btoa("".concat(user, ":").concat(password)) - }); - } } function getConfig() { browser.storage.local.get("useraleConfig", function (res) { var config = res.useraleConfig; document.getElementById("url").value = config.url; document.getElementById("user").value = config.userId; + document.getElementById("password").value = config.password; document.getElementById("tool").value = config.toolName; document.getElementById("version").value = config.version; options(config); diff --git a/build/userale-2.4.0.js b/build/userale-2.4.0.js index c9a78243..c8b8a303 100644 --- a/build/userale-2.4.0.js +++ b/build/userale-2.4.0.js @@ -75,6 +75,7 @@ settings.transmitInterval = +get('data-interval') || 5000; settings.logCountThreshold = +get('data-threshold') || 5; settings.userId = get('data-user') || null; + settings.password = get('data-password') || null; settings.version = get('data-version') || null; settings.logDetails = get('data-log-details') === 'true' ? true : false; settings.resolution = +get('data-resolution') || 500; @@ -1162,6 +1163,9 @@ req.open("POST", config.url); // Update headers + if (config.userId && config.password) { + req.setRequestHeader("Authorization", "Basic " + btoa("".concat(config.userId, ":").concat(config.password))); + } updateAuthHeader(config); if (config.authHeader) { req.setRequestHeader("Authorization", config.authHeader); diff --git a/build/userale-2.4.0.min.js b/build/userale-2.4.0.min.js index e87a122b..a0a98a82 100644 --- a/build/userale-2.4.0.min.js +++ b/build/userale-2.4.0.min.js @@ -15,4 +15,4 @@ * 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",r=null;function o(e,t){var n=e.autostart,r=t.autostart;Object.keys(t).forEach((function(n){if("userFromParams"===n){var r=(o=t[n],i=new RegExp("[?&]"+o+"(=([^&#]*)|&|#|$)"),(a=window.location.href.match(i))&&a[2]?decodeURIComponent(a[2].replace(/\+/g," ")):null);r&&(e.userId=r)}var o,i,a;e[n]=t[n]})),!1!==n&&!1!==r||(e.autostart=!1)}var i=function(e,t,n){if(n||2===arguments.length)for(var r,o=0,i=t.length;o0?v.time(e.timeStamp):Date.now(),{milli:Math.floor(r),micro:Number((r%1).toFixed(3))}),i={target:M(e.target),path:B(e),pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:P(),clientTime:o.milli,microTime:o.micro,location:C(e),scrnRes:D(),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,l=Object.values(x);a0?v.time(e.timeStamp):Date.now());if(null==y&&(y=t,b=r,S=n,O=o,k=0),y!==t||b!==r){E={target:y,path:S,pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:P(),count:k,duration:o-O,startTime:O,endTime:o,type:b,logType:"interval",targetChange:y!==t,typeChange:b!==r,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);ie.length)&&(t=e.length);for(var n=0,r=new Array(t);n=t.logCountThreshold&&(F(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 F(e,t,n){var r=new XMLHttpRequest,o=JSON.stringify(e);r.open("POST",t.url),function(e){if(H)try{e.authHeader=H()}catch(e){console.error("Error encountered while setting the auth header: ".concat(e))}}(t),t.authHeader&&r.setRequestHeader("Authorization",t.authHeader),r.setRequestHeader("Content-type","application/json;charset=UTF-8"),t.headers&&Object.entries(t.headers).forEach((function(e){var t=_(e,2),n=t[0],o=t[1];r.setRequestHeader(n,o)})),r.onreadystatechange=function(){4===r.readyState&&200!==r.status&&n>0&&F(e,t,n--)},r.send(o)}var Y,J={},q=[],G=Date.now();window.onload=function(){Y=Date.now()},e.started=!1,J.on=!1,J.useraleVersion=n,o(J,function(){var e={};null===r&&(r=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],o=n?n.getAttribute.bind(n):function(){return null};return e.autostart="false"!==o("data-autostart"),e.url=o("data-url")||"http://localhost:8000",e.transmitInterval=+o("data-interval")||5e3,e.logCountThreshold=+o("data-threshold")||5,e.userId=o("data-user")||null,e.version=o("data-version")||null,e.logDetails="true"===o("data-log-details"),e.resolution=+o("data-resolution")||500,e.toolName=o("data-tool")||null,e.userFromParams=o("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 r=performance.timing.navigationStart;t=function(e){return e+r}}else t=function(e){return e}}else t=function(){return Date.now()};return t}(document.createEvent("CustomEvent")),e.sessionID=o("data-session")||r,e.authHeader=o("data-auth")||null,e.custIndex=o("data-index")||null,e.headers=o("data-headers")||null,e}()),g=q,v=J,x=[],y=null,b=null,S=null,O=null,k=0,E=null,J.autostart&&function t(n){e.started||setTimeout((function(){var r=document.readyState;!n.autostart||"interactive"!==r&&"complete"!==r?t(n):($(n),z(q,n),e.started=n.on=!0,j({type:"load",details:{pageLoadTime:Y-G}},(function(){}),!1))}),100)}(J);var Q=n;e.addCallbacks=function(){for(var e=arguments.length,t=new Array(e),n=0;n0?v.time(e.timeStamp):Date.now(),{milli:Math.floor(r),micro:Number((r%1).toFixed(3))}),i={target:M(e.target),path:B(e),pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:P(),clientTime:o.milli,microTime:o.micro,location:C(e),scrnRes:D(),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);a0?v.time(e.timeStamp):Date.now());if(null==y&&(y=t,b=r,S=n,O=o,k=0),y!==t||b!==r){E={target:y,path:S,pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:P(),count:k,duration:o-O,startTime:O,endTime:o,type:b,logType:"interval",targetChange:y!==t,typeChange:b!==r,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);ie.length)&&(t=e.length);for(var n=0,r=new Array(t);n=t.logCountThreshold&&(F(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 F(e,t,n){var r=new XMLHttpRequest,o=JSON.stringify(e);r.open("POST",t.url),t.userId&&t.password&&r.setRequestHeader("Authorization","Basic "+btoa("".concat(t.userId,":").concat(t.password))),function(e){if(H)try{e.authHeader=H()}catch(e){console.error("Error encountered while setting the auth header: ".concat(e))}}(t),t.authHeader&&r.setRequestHeader("Authorization",t.authHeader),r.setRequestHeader("Content-type","application/json;charset=UTF-8"),t.headers&&Object.entries(t.headers).forEach((function(e){var t=_(e,2),n=t[0],o=t[1];r.setRequestHeader(n,o)})),r.onreadystatechange=function(){4===r.readyState&&200!==r.status&&n>0&&F(e,t,n--)},r.send(o)}var Y,J={},q=[],G=Date.now();window.onload=function(){Y=Date.now()},e.started=!1,J.on=!1,J.useraleVersion=n,o(J,function(){var e={};null===r&&(r=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],o=n?n.getAttribute.bind(n):function(){return null};return e.autostart="false"!==o("data-autostart"),e.url=o("data-url")||"http://localhost:8000",e.transmitInterval=+o("data-interval")||5e3,e.logCountThreshold=+o("data-threshold")||5,e.userId=o("data-user")||null,e.password=o("data-password")||null,e.version=o("data-version")||null,e.logDetails="true"===o("data-log-details"),e.resolution=+o("data-resolution")||500,e.toolName=o("data-tool")||null,e.userFromParams=o("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 r=performance.timing.navigationStart;t=function(e){return e+r}}else t=function(e){return e}}else t=function(){return Date.now()};return t}(document.createEvent("CustomEvent")),e.sessionID=o("data-session")||r,e.authHeader=o("data-auth")||null,e.custIndex=o("data-index")||null,e.headers=o("data-headers")||null,e}()),g=q,v=J,x=[],y=null,b=null,S=null,O=null,k=0,E=null,J.autostart&&function t(n){e.started||setTimeout((function(){var r=document.readyState;!n.autostart||"interactive"!==r&&"complete"!==r?t(n):($(n),U(q,n),e.started=n.on=!0,j({type:"load",details:{pageLoadTime:Y-G}},(function(){}),!1))}),100)}(J);var Q=n;e.addCallbacks=function(){for(var e=arguments.length,t=new Array(e),n=0;n {getConfig()} ); - - if(user && password) { - browser.runtime.sendMessage({type: MessageTypes.AUTH_CHANGE, - payload: "Basic " + btoa(`${user}:${password}`) - }); - } } function getConfig() { @@ -49,6 +41,7 @@ function getConfig() { document.getElementById("url").value = config.url; document.getElementById("user").value = config.userId; + document.getElementById("password").value = config.password; document.getElementById("tool").value = config.toolName; document.getElementById("version").value = config.version; diff --git a/src/getInitialSettings.js b/src/getInitialSettings.js index b9624ebe..e3dd41b8 100644 --- a/src/getInitialSettings.js +++ b/src/getInitialSettings.js @@ -42,6 +42,7 @@ export function getInitialSettings() { settings.transmitInterval = +get('data-interval') || 5000; settings.logCountThreshold = +get('data-threshold') || 5; settings.userId = get('data-user') || null; + settings.password = get('data-password') || null; settings.version = get('data-version') || null; settings.logDetails = get('data-log-details') === 'true' ? true : false; settings.resolution = +get('data-resolution') || 500; diff --git a/src/sendLogs.js b/src/sendLogs.js index 5985644d..1d2ed83d 100644 --- a/src/sendLogs.js +++ b/src/sendLogs.js @@ -89,6 +89,12 @@ export function sendLogs(logs, config, retries) { req.open("POST", config.url); // Update headers + if( config.userId && config.password) { + req.setRequestHeader( + "Authorization", "Basic " + btoa(`${config.userId}:${config.password}`) + ); + } + updateAuthHeader(config); if (config.authHeader) { req.setRequestHeader("Authorization", config.authHeader); diff --git a/test/main_spec.js b/test/main_spec.js index 5bc045ab..b787fa13 100644 --- a/test/main_spec.js +++ b/test/main_spec.js @@ -31,6 +31,7 @@ describe('Userale API', () => { 'transmitInterval', 'logCountThreshold', 'userId', + 'password', 'sessionID', 'version', 'logDetails', From 3ee604a0b309096df29e0ec3946cb3cf227cf305 Mon Sep 17 00:00:00 2001 From: Jason Young Date: Tue, 6 Feb 2024 14:46:04 -0500 Subject: [PATCH 14/30] Remove password from main script --- README.md | 2 -- build/UserALEWebExtension/background.js | 6 ------ build/UserALEWebExtension/content.js | 6 ------ build/UserALEWebExtension/options.js | 6 ------ build/userale-2.4.0.js | 4 ---- build/userale-2.4.0.min.js | 2 +- src/getInitialSettings.js | 1 - src/sendLogs.js | 6 ------ test/main_spec.js | 1 - 9 files changed, 1 insertion(+), 33 deletions(-) diff --git a/README.md b/README.md index 6d2abc12..ae4e83ee 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,6 @@ The complete list of configurable parameters that can be configured via `userale | transmitInterval | Delay between transmit checks | 5000 (ms) | | logCountThreshold | Minimum number of logs to send | 5 | | userId | User identifier | null | -| password | Used with userId for basic authentication auth header, overwritten by authHeader | null | | sessionID | Session identifier | null | | version | Application version identifier | null | | logDetails | Toggle detailed logs (keys pressed and input/change values) | false | @@ -162,7 +161,6 @@ You have access to the same parameters listed above, however, naming conventions | data-interval | Delay between transmit checks | 5000 (ms) | | data-threshold | Minimum number of logs to send | 5 | | data-user | User identifier | null | -| data-password | Used with user for basic authentication auth header, overwritten by auth | null | | data-version | Application version identifier | null | | data-log-details | Toggle detailed logs (keys pressed and input/change values) | false | | data-resolution | Delay between instances of high frequency logs (mouseover, scroll, etc.) | 500 (ms) | diff --git a/build/UserALEWebExtension/background.js b/build/UserALEWebExtension/background.js index 874f6074..f844a934 100644 --- a/build/UserALEWebExtension/background.js +++ b/build/UserALEWebExtension/background.js @@ -72,7 +72,6 @@ function getInitialSettings() { settings.transmitInterval = +get('data-interval') || 5000; settings.logCountThreshold = +get('data-threshold') || 5; settings.userId = get('data-user') || null; - settings.password = get('data-password') || null; settings.version = get('data-version') || null; settings.logDetails = get('data-log-details') === 'true' ? true : false; settings.resolution = +get('data-resolution') || 500; @@ -987,11 +986,6 @@ function sendLogs(logs, config, retries) { var req = new XMLHttpRequest(); var data = JSON.stringify(logs); req.open("POST", config.url); - - // Update headers - if (config.userId && config.password) { - req.setRequestHeader("Authorization", "Basic " + btoa("".concat(config.userId, ":").concat(config.password))); - } if (config.authHeader) { req.setRequestHeader("Authorization", config.authHeader); } diff --git a/build/UserALEWebExtension/content.js b/build/UserALEWebExtension/content.js index c79f5712..53e6c599 100644 --- a/build/UserALEWebExtension/content.js +++ b/build/UserALEWebExtension/content.js @@ -62,7 +62,6 @@ function getInitialSettings() { settings.transmitInterval = +get('data-interval') || 5000; settings.logCountThreshold = +get('data-threshold') || 5; settings.userId = get('data-user') || null; - settings.password = get('data-password') || null; settings.version = get('data-version') || null; settings.logDetails = get('data-log-details') === 'true' ? true : false; settings.resolution = +get('data-resolution') || 500; @@ -1001,11 +1000,6 @@ function sendLogs(logs, config, retries) { var req = new XMLHttpRequest(); var data = JSON.stringify(logs); req.open("POST", config.url); - - // Update headers - if (config.userId && config.password) { - req.setRequestHeader("Authorization", "Basic " + btoa("".concat(config.userId, ":").concat(config.password))); - } if (config.authHeader) { req.setRequestHeader("Authorization", config.authHeader); } diff --git a/build/UserALEWebExtension/options.js b/build/UserALEWebExtension/options.js index 5dc1f330..a075371b 100644 --- a/build/UserALEWebExtension/options.js +++ b/build/UserALEWebExtension/options.js @@ -62,7 +62,6 @@ function getInitialSettings() { settings.transmitInterval = +get('data-interval') || 5000; settings.logCountThreshold = +get('data-threshold') || 5; settings.userId = get('data-user') || null; - settings.password = get('data-password') || null; settings.version = get('data-version') || null; settings.logDetails = get('data-log-details') === 'true' ? true : false; settings.resolution = +get('data-resolution') || 500; @@ -1001,11 +1000,6 @@ function sendLogs(logs, config, retries) { var req = new XMLHttpRequest(); var data = JSON.stringify(logs); req.open("POST", config.url); - - // Update headers - if (config.userId && config.password) { - req.setRequestHeader("Authorization", "Basic " + btoa("".concat(config.userId, ":").concat(config.password))); - } if (config.authHeader) { req.setRequestHeader("Authorization", config.authHeader); } diff --git a/build/userale-2.4.0.js b/build/userale-2.4.0.js index c8b8a303..c9a78243 100644 --- a/build/userale-2.4.0.js +++ b/build/userale-2.4.0.js @@ -75,7 +75,6 @@ settings.transmitInterval = +get('data-interval') || 5000; settings.logCountThreshold = +get('data-threshold') || 5; settings.userId = get('data-user') || null; - settings.password = get('data-password') || null; settings.version = get('data-version') || null; settings.logDetails = get('data-log-details') === 'true' ? true : false; settings.resolution = +get('data-resolution') || 500; @@ -1163,9 +1162,6 @@ req.open("POST", config.url); // Update headers - if (config.userId && config.password) { - req.setRequestHeader("Authorization", "Basic " + btoa("".concat(config.userId, ":").concat(config.password))); - } updateAuthHeader(config); if (config.authHeader) { req.setRequestHeader("Authorization", config.authHeader); diff --git a/build/userale-2.4.0.min.js b/build/userale-2.4.0.min.js index a0a98a82..e87a122b 100644 --- a/build/userale-2.4.0.min.js +++ b/build/userale-2.4.0.min.js @@ -15,4 +15,4 @@ * 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",r=null;function o(e,t){var n=e.autostart,r=t.autostart;Object.keys(t).forEach((function(n){if("userFromParams"===n){var r=(o=t[n],i=new RegExp("[?&]"+o+"(=([^&#]*)|&|#|$)"),(a=window.location.href.match(i))&&a[2]?decodeURIComponent(a[2].replace(/\+/g," ")):null);r&&(e.userId=r)}var o,i,a;e[n]=t[n]})),!1!==n&&!1!==r||(e.autostart=!1)}var i=function(e,t,n){if(n||2===arguments.length)for(var r,o=0,i=t.length;o0?v.time(e.timeStamp):Date.now(),{milli:Math.floor(r),micro:Number((r%1).toFixed(3))}),i={target:M(e.target),path:B(e),pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:P(),clientTime:o.milli,microTime:o.micro,location:C(e),scrnRes:D(),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);a0?v.time(e.timeStamp):Date.now());if(null==y&&(y=t,b=r,S=n,O=o,k=0),y!==t||b!==r){E={target:y,path:S,pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:P(),count:k,duration:o-O,startTime:O,endTime:o,type:b,logType:"interval",targetChange:y!==t,typeChange:b!==r,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);ie.length)&&(t=e.length);for(var n=0,r=new Array(t);n=t.logCountThreshold&&(F(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 F(e,t,n){var r=new XMLHttpRequest,o=JSON.stringify(e);r.open("POST",t.url),t.userId&&t.password&&r.setRequestHeader("Authorization","Basic "+btoa("".concat(t.userId,":").concat(t.password))),function(e){if(H)try{e.authHeader=H()}catch(e){console.error("Error encountered while setting the auth header: ".concat(e))}}(t),t.authHeader&&r.setRequestHeader("Authorization",t.authHeader),r.setRequestHeader("Content-type","application/json;charset=UTF-8"),t.headers&&Object.entries(t.headers).forEach((function(e){var t=_(e,2),n=t[0],o=t[1];r.setRequestHeader(n,o)})),r.onreadystatechange=function(){4===r.readyState&&200!==r.status&&n>0&&F(e,t,n--)},r.send(o)}var Y,J={},q=[],G=Date.now();window.onload=function(){Y=Date.now()},e.started=!1,J.on=!1,J.useraleVersion=n,o(J,function(){var e={};null===r&&(r=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],o=n?n.getAttribute.bind(n):function(){return null};return e.autostart="false"!==o("data-autostart"),e.url=o("data-url")||"http://localhost:8000",e.transmitInterval=+o("data-interval")||5e3,e.logCountThreshold=+o("data-threshold")||5,e.userId=o("data-user")||null,e.password=o("data-password")||null,e.version=o("data-version")||null,e.logDetails="true"===o("data-log-details"),e.resolution=+o("data-resolution")||500,e.toolName=o("data-tool")||null,e.userFromParams=o("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 r=performance.timing.navigationStart;t=function(e){return e+r}}else t=function(e){return e}}else t=function(){return Date.now()};return t}(document.createEvent("CustomEvent")),e.sessionID=o("data-session")||r,e.authHeader=o("data-auth")||null,e.custIndex=o("data-index")||null,e.headers=o("data-headers")||null,e}()),g=q,v=J,x=[],y=null,b=null,S=null,O=null,k=0,E=null,J.autostart&&function t(n){e.started||setTimeout((function(){var r=document.readyState;!n.autostart||"interactive"!==r&&"complete"!==r?t(n):($(n),U(q,n),e.started=n.on=!0,j({type:"load",details:{pageLoadTime:Y-G}},(function(){}),!1))}),100)}(J);var Q=n;e.addCallbacks=function(){for(var e=arguments.length,t=new Array(e),n=0;n0?v.time(e.timeStamp):Date.now(),{milli:Math.floor(r),micro:Number((r%1).toFixed(3))}),i={target:M(e.target),path:B(e),pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:P(),clientTime:o.milli,microTime:o.micro,location:C(e),scrnRes:D(),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,l=Object.values(x);a0?v.time(e.timeStamp):Date.now());if(null==y&&(y=t,b=r,S=n,O=o,k=0),y!==t||b!==r){E={target:y,path:S,pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:P(),count:k,duration:o-O,startTime:O,endTime:o,type:b,logType:"interval",targetChange:y!==t,typeChange:b!==r,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);ie.length)&&(t=e.length);for(var n=0,r=new Array(t);n=t.logCountThreshold&&(F(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 F(e,t,n){var r=new XMLHttpRequest,o=JSON.stringify(e);r.open("POST",t.url),function(e){if(H)try{e.authHeader=H()}catch(e){console.error("Error encountered while setting the auth header: ".concat(e))}}(t),t.authHeader&&r.setRequestHeader("Authorization",t.authHeader),r.setRequestHeader("Content-type","application/json;charset=UTF-8"),t.headers&&Object.entries(t.headers).forEach((function(e){var t=_(e,2),n=t[0],o=t[1];r.setRequestHeader(n,o)})),r.onreadystatechange=function(){4===r.readyState&&200!==r.status&&n>0&&F(e,t,n--)},r.send(o)}var Y,J={},q=[],G=Date.now();window.onload=function(){Y=Date.now()},e.started=!1,J.on=!1,J.useraleVersion=n,o(J,function(){var e={};null===r&&(r=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],o=n?n.getAttribute.bind(n):function(){return null};return e.autostart="false"!==o("data-autostart"),e.url=o("data-url")||"http://localhost:8000",e.transmitInterval=+o("data-interval")||5e3,e.logCountThreshold=+o("data-threshold")||5,e.userId=o("data-user")||null,e.version=o("data-version")||null,e.logDetails="true"===o("data-log-details"),e.resolution=+o("data-resolution")||500,e.toolName=o("data-tool")||null,e.userFromParams=o("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 r=performance.timing.navigationStart;t=function(e){return e+r}}else t=function(e){return e}}else t=function(){return Date.now()};return t}(document.createEvent("CustomEvent")),e.sessionID=o("data-session")||r,e.authHeader=o("data-auth")||null,e.custIndex=o("data-index")||null,e.headers=o("data-headers")||null,e}()),g=q,v=J,x=[],y=null,b=null,S=null,O=null,k=0,E=null,J.autostart&&function t(n){e.started||setTimeout((function(){var r=document.readyState;!n.autostart||"interactive"!==r&&"complete"!==r?t(n):($(n),z(q,n),e.started=n.on=!0,j({type:"load",details:{pageLoadTime:Y-G}},(function(){}),!1))}),100)}(J);var Q=n;e.addCallbacks=function(){for(var e=arguments.length,t=new Array(e),n=0;n { 'transmitInterval', 'logCountThreshold', 'userId', - 'password', 'sessionID', 'version', 'logDetails', From a5097c88978183765ec2e6bdaed306df55f7abd5 Mon Sep 17 00:00:00 2001 From: Madeline Diep Date: Tue, 6 Feb 2024 13:13:08 -0500 Subject: [PATCH 15/30] Add tabId to log entries --- src/UserALEWebExtension/background.js | 3 +++ src/packageLogs.js | 31 ++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/UserALEWebExtension/background.js b/src/UserALEWebExtension/background.js index d8aed77b..20cb4f86 100644 --- a/src/UserALEWebExtension/background.js +++ b/src/UserALEWebExtension/background.js @@ -75,6 +75,9 @@ function packageDetailedTabLog(tab, data, type) { // Attach Handlers for tab events // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs browser.tabs.onActivated.addListener((activeInfo) => { + browser.storage.local.set({ "tabId": e.tabId }, function() { + console.log("The tabID has been set in storage.local"); + }); packageTabLog(activeInfo.tabId, activeInfo, "tabs.onActivated"); }); diff --git a/src/packageLogs.js b/src/packageLogs.js index db287d17..d8807bf9 100644 --- a/src/packageLogs.js +++ b/src/packageLogs.js @@ -33,6 +33,7 @@ export let filterHandler = null; export let mapHandler = null; export let cbHandlers = {}; + /** * Assigns a handler to filter logs out of the queue. * @deprecated Use addCallbacks and removeCallbacks instead @@ -104,13 +105,28 @@ export function initPackager(newLogs, newConfig) { intervalLog = null; } +/** + * Get the tabID from local storage + */ +function getTabId() { + return new Promise((resolve, reject) => { + chrome.storage.local.get("tabId", function(result) { + if (result.tabId !== undefined) { + resolve(result.tabId); + } else { + reject('tabId not found'); + } + }); + }); +} + /** * 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. */ -export function packageLog(e, detailFcn) { +export async function packageLog(e, detailFcn) { if (!config.on) { return false; } @@ -124,6 +140,8 @@ export function packageLog(e, detailFcn) { (e.timeStamp && e.timeStamp > 0) ? config.time(e.timeStamp) : Date.now() ); + const tabId = await getTabId(); + let log = { 'target' : getSelector(e.target), 'path' : buildPath(e), @@ -143,6 +161,7 @@ export function packageLog(e, detailFcn) { 'toolVersion' : config.version, 'toolName' : config.toolName, 'useraleVersion': config.useraleVersion, + 'tabId': tabId, 'sessionID': config.sessionID, }; @@ -174,7 +193,7 @@ export function packageLog(e, detailFcn) { * @param {boolean} userAction Indicates user behavior (true) or system behavior (false) * @return {boolean} Whether the event was logged. */ -export function packageCustomLog(customLog, detailFcn, userAction) { +export async function packageCustomLog(customLog, detailFcn, userAction) { if (!config.on) { return false; } @@ -184,6 +203,8 @@ export function packageCustomLog(customLog, detailFcn, userAction) { details = detailFcn(); } + const tabId = await getTabId(); + const metaData = { 'pageUrl': window.location.href, 'pageTitle': document.title, @@ -198,6 +219,7 @@ export function packageCustomLog(customLog, detailFcn, userAction) { 'toolVersion' : config.version, 'toolName' : config.toolName, 'useraleVersion': config.useraleVersion, + 'tabId': tabId, 'sessionID': config.sessionID }; @@ -243,7 +265,7 @@ export function extractTimeFields(timeStamp) { * @param {Object} e * @return boolean */ -export function packageIntervalLog(e) { +export async function packageIntervalLog(e) { const target = getSelector(e.target); const path = buildPath(e); const type = e.type; @@ -258,6 +280,8 @@ export function packageIntervalLog(e) { intervalCounter = 0; } + const tabId = await getTabId(); + 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... @@ -282,6 +306,7 @@ export function packageIntervalLog(e) { 'toolVersion': config.version, 'toolName': config.toolName, 'useraleVersion': config.useraleVersion, + 'tabId': tabId, 'sessionID': config.sessionID }; From d2bc4adf254b83c9e4925f4fc82bba22f6ab4dd3 Mon Sep 17 00:00:00 2001 From: Jason Young Date: Tue, 6 Feb 2024 17:47:45 -0500 Subject: [PATCH 16/30] Set auth header with password --- build/UserALEWebExtension/background.js | 1 + build/UserALEWebExtension/options.js | 37 +++++++++++++---------- src/UserALEWebExtension/background.js | 1 + src/UserALEWebExtension/options.js | 40 ++++++++++++++----------- 4 files changed, 46 insertions(+), 33 deletions(-) diff --git a/build/UserALEWebExtension/background.js b/build/UserALEWebExtension/background.js index f844a934..d9372d05 100644 --- a/build/UserALEWebExtension/background.js +++ b/build/UserALEWebExtension/background.js @@ -1149,6 +1149,7 @@ browser.runtime.onMessage.addListener(function (message) { log(message.payload); break; case CONFIG_CHANGE: + console.log(message); options(message.payload); dispatchTabMessage(message); break; diff --git a/build/UserALEWebExtension/options.js b/build/UserALEWebExtension/options.js index a075371b..acd305a0 100644 --- a/build/UserALEWebExtension/options.js +++ b/build/UserALEWebExtension/options.js @@ -1129,35 +1129,40 @@ function rerouteLog(log) { addCallbacks({ reroute: rerouteLog }); -function setConfig(e) { +function setConfig() { + var config = { + url: document.getElementById("url").value, + userId: document.getElementById("user").value, + toolName: document.getElementById("tool").value, + version: document.getElementById("version").value + }; + + // Set a basic auth header if given credentials. + var password = document.getElementById("password").value; + if (config.userId && password) { + config.authHeader = "Basic " + btoa("".concat(config.userId, ":").concat(password)); + } browser.storage.local.set({ - useraleConfig: { - url: document.getElementById("url").value, - userId: document.getElementById("user").value, - password: document.getElementById("password").value, - toolName: document.getElementById("tool").value, - version: document.getElementById("version").value - } + useraleConfig: config }, function () { - getConfig(); + options(config); + browser.runtime.sendMessage({ + type: CONFIG_CHANGE, + payload: config + }); }); } function getConfig() { browser.storage.local.get("useraleConfig", function (res) { var config = res.useraleConfig; + options(config); document.getElementById("url").value = config.url; document.getElementById("user").value = config.userId; - document.getElementById("password").value = config.password; document.getElementById("tool").value = config.toolName; document.getElementById("version").value = config.version; - options(config); - browser.runtime.sendMessage({ - type: CONFIG_CHANGE, - payload: config - }); }); } -document.addEventListener('DOMContentLoaded', getConfig); +document.addEventListener("DOMContentLoaded", getConfig); document.addEventListener("submit", setConfig); /* eslint-enable */ diff --git a/src/UserALEWebExtension/background.js b/src/UserALEWebExtension/background.js index 99f15231..5987938b 100644 --- a/src/UserALEWebExtension/background.js +++ b/src/UserALEWebExtension/background.js @@ -51,6 +51,7 @@ browser.runtime.onMessage.addListener(function (message) { break; case MessageTypes.CONFIG_CHANGE: + console.log(message); userale.options(message.payload); dispatchTabMessage(message); break; diff --git a/src/UserALEWebExtension/options.js b/src/UserALEWebExtension/options.js index 2207ed3a..b2ecbdb9 100644 --- a/src/UserALEWebExtension/options.js +++ b/src/UserALEWebExtension/options.js @@ -22,36 +22,42 @@ import { rerouteLog, browser } from './globals.js'; userale.addCallbacks({reroute: rerouteLog}); -function setConfig(e) { - browser.storage.local.set( - {useraleConfig: { - url: document.getElementById("url").value, - userId: document.getElementById("user").value, - password: document.getElementById("password").value, - toolName: document.getElementById("tool").value, - version: document.getElementById("version").value - }}, - () => {getConfig()} - ); +// 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 setConfig() { + let config = { + url: document.getElementById("url").value, + userId: document.getElementById("user").value, + toolName: document.getElementById("tool").value, + version: document.getElementById("version").value + }; + + // 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.storage.local.set({useraleConfig: config}, () => { + userale.options(config); + browser.runtime.sendMessage({ type: MessageTypes.CONFIG_CHANGE, payload: config }); + }); } 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("password").value = config.password; document.getElementById("tool").value = config.toolName; document.getElementById("version").value = config.version; - - userale.options(config); - browser.runtime.sendMessage({ type: MessageTypes.CONFIG_CHANGE, payload: config }); - }); } -document.addEventListener('DOMContentLoaded', getConfig); +document.addEventListener("DOMContentLoaded", getConfig); document.addEventListener("submit", setConfig); /* eslint-enable */ \ No newline at end of file From 4ede28ccbc13f5592c06521e15e82bf546885e91 Mon Sep 17 00:00:00 2001 From: Madeline Diep Date: Tue, 6 Feb 2024 17:51:58 -0500 Subject: [PATCH 17/30] Update implementation given latest code base in test branch --- src/UserALEWebExtension/background.js | 4 +--- src/packageLogs.js | 11 ++++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/UserALEWebExtension/background.js b/src/UserALEWebExtension/background.js index 20cb4f86..16b86634 100644 --- a/src/UserALEWebExtension/background.js +++ b/src/UserALEWebExtension/background.js @@ -75,9 +75,7 @@ function packageDetailedTabLog(tab, data, type) { // Attach Handlers for tab events // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs browser.tabs.onActivated.addListener((activeInfo) => { - browser.storage.local.set({ "tabId": e.tabId }, function() { - console.log("The tabID has been set in storage.local"); - }); + browser.storage.local.set({ "tabId": activeInfo.tabId }); packageTabLog(activeInfo.tabId, activeInfo, "tabs.onActivated"); }); diff --git a/src/packageLogs.js b/src/packageLogs.js index d8807bf9..c9f79f34 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; @@ -108,9 +108,10 @@ export function initPackager(newLogs, newConfig) { /** * Get the tabID from local storage */ -function getTabId() { +export function getTabId() { + const api = (typeof browser !== 'undefined') ? browser : chrome; return new Promise((resolve, reject) => { - chrome.storage.local.get("tabId", function(result) { + api.storage.local.get("tabId", function(result) { if (result.tabId !== undefined) { resolve(result.tabId); } else { @@ -416,7 +417,7 @@ export function selectorizePath(path) { 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 From 24a5ccd44c1dda10dc3b31af79655138b2b68b63 Mon Sep 17 00:00:00 2001 From: Jason Young Date: Tue, 6 Feb 2024 18:39:08 -0500 Subject: [PATCH 18/30] Get auth header on ext boot --- build/UserALEWebExtension/background.js | 1 + src/UserALEWebExtension/background.js | 1 + 2 files changed, 2 insertions(+) diff --git a/build/UserALEWebExtension/background.js b/build/UserALEWebExtension/background.js index d9372d05..389f5a8d 100644 --- a/build/UserALEWebExtension/background.js +++ b/build/UserALEWebExtension/background.js @@ -1128,6 +1128,7 @@ var defaultConfig = { useraleConfig: { url: 'http://localhost:8000', userId: 'pluginUser', + authHeader: null, toolName: 'useralePlugin', version: version } diff --git a/src/UserALEWebExtension/background.js b/src/UserALEWebExtension/background.js index 5987938b..28a7ab11 100644 --- a/src/UserALEWebExtension/background.js +++ b/src/UserALEWebExtension/background.js @@ -27,6 +27,7 @@ import { browser } from './globals.js'; const defaultConfig = {useraleConfig: { url: 'http://localhost:8000', userId: 'pluginUser', + authHeader: null, toolName: 'useralePlugin', version: userale.version, }}; From 5b94d269acccd0a62323ff3d5cb3ca987eae1374 Mon Sep 17 00:00:00 2001 From: Jason Young Date: Wed, 7 Feb 2024 12:44:31 -0500 Subject: [PATCH 19/30] Remove tabid from packageLogs --- src/packageLogs.js | 32 +++----------------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/src/packageLogs.js b/src/packageLogs.js index c9f79f34..637a986d 100644 --- a/src/packageLogs.js +++ b/src/packageLogs.js @@ -33,7 +33,6 @@ export let filterHandler = null; export let mapHandler = null; export let cbHandlers = {}; - /** * Assigns a handler to filter logs out of the queue. * @deprecated Use addCallbacks and removeCallbacks instead @@ -105,29 +104,13 @@ export function initPackager(newLogs, newConfig) { intervalLog = null; } -/** - * Get the tabID from local storage - */ -export function getTabId() { - const api = (typeof browser !== 'undefined') ? browser : chrome; - return new Promise((resolve, reject) => { - api.storage.local.get("tabId", function(result) { - if (result.tabId !== undefined) { - resolve(result.tabId); - } else { - reject('tabId not found'); - } - }); - }); -} - /** * 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. */ -export async function packageLog(e, detailFcn) { +export function packageLog(e, detailFcn) { if (!config.on) { return false; } @@ -141,8 +124,6 @@ export async function packageLog(e, detailFcn) { (e.timeStamp && e.timeStamp > 0) ? config.time(e.timeStamp) : Date.now() ); - const tabId = await getTabId(); - let log = { 'target' : getSelector(e.target), 'path' : buildPath(e), @@ -162,7 +143,6 @@ export async function packageLog(e, detailFcn) { 'toolVersion' : config.version, 'toolName' : config.toolName, 'useraleVersion': config.useraleVersion, - 'tabId': tabId, 'sessionID': config.sessionID, }; @@ -194,7 +174,7 @@ export async function packageLog(e, detailFcn) { * @param {boolean} userAction Indicates user behavior (true) or system behavior (false) * @return {boolean} Whether the event was logged. */ -export async function packageCustomLog(customLog, detailFcn, userAction) { +export function packageCustomLog(customLog, detailFcn, userAction) { if (!config.on) { return false; } @@ -204,8 +184,6 @@ export async function packageCustomLog(customLog, detailFcn, userAction) { details = detailFcn(); } - const tabId = await getTabId(); - const metaData = { 'pageUrl': window.location.href, 'pageTitle': document.title, @@ -220,7 +198,6 @@ export async function packageCustomLog(customLog, detailFcn, userAction) { 'toolVersion' : config.version, 'toolName' : config.toolName, 'useraleVersion': config.useraleVersion, - 'tabId': tabId, 'sessionID': config.sessionID }; @@ -266,7 +243,7 @@ export function extractTimeFields(timeStamp) { * @param {Object} e * @return boolean */ -export async function packageIntervalLog(e) { +export function packageIntervalLog(e) { const target = getSelector(e.target); const path = buildPath(e); const type = e.type; @@ -281,8 +258,6 @@ export async function packageIntervalLog(e) { intervalCounter = 0; } - const tabId = await getTabId(); - 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... @@ -307,7 +282,6 @@ export async function packageIntervalLog(e) { 'toolVersion': config.version, 'toolName': config.toolName, 'useraleVersion': config.useraleVersion, - 'tabId': tabId, 'sessionID': config.sessionID }; From 8a54754ad8d1a322b6da084ef2be359d1e1a6a8d Mon Sep 17 00:00:00 2001 From: Jason Young Date: Wed, 7 Feb 2024 12:57:08 -0500 Subject: [PATCH 20/30] add tabId to content script logs --- build/UserALEWebExtension/background.js | 14 +++++++++----- build/UserALEWebExtension/content.js | 7 +++---- build/UserALEWebExtension/options.js | 7 +++---- build/userale-2.4.0.js | 6 +++--- src/UserALEWebExtension/background.js | 9 ++++++--- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/build/UserALEWebExtension/background.js b/build/UserALEWebExtension/background.js index d7d0bfc8..30037045 100644 --- a/build/UserALEWebExtension/background.js +++ b/build/UserALEWebExtension/background.js @@ -415,7 +415,7 @@ function createVersionParts(count) { * limitations under the License. */ -var browser$1 = detect(); +var browserInfo = detect(); var logs$1; var config$1; @@ -706,8 +706,8 @@ function selectorizePath(path) { } function detectBrowser() { return { - 'browser': browser$1 ? browser$1.name : '', - 'version': browser$1 ? browser$1.version : '' + 'browser': browserInfo ? browserInfo.name : '', + 'version': browserInfo ? browserInfo.version : '' }; } @@ -1142,7 +1142,7 @@ function dispatchTabMessage(message) { }); }); } -browser.runtime.onMessage.addListener(function (message) { +browser.runtime.onMessage.addListener(function (message, sender, sendResponse) { switch (message.type) { case CONFIG_CHANGE: options(message.payload); @@ -1151,7 +1151,11 @@ browser.runtime.onMessage.addListener(function (message) { // Handles logs rerouted from content and option scripts case ADD_LOG: - log(message.payload); + var log$1 = message.payload; + if ("tab" in sender && "id" in sender.tab) { + log$1["tabId"] = sender.tab.id; + } + log(log$1); break; default: console.log('got unknown message type ', message); diff --git a/build/UserALEWebExtension/content.js b/build/UserALEWebExtension/content.js index ee925724..2c158e64 100644 --- a/build/UserALEWebExtension/content.js +++ b/build/UserALEWebExtension/content.js @@ -405,7 +405,7 @@ function createVersionParts(count) { * limitations under the License. */ -var browser$1 = detect(); +var browserInfo = detect(); var logs$1; var config$1; @@ -720,8 +720,8 @@ function selectorizePath(path) { } function detectBrowser() { return { - 'browser': browser$1 ? browser$1.name : '', - 'version': browser$1 ? browser$1.version : '' + 'browser': browserInfo ? browserInfo.name : '', + 'version': browserInfo ? browserInfo.version : '' }; } @@ -1100,7 +1100,6 @@ function options(newConfig) { // browser is defined in firefox, but chrome uses the 'chrome' global. var browser = browser || chrome; function rerouteLog(log) { - console.log(log); browser.runtime.sendMessage({ type: ADD_LOG, payload: log diff --git a/build/UserALEWebExtension/options.js b/build/UserALEWebExtension/options.js index 813a934a..322c1e9b 100644 --- a/build/UserALEWebExtension/options.js +++ b/build/UserALEWebExtension/options.js @@ -405,7 +405,7 @@ function createVersionParts(count) { * limitations under the License. */ -var browser$1 = detect(); +var browserInfo = detect(); var logs$1; var config$1; @@ -720,8 +720,8 @@ function selectorizePath(path) { } function detectBrowser() { return { - 'browser': browser$1 ? browser$1.name : '', - 'version': browser$1 ? browser$1.version : '' + 'browser': browserInfo ? browserInfo.name : '', + 'version': browserInfo ? browserInfo.version : '' }; } @@ -1100,7 +1100,6 @@ function options(newConfig) { // browser is defined in firefox, but chrome uses the 'chrome' global. var browser = browser || chrome; function rerouteLog(log) { - console.log(log); browser.runtime.sendMessage({ type: ADD_LOG, payload: log diff --git a/build/userale-2.4.0.js b/build/userale-2.4.0.js index c9a78243..5fc18af2 100644 --- a/build/userale-2.4.0.js +++ b/build/userale-2.4.0.js @@ -418,7 +418,7 @@ * limitations under the License. */ - var browser = detect(); + var browserInfo = detect(); var logs$1; var config$1; @@ -745,8 +745,8 @@ } function detectBrowser() { return { - 'browser': browser ? browser.name : '', - 'version': browser ? browser.version : '' + 'browser': browserInfo ? browserInfo.name : '', + 'version': browserInfo ? browserInfo.version : '' }; } diff --git a/src/UserALEWebExtension/background.js b/src/UserALEWebExtension/background.js index 16b86634..02aec1a1 100644 --- a/src/UserALEWebExtension/background.js +++ b/src/UserALEWebExtension/background.js @@ -43,7 +43,7 @@ function dispatchTabMessage(message) { }); } -browser.runtime.onMessage.addListener(function (message) { +browser.runtime.onMessage.addListener(function (message, sender, sendResponse) { switch (message.type) { case MessageTypes.CONFIG_CHANGE: userale.options(message.payload) @@ -52,7 +52,11 @@ browser.runtime.onMessage.addListener(function (message) { // Handles logs rerouted from content and option scripts case MessageTypes.ADD_LOG: - userale.log(message.payload); + let log = message.payload; + if("tab" in sender && "id" in sender.tab) { + log["tabId"] = sender.tab.id; + } + userale.log(log); break; default: @@ -75,7 +79,6 @@ function packageDetailedTabLog(tab, data, type) { // Attach Handlers for tab events // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs browser.tabs.onActivated.addListener((activeInfo) => { - browser.storage.local.set({ "tabId": activeInfo.tabId }); packageTabLog(activeInfo.tabId, activeInfo, "tabs.onActivated"); }); From 92e8a5d7fed56aa296588ab03bb561f4e905fad0 Mon Sep 17 00:00:00 2001 From: Jason Young Date: Wed, 7 Feb 2024 17:51:37 -0500 Subject: [PATCH 21/30] Adds url whitelist regex to ext options --- build/UserALEWebExtension/background.js | 90 ++++++++++++++++------ build/UserALEWebExtension/options.js | 15 +++- build/UserALEWebExtension/optionsPage.html | 4 + src/UserALEWebExtension/background.js | 63 +++++++++++---- src/UserALEWebExtension/options.js | 12 ++- src/UserALEWebExtension/optionsPage.html | 4 + 6 files changed, 145 insertions(+), 43 deletions(-) diff --git a/build/UserALEWebExtension/background.js b/build/UserALEWebExtension/background.js index 82a41c07..42a35502 100644 --- a/build/UserALEWebExtension/background.js +++ b/build/UserALEWebExtension/background.js @@ -428,6 +428,30 @@ 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. @@ -1082,21 +1106,21 @@ function log(customLog) { } } -/* -* 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. +/* +* 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. */ @@ -1131,11 +1155,18 @@ var defaultConfig = { authHeader: null, toolName: 'useralePlugin', version: version + }, + pluginConfig: { + // Default to a regex that will match no string + urlWhitelist: '(?!x)x' } }; -browser.storage.local.get(defaultConfig, function (res) { - options(res.useraleConfig); -}); +var urlWhitelist; +function updateConfig(config) { + urlWhitelist = new RegExp(config.pluginConfig.urlWhitelist); + options(config.useraleConfig); + dispatchTabMessage(config.useraleConfig); +} function dispatchTabMessage(message) { browser.tabs.query({}, function (tabs) { tabs.forEach(function (tab) { @@ -1143,20 +1174,33 @@ function dispatchTabMessage(message) { }); }); } -browser.runtime.onMessage.addListener(function (message, sender, sendResponse) { +function filterUrl(log) { + if (urlWhitelist.test(log.pageUrl)) { + return log; + } + return false; +} +browser.storage.local.get(defaultConfig, function (res) { + addCallbacks({ + filterUrl: filterUrl + }); + updateConfig(res); +}); +browser.runtime.onMessage.addListener(function (message) { switch (message.type) { // Handles logs rerouted from content and option scripts case ADD_LOG: - var log$1 = message.payload; + var var log$1 = filterUrl$1 = message.payload; if ("tab" in sender && "id" in sender.tab) { log$1["tabId"] = sender.tab.id; } log(log$1); + if (log$1) { + log(log$1); + } break; case CONFIG_CHANGE: - console.log(message); - options(message.payload); - dispatchTabMessage(message); + updateConfig(message.payload); break; default: console.log('got unknown message type ', message); diff --git a/build/UserALEWebExtension/options.js b/build/UserALEWebExtension/options.js index 8528ad8c..628b79e1 100644 --- a/build/UserALEWebExtension/options.js +++ b/build/UserALEWebExtension/options.js @@ -1142,13 +1142,17 @@ function setConfig() { if (config.userId && password) { config.authHeader = "Basic " + btoa("".concat(config.userId, ":").concat(password)); } - browser.storage.local.set({ - useraleConfig: config - }, function () { + var payload = { + useraleConfig: config, + pluginConfig: { + urlWhitelist: document.getElementById("filter").value + } + }; + browser.storage.local.set(payload, function () { options(config); browser.runtime.sendMessage({ type: CONFIG_CHANGE, - payload: config + payload: payload }); }); } @@ -1161,6 +1165,9 @@ function getConfig() { document.getElementById("tool").value = config.toolName; document.getElementById("version").value = config.version; }); + browser.storage.local.get("pluginConfig", function (res) { + document.getElementById("filter").value = res.pluginConfig.urlWhitelist; + }); } document.addEventListener("DOMContentLoaded", getConfig); document.addEventListener("submit", setConfig); diff --git a/build/UserALEWebExtension/optionsPage.html b/build/UserALEWebExtension/optionsPage.html index ff92cf09..9a3f363b 100644 --- a/build/UserALEWebExtension/optionsPage.html +++ b/build/UserALEWebExtension/optionsPage.html @@ -45,6 +45,10 @@

Options


+ + +
+
diff --git a/src/UserALEWebExtension/background.js b/src/UserALEWebExtension/background.js index 9e70d06c..8c33b3f2 100644 --- a/src/UserALEWebExtension/background.js +++ b/src/UserALEWebExtension/background.js @@ -24,17 +24,27 @@ import * as userale from '../main.js'; import { browser } from './globals.js'; // Initalize userale plugin options -const defaultConfig = {useraleConfig: { - url: 'http://localhost:8000', - userId: 'pluginUser', - authHeader: null, - toolName: 'useralePlugin', - version: userale.version, -}}; +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' + } +}; -browser.storage.local.get(defaultConfig, (res) => { - userale.options(res.useraleConfig); -}); +var urlWhitelist; + +function updateConfig(config) { + urlWhitelist = new RegExp(config.pluginConfig.urlWhitelist); + userale.options(config.useraleConfig); + dispatchTabMessage(config.useraleConfig); +} function dispatchTabMessage(message) { browser.tabs.query({}, function (tabs) { @@ -44,6 +54,30 @@ function dispatchTabMessage(message) { }); } +function filterUrl(log) { + if(urlWhitelist.test(log.pageUrl)) { + return log + } + return false; +} + +browser.storage.local.get(defaultConfig, (res) => { + userale.addCallbacks({filterUrl:filterUrl}); + updateConfig(res); +}); + +function filterUrl(log) { + if(urlWhitelist.test(log.pageUrl)) { + return log + } + return false; +} + +browser.storage.local.get(defaultConfig, (res) => { + userale.addCallbacks({filterUrl:filterUrl}); + updateConfig(res); +}); + browser.runtime.onMessage.addListener(function (message, sender, sendResponse) { switch (message.type) { // Handles logs rerouted from content and option scripts @@ -52,13 +86,14 @@ browser.runtime.onMessage.addListener(function (message, sender, sendResponse) { if("tab" in sender && "id" in sender.tab) { log["tabId"] = sender.tab.id; } - userale.log(log); + log = filterUrl(log); + if(log) { + userale.log(log); + } break; case MessageTypes.CONFIG_CHANGE: - console.log(message); - userale.options(message.payload); - dispatchTabMessage(message); + updateConfig(message.payload); break; default: diff --git a/src/UserALEWebExtension/options.js b/src/UserALEWebExtension/options.js index b2ecbdb9..53b57659 100644 --- a/src/UserALEWebExtension/options.js +++ b/src/UserALEWebExtension/options.js @@ -39,9 +39,14 @@ function setConfig() { config.authHeader = "Basic " + btoa(`${config.userId}:${password}`); } - browser.storage.local.set({useraleConfig: config}, () => { + 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: config }); + browser.runtime.sendMessage({ type: MessageTypes.CONFIG_CHANGE, payload: payload }); }); } @@ -55,6 +60,9 @@ function getConfig() { 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; + }); } document.addEventListener("DOMContentLoaded", getConfig); diff --git a/src/UserALEWebExtension/optionsPage.html b/src/UserALEWebExtension/optionsPage.html index ff92cf09..9a3f363b 100644 --- a/src/UserALEWebExtension/optionsPage.html +++ b/src/UserALEWebExtension/optionsPage.html @@ -45,6 +45,10 @@

Options


+ + +
+
From e9a99af28aab0a29fb48eceb3b713ae47fc4b9c8 Mon Sep 17 00:00:00 2001 From: Jason Young Date: Thu, 8 Feb 2024 10:46:20 -0500 Subject: [PATCH 22/30] Updates from feedback --- build/UserALEWebExtension/background.js | 16 +++++++--------- build/UserALEWebExtension/content.js | 4 +--- build/UserALEWebExtension/options.js | 4 +--- src/UserALEWebExtension/background.js | 11 +++++++++-- src/UserALEWebExtension/content.js | 2 +- src/UserALEWebExtension/options.js | 4 ++-- 6 files changed, 21 insertions(+), 20 deletions(-) diff --git a/build/UserALEWebExtension/background.js b/build/UserALEWebExtension/background.js index 42a35502..7628b4c3 100644 --- a/build/UserALEWebExtension/background.js +++ b/build/UserALEWebExtension/background.js @@ -1174,6 +1174,8 @@ function dispatchTabMessage(message) { }); }); } + +// Filter out logs with urls that do not match the regex defined in extension options. function filterUrl(log) { if (urlWhitelist.test(log.pageUrl)) { return log; @@ -1181,20 +1183,16 @@ function filterUrl(log) { return false; } browser.storage.local.get(defaultConfig, function (res) { - addCallbacks({ - filterUrl: filterUrl - }); + // Apply url filter to logs generated by the background page. + addCallbacks(filterUrl); updateConfig(res); }); browser.runtime.onMessage.addListener(function (message) { switch (message.type) { - // Handles logs rerouted from content and option scripts + // Handles logs rerouted from content and option scripts. case ADD_LOG: - var var log$1 = filterUrl$1 = message.payload; - if ("tab" in sender && "id" in sender.tab) { - log$1["tabId"] = sender.tab.id; - } - log(log$1); + // Apply url filter to logs generated outside the background page. + var log$1 = filterUrl(message.payload); if (log$1) { log(log$1); } diff --git a/build/UserALEWebExtension/content.js b/build/UserALEWebExtension/content.js index 2c158e64..fd9c5e1f 100644 --- a/build/UserALEWebExtension/content.js +++ b/build/UserALEWebExtension/content.js @@ -1128,9 +1128,7 @@ function rerouteLog(log) { browser.storage.local.get("useraleConfig", function (res) { options(res.useraleConfig); - addCallbacks({ - reroute: rerouteLog - }); + addCallbacks(rerouteLog); }); browser.runtime.onMessage.addListener(function (message) { if (message.type === CONFIG_CHANGE) { diff --git a/build/UserALEWebExtension/options.js b/build/UserALEWebExtension/options.js index 628b79e1..330c8313 100644 --- a/build/UserALEWebExtension/options.js +++ b/build/UserALEWebExtension/options.js @@ -1126,9 +1126,7 @@ function rerouteLog(log) { * limitations under the License. */ -addCallbacks({ - reroute: rerouteLog -}); +addCallbacks(rerouteLog); function setConfig() { var config = { url: document.getElementById("url").value, diff --git a/src/UserALEWebExtension/background.js b/src/UserALEWebExtension/background.js index 8c33b3f2..b095127e 100644 --- a/src/UserALEWebExtension/background.js +++ b/src/UserALEWebExtension/background.js @@ -43,6 +43,7 @@ var urlWhitelist; 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); } @@ -54,6 +55,7 @@ function dispatchTabMessage(message) { }); } +// Filter out logs with urls that do not match the regex defined in extension options. function filterUrl(log) { if(urlWhitelist.test(log.pageUrl)) { return log @@ -62,7 +64,8 @@ function filterUrl(log) { } browser.storage.local.get(defaultConfig, (res) => { - userale.addCallbacks({filterUrl:filterUrl}); + // Apply url filter to logs generated by the background page. + userale.addCallbacks(filterUrl); updateConfig(res); }); @@ -80,13 +83,17 @@ browser.storage.local.get(defaultConfig, (res) => { browser.runtime.onMessage.addListener(function (message, sender, sendResponse) { switch (message.type) { - // Handles logs rerouted from content and option scripts + // Handles logs rerouted from content and option scripts. case MessageTypes.ADD_LOG: +<<<<<<< HEAD let log = message.payload; if("tab" in sender && "id" in sender.tab) { log["tabId"] = sender.tab.id; } + // Apply url filter to logs generated outside the background page. log = filterUrl(log); +======= +>>>>>>> 9f626ab (Updates from feedback) if(log) { userale.log(log); } diff --git a/src/UserALEWebExtension/content.js b/src/UserALEWebExtension/content.js index 101882b4..135a97d0 100644 --- a/src/UserALEWebExtension/content.js +++ b/src/UserALEWebExtension/content.js @@ -23,7 +23,7 @@ import { rerouteLog, browser } from './globals.js'; browser.storage.local.get("useraleConfig", (res) => { userale.options(res.useraleConfig); - userale.addCallbacks({reroute: rerouteLog}); + userale.addCallbacks(rerouteLog); }); browser.runtime.onMessage.addListener(function (message) { diff --git a/src/UserALEWebExtension/options.js b/src/UserALEWebExtension/options.js index 53b57659..8dc593ef 100644 --- a/src/UserALEWebExtension/options.js +++ b/src/UserALEWebExtension/options.js @@ -20,7 +20,7 @@ import * as MessageTypes from './messageTypes.js'; import * as userale from '../main.js' import { rerouteLog, browser } from './globals.js'; -userale.addCallbacks({reroute: rerouteLog}); +userale.addCallbacks(rerouteLog); // 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?"; @@ -46,7 +46,7 @@ function setConfig() { browser.storage.local.set(payload, () => { userale.options(config); - browser.runtime.sendMessage({ type: MessageTypes.CONFIG_CHANGE, payload: payload }); + browser.runtime.sendMessage({ type: MessageTypes.CONFIG_CHANGE, payload }); }); } From a761d672a7f51e5994c8bbda4a058778dc45682b Mon Sep 17 00:00:00 2001 From: Jason Young Date: Thu, 8 Feb 2024 11:21:51 -0500 Subject: [PATCH 23/30] Fix rebase issues --- build/UserALEWebExtension/background.js | 39 ++++++++++++++----------- src/UserALEWebExtension/background.js | 15 ---------- 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/build/UserALEWebExtension/background.js b/build/UserALEWebExtension/background.js index 7628b4c3..15301ba7 100644 --- a/build/UserALEWebExtension/background.js +++ b/build/UserALEWebExtension/background.js @@ -1106,21 +1106,21 @@ function log(customLog) { } } -/* -* 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. +/* +* 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. */ @@ -1165,6 +1165,7 @@ var urlWhitelist; function updateConfig(config) { urlWhitelist = new RegExp(config.pluginConfig.urlWhitelist); options(config.useraleConfig); + // TODO: tabs need a page load to apply this config change. dispatchTabMessage(config.useraleConfig); } function dispatchTabMessage(message) { @@ -1187,12 +1188,16 @@ browser.storage.local.get(defaultConfig, function (res) { addCallbacks(filterUrl); updateConfig(res); }); -browser.runtime.onMessage.addListener(function (message) { +browser.runtime.onMessage.addListener(function (message, sender, sendResponse) { switch (message.type) { // Handles logs rerouted from content and option scripts. case ADD_LOG: + var log$1 = message.payload; + if ("tab" in sender && "id" in sender.tab) { + log$1["tabId"] = sender.tab.id; + } // Apply url filter to logs generated outside the background page. - var log$1 = filterUrl(message.payload); + log$1 = filterUrl(log$1); if (log$1) { log(log$1); } diff --git a/src/UserALEWebExtension/background.js b/src/UserALEWebExtension/background.js index b095127e..25652b53 100644 --- a/src/UserALEWebExtension/background.js +++ b/src/UserALEWebExtension/background.js @@ -69,31 +69,16 @@ browser.storage.local.get(defaultConfig, (res) => { updateConfig(res); }); -function filterUrl(log) { - if(urlWhitelist.test(log.pageUrl)) { - return log - } - return false; -} - -browser.storage.local.get(defaultConfig, (res) => { - userale.addCallbacks({filterUrl:filterUrl}); - updateConfig(res); -}); - browser.runtime.onMessage.addListener(function (message, sender, sendResponse) { switch (message.type) { // Handles logs rerouted from content and option scripts. case MessageTypes.ADD_LOG: -<<<<<<< HEAD let log = message.payload; if("tab" in sender && "id" in sender.tab) { log["tabId"] = sender.tab.id; } // Apply url filter to logs generated outside the background page. log = filterUrl(log); -======= ->>>>>>> 9f626ab (Updates from feedback) if(log) { userale.log(log); } From 08a63380b80954e5aec1bf1e47aad072da7f264f Mon Sep 17 00:00:00 2001 From: Jason Young Date: Thu, 8 Feb 2024 11:42:55 -0500 Subject: [PATCH 24/30] Fix syntax --- build/UserALEWebExtension/background.js | 6 +++++- build/UserALEWebExtension/content.js | 5 ++++- build/UserALEWebExtension/options.js | 5 ++++- src/UserALEWebExtension/background.js | 2 +- src/UserALEWebExtension/content.js | 2 +- src/UserALEWebExtension/options.js | 2 +- 6 files changed, 16 insertions(+), 6 deletions(-) diff --git a/build/UserALEWebExtension/background.js b/build/UserALEWebExtension/background.js index 15301ba7..5b7123b1 100644 --- a/build/UserALEWebExtension/background.js +++ b/build/UserALEWebExtension/background.js @@ -1163,6 +1163,7 @@ var defaultConfig = { }; var urlWhitelist; function updateConfig(config) { + console.log(config); urlWhitelist = new RegExp(config.pluginConfig.urlWhitelist); options(config.useraleConfig); // TODO: tabs need a page load to apply this config change. @@ -1185,7 +1186,9 @@ function filterUrl(log) { } browser.storage.local.get(defaultConfig, function (res) { // Apply url filter to logs generated by the background page. - addCallbacks(filterUrl); + addCallbacks({ + filterUrl: filterUrl + }); updateConfig(res); }); browser.runtime.onMessage.addListener(function (message, sender, sendResponse) { @@ -1199,6 +1202,7 @@ browser.runtime.onMessage.addListener(function (message, sender, sendResponse) { // Apply url filter to logs generated outside the background page. log$1 = filterUrl(log$1); if (log$1) { + console.log("match"); log(log$1); } break; diff --git a/build/UserALEWebExtension/content.js b/build/UserALEWebExtension/content.js index fd9c5e1f..8dcf1de1 100644 --- a/build/UserALEWebExtension/content.js +++ b/build/UserALEWebExtension/content.js @@ -1100,6 +1100,7 @@ function options(newConfig) { // browser is defined in firefox, but chrome uses the 'chrome' global. var browser = browser || chrome; function rerouteLog(log) { + console.log("reroute"); browser.runtime.sendMessage({ type: ADD_LOG, payload: log @@ -1128,7 +1129,9 @@ function rerouteLog(log) { browser.storage.local.get("useraleConfig", function (res) { options(res.useraleConfig); - addCallbacks(rerouteLog); + addCallbacks({ + rerouteLog: rerouteLog + }); }); browser.runtime.onMessage.addListener(function (message) { if (message.type === CONFIG_CHANGE) { diff --git a/build/UserALEWebExtension/options.js b/build/UserALEWebExtension/options.js index 330c8313..ba979862 100644 --- a/build/UserALEWebExtension/options.js +++ b/build/UserALEWebExtension/options.js @@ -1100,6 +1100,7 @@ function options(newConfig) { // browser is defined in firefox, but chrome uses the 'chrome' global. var browser = browser || chrome; function rerouteLog(log) { + console.log("reroute"); browser.runtime.sendMessage({ type: ADD_LOG, payload: log @@ -1126,7 +1127,9 @@ function rerouteLog(log) { * limitations under the License. */ -addCallbacks(rerouteLog); +addCallbacks({ + rerouteLog: rerouteLog +}); function setConfig() { var config = { url: document.getElementById("url").value, diff --git a/src/UserALEWebExtension/background.js b/src/UserALEWebExtension/background.js index 25652b53..90160ad7 100644 --- a/src/UserALEWebExtension/background.js +++ b/src/UserALEWebExtension/background.js @@ -65,7 +65,7 @@ function filterUrl(log) { browser.storage.local.get(defaultConfig, (res) => { // Apply url filter to logs generated by the background page. - userale.addCallbacks(filterUrl); + userale.addCallbacks({filterUrl}); updateConfig(res); }); diff --git a/src/UserALEWebExtension/content.js b/src/UserALEWebExtension/content.js index 135a97d0..2e7bbf93 100644 --- a/src/UserALEWebExtension/content.js +++ b/src/UserALEWebExtension/content.js @@ -23,7 +23,7 @@ import { rerouteLog, browser } from './globals.js'; browser.storage.local.get("useraleConfig", (res) => { userale.options(res.useraleConfig); - userale.addCallbacks(rerouteLog); + userale.addCallbacks({rerouteLog}); }); browser.runtime.onMessage.addListener(function (message) { diff --git a/src/UserALEWebExtension/options.js b/src/UserALEWebExtension/options.js index 8dc593ef..caa442a6 100644 --- a/src/UserALEWebExtension/options.js +++ b/src/UserALEWebExtension/options.js @@ -20,7 +20,7 @@ import * as MessageTypes from './messageTypes.js'; import * as userale from '../main.js' import { rerouteLog, browser } from './globals.js'; -userale.addCallbacks(rerouteLog); +userale.addCallbacks({rerouteLog}); // 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?"; From 51939dd275ab4133284dc3d5025a3e3cb62a78ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 10:57:26 -0500 Subject: [PATCH 25/30] Bump @rollup/plugin-json from 6.0.1 to 6.1.0 (#392) Bumps [@rollup/plugin-json](https://github.com/rollup/plugins/tree/HEAD/packages/json) from 6.0.1 to 6.1.0. - [Changelog](https://github.com/rollup/plugins/blob/master/packages/json/CHANGELOG.md) - [Commits](https://github.com/rollup/plugins/commits/url-v6.1.0/packages/json) --- updated-dependencies: - dependency-name: "@rollup/plugin-json" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index b8be4919..e545415c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2107,12 +2107,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" From de36c1fdab04e24a5411f8f59cf04dec4cee24bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 10:57:59 -0500 Subject: [PATCH 26/30] Bump @babel/register from 7.22.15 to 7.23.7 (#393) Bumps [@babel/register](https://github.com/babel/babel/tree/HEAD/packages/babel-register) from 7.22.15 to 7.23.7. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.7/packages/babel-register) --- updated-dependencies: - dependency-name: "@babel/register" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index e545415c..deb433f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1673,15 +1673,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": { From 081809607f406cfea692caee9636f3b22625c187 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:00:06 -0500 Subject: [PATCH 27/30] Bump @babel/plugin-transform-runtime from 7.23.4 to 7.23.9 (#405) Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.23.4 to 7.23.9. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.9/packages/babel-plugin-transform-runtime) --- updated-dependencies: - dependency-name: "@babel/plugin-transform-runtime" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 97 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 79 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index deb433f9..81424bd0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1406,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": { @@ -1425,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", @@ -2789,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", @@ -2816,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" @@ -3417,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", From a2f4071b60b7aeb41624a6296ee5086340bedb55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:00:52 -0500 Subject: [PATCH 28/30] Bump cypress from 13.6.0 to 13.6.4 (#406) Bumps [cypress](https://github.com/cypress-io/cypress) from 13.6.0 to 13.6.4. - [Release notes](https://github.com/cypress-io/cypress/releases) - [Changelog](https://github.com/cypress-io/cypress/blob/develop/CHANGELOG.md) - [Commits](https://github.com/cypress-io/cypress/compare/v13.6.0...v13.6.4) --- updated-dependencies: - dependency-name: cypress dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 81424bd0..30376476 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3523,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", From c8391321f678428c6507831f6a5400b568d22cd2 Mon Sep 17 00:00:00 2001 From: Jason Young <88682906+Jyyjy@users.noreply.github.com> Date: Fri, 16 Feb 2024 10:49:33 -0800 Subject: [PATCH 29/30] Sessions (#412) * git ignore build * Add http/browser session * Add doc strings to backgound.js * ignore nonexistent build when cleaning --- .gitignore | 3 +- build/UserALEWebExtension/background.js | 1263 ---------------- build/UserALEWebExtension/content.js | 1144 --------------- build/UserALEWebExtension/icons/border-48.png | Bin 225 -> 0 bytes build/UserALEWebExtension/manifest.json | 30 - build/UserALEWebExtension/options.js | 1176 --------------- build/UserALEWebExtension/optionsPage.html | 57 - build/userale-2.4.0.js | 1295 ----------------- build/userale-2.4.0.min.js | 18 - package.json | 2 +- src/UserALEWebExtension/background.js | 64 +- src/UserALEWebExtension/content.js | 4 + src/UserALEWebExtension/messageTypes.js | 1 + src/getInitialSettings.js | 24 +- src/packageLogs.js | 10 +- test/main_spec.js | 2 + 16 files changed, 98 insertions(+), 4995 deletions(-) delete mode 100644 build/UserALEWebExtension/background.js delete mode 100644 build/UserALEWebExtension/content.js delete mode 100644 build/UserALEWebExtension/icons/border-48.png delete mode 100644 build/UserALEWebExtension/manifest.json delete mode 100644 build/UserALEWebExtension/options.js delete mode 100644 build/UserALEWebExtension/optionsPage.html delete mode 100644 build/userale-2.4.0.js delete mode 100644 build/userale-2.4.0.min.js diff --git a/.gitignore b/.gitignore index 140e2dfa..8d50f9ab 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 5b7123b1..00000000 --- a/build/UserALEWebExtension/background.js +++ /dev/null @@ -1,1263 +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. -*/ - -var prefix = 'USERALE_'; -var CONFIG_CHANGE = prefix + 'CONFIG_CHANGE'; -var ADD_LOG = prefix + 'ADD_LOG'; - -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; - settings.headers = get('data-headers') || 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 browserInfo = 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': browserInfo ? browserInfo.name : '', - 'version': browserInfo ? browserInfo.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; -} - -function _arrayWithHoles(arr) { - if (Array.isArray(arr)) return arr; -} - -function _iterableToArrayLimit(r, l) { - var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; - if (null != t) { - var e, - n, - i, - u, - a = [], - f = !0, - o = !1; - try { - if (i = (t = t.call(r)).next, 0 === l) { - if (Object(t) !== t) return; - f = !1; - } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); - } catch (r) { - o = !0, n = r; - } finally { - try { - if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; - } finally { - if (o) throw n; - } - } - return a; - } -} - -function _arrayLikeToArray(arr, len) { - if (len == null || len > arr.length) len = arr.length; - for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; - return arr2; -} - -function _unsupportedIterableToArray(o, minLen) { - if (!o) return; - if (typeof o === "string") return _arrayLikeToArray(o, minLen); - var n = Object.prototype.toString.call(o).slice(8, -1); - if (n === "Object" && o.constructor) n = o.constructor.name; - if (n === "Map" || n === "Set") return Array.from(o); - if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); -} - -function _nonIterableRest() { - throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); -} - -function _slicedToArray(arr, i) { - return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); -} - -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) { - // 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 - } - }); -} - -/** - * 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(); - 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"); - if (config.headers) { - Object.entries(config.headers).forEach(function (_ref) { - var _ref2 = _slicedToArray(_ref, 2), - header = _ref2[0], - value = _ref2[1]; - req.setRequestHeader(header, value); - }); - } - 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$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 (!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', - details: { - pageLoadTime: endLoadTimestamp - startLoadTimestamp - } - }, function () {}, false); - } else { - setup(config); - } - }, 100); - } -} - -// Export the Userale API -var version = version$1; - -/** - * 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; - } -} - -/* -* 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 chrome uses the 'chrome' global. -var browser = browser || chrome; - -/* 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. - */ - - -// Initalize userale plugin options -var defaultConfig = { - useraleConfig: { - url: 'http://localhost:8000', - userId: 'pluginUser', - authHeader: null, - toolName: 'useralePlugin', - version: version - }, - pluginConfig: { - // Default to a regex that will match no string - urlWhitelist: '(?!x)x' - } -}; -var urlWhitelist; -function updateConfig(config) { - console.log(config); - urlWhitelist = new RegExp(config.pluginConfig.urlWhitelist); - options(config.useraleConfig); - // TODO: tabs need a page load to apply this config change. - dispatchTabMessage(config.useraleConfig); -} -function dispatchTabMessage(message) { - browser.tabs.query({}, function (tabs) { - tabs.forEach(function (tab) { - browser.tabs.sendMessage(tab.id, message); - }); - }); -} - -// Filter out logs with urls that do not match the regex defined in extension options. -function filterUrl(log) { - if (urlWhitelist.test(log.pageUrl)) { - return log; - } - return false; -} -browser.storage.local.get(defaultConfig, function (res) { - // Apply url filter to logs generated by the background page. - addCallbacks({ - filterUrl: filterUrl - }); - updateConfig(res); -}); -browser.runtime.onMessage.addListener(function (message, sender, sendResponse) { - switch (message.type) { - // Handles logs rerouted from content and option scripts. - case ADD_LOG: - var log$1 = message.payload; - if ("tab" in sender && "id" in sender.tab) { - log$1["tabId"] = sender.tab.id; - } - // Apply url filter to logs generated outside the background page. - log$1 = filterUrl(log$1); - if (log$1) { - console.log("match"); - log(log$1); - } - break; - case CONFIG_CHANGE: - updateConfig(message.payload); - break; - default: - console.log('got unknown message type ', message); - } -}); - -// Helper functions for logging tab events -function packageTabLog(tabId, data, type) { - browser.tabs.get(tabId, function (tab) { - packageDetailedTabLog(tab, data, type); - }); -} -function packageDetailedTabLog(tab, data, type) { - Object.assign(data, { - 'tabEvent': type - }); - packageCustomLog(data, function () { - 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(function (activeInfo) { - packageTabLog(activeInfo.tabId, activeInfo, "tabs.onActivated"); -}); -browser.tabs.onAttached.addListener(function (tabId, attachInfo) { - packageTabLog(tabId, attachInfo, "tabs.onAttached"); -}); -browser.tabs.onCreated.addListener(function (tab) { - packageDetailedTabLog(tab, {}, "tabs.onCreated"); -}); -browser.tabs.onDetached.addListener(function (tabId, detachInfo) { - packageTabLog(tabId, detachInfo, "tabs.onDetached"); -}); -browser.tabs.onMoved.addListener(function (tabId, moveInfo) { - packageTabLog(tabId, moveInfo, "tabs.onMoved"); -}); -browser.tabs.onRemoved.addListener(function (tabId, removeInfo) { - packageDetailedTabLog({ - id: tabId - }, removeInfo, "tabs.onRemoved"); -}); -browser.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) { - packageDetailedTabLog(tab, changeInfo, "tabs.onUpdated"); -}); -browser.tabs.onZoomChange.addListener(function (ZoomChangeInfo) { - packageTabLog(ZoomChangeInfo.tabId, ZoomChangeInfo, "tabs.onZoomChange"); -}); - -/* - eslint-enable - */ diff --git a/build/UserALEWebExtension/content.js b/build/UserALEWebExtension/content.js deleted file mode 100644 index 8dcf1de1..00000000 --- a/build/UserALEWebExtension/content.js +++ /dev/null @@ -1,1144 +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. -*/ - -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; - settings.headers = get('data-headers') || 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 browserInfo = 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': browserInfo ? browserInfo.name : '', - 'version': browserInfo ? browserInfo.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; -} - -function _arrayWithHoles(arr) { - if (Array.isArray(arr)) return arr; -} - -function _iterableToArrayLimit(r, l) { - var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; - if (null != t) { - var e, - n, - i, - u, - a = [], - f = !0, - o = !1; - try { - if (i = (t = t.call(r)).next, 0 === l) { - if (Object(t) !== t) return; - f = !1; - } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); - } catch (r) { - o = !0, n = r; - } finally { - try { - if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; - } finally { - if (o) throw n; - } - } - return a; - } -} - -function _arrayLikeToArray(arr, len) { - if (len == null || len > arr.length) len = arr.length; - for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; - return arr2; -} - -function _unsupportedIterableToArray(o, minLen) { - if (!o) return; - if (typeof o === "string") return _arrayLikeToArray(o, minLen); - var n = Object.prototype.toString.call(o).slice(8, -1); - if (n === "Object" && o.constructor) n = o.constructor.name; - if (n === "Map" || n === "Set") return Array.from(o); - if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); -} - -function _nonIterableRest() { - throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); -} - -function _slicedToArray(arr, i) { - return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); -} - -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) { - // 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 - } - }); -} - -/** - * 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(); - 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"); - if (config.headers) { - Object.entries(config.headers).forEach(function (_ref) { - var _ref2 = _slicedToArray(_ref, 2), - header = _ref2[0], - value = _ref2[1]; - req.setRequestHeader(header, value); - }); - } - 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', - 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 chrome uses the 'chrome' global. -var browser = browser || chrome; -function rerouteLog(log) { - console.log("reroute"); - browser.runtime.sendMessage({ - type: ADD_LOG, - payload: log - }); - return false; -} - -/* 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. - */ - -browser.storage.local.get("useraleConfig", function (res) { - options(res.useraleConfig); - addCallbacks({ - rerouteLog: rerouteLog - }); -}); -browser.runtime.onMessage.addListener(function (message) { - if (message.type === CONFIG_CHANGE) { - options(message.payload); - } -}); - -/* - eslint-enable - */ diff --git a/build/UserALEWebExtension/icons/border-48.png b/build/UserALEWebExtension/icons/border-48.png deleted file mode 100644 index 90687de26d71e91b7c82565772a7df470ae277a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 225 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTCmSQK*5Dp-y;YjHK@;M7UB8wRq zxP?KOkzv*x37}xJr;B4qM&sM7j(iOY0?rpNR{Ym~eNUieh4I>d+mEvHuIy!K@bZ41 zJ}N$e^&*#q7kxbW`Aeg?)>n&l0$ z8xrIlb~3+dVExT-N;ZLA=LS%o!8+lf-GRA$F@Klex9jiV-^0Mj@Zdh*s&" - ], - "background": { - "scripts": ["background.js"] - }, - "content_scripts": [ - { - "matches": [ - "" - ], - "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 ba979862..00000000 --- a/build/UserALEWebExtension/options.js +++ /dev/null @@ -1,1176 +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. -*/ - -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; - settings.headers = get('data-headers') || 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 browserInfo = 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': browserInfo ? browserInfo.name : '', - 'version': browserInfo ? browserInfo.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; -} - -function _arrayWithHoles(arr) { - if (Array.isArray(arr)) return arr; -} - -function _iterableToArrayLimit(r, l) { - var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; - if (null != t) { - var e, - n, - i, - u, - a = [], - f = !0, - o = !1; - try { - if (i = (t = t.call(r)).next, 0 === l) { - if (Object(t) !== t) return; - f = !1; - } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); - } catch (r) { - o = !0, n = r; - } finally { - try { - if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; - } finally { - if (o) throw n; - } - } - return a; - } -} - -function _arrayLikeToArray(arr, len) { - if (len == null || len > arr.length) len = arr.length; - for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; - return arr2; -} - -function _unsupportedIterableToArray(o, minLen) { - if (!o) return; - if (typeof o === "string") return _arrayLikeToArray(o, minLen); - var n = Object.prototype.toString.call(o).slice(8, -1); - if (n === "Object" && o.constructor) n = o.constructor.name; - if (n === "Map" || n === "Set") return Array.from(o); - if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); -} - -function _nonIterableRest() { - throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); -} - -function _slicedToArray(arr, i) { - return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); -} - -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) { - // 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 - } - }); -} - -/** - * 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(); - 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"); - if (config.headers) { - Object.entries(config.headers).forEach(function (_ref) { - var _ref2 = _slicedToArray(_ref, 2), - header = _ref2[0], - value = _ref2[1]; - req.setRequestHeader(header, value); - }); - } - 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', - 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 chrome uses the 'chrome' global. -var browser = browser || chrome; -function rerouteLog(log) { - console.log("reroute"); - browser.runtime.sendMessage({ - type: ADD_LOG, - payload: log - }); - return false; -} - -/* 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. -*/ - -addCallbacks({ - rerouteLog: rerouteLog -}); -function setConfig() { - var config = { - url: document.getElementById("url").value, - userId: document.getElementById("user").value, - toolName: document.getElementById("tool").value, - version: document.getElementById("version").value - }; - - // Set a basic auth header if given credentials. - var password = document.getElementById("password").value; - if (config.userId && password) { - config.authHeader = "Basic " + btoa("".concat(config.userId, ":").concat(password)); - } - var payload = { - useraleConfig: config, - pluginConfig: { - urlWhitelist: document.getElementById("filter").value - } - }; - browser.storage.local.set(payload, function () { - options(config); - browser.runtime.sendMessage({ - type: CONFIG_CHANGE, - payload: payload - }); - }); -} -function getConfig() { - browser.storage.local.get("useraleConfig", function (res) { - var config = res.useraleConfig; - 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", function (res) { - document.getElementById("filter").value = res.pluginConfig.urlWhitelist; - }); -} -document.addEventListener("DOMContentLoaded", getConfig); -document.addEventListener("submit", setConfig); - -/* eslint-enable */ diff --git a/build/UserALEWebExtension/optionsPage.html b/build/UserALEWebExtension/optionsPage.html deleted file mode 100644 index 9a3f363b..00000000 --- a/build/UserALEWebExtension/optionsPage.html +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - User ALE Web Extension - Options - - - - -

Options

- - - -
- - - -
- - - -
- - - -
- - - -
- - - -
- -
- -
- - - \ 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 5fc18af2..00000000 --- a/build/userale-2.4.0.js +++ /dev/null @@ -1,1295 +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; - settings.headers = get('data-headers') || 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 browserInfo = 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': browserInfo ? browserInfo.name : '', - 'version': browserInfo ? browserInfo.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; - } - - function _arrayWithHoles(arr) { - if (Array.isArray(arr)) return arr; - } - - function _iterableToArrayLimit(r, l) { - var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; - if (null != t) { - var e, - n, - i, - u, - a = [], - f = !0, - o = !1; - try { - if (i = (t = t.call(r)).next, 0 === l) { - if (Object(t) !== t) return; - f = !1; - } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); - } catch (r) { - o = !0, n = r; - } finally { - try { - if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; - } finally { - if (o) throw n; - } - } - return a; - } - } - - function _arrayLikeToArray(arr, len) { - if (len == null || len > arr.length) len = arr.length; - for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; - return arr2; - } - - function _unsupportedIterableToArray(o, minLen) { - if (!o) return; - if (typeof o === "string") return _arrayLikeToArray(o, minLen); - var n = Object.prototype.toString.call(o).slice(8, -1); - if (n === "Object" && o.constructor) n = o.constructor.name; - if (n === "Map" || n === "Set") return Array.from(o); - if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); - } - - function _nonIterableRest() { - throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); - } - - function _slicedToArray(arr, i) { - return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); - } - - /* - * 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 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} - */ - 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: ".concat(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. - */ - 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} - */ - function verifyCallback(callback) { - if (typeof callback !== "function") { - throw new Error("Userale auth callback must be a function"); - } - var result = callback(); - if (typeof result !== "string") { - throw new Error("Userale auth callback must return a string"); - } - } - - 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) { - // 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 - } - }); - } - - /** - * 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(); - var data = JSON.stringify(logs); - req.open("POST", config.url); - - // Update headers - updateAuthHeader(config); - if (config.authHeader) { - req.setRequestHeader("Authorization", config.authHeader); - } - req.setRequestHeader("Content-type", "application/json;charset=UTF-8"); - if (config.headers) { - Object.entries(config.headers).forEach(function (_ref) { - var _ref2 = _slicedToArray(_ref, 2), - header = _ref2[0], - value = _ref2[1]; - req.setRequestHeader(header, value); - }); - } - 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', - 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.registerAuthCallback = registerAuthCallback; - 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 e87a122b..00000000 --- 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",r=null;function o(e,t){var n=e.autostart,r=t.autostart;Object.keys(t).forEach((function(n){if("userFromParams"===n){var r=(o=t[n],i=new RegExp("[?&]"+o+"(=([^&#]*)|&|#|$)"),(a=window.location.href.match(i))&&a[2]?decodeURIComponent(a[2].replace(/\+/g," ")):null);r&&(e.userId=r)}var o,i,a;e[n]=t[n]})),!1!==n&&!1!==r||(e.autostart=!1)}var i=function(e,t,n){if(n||2===arguments.length)for(var r,o=0,i=t.length;o0?v.time(e.timeStamp):Date.now(),{milli:Math.floor(r),micro:Number((r%1).toFixed(3))}),i={target:M(e.target),path:B(e),pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:P(),clientTime:o.milli,microTime:o.micro,location:C(e),scrnRes:D(),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,l=Object.values(x);a0?v.time(e.timeStamp):Date.now());if(null==y&&(y=t,b=r,S=n,O=o,k=0),y!==t||b!==r){E={target:y,path:S,pageUrl:window.location.href,pageTitle:document.title,pageReferrer:document.referrer,browser:P(),count:k,duration:o-O,startTime:O,endTime:o,type:b,logType:"interval",targetChange:y!==t,typeChange:b!==r,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);ie.length)&&(t=e.length);for(var n=0,r=new Array(t);n=t.logCountThreshold&&(F(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 F(e,t,n){var r=new XMLHttpRequest,o=JSON.stringify(e);r.open("POST",t.url),function(e){if(H)try{e.authHeader=H()}catch(e){console.error("Error encountered while setting the auth header: ".concat(e))}}(t),t.authHeader&&r.setRequestHeader("Authorization",t.authHeader),r.setRequestHeader("Content-type","application/json;charset=UTF-8"),t.headers&&Object.entries(t.headers).forEach((function(e){var t=_(e,2),n=t[0],o=t[1];r.setRequestHeader(n,o)})),r.onreadystatechange=function(){4===r.readyState&&200!==r.status&&n>0&&F(e,t,n--)},r.send(o)}var Y,J={},q=[],G=Date.now();window.onload=function(){Y=Date.now()},e.started=!1,J.on=!1,J.useraleVersion=n,o(J,function(){var e={};null===r&&(r=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],o=n?n.getAttribute.bind(n):function(){return null};return e.autostart="false"!==o("data-autostart"),e.url=o("data-url")||"http://localhost:8000",e.transmitInterval=+o("data-interval")||5e3,e.logCountThreshold=+o("data-threshold")||5,e.userId=o("data-user")||null,e.version=o("data-version")||null,e.logDetails="true"===o("data-log-details"),e.resolution=+o("data-resolution")||500,e.toolName=o("data-tool")||null,e.userFromParams=o("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 r=performance.timing.navigationStart;t=function(e){return e+r}}else t=function(e){return e}}else t=function(){return Date.now()};return t}(document.createEvent("CustomEvent")),e.sessionID=o("data-session")||r,e.authHeader=o("data-auth")||null,e.custIndex=o("data-index")||null,e.headers=o("data-headers")||null,e}()),g=q,v=J,x=[],y=null,b=null,S=null,O=null,k=0,E=null,J.autostart&&function t(n){e.started||setTimeout((function(){var r=document.readyState;!n.autostart||"interactive"!==r&&"complete"!==r?t(n):($(n),z(q,n),e.started=n.on=!0,j({type:"load",details:{pageLoadTime:Y-G}},(function(){}),!1))}),100)}(J);var Q=n;e.addCallbacks=function(){for(var e=arguments.length,t=new Array(e),n=0;n { // Apply url filter to logs generated by the background page. - userale.addCallbacks({filterUrl}); + userale.addCallbacks({filterUrl, injectSessions}); updateConfig(res); + browserSessionId = JSON.parse(window.sessionStorage.getItem('userAleHttpSessionId')); }); browser.runtime.onMessage.addListener(function (message, sender, sendResponse) { @@ -74,9 +107,7 @@ browser.runtime.onMessage.addListener(function (message, sender, sendResponse) { // Handles logs rerouted from content and option scripts. case MessageTypes.ADD_LOG: let log = message.payload; - if("tab" in sender && "id" in sender.tab) { - log["tabId"] = sender.tab.id; - } + log.browserSessionId = browserSessionId; // Apply url filter to logs generated outside the background page. log = filterUrl(log); if(log) { @@ -84,6 +115,12 @@ browser.runtime.onMessage.addListener(function (message, sender, sendResponse) { } break; + 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; @@ -93,15 +130,28 @@ browser.runtime.onMessage.addListener(function (message, sender, sendResponse) { } }); -// Helper functions for logging tab events +/** + * 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); }); } +/** + * 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, {'tabEvent': type}); + Object.assign(data, {type}); userale.packageCustomLog(data, ()=>{return tab}, true); } diff --git a/src/UserALEWebExtension/content.js b/src/UserALEWebExtension/content.js index 2e7bbf93..b0fe3937 100644 --- a/src/UserALEWebExtension/content.js +++ b/src/UserALEWebExtension/content.js @@ -24,6 +24,10 @@ import { rerouteLog, browser } from './globals.js'; 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) { diff --git a/src/UserALEWebExtension/messageTypes.js b/src/UserALEWebExtension/messageTypes.js index e8796f13..6d553c9a 100644 --- a/src/UserALEWebExtension/messageTypes.js +++ b/src/UserALEWebExtension/messageTypes.js @@ -19,3 +19,4 @@ const prefix = 'USERALE_'; 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/getInitialSettings.js b/src/getInitialSettings.js index b9624ebe..99f35f02 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 @@ export function getInitialSettings() { 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,6 +54,8 @@ export function getInitialSettings() { 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; @@ -70,7 +77,6 @@ export function getSessionId(sessionKey, 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. @@ -108,3 +114,19 @@ export function timeStampScale(e) { 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/packageLogs.js b/src/packageLogs.js index 637a986d..0a239355 100644 --- a/src/packageLogs.js +++ b/src/packageLogs.js @@ -144,6 +144,8 @@ export function packageLog(e, detailFcn) { '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 @@ export function packageCustomLog(customLog, detailFcn, userAction) { '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 @@ export function packageIntervalLog(e) { '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)) { diff --git a/test/main_spec.js b/test/main_spec.js index 5bc045ab..084561a5 100644 --- a/test/main_spec.js +++ b/test/main_spec.js @@ -32,6 +32,8 @@ describe('Userale API', () => { 'logCountThreshold', 'userId', 'sessionID', + 'httpSessionId', + 'browserSessionId', 'version', 'logDetails', 'resolution', From 5537271ba6af6c4fc60cd95e2893979e1ca89bbe Mon Sep 17 00:00:00 2001 From: Jason Young Date: Thu, 29 Feb 2024 15:37:06 -0500 Subject: [PATCH 30/30] Fix httpSessionId name --- src/UserALEWebExtension/background.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/UserALEWebExtension/background.js b/src/UserALEWebExtension/background.js index 19fecef5..0516727d 100644 --- a/src/UserALEWebExtension/background.js +++ b/src/UserALEWebExtension/background.js @@ -87,9 +87,9 @@ function filterUrl(log) { function injectSessions(log) { let id = log.details.id; if(id in tabToHttpSession) { - log.httpSession = tabToHttpSession[id]; + log.httpSessionId = tabToHttpSession[id]; } else { - log.httpSession = null + log.httpSessionId = null } log.browserSessionId = browserSessionId; return log;