This is an automated email from the ASF dual-hosted git repository. jky pushed a commit to branch mv3-migrate in repository https://gitbox.apache.org/repos/asf/flagon.git
commit f53b0890cd13521ff8e0f76989eadc2179dd68d6 Author: Jason Young <[email protected]> AuthorDate: Fri May 23 15:48:21 2025 -0700 tab event logs and major mv3 migration changes --- .../flagon-userale-ext/src/background/index.ts | 86 +- .../src/background/messages/config_change.ts | 21 +- .../src/background/messages/http_session.ts | 3 - .../src/background/messages/issue_report.ts | 0 .../flagon-userale-ext/src/background/ports/log.ts | 31 +- .../packages/flagon-userale-ext/src/content.ts | 25 +- .../packages/flagon-userale-ext/src/popup.tsx | 25 +- .../flagon-userale-ext/src/utils/messaging.ts | 12 + .../flagon-userale-ext/src/utils/storage.ts | 8 +- .../packages/flagon-userale/build/esm/main.d.ts | 235 ----- .../packages/flagon-userale/build/esm/main.mjs | 1083 -------------------- .../packages/flagon-userale/build/esm/main.mjs.map | 1 - .../packages/flagon-userale/build/main.d.ts | 60 +- .../userale/packages/flagon-userale/build/main.mjs | 402 +++----- .../packages/flagon-userale/build/main.mjs.map | 2 +- .../packages/flagon-userale/src/attachHandlers.ts | 8 +- .../flagon-userale/src/getInitialSettings.ts | 57 +- .../userale/packages/flagon-userale/src/main.ts | 32 +- .../packages/flagon-userale/src/packageLogs.ts | 27 +- .../packages/flagon-userale/src/sendLogs.ts | 89 +- .../userale/packages/flagon-userale/tsconfig.json | 6 +- .../userale/packages/flagon-userale/tsup.config.js | 2 +- 22 files changed, 436 insertions(+), 1779 deletions(-) diff --git a/products/userale/packages/flagon-userale-ext/src/background/index.ts b/products/userale/packages/flagon-userale-ext/src/background/index.ts index ee5cda3..6e8df51 100644 --- a/products/userale/packages/flagon-userale-ext/src/background/index.ts +++ b/products/userale/packages/flagon-userale-ext/src/background/index.ts @@ -1,12 +1,84 @@ -import * as userale from "flagon-userale"; -import { getStoredOptions,} from "~/utils/storage"; +import { getStoredOptions } from "~/utils/storage"; +import { setOptions } from "./messages/config_change"; +import { sendToContent } from "~utils/messaging"; -console.log("Service worker loaded!"); +// Top level await is not supported so immediately execute this async function to set options from storage +(async () => { + const options = await getStoredOptions(); + setOptions(options); +})(); -//TODO apply logging url from getstoredoptions to userale.setup +// Takes a tabId and event data, gets the tab, and sends it +function sendTabEvent(tabId: number, data: Record<string, any>, type: string) { + chrome.tabs.get(tabId, (tab) => { + if (!tab) return + sendTabEventFromTab(tab, data, type) + }); +} -userale.setup(); +// Sends an event directly from a tab object +function sendTabEventFromTab(tab: chrome.tabs.Tab, data: Record<string, any>, type: string) { + const payload = { + type, + tab, + data + }; -//TODO Create browser session id similar to how http session id is created and export it be used in background/ports/log.ts + sendToContent(tab.id!, { type: "tab-event", payload }).catch((err) => + console.warn(`Failed to send ${type} to tab ${tab.id}:`, err.message) + ); +} -//TODO attach tab event listeners and add log them to userale. This can mostly be copied from the old code, but use .log() instead of .packagecustomlog() \ No newline at end of file +// Tab event handlers +// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs + +// TODO handle events that are sent when the tab isn't active. +// For example: +// onCreated events are sent before the content script listener is ready. +// onDeleted events are sent afeter the content script listener is shut down. + +chrome.tabs.onActivated.addListener((activeInfo) => + sendTabEvent(activeInfo.tabId, activeInfo, "tabs.onActivated") +); + +chrome.tabs.onAttached.addListener((tabId, attachInfo) => + sendTabEvent(tabId, attachInfo, "tabs.onAttached") +); + +chrome.tabs.onCreated.addListener((tab) => + sendTabEventFromTab(tab, {}, "tabs.onCreated") +); + +chrome.tabs.onDetached.addListener((tabId, detachInfo) => + sendTabEvent(tabId, detachInfo, "tabs.onDetached") +); + +chrome.tabs.onMoved.addListener((tabId, moveInfo) => + sendTabEvent(tabId, moveInfo, "tabs.onMoved") +); + +chrome.tabs.onRemoved.addListener((tabId, removeInfo) => + sendTabEvent(tabId, removeInfo, "tabs.onRemoved") +); + +chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => + sendTabEventFromTab(tab, changeInfo, "tabs.onUpdated") +); + +chrome.tabs.onZoomChange.addListener((zoomChangeInfo) => + sendTabEvent(zoomChangeInfo.tabId, zoomChangeInfo, "tabs.onZoomChange") +); + +chrome.tabs.onHighlighted.addListener((highlightInfo) => { + // Note: No tabId is available, so send with windowId and tabIds + const data = { ...highlightInfo } + // Loop over highlightInfo.tabIds and call sendTabEvent on each + for (const tabId of highlightInfo.tabIds) { + sendTabEvent(tabId, data, "tabs.onHighlighted") + } +}); + +chrome.tabs.onReplaced.addListener((addedTabId, removedTabId) => { + const data = { addedTabId, removedTabId } + sendTabEvent(addedTabId, data, "tabs.onReplaced") +}); \ No newline at end of file diff --git a/products/userale/packages/flagon-userale-ext/src/background/messages/config_change.ts b/products/userale/packages/flagon-userale-ext/src/background/messages/config_change.ts index a4e71a9..8ee0bf4 100644 --- a/products/userale/packages/flagon-userale-ext/src/background/messages/config_change.ts +++ b/products/userale/packages/flagon-userale-ext/src/background/messages/config_change.ts @@ -1,10 +1,23 @@ import type { PlasmoMessaging } from "@plasmohq/messaging"; import * as userale from "flagon-userale"; -import { getStoredOptions } from "~/utils/storage"; +import type { StoredOptions } from "~utils/storage"; +let allowListRegExp: RegExp; + const handler: PlasmoMessaging.MessageHandler = async (req, res) => { - // call - userale.options(req); + setOptions(req.body); } - + +export function setOptions(options: StoredOptions) { + console.log(options); + userale.options({url: options.loggingUrl}); + allowListRegExp = new RegExp(options.allowList); +} + +export function getAllowListRegExp() { + console.log("getAllowListRegExp"); + console.log(allowListRegExp); + return allowListRegExp; +} + export default handler \ No newline at end of file diff --git a/products/userale/packages/flagon-userale-ext/src/background/messages/http_session.ts b/products/userale/packages/flagon-userale-ext/src/background/messages/http_session.ts deleted file mode 100644 index 1bb712b..0000000 --- a/products/userale/packages/flagon-userale-ext/src/background/messages/http_session.ts +++ /dev/null @@ -1,3 +0,0 @@ -// This is a bit complicated, but once tab logs are added in main. You can create a mapping of tab ids (a browser construct) to http session ids (a userale construct). -// The content script should send a message containing its http session id, and it should be added to the mapping here, then use the mapping in background/index.ts to set the http session field of tab logs. -// This is also the least import part, so save it for last. \ No newline at end of file diff --git a/products/userale/packages/flagon-userale-ext/src/background/messages/issue_report.ts b/products/userale/packages/flagon-userale-ext/src/background/messages/issue_report.ts deleted file mode 100644 index e69de29..0000000 diff --git a/products/userale/packages/flagon-userale-ext/src/background/ports/log.ts b/products/userale/packages/flagon-userale-ext/src/background/ports/log.ts index 3a08910..6e0fe6f 100644 --- a/products/userale/packages/flagon-userale-ext/src/background/ports/log.ts +++ b/products/userale/packages/flagon-userale-ext/src/background/ports/log.ts @@ -1,17 +1,28 @@ import type { PlasmoMessaging } from "@plasmohq/messaging"; import * as userale from "flagon-userale"; - +import { getAllowListRegExp } from "~/background/messages/config_change"; + const handler: PlasmoMessaging.PortHandler = async (req, res) => { - console.log(req); - // todo apply browser session id to logs - // // log.browserSessionId = browserSessionId; - // todo filter logs based off filter url in getstorageoptions - // // req = filterUrl(req); - if (req) { - console.log(req); - userale.log(req); + let log = req.body + log.browserSessionId = browserSessionId; + let allowListRegExp = getAllowListRegExp(); + console.log(allowListRegExp); + if (allowListRegExp.test(log.pageUrl)) { + userale.log(log); } - +} + +let browserSessionId = generateSessionId(); + +// TODO move this to a shared utils workspace (this is from packages/flagon-userale/src/, but shouldn't be publicly exported) +function generateSessionId(): string { + // 32 digit hex -> 128 bits of info -> 2^64 ~= 10^19 sessions needed for 50% chance of collison + const len = 32; + const arr = new Uint8Array(len / 2); + self.crypto.getRandomValues(arr); + return Array.from(arr, (dec) => { + return dec.toString(16).padStart(2, "0"); + }).join(""); } export default handler; \ No newline at end of file diff --git a/products/userale/packages/flagon-userale-ext/src/content.ts b/products/userale/packages/flagon-userale-ext/src/content.ts index 967507d..e2e5551 100644 --- a/products/userale/packages/flagon-userale-ext/src/content.ts +++ b/products/userale/packages/flagon-userale-ext/src/content.ts @@ -11,10 +11,29 @@ const logPort = getPort("log"); userale.addCallbacks({ rerouteLog(log) { - console.log(log) - logPort.postMessage(log); + console.log(log); + logPort.postMessage({body: log}); return false; } }); -userale.start(); \ No newline at end of file +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if( message.type == "tab-event") { + const { type, tab, data } = message.payload; + userale.packageCustomLog( + {type}, + () => { return data; }, + true, + ); + sendResponse({ status: "received" }); + } else if (message.type === "issue-report") { + userale.packageCustomLog( + {type: message.type}, + () => { return message.payload; }, + true, + ); + sendResponse({ status: "received" }); + } + + return true +}) \ No newline at end of file diff --git a/products/userale/packages/flagon-userale-ext/src/popup.tsx b/products/userale/packages/flagon-userale-ext/src/popup.tsx index 2c88894..43a8ee4 100644 --- a/products/userale/packages/flagon-userale-ext/src/popup.tsx +++ b/products/userale/packages/flagon-userale-ext/src/popup.tsx @@ -1,15 +1,32 @@ import { useState } from "react"; import Options from "~/options"; +import { sendToContent } from "~utils/messaging" function IndexPopup() { const [issueType, setIssueType] = useState("Bug"); const [issueDescription, setIssueDescription] = useState(""); - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - // TODO add messaging - }; + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + try { + const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); + if (!tab?.id) throw new Error("No active tab found"); + const response = await sendToContent(tab.id, { + type: "issue-report", + payload: { + issueType, + issueDescription + } + }); + console.log("Content script response:", response) + alert("Issue report sent!") + setIssueDescription("") // clear after send + } catch (error) { + console.error("Failed to send message", error) + alert("Failed to send issue report.") + } + } return ( <div> <Options /> diff --git a/products/userale/packages/flagon-userale-ext/src/utils/messaging.ts b/products/userale/packages/flagon-userale-ext/src/utils/messaging.ts new file mode 100644 index 0000000..b53f411 --- /dev/null +++ b/products/userale/packages/flagon-userale-ext/src/utils/messaging.ts @@ -0,0 +1,12 @@ +/** + * Sends a message to the content script from the popup, options, or background page + */ +export async function sendToContent (tabId: number, message: any): Promise<any> { + return new Promise((resolve, reject) => { + chrome.tabs.sendMessage(tabId, message, (response) => { + const err = chrome.runtime.lastError + if (err) reject(err) + else resolve(response) + }) + }) +} \ No newline at end of file diff --git a/products/userale/packages/flagon-userale-ext/src/utils/storage.ts b/products/userale/packages/flagon-userale-ext/src/utils/storage.ts index f95a274..4e1e261 100644 --- a/products/userale/packages/flagon-userale-ext/src/utils/storage.ts +++ b/products/userale/packages/flagon-userale-ext/src/utils/storage.ts @@ -35,6 +35,7 @@ export async function getStoredOptions(): Promise<StoredOptions> { // Only to be used in ~/options export async function setStoredOptions(values: Partial<StoredOptions>) { + // Validate the new options try { new RegExp(values.allowList); new URL(values.loggingUrl); @@ -42,9 +43,12 @@ export async function setStoredOptions(values: Partial<StoredOptions>) { return error; } + // Store the options for after the browser is closed await browser.storage.local.set(values); + + // Notify the background script of the change return await sendToBackground({ name: "config_change", - body: { values } + body: values }) -} +} \ No newline at end of file diff --git a/products/userale/packages/flagon-userale/build/esm/main.d.ts b/products/userale/packages/flagon-userale/build/esm/main.d.ts deleted file mode 100644 index 5e56576..0000000 --- a/products/userale/packages/flagon-userale/build/esm/main.d.ts +++ /dev/null @@ -1,235 +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. - */ -declare namespace Settings { - type Version = string | null; - type UserId = string | null; - type SessionId = string | null; - type UserFromParams = string | null; - type ToolName = string | null; - type AuthHeader = CallableFunction | string | null; - type CustomIndex = string | null; - type HeaderObject = { [key: string]: string }; - type Headers = HeaderObject | null; - type ConfigValueTypes = - | string - | number - | boolean - | null - | Version - | UserId - | SessionId - | UserFromParams - | ToolName - | AuthHeader - | CustomIndex - | Headers; - - type TimeFunction = (() => number) | ((ts: number) => number); - - export interface DefaultConfig { - [key: string]: ConfigValueTypes; - } - - export interface Config extends DefaultConfig { - autostart: boolean; - authHeader: AuthHeader; - browserSessionId: SessionId; - custIndex: CustomIndex; - headers: Headers; - httpSessionId: SessionId; - logCountThreshold: number; - logDetails: boolean; - on?: boolean; - resolution: number; - sessionId: SessionId; - time: TimeFunction; - toolName: ToolName; - toolVersion?: Version; - transmitInterval: number; - url: string; - userFromParams: UserFromParams; - useraleVersion: Version; - userId: UserId; - version?: Version; - websocketsEnabled?: boolean; - } - - export interface IConfiguration extends Config { - getInstance(): Configuration; - configure(newConfig: Config): void; - } -} - -// TODO: Switch to protobuf for managing log types -declare namespace Logging { - type JSONObject = { - [key: string]: - | string - | number - | boolean - | null - | undefined - | JSONObject - | Array<string | number | boolean | null | JSONObject>; - }; - export type Log = JSONObject; // TODO: Intersect this with the default log objects (raw & interval) - export type CustomLog = JSONObject; - - export type DynamicDetailFunction<E extends Event = Event> = ( - e: E, - ) => JSONObject; - export type StaticDetailFunction = () => JSONObject; -} - -declare namespace Events { - type FormElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement; - type ChangeEvent = Event<FormElement>; - export type RawEvents = - | "dblclick" - | "mouseup" - | "mousedown" - | "dragstart" - | "dragend" - | "drag" - | "drop" - | "keydown"; - export type IntervalEvents = - | "click" - | "focus" - | "blur" - | "input" - | "change" - | "mouseover" - | "submit"; - export type WindowEvents = "load" | "blur" | "focus"; - export type BufferedEvents = "wheel" | "scroll" | "resize"; - export type RefreshEvents = "submit"; - export type AllowedEvents = - | RawEvents - | IntervalEvents - | WindowEvents - | BufferedEvents - | RefreshEvents; - - export type EventDetailsMap<T extends string> = Partial<{ - [key in T]: - | Logging.DynamicDetailFunction< - | MouseEvent - | KeyboardEvent - | InputEvent - | Events.ChangeEvent - | WheelEvent - > - | Logging.StaticDetailFunction - | null; - }>; - - export type EventBoolMap<T extends string> = Partial<{ - [key in T]: boolean; - }>; -} - -declare namespace Callbacks { - export type AuthCallback = () => string; - export type HeadersCallback = () => Settings.HeaderObject; - - export type CallbackMap = { - [key in string]: CallableFunction; - }; -} - -/** - * Defines the way information is extracted from various events. - * Also defines which events we will listen to. - * @param {Settings.Config} options UserALE Configuration object to read from. - * @param {Events.AllowedEvents} type of html event (e.g., 'click', 'mouseover', etc.), such as passed to addEventListener methods. - */ -declare function defineCustomDetails(options: Settings.DefaultConfig, type: Events.AllowedEvents): Logging.DynamicDetailFunction | null | undefined; - -/** - * Registers the provided callback to be used when updating the auth header. - * @param {Callbacks.AuthCallback} callback Callback used to fetch the newest header. Should return a string. - * @returns {boolean} Whether the operation succeeded. - */ -declare function registerAuthCallback(callback: Callbacks.AuthCallback): boolean; - -/** - * Adds named callbacks to be executed when logging. - * @param {Object } newCallbacks An object containing named callback functions. - */ -declare function addCallbacks(...newCallbacks: Record<symbol | string, CallableFunction>[]): Callbacks.CallbackMap; -/** - * Removes callbacks by name. - * @param {String[]} targetKeys A list of names of functions to remove. - */ -declare function removeCallbacks(targetKeys: string[]): void; -/** - * Transforms the provided HTML event into a log and appends it to the log queue. - * @param {Event} 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. - */ -declare function packageLog(e: Event, detailFcn?: Logging.DynamicDetailFunction | null): boolean; -/** - * Packages the provided customLog to include standard meta data and appends it to the log queue. - * @param {Logging.CustomLog} customLog The behavior to be logged. - * @param {Logging.DynamicDetailFunction} 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. - */ -declare function packageCustomLog(customLog: Logging.CustomLog, detailFcn: Logging.DynamicDetailFunction | Logging.StaticDetailFunction, userAction: boolean): boolean; -/** - * Builds a string CSS selector from the provided element - * @param {EventTarget} 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. - */ -declare function getSelector(ele: EventTarget): string; -/** - * Builds an array of elements from the provided event target, to the root element. - * @param {Event} e Event from which the path should be built. - * @return {HTMLElement[]} Array of elements, starting at the event target, ending at the root element. - */ -declare function buildPath(e: Event): string[]; - -declare let started: boolean; -declare let wsock: WebSocket; - -declare const version: string; -/** - * Used to start the logging process if the - * autostart configuration option is set to false. - */ -declare function start(): void; -/** - * Halts the logging process. Logs will no longer be sent. - */ -declare function stop(): void; -/** - * Updates the current configuration - * object with the provided values. - * @param {Partial<Settings.Config>} newConfig The configuration options to use. - * @return {Settings.Config} Returns the updated configuration. - */ -declare function options(newConfig: Partial<Settings.Config> | undefined): Settings.Config; -/** - * Appends a log to the log queue. - * @param {Logging.CustomLog} customLog The log to append. - * @return {boolean} Whether the operation succeeded. - */ -declare function log(customLog: Logging.CustomLog | undefined): boolean; - -export { addCallbacks, buildPath, defineCustomDetails as details, getSelector, log, options, packageCustomLog, packageLog, registerAuthCallback, removeCallbacks, start, started, stop, version, wsock }; diff --git a/products/userale/packages/flagon-userale/build/esm/main.mjs b/products/userale/packages/flagon-userale/build/esm/main.mjs deleted file mode 100644 index 40bdfcc..0000000 --- a/products/userale/packages/flagon-userale/build/esm/main.mjs +++ /dev/null @@ -1,1083 +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.*/ -// package.json -var version = "2.4.0"; - -// src/getInitialSettings.ts -var sessionId = null; -var httpSessionId = null; -function getInitialSettings() { - if (sessionId === null) { - 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]; - }(); - const get = script ? script.getAttribute.bind(script) : function() { - return null; - }; - const headers = get("data-headers"); - const settings = { - authHeader: get("data-auth") || null, - autostart: get("data-autostart") === "false" ? false : true, - browserSessionId: null, - custIndex: get("data-index") || null, - headers: headers ? JSON.parse(headers) : null, - httpSessionId, - logCountThreshold: +(get("data-threshold") || 5), - logDetails: get("data-log-details") === "true" ? true : false, - resolution: +(get("data-resolution") || 500), - sessionId: get("data-session") || sessionId, - time: timeStampScale(document.createEvent("CustomEvent")), - toolName: get("data-tool") || null, - toolVersion: get("data-version") || null, - transmitInterval: +(get("data-interval") || 5e3), - url: get("data-url") || "http://localhost:8000", - useraleVersion: get("data-userale-version") || null, - userFromParams: get("data-user-from-params") || null, - userId: get("data-user") || null - }; - return settings; -} -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) || ""); -} -function timeStampScale(e) { - let tsScaler; - if (e.timeStamp && e.timeStamp > 0) { - const delta = Date.now() - e.timeStamp; - if (delta < 0) { - tsScaler = function() { - return e.timeStamp / 1e3; - }; - } else if (delta > e.timeStamp) { - const navStart = performance.timeOrigin; - tsScaler = function(ts) { - return ts + navStart; - }; - } else { - tsScaler = function(ts) { - return ts; - }; - } - } else { - tsScaler = function() { - return Date.now(); - }; - } - return tsScaler; -} -function generatehttpSessionId() { - const len = 32; - const arr = new Uint8Array(len / 2); - window.crypto.getRandomValues(arr); - return Array.from(arr, (dec) => { - return dec.toString(16).padStart(2, "0"); - }).join(""); -} - -// src/configure.ts -var _Configuration = class { - constructor() { - this.autostart = false; - this.authHeader = null; - this.browserSessionId = null; - this.custIndex = null; - this.headers = null; - this.httpSessionId = null; - this.logCountThreshold = 0; - this.logDetails = false; - this.on = false; - this.resolution = 0; - this.sessionId = null; - this.time = () => Date.now(); - this.toolName = null; - this.toolVersion = null; - this.transmitInterval = 0; - this.url = ""; - this.userFromParams = null; - this.useraleVersion = null; - this.userId = null; - this.version = null; - this.websocketsEnabled = false; - if (_Configuration.instance === null) { - this.initialize(); - } - } - static getInstance() { - if (_Configuration.instance === null) { - _Configuration.instance = new _Configuration(); - } - return _Configuration.instance; - } - initialize() { - const settings = getInitialSettings(); - this.update(settings); - } - reset() { - this.initialize(); - } - update(newConfig) { - Object.keys(newConfig).forEach((option) => { - if (option === "userFromParams") { - const userParamString = newConfig[option]; - const userId = userParamString ? _Configuration.getUserIdFromParams(userParamString) : null; - if (userId) { - this["userId"] = userId; - } - } - const hasNewUserFromParams = newConfig["userFromParams"]; - const willNullifyUserId = option === "userId" && newConfig[option] === null; - if (willNullifyUserId && hasNewUserFromParams) { - return; - } - const newOption = newConfig[option]; - if (newOption !== void 0) { - this[option] = newOption; - } - }); - } - static getUserIdFromParams(param) { - const userField = param; - const regex = new RegExp("[?&]" + userField + "(=([^&#]*)|&|#|$)"); - const results = window.location.href.match(regex); - if (results && results[2]) { - return decodeURIComponent(results[2].replace(/\+/g, " ")); - } - return null; - } -}; -var Configuration = _Configuration; -Configuration.instance = null; - -// ../../node_modules/.pnpm/[email protected]/node_modules/detect-browser/es/index.js -var __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 = function() { - function BrowserInfo2(name, version3, os) { - this.name = name; - this.version = version3; - this.os = os; - this.type = "browser"; - } - return BrowserInfo2; -}(); -var NodeInfo = function() { - function NodeInfo2(version3) { - this.version = version3; - this.type = "node"; - this.name = "node"; - this.os = process.platform; - } - return NodeInfo2; -}(); -var SearchBotDeviceInfo = function() { - function SearchBotDeviceInfo2(name, version3, os, bot) { - this.name = name; - this.version = version3; - this.os = os; - this.bot = bot; - this.type = "bot-device"; - } - return SearchBotDeviceInfo2; -}(); -var BotInfo = function() { - function BotInfo2() { - this.type = "bot"; - this.bot = true; - this.name = "bot"; - this.version = null; - this.os = null; - } - return BotInfo2; -}(); -var ReactNativeInfo = function() { - function ReactNativeInfo2() { - this.type = "react-native"; - this.name = "react-native"; - this.version = null; - this.os = null; - } - return ReactNativeInfo2; -}(); -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) { - 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(); - } - 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 version3 = versionParts.join("."); - var os = detectOS(ua); - var searchBotMatch = SEARCHBOT_OS_REGEX.exec(ua); - if (searchBotMatch && searchBotMatch[1]) { - return new SearchBotDeviceInfo(name, version3, os, searchBotMatch[1]); - } - return new BrowserInfo(name, version3, 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; -} - -// src/packageLogs.ts -var browserInfo = detect(); -var logs; -var config; -var intervalId; -var intervalType; -var intervalPath; -var intervalTimer; -var intervalCounter; -var intervalLog; -var filterHandler = null; -var mapHandler = null; -var cbHandlers = {}; -function addCallbacks(...newCallbacks) { - newCallbacks.forEach((source) => { - let descriptors = {}; - descriptors = Object.keys(source).reduce((descriptors2, key) => { - descriptors2[key] = Object.getOwnPropertyDescriptor(source, key); - return descriptors2; - }, descriptors); - Object.getOwnPropertySymbols(source).forEach((sym) => { - const descriptor = Object.getOwnPropertyDescriptor(source, sym); - if (descriptor?.enumerable) { - descriptors[sym] = descriptor; - } - }); - Object.defineProperties(cbHandlers, descriptors); - }); - return cbHandlers; -} -function removeCallbacks(targetKeys) { - targetKeys.forEach((key) => { - if (Object.prototype.hasOwnProperty.call(cbHandlers, key)) { - delete cbHandlers[key]; - } - }); -} -function initPackager(newLogs, newConfig) { - logs = newLogs; - config = newConfig; - cbHandlers = {}; - intervalId = null; - intervalType = null; - intervalPath = null; - intervalTimer = null; - intervalCounter = 0; - intervalLog = null; -} -function packageLog(e, detailFcn) { - if (!config.on) { - return false; - } - let details = null; - if (detailFcn) { - details = detailFcn(e); - } - const timeFields = extractTimeFields( - e.timeStamp && e.timeStamp > 0 ? config.time(e.timeStamp) : Date.now() - ); - let log2 = { - target: e.target ? getSelector(e.target) : null, - 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: getScreenRes(), - type: e.type, - logType: "raw", - userAction: true, - details, - userId: config.userId, - toolVersion: config.toolVersion, - toolName: config.toolName, - useraleVersion: config.useraleVersion, - sessionId: config.sessionId, - httpSessionId: config.httpSessionId, - browserSessionId: config.browserSessionId, - attributes: buildAttrs(e), - style: buildCSS(e) - }; - if (typeof filterHandler === "function" && !filterHandler(log2)) { - return false; - } - if (typeof mapHandler === "function") { - log2 = mapHandler(log2, e); - } - for (const func of Object.values(cbHandlers)) { - if (typeof func === "function") { - log2 = func(log2, e); - if (!log2) { - return false; - } - } - } - logs.push(log2); - return true; -} -function packageCustomLog(customLog, detailFcn, userAction) { - if (!config.on) { - return false; - } - let details = null; - if (detailFcn.length === 0) { - const staticDetailFcn = detailFcn; - details = staticDetailFcn(); - } - const metaData = { - pageUrl: window.location.href, - pageTitle: document.title, - pageReferrer: document.referrer, - browser: detectBrowser(), - clientTime: Date.now(), - scrnRes: getScreenRes(), - logType: "custom", - userAction, - details, - userId: config.userId, - toolVersion: config.toolVersion, - toolName: config.toolName, - useraleVersion: config.useraleVersion, - sessionId: config.sessionId, - httpSessionId: config.httpSessionId, - browserSessionId: config.browserSessionId - }; - let log2 = Object.assign(metaData, customLog); - if (typeof filterHandler === "function" && !filterHandler(log2)) { - return false; - } - if (typeof mapHandler === "function") { - log2 = mapHandler(log2); - } - for (const func of Object.values(cbHandlers)) { - if (typeof func === "function") { - log2 = func(log2, null); - if (!log2) { - return false; - } - } - } - logs.push(log2); - return true; -} -function extractTimeFields(timeStamp) { - return { - milli: Math.floor(timeStamp), - micro: Number((timeStamp % 1).toFixed(3)) - }; -} -function packageIntervalLog(e) { - try { - const target = e.target ? getSelector(e.target) : null; - const path = buildPath(e); - const type = e.type; - const timestamp = Math.floor( - e.timeStamp && e.timeStamp > 0 ? config.time(e.timeStamp) : Date.now() - ); - if (intervalId == null) { - intervalId = target; - intervalType = type; - intervalPath = path; - intervalTimer = timestamp; - intervalCounter = 0; - } - if ((intervalId !== target || intervalType !== type) && intervalTimer) { - intervalLog = { - target: intervalId, - path: intervalPath, - pageUrl: window.location.href, - pageTitle: document.title, - pageReferrer: document.referrer, - browser: detectBrowser(), - count: intervalCounter, - duration: timestamp - intervalTimer, - startTime: intervalTimer, - endTime: timestamp, - type: intervalType, - logType: "interval", - targetChange: intervalId !== target, - typeChange: intervalType !== type, - userAction: false, - userId: config.userId, - toolVersion: config.toolVersion, - toolName: config.toolName, - useraleVersion: config.useraleVersion, - sessionId: config.sessionId, - httpSessionId: config.httpSessionId, - browserSessionId: config.browserSessionId - }; - if (typeof filterHandler === "function" && !filterHandler(intervalLog)) { - return false; - } - if (typeof mapHandler === "function") { - intervalLog = mapHandler(intervalLog, e); - } - for (const func of Object.values(cbHandlers)) { - if (typeof func === "function") { - intervalLog = func(intervalLog, null); - if (!intervalLog) { - return false; - } - } - } - if (intervalLog) - logs.push(intervalLog); - intervalId = target; - intervalType = type; - intervalPath = path; - intervalTimer = timestamp; - intervalCounter = 0; - } - if (intervalId == target && intervalType == type && intervalCounter) { - intervalCounter = intervalCounter + 1; - } - return true; - } catch { - return false; - } -} -function getLocation(e) { - if (e instanceof MouseEvent) { - 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 }; - } -} -function getScreenRes() { - return { width: window.innerWidth, height: window.innerHeight }; -} -function getSelector(ele) { - if (ele instanceof HTMLElement || ele instanceof Element) { - 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 instanceof Document) { - return "#document"; - } else if (ele === globalThis) { - return "Window"; - } - return "Unknown"; -} -function buildPath(e) { - const path = e.composedPath(); - return selectorizePath(path); -} -function selectorizePath(path) { - let i = 0; - let pathEle; - const pathSelectors = []; - while (pathEle = path[i]) { - pathSelectors.push(getSelector(pathEle)); - ++i; - pathEle = path[i]; - } - return pathSelectors; -} -function detectBrowser() { - return { - browser: browserInfo ? browserInfo.name : "", - version: browserInfo ? browserInfo.version : "" - }; -} -function buildAttrs(e) { - const attributes = {}; - const attributeBlackList = ["style"]; - if (e.target && e.target instanceof Element) { - for (const attr of e.target.attributes) { - if (attributeBlackList.includes(attr.name)) - continue; - let val = attr.value; - try { - val = JSON.parse(val); - } catch (error) { - } - attributes[attr.name] = val; - } - } - return attributes; -} -function buildCSS(e) { - const properties = {}; - if (e.target && e.target instanceof HTMLElement) { - const styleObj = e.target.style; - for (let i = 0; i < styleObj.length; i++) { - const prop = styleObj[i]; - properties[prop] = styleObj.getPropertyValue(prop); - } - } - return properties; -} - -// src/attachHandlers.ts -var events; -var bufferBools; -var bufferedEvents; -var refreshEvents; -var intervalEvents = [ - "click", - "focus", - "blur", - "input", - "change", - "mouseover", - "submit" -]; -var windowEvents = ["load", "blur", "focus"]; -function extractMouseDetails(e) { - return { - clicks: e.detail, - ctrl: e.ctrlKey, - alt: e.altKey, - shift: e.shiftKey, - meta: e.metaKey - }; -} -function extractKeyboardDetails(e) { - return { - key: e.key, - code: e.code, - ctrl: e.ctrlKey, - alt: e.altKey, - shift: e.shiftKey, - meta: e.metaKey - }; -} -function extractChangeDetails(e) { - return { - value: e.target.value - }; -} -function extractWheelDetails(e) { - return { - x: e.deltaX, - y: e.deltaY, - z: e.deltaZ - }; -} -function extractScrollDetails() { - return { - x: window.scrollX, - y: window.scrollY - }; -} -function extractResizeDetails() { - return { - width: window.outerWidth, - height: window.outerHeight - }; -} -function defineDetails(config3) { - events = { - click: extractMouseDetails, - dblclick: extractMouseDetails, - mousedown: extractMouseDetails, - mouseup: extractMouseDetails, - focus: null, - blur: null, - input: config3.logDetails ? extractKeyboardDetails : null, - change: config3.logDetails ? extractChangeDetails : null, - dragstart: null, - dragend: null, - drag: null, - drop: null, - keydown: config3.logDetails ? extractKeyboardDetails : null, - mouseover: null - }; - bufferBools = {}; - bufferedEvents = { - wheel: extractWheelDetails, - scroll: extractScrollDetails, - resize: extractResizeDetails - }; - refreshEvents = { - submit: null - }; -} -function defineCustomDetails(options2, type) { - const eventType = { - click: extractMouseDetails, - dblclick: extractMouseDetails, - mousedown: extractMouseDetails, - mouseup: extractMouseDetails, - focus: null, - blur: null, - load: null, - input: options2.logDetails ? extractKeyboardDetails : null, - change: options2.logDetails ? extractChangeDetails : null, - dragstart: null, - dragend: null, - drag: null, - drop: null, - keydown: options2.logDetails ? extractKeyboardDetails : null, - mouseover: null, - wheel: extractWheelDetails, - scroll: extractScrollDetails, - resize: extractResizeDetails, - submit: null - }; - return eventType[type]; -} -function attachHandlers(config3) { - try { - defineDetails(config3); - 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; - }, config3.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; - } catch { - return false; - } -} - -// src/utils/auth/index.ts -var authCallback = null; -function updateAuthHeader(config3) { - if (authCallback) { - try { - config3.authHeader = authCallback(); - } catch (e) { - console.error(`Error encountered while setting the auth header: ${e}`); - } - } -} -function registerAuthCallback(callback) { - try { - verifyCallback(callback); - authCallback = callback; - return true; - } catch (e) { - return false; - } -} -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"); - } -} - -// src/utils/headers/index.ts -var headersCallback = null; -function updateCustomHeaders(config3) { - if (headersCallback) { - try { - config3.headers = headersCallback(); - } catch (e) { - console.error(`Error encountered while setting the headers: ${e}`); - } - } -} - -// src/sendLogs.ts -var sendIntervalId; -function initSender(logs3, config3) { - if (sendIntervalId) { - clearInterval(sendIntervalId); - } - sendIntervalId = sendOnInterval(logs3, config3); - sendOnClose(logs3, config3); -} -function sendOnInterval(logs3, config3) { - return setInterval(function() { - if (!config3.on) { - return; - } - if (logs3.length >= config3.logCountThreshold) { - sendLogs(logs3.slice(0), config3, 0); - logs3.splice(0); - } - }, config3.transmitInterval); -} -function sendOnClose(logs3, config3) { - window.addEventListener("pagehide", function() { - if (!config3.on) { - return; - } - if (logs3.length > 0) { - if (config3.websocketsEnabled) { - const data = JSON.stringify(logs3); - wsock.send(data); - } else { - const headers = new Headers(); - headers.set("Content-Type", "applicaiton/json;charset=UTF-8"); - if (config3.authHeader) { - headers.set("Authorization", config3.authHeader.toString()); - } - fetch(config3.url, { - keepalive: true, - method: "POST", - headers, - body: JSON.stringify(logs3) - }).catch((error) => { - console.error(error); - }); - } - logs3.splice(0); - } - }); -} -function sendLogs(logs3, config3, retries) { - const data = JSON.stringify(logs3); - if (config3.websocketsEnabled) { - wsock.send(data); - } else { - const req = new XMLHttpRequest(); - req.open("POST", config3.url); - updateAuthHeader(config3); - if (config3.authHeader) { - req.setRequestHeader( - "Authorization", - typeof config3.authHeader === "function" ? config3.authHeader() : config3.authHeader - ); - } - req.setRequestHeader("Content-type", "application/json;charset=UTF-8"); - updateCustomHeaders(config3); - if (config3.headers) { - Object.entries(config3.headers).forEach(([header, value]) => { - req.setRequestHeader(header, value); - }); - } - req.onreadystatechange = function() { - if (req.readyState === 4 && req.status !== 200) { - if (retries > 0) { - sendLogs(logs3, config3, retries--); - } - } - }; - req.send(data); - } -} - -// src/main.ts -var config2 = Configuration.getInstance(); -var logs2 = []; -var startLoadTimestamp = Date.now(); -var endLoadTimestamp; -try { - window.onload = function() { - endLoadTimestamp = Date.now(); - }; -} catch (error) { - endLoadTimestamp = Date.now(); -} -var started = false; -var wsock; -config2.update({ - useraleVersion: version -}); -initPackager(logs2, config2); -getWebsocketsEnabled(config2); -if (config2.autostart) { - setup(config2); -} -function setup(config3) { - if (!started) { - setTimeout(function() { - let state; - try { - state = document.readyState; - } catch (error) { - initSender(logs2, config3); - } - if (config3.autostart && (state === "interactive" || state === "complete")) { - attachHandlers(config3); - initSender(logs2, config3); - started = config3.on = true; - packageCustomLog( - { - type: "load", - details: { pageLoadTime: endLoadTimestamp - startLoadTimestamp } - }, - () => ({}), - false - ); - } else { - setup(config3); - } - }, 100); - } -} -function getWebsocketsEnabled(config3) { - wsock = new WebSocket(config3.url.replace("http://", "ws://")); - wsock.onerror = () => { - console.log("no websockets detected"); - }; - wsock.onopen = () => { - console.log("connection established with websockets"); - config3.websocketsEnabled = true; - }; - wsock.onclose = () => { - sendOnClose(logs2, config3); - }; -} -var version2 = version; -function start() { - if (!started || config2.autostart === false) { - started = config2.on = true; - config2.update({ autostart: true }); - } -} -function stop() { - started = config2.on = false; - config2.update({ autostart: false }); -} -function options(newConfig) { - if (newConfig) { - config2.update(newConfig); - } - return config2; -} -function log(customLog) { - if (customLog) { - logs2.push(customLog); - return true; - } else { - return false; - } -} -export { - addCallbacks, - buildPath, - defineCustomDetails as details, - getSelector, - log, - options, - packageCustomLog, - packageLog, - registerAuthCallback, - removeCallbacks, - start, - started, - stop, - version2 as version, - wsock -}; -//# sourceMappingURL=main.mjs.map \ No newline at end of file diff --git a/products/userale/packages/flagon-userale/build/esm/main.mjs.map b/products/userale/packages/flagon-userale/build/esm/main.mjs.map deleted file mode 100644 index 66a020b..0000000 --- a/products/userale/packages/flagon-userale/build/esm/main.mjs.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../src/getInitialSettings.ts","../../src/configure.ts","../../../../node_modules/.pnpm/[email protected]/node_modules/detect-browser/es/index.js","../../src/packageLogs.ts","../../src/attachHandlers.ts","../../src/utils/auth/index.ts","../../src/utils/headers/index.ts","../../src/sendLogs.ts","../../src/main.ts"],"sourcesContent":["/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements. See the NOTICE f [...] \ No newline at end of file diff --git a/products/userale/packages/flagon-userale/build/main.d.ts b/products/userale/packages/flagon-userale/build/main.d.ts index e08e30d..5e56576 100644 --- a/products/userale/packages/flagon-userale/build/main.d.ts +++ b/products/userale/packages/flagon-userale/build/main.d.ts @@ -65,6 +65,7 @@ declare namespace Settings { useraleVersion: Version; userId: UserId; version?: Version; + websocketsEnabled?: boolean; } export interface IConfiguration extends Config { @@ -151,56 +152,6 @@ declare namespace Callbacks { }; } -declare class Configuration$1 { - [key: string]: Settings.ConfigValueTypes; - private static instance; - autostart: boolean; - authHeader: Settings.AuthHeader; - browserSessionId: Settings.SessionId; - custIndex: Settings.CustomIndex; - headers: Settings.Headers; - httpSessionId: Settings.SessionId; - logCountThreshold: number; - logDetails: boolean; - on: boolean; - resolution: number; - sessionId: Settings.SessionId; - time: Settings.TimeFunction; - toolName: Settings.ToolName; - toolVersion: Settings.Version; - transmitInterval: number; - url: string; - userFromParams: Settings.UserFromParams; - useraleVersion: Settings.Version; - userId: Settings.UserId; - version: Settings.Version; - websocketsEnabled: boolean; - private constructor(); - static getInstance(): Configuration$1; - private initialize; - /** - * Resets the configuration to its initial state. - */ - reset(): void; - /** - * Shallow merges a newConfig with the configuration class, updating it. - * Retrieves/updates the userid if userFromParams is provided. - * @param {Partial<Settings.Config>} newConfig Configuration object to merge into the current config. - */ - update(newConfig: Partial<Settings.Config>): void; - /** - * 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. - */ - static getUserIdFromParams(param: string): string | null; - /** - * - * @return {bool} - */ - isWebSocket(): boolean; -} - /** * Defines the way information is extracted from various events. * Also defines which events we will listen to. @@ -255,13 +206,8 @@ declare function getSelector(ele: EventTarget): string; declare function buildPath(e: Event): string[]; declare let started: boolean; +declare let wsock: WebSocket; -/** - * Hooks the global event listener, and starts up the - * logging interval. - * @param {Configuration} config Configuration settings for the logger - */ -declare function setup(config: Configuration$1): void; declare const version: string; /** * Used to start the logging process if the @@ -286,4 +232,4 @@ declare function options(newConfig: Partial<Settings.Config> | undefined): Setti */ declare function log(customLog: Logging.CustomLog | undefined): boolean; -export { addCallbacks, buildPath, defineCustomDetails as details, getSelector, log, options, packageCustomLog, packageLog, registerAuthCallback, removeCallbacks, setup, start, started, stop, version }; +export { addCallbacks, buildPath, defineCustomDetails as details, getSelector, log, options, packageCustomLog, packageLog, registerAuthCallback, removeCallbacks, start, started, stop, version, wsock }; diff --git a/products/userale/packages/flagon-userale/build/main.mjs b/products/userale/packages/flagon-userale/build/main.mjs index f4cc151..8e511bc 100644 --- a/products/userale/packages/flagon-userale/build/main.mjs +++ b/products/userale/packages/flagon-userale/build/main.mjs @@ -1,17 +1,17 @@ /* 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 + 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 + 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.*/ + 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.*/ // package.json var version = "2.4.0"; @@ -19,6 +19,29 @@ var version = "2.4.0"; var sessionId = null; var httpSessionId = null; function getInitialSettings() { + if (typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope) { + const settings2 = { + authHeader: null, + autostart: true, + browserSessionId: null, + custIndex: null, + headers: null, + httpSessionId: null, + logCountThreshold: 5, + logDetails: false, + resolution: 500, + sessionId, + time: (ts) => ts !== void 0 ? ts : Date.now(), + toolName: null, + toolVersion: null, + transmitInterval: 5e3, + url: "http://localhost:8000", + useraleVersion: null, + userFromParams: null, + userId: null + }; + return settings2; + } if (sessionId === null) { sessionId = getsessionId( "userAlesessionId", @@ -62,11 +85,11 @@ function getInitialSettings() { return settings; } function getsessionId(sessionKey, value) { - if (window.sessionStorage.getItem(sessionKey) === null) { - window.sessionStorage.setItem(sessionKey, JSON.stringify(value)); + if (self.sessionStorage.getItem(sessionKey) === null) { + self.sessionStorage.setItem(sessionKey, JSON.stringify(value)); return value; } - return JSON.parse(window.sessionStorage.getItem(sessionKey) || ""); + return JSON.parse(self.sessionStorage.getItem(sessionKey) || ""); } function timeStampScale(e) { let tsScaler; @@ -137,12 +160,8 @@ var _Configuration = class { return _Configuration.instance; } initialize() { - try { - const settings = getInitialSettings(); - this.update(settings); - } catch (error) { - console.log(error); - } + const settings = getInitialSettings(); + this.update(settings); } reset() { this.initialize(); @@ -176,214 +195,11 @@ var _Configuration = class { } return null; } - isWebSocket() { - return this.url.startsWith("ws://") || this.url.startsWith("wss://"); - } }; var Configuration = _Configuration; Configuration.instance = null; -// ../../node_modules/.pnpm/[email protected]/node_modules/detect-browser/es/index.js -var __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 = function() { - function BrowserInfo2(name, version3, os) { - this.name = name; - this.version = version3; - this.os = os; - this.type = "browser"; - } - return BrowserInfo2; -}(); -var NodeInfo = function() { - function NodeInfo2(version3) { - this.version = version3; - this.type = "node"; - this.name = "node"; - this.os = process.platform; - } - return NodeInfo2; -}(); -var SearchBotDeviceInfo = function() { - function SearchBotDeviceInfo2(name, version3, os, bot) { - this.name = name; - this.version = version3; - this.os = os; - this.bot = bot; - this.type = "bot-device"; - } - return SearchBotDeviceInfo2; -}(); -var BotInfo = function() { - function BotInfo2() { - this.type = "bot"; - this.bot = true; - this.name = "bot"; - this.version = null; - this.os = null; - } - return BotInfo2; -}(); -var ReactNativeInfo = function() { - function ReactNativeInfo2() { - this.type = "react-native"; - this.name = "react-native"; - this.version = null; - this.os = null; - } - return ReactNativeInfo2; -}(); -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) { - 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(); - } - 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 version3 = versionParts.join("."); - var os = detectOS(ua); - var searchBotMatch = SEARCHBOT_OS_REGEX.exec(ua); - if (searchBotMatch && searchBotMatch[1]) { - return new SearchBotDeviceInfo(name, version3, os, searchBotMatch[1]); - } - return new BrowserInfo(name, version3, 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; -} - // src/packageLogs.ts -var browserInfo = detect(); var logs; var config; var intervalId; @@ -444,10 +260,10 @@ function packageLog(e, detailFcn) { let log2 = { target: e.target ? getSelector(e.target) : null, path: buildPath(e), - pageUrl: window.location.href, + pageUrl: self.location.href, pageTitle: document.title, pageReferrer: document.referrer, - browser: detectBrowser(), + browser: self.navigator.userAgent, clientTime: timeFields.milli, microTime: timeFields.micro, location: getLocation(e), @@ -493,10 +309,8 @@ function packageCustomLog(customLog, detailFcn, userAction) { details = staticDetailFcn(); } const metaData = { - pageUrl: window.location.href, - pageTitle: document.title, - pageReferrer: document.referrer, - browser: detectBrowser(), + pageUrl: self.location.href, + browser: self.navigator.userAgent, clientTime: Date.now(), scrnRes: getScreenRes(), logType: "custom", @@ -553,10 +367,10 @@ function packageIntervalLog(e) { intervalLog = { target: intervalId, path: intervalPath, - pageUrl: window.location.href, + pageUrl: self.location.href, pageTitle: document.title, pageReferrer: document.referrer, - browser: detectBrowser(), + browser: self.navigator.userAgent, count: intervalCounter, duration: timestamp - intervalTimer, startTime: intervalTimer, @@ -619,7 +433,7 @@ function getLocation(e) { } } function getScreenRes() { - return { width: window.innerWidth, height: window.innerHeight }; + return { width: self.innerWidth, height: self.innerHeight }; } function getSelector(ele) { if (ele instanceof HTMLElement || ele instanceof Element) { @@ -650,12 +464,6 @@ function selectorizePath(path) { } return pathSelectors; } -function detectBrowser() { - return { - browser: browserInfo ? browserInfo.name : "", - version: browserInfo ? browserInfo.version : "" - }; -} function buildAttrs(e) { const attributes = {}; const attributeBlackList = ["style"]; @@ -798,7 +606,7 @@ function attachHandlers(config3) { try { defineDetails(config3); Object.keys(events).forEach(function(ev) { - document.addEventListener( + self.addEventListener( ev, function(e) { packageLog(e, events[ev]); @@ -807,7 +615,7 @@ function attachHandlers(config3) { ); }); intervalEvents.forEach(function(ev) { - document.addEventListener( + self.addEventListener( ev, function(e) { packageIntervalLog(e); @@ -818,7 +626,7 @@ function attachHandlers(config3) { Object.keys(bufferedEvents).forEach( function(ev) { bufferBools[ev] = true; - window.addEventListener( + self.addEventListener( ev, function(e) { if (bufferBools[ev]) { @@ -845,7 +653,7 @@ function attachHandlers(config3) { } ); windowEvents.forEach(function(ev) { - window.addEventListener( + self.addEventListener( ev, function(e) { packageLog(e, function() { @@ -863,6 +671,15 @@ function attachHandlers(config3) { // src/utils/auth/index.ts var authCallback = null; +function updateAuthHeader(config3) { + if (authCallback) { + try { + config3.authHeader = authCallback(); + } catch (e) { + console.error(`Error encountered while setting the auth header: ${e}`); + } + } +} function registerAuthCallback(callback) { try { verifyCallback(callback); @@ -882,37 +699,51 @@ function verifyCallback(callback) { } } +// src/utils/headers/index.ts +var headersCallback = null; +function updateCustomHeaders(config3) { + if (headersCallback) { + try { + config3.headers = headersCallback(); + } catch (e) { + console.error(`Error encountered while setting the headers: ${e}`); + } + } +} + // src/sendLogs.ts var sendIntervalId; -var wsock; function initSender(logs3, config3) { if (sendIntervalId) { clearInterval(sendIntervalId); } - wsock = new WebSocket(config3.url); - wsock.onerror = () => { - console.log("no websockets detected"); - }; - wsock.onopen = () => { - console.log("connection established with websockets"); - }; - wsock.onclose = () => { - sendOnClose(logs3, config3); - }; + sendIntervalId = sendOnInterval(logs3, config3); sendOnClose(logs3, config3); } +function sendOnInterval(logs3, config3) { + return setInterval(function() { + if (!config3.on) { + return; + } + if (logs3.length >= config3.logCountThreshold) { + sendLogs(logs3.slice(0), config3, 0); + logs3.splice(0); + } + }, config3.transmitInterval); +} function sendOnClose(logs3, config3) { - window.addEventListener("pagehide", function() { + self.addEventListener("pagehide", function() { if (!config3.on) { return; } if (logs3.length > 0) { - if (config3.isWebSocket()) { + const url = new URL(config3.url); + if (url.protocol === "ws:" || url.protocol === "wss:") { const data = JSON.stringify(logs3); wsock.send(data); } else { const headers = new Headers(); - headers.set("Content-Type", "application/json;charset=UTF-8"); + headers.set("Content-Type", "applicaiton/json;charset=UTF-8"); if (config3.authHeader) { headers.set("Authorization", config3.authHeader.toString()); } @@ -929,20 +760,61 @@ function sendOnClose(logs3, config3) { } }); } +async function sendLogs(logs3, config3, retries) { + const data = JSON.stringify(logs3); + const url = new URL(config3.url); + if (url.protocol === "ws:" || url.protocol === "wss:") { + wsock.send(data); + return; + } + const headers = new Headers({ + "Content-Type": "application/json;charset=UTF-8" + }); + updateAuthHeader(config3); + if (config3.authHeader) { + const authHeaderValue = typeof config3.authHeader === "function" ? config3.authHeader() : config3.authHeader; + headers.set("Authorization", authHeaderValue); + } + updateCustomHeaders(config3); + if (config3.headers) { + for (const [header, value] of Object.entries(config3.headers)) { + headers.set(header, value); + } + } + async function attemptSend(remainingRetries) { + try { + const response = await fetch(config3.url, { + method: "POST", + headers, + body: data + }); + if (!response.ok) { + if (remainingRetries > 0) { + return attemptSend(remainingRetries - 1); + } else { + throw new Error(`Failed to send logs: ${response.statusText}`); + } + } + } catch (error) { + if (remainingRetries > 0) { + return attemptSend(remainingRetries - 1); + } + throw error; + } + } + return attemptSend(retries); +} // src/main.ts var config2 = Configuration.getInstance(); var logs2 = []; var startLoadTimestamp = Date.now(); var endLoadTimestamp; -try { - window.onload = function() { - endLoadTimestamp = Date.now(); - }; -} catch (error) { +self.onload = function() { endLoadTimestamp = Date.now(); -} +}; var started = false; +var wsock; config2.update({ useraleVersion: version }); @@ -957,7 +829,7 @@ function setup(config3) { try { state = document.readyState; } catch (error) { - return; + state = "complete"; } if (config3.autostart && (state === "interactive" || state === "complete")) { attachHandlers(config3); @@ -1013,10 +885,10 @@ export { packageLog, registerAuthCallback, removeCallbacks, - setup, start, started, stop, - version2 as version + version2 as version, + wsock }; //# sourceMappingURL=main.mjs.map \ No newline at end of file diff --git a/products/userale/packages/flagon-userale/build/main.mjs.map b/products/userale/packages/flagon-userale/build/main.mjs.map index fcf8d8b..3c5430b 100644 --- a/products/userale/packages/flagon-userale/build/main.mjs.map +++ b/products/userale/packages/flagon-userale/build/main.mjs.map @@ -1 +1 @@ -{"version":3,"sources":["../src/getInitialSettings.ts","../src/configure.ts","../../../node_modules/.pnpm/[email protected]/node_modules/detect-browser/es/index.js","../src/packageLogs.ts","../src/attachHandlers.ts","../src/utils/auth/index.ts","../src/sendLogs.ts","../src/main.ts"],"sourcesContent":["/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements. See the NOTICE file distributed with\n * this work for additional informati [...] \ No newline at end of file +{"version":3,"sources":["../src/getInitialSettings.ts","../src/configure.ts","../src/packageLogs.ts","../src/attachHandlers.ts","../src/utils/auth/index.ts","../src/utils/headers/index.ts","../src/sendLogs.ts","../src/main.ts"],"sourcesContent":["/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements. See the NOTICE file distributed with\n * this work for additional information regarding copyright ownership.\n * The ASF licenses this [...] \ No newline at end of file diff --git a/products/userale/packages/flagon-userale/src/attachHandlers.ts b/products/userale/packages/flagon-userale/src/attachHandlers.ts index 36975fb..18cc2cf 100644 --- a/products/userale/packages/flagon-userale/src/attachHandlers.ts +++ b/products/userale/packages/flagon-userale/src/attachHandlers.ts @@ -201,7 +201,7 @@ export function attachHandlers(config: Configuration): boolean { defineDetails(config); (Object.keys(events) as Events.AllowedEvents[]).forEach(function (ev) { - document.addEventListener( + self.addEventListener( ev, function (e) { packageLog(e, events[ev]); @@ -211,7 +211,7 @@ export function attachHandlers(config: Configuration): boolean { }); intervalEvents.forEach(function (ev) { - document.addEventListener( + self.addEventListener( ev, function (e) { packageIntervalLog(e); @@ -224,7 +224,7 @@ export function attachHandlers(config: Configuration): boolean { function (ev) { bufferBools[ev] = true; - window.addEventListener( + self.addEventListener( ev, function (e) { if (bufferBools[ev]) { @@ -253,7 +253,7 @@ export function attachHandlers(config: Configuration): boolean { ); windowEvents.forEach(function (ev) { - window.addEventListener( + self.addEventListener( ev, function (e) { packageLog(e, function () { diff --git a/products/userale/packages/flagon-userale/src/getInitialSettings.ts b/products/userale/packages/flagon-userale/src/getInitialSettings.ts index 46f7844..ed8b758 100644 --- a/products/userale/packages/flagon-userale/src/getInitialSettings.ts +++ b/products/userale/packages/flagon-userale/src/getInitialSettings.ts @@ -26,19 +26,46 @@ let httpSessionId: string | null = null; * @return {Object} The extracted configuration object */ export function getInitialSettings(): Settings.Config { - if (sessionId === null) { - sessionId = getsessionId( - "userAlesessionId", - "session_" + String(Date.now()), - ); - } - if (httpSessionId === null) { - httpSessionId = getsessionId( - "userAleHttpSessionId", - generatehttpSessionId(), - ); - } + if (typeof WorkerGlobalScope !== "undefined" && + self instanceof WorkerGlobalScope) { + const settings: Settings.Config = { + authHeader: null, + autostart: true, + browserSessionId: null, + custIndex: null, + headers: null, + httpSessionId: null, + logCountThreshold: +(5), + logDetails: false, + resolution: +(500), + sessionId: sessionId, + time: (ts?: number) => (ts !== undefined ? ts : Date.now()), + toolName: null, + toolVersion: null, + transmitInterval: +(5000), + url: "http://localhost:8000", + useraleVersion: null, + userFromParams: null, + userId: null, + }; + return settings; + } + + if (sessionId === null) { + sessionId = getsessionId( + "userAlesessionId", + "session_" + String(Date.now()), + ); + } + + if (httpSessionId === null) { + httpSessionId = getsessionId( + "userAleHttpSessionId", + generatehttpSessionId(), + ); + } + const script = document.currentScript || @@ -83,12 +110,12 @@ export function getInitialSettings(): Settings.Config { * */ export function getsessionId(sessionKey: string, value: any) { - if (window.sessionStorage.getItem(sessionKey) === null) { - window.sessionStorage.setItem(sessionKey, JSON.stringify(value)); + if (self.sessionStorage.getItem(sessionKey) === null) { + self.sessionStorage.setItem(sessionKey, JSON.stringify(value)); return value; } - return JSON.parse(window.sessionStorage.getItem(sessionKey) || ""); + return JSON.parse(self.sessionStorage.getItem(sessionKey) || ""); } /** diff --git a/products/userale/packages/flagon-userale/src/main.ts b/products/userale/packages/flagon-userale/src/main.ts index 9f75c21..45336f4 100644 --- a/products/userale/packages/flagon-userale/src/main.ts +++ b/products/userale/packages/flagon-userale/src/main.ts @@ -19,7 +19,7 @@ import { version as userAleVersion } from "../package.json"; import { Configuration } from "@/configure"; import { attachHandlers } from "@/attachHandlers"; import { initPackager, packageCustomLog } from "@/packageLogs"; -import { initSender, sendOnClose } from "@/sendLogs"; +import { initSender } from "@/sendLogs"; import type { Settings, Logging } from "@/types"; @@ -28,13 +28,9 @@ const logs: Array<Logging.Log> = []; const startLoadTimestamp = Date.now(); let endLoadTimestamp: number; -try { - window.onload = function() { - endLoadTimestamp = Date.now(); - }; -} catch (error) { +self.onload = function() { endLoadTimestamp = Date.now(); -} +}; export let started = false; export let wsock: WebSocket; @@ -53,7 +49,6 @@ config.update({ useraleVersion: userAleVersion, }); initPackager(logs, config); -// getWebsocketsEnabled(config); if (config.autostart) { setup(config); } @@ -70,7 +65,8 @@ function setup(config: Configuration) { try { state = document.readyState; } catch (error) { - initSender(logs, config); + // Assume there is no DOM and this is a web worker context + state = "complete"; } if ( @@ -95,24 +91,6 @@ function setup(config: Configuration) { } } -/** - * Checks to see if the specified backend URL supporsts Websockets - * and updates the config accordingly - */ -function getWebsocketsEnabled(config: Configuration) { - wsock = new WebSocket(config.url.replace("http://", "ws://")); - wsock.onerror = () => { - console.log("no websockets detected"); - }; - wsock.onopen = () => { - console.log("connection established with websockets"); - config.websocketsEnabled = true; - }; - wsock.onclose = () => { - sendOnClose(logs, config); - }; -} - // Export the Userale API export const version = userAleVersion; diff --git a/products/userale/packages/flagon-userale/src/packageLogs.ts b/products/userale/packages/flagon-userale/src/packageLogs.ts index 2cb0cbe..10da80f 100644 --- a/products/userale/packages/flagon-userale/src/packageLogs.ts +++ b/products/userale/packages/flagon-userale/src/packageLogs.ts @@ -15,10 +15,8 @@ * limitations under the License. */ -import { detect } from "detect-browser"; import { Callbacks, Logging } from "@/types"; import { Configuration } from "@/configure"; -const browserInfo = detect(); export let logs: Array<Logging.Log>; let config: Configuration; @@ -119,10 +117,10 @@ export function packageLog( let log: Logging.Log = { target: e.target ? getSelector(e.target) : null, path: buildPath(e), - pageUrl: window.location.href, + pageUrl: self.location.href, pageTitle: document.title, pageReferrer: document.referrer, - browser: detectBrowser(), + browser: self.navigator.userAgent, clientTime: timeFields.milli, microTime: timeFields.micro, location: getLocation(e), @@ -191,10 +189,10 @@ export function packageCustomLog( } const metaData = { - pageUrl: window.location.href, - pageTitle: document.title, - pageReferrer: document.referrer, - browser: detectBrowser(), + pageUrl: self.location.href, + // pageTitle: document.title, + // pageReferrer: document.referrer, + browser: self.navigator.userAgent, clientTime: Date.now(), scrnRes: getScreenRes(), logType: "custom", @@ -276,10 +274,10 @@ export function packageIntervalLog(e: Event) { intervalLog = { target: intervalId, path: intervalPath, - pageUrl: window.location.href, + pageUrl: self.location.href, pageTitle: document.title, pageReferrer: document.referrer, - browser: detectBrowser(), + browser: self.navigator.userAgent, count: intervalCounter, duration: timestamp - intervalTimer, // microseconds startTime: intervalTimer, @@ -362,7 +360,7 @@ export function getLocation(e: Event) { * @return {Object} An object containing the innerWidth and InnerHeight */ export function getScreenRes() { - return { width: window.innerWidth, height: window.innerHeight }; + return { width: self.innerWidth, height: self.innerHeight }; } /** @@ -420,13 +418,6 @@ export function selectorizePath(path: EventTarget[]) { return pathSelectors; } -export function detectBrowser() { - return { - browser: browserInfo ? browserInfo.name : "", - version: browserInfo ? browserInfo.version : "", - }; -} - /** * Builds an object containing attributes of an element. * Attempts to parse all attribute values as JSON text. diff --git a/products/userale/packages/flagon-userale/src/sendLogs.ts b/products/userale/packages/flagon-userale/src/sendLogs.ts index 2c9e6fc..0456e0e 100644 --- a/products/userale/packages/flagon-userale/src/sendLogs.ts +++ b/products/userale/packages/flagon-userale/src/sendLogs.ts @@ -68,13 +68,15 @@ export function sendOnClose( logs: Array<Logging.Log>, config: Configuration, ): void { - window.addEventListener("pagehide", function() { + self.addEventListener("pagehide", function() { if (!config.on) { return; } if (logs.length > 0) { - if (config.websocketsEnabled) { + const url = new URL(config.url); + + if (url.protocol === "ws:" || url.protocol === "wss:") { const data = JSON.stringify(logs); wsock.send(data); } else { @@ -106,51 +108,64 @@ export function sendOnClose( * @param {Configuration} config configuration singleton. * @param {Number} retries Maximum number of attempts to send the logs. */ - -// @todo expose config object to sendLogs replate url with config.url -export function sendLogs( +export async function sendLogs( logs: Array<Logging.Log>, config: Configuration, retries: number, -) { +): Promise<void> { const data = JSON.stringify(logs); + const url = new URL(config.url); - if (config.websocketsEnabled) { + if (url.protocol === "ws:" || url.protocol === "wss:") { wsock.send(data); - } else { - const req = new XMLHttpRequest(); - - req.open("POST", config.url); - - // Update headers - updateAuthHeader(config); - if (config.authHeader) { - req.setRequestHeader( - "Authorization", - typeof config.authHeader === "function" - ? config.authHeader() - : config.authHeader, - ); + return; + } + + // Build headers + const headers = new Headers({ + "Content-Type": "application/json;charset=UTF-8", + }); + + updateAuthHeader(config); + if (config.authHeader) { + const authHeaderValue = + typeof config.authHeader === "function" + ? config.authHeader() + : config.authHeader; + headers.set("Authorization", authHeaderValue); + } + + // 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) { + for (const [header, value] of Object.entries(config.headers)) { + headers.set(header, value); } - 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); + } + + async function attemptSend(remainingRetries: number): Promise<void> { + try { + const response = await fetch(config.url, { + method: "POST", + headers, + body: data, }); - } - req.onreadystatechange = function() { - if (req.readyState === 4 && req.status !== 200) { - if (retries > 0) { - sendLogs(logs, config, retries--); + if (!response.ok) { + if (remainingRetries > 0) { + return attemptSend(remainingRetries - 1); + } else { + throw new Error(`Failed to send logs: ${response.statusText}`); } } - }; - - req.send(data); + } catch (error) { + if (remainingRetries > 0) { + return attemptSend(remainingRetries - 1); + } + throw error; + } } + + return attemptSend(retries); } diff --git a/products/userale/packages/flagon-userale/tsconfig.json b/products/userale/packages/flagon-userale/tsconfig.json index 129b7b2..12d6a32 100644 --- a/products/userale/packages/flagon-userale/tsconfig.json +++ b/products/userale/packages/flagon-userale/tsconfig.json @@ -13,12 +13,14 @@ "isolatedModules": true, "jsx": "preserve", "lib": [ + "ES2021", + "WebWorker", "dom", "dom.iterable", "esnext" ], "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "bundler", "noEmit": true, "noFallthroughCasesInSwitch": true, "noImplicitReturns": false, @@ -34,7 +36,7 @@ "skipLibCheck": true, "sourceMap": true, "strict": true, - "target": "es2015", + "target": "ES2021", "typeRoots": [ "node_modules/@types/" ], diff --git a/products/userale/packages/flagon-userale/tsup.config.js b/products/userale/packages/flagon-userale/tsup.config.js index 7e734de..5bed426 100644 --- a/products/userale/packages/flagon-userale/tsup.config.js +++ b/products/userale/packages/flagon-userale/tsup.config.js @@ -4,7 +4,7 @@ export default defineConfig([ { tsconfig: './tsconfig.json', entry: ['src/main.ts'], - outDir: 'build/esm', + outDir: 'build', format: ['esm'], name: 'userale', target: 'es2021',
