From a65e285551c275164a7c21b7f8a0cd155f3fc5e5 Mon Sep 17 00:00:00 2001 From: mohiit1502 Date: Wed, 12 Mar 2025 23:06:39 +0530 Subject: [PATCH] Linters, dependencies clean up, jquery out --- analytics.ts | 1010 +++++++++++++++++++++++-------------------- constants.ts | 4 +- flush.ts | 59 +-- global-modules.d.ts | 8 +- helper.ts | 4 +- index.interface.ts | 38 +- index.ts | 18 +- location.ts | 62 +-- package-lock.json | 309 ------------- package.json | 6 - session.ts | 91 ++-- 11 files changed, 700 insertions(+), 909 deletions(-) delete mode 100644 package-lock.json diff --git a/analytics.ts b/analytics.ts index 5609229..7c3c90e 100644 --- a/analytics.ts +++ b/analytics.ts @@ -1,333 +1,400 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import $ from "jquery"; -import { v4 as uuidv4 } from "uuid"; -import { ADD, ARMCO_SERVER } from "./constants"; -import { startSession, getSessionId, terminateSession } from "./session"; -import { ipLookup, success, error, localTimeRegion } from "./location"; -import { isArClient } from './helper'; -import { hookFlushHandlers, queueEvent } from './flush'; -import { User, Event, ConfigType, objType } from "./index.interface"; +import * as fs from "fs" +import * as path from "path" +import { v4 as uuidv4 } from "uuid" +import { ADD, ARMCO_SERVER } from "./constants" +import { startSession, getSessionId, terminateSession } from "./session" +import { ipLookup, success, error, localTimeRegion } from "./location" +import { isArClient } from "./helper" +import { hookFlushHandlers, queueEvent } from "./flush" +import { User, Event, ConfigType, objType } from "./index.interface" -const tsConfigPath = getEnvironmentType() === "node" ? path.resolve(process.cwd(), 'tsconfig.json') : "../../../../tsConfig.json"; -const packageJsonPath = getEnvironmentType() === "node" ? path.resolve(process.cwd(), 'package.json') : "../../../../package.json"; +const tsConfigPath = + getEnvironmentType() === "node" + ? path.resolve(process.cwd(), "tsconfig.json") + : "../../../../tsConfig.json" +const packageJsonPath = + getEnvironmentType() === "node" + ? path.resolve(process.cwd(), "package.json") + : "../../../../package.json" -let ar_anonymous_id: string | null; -let user: User | null = null; -let apiKey: string | null = null; -let analyticsLogEndpoint: string | null; -let analyticsTagEndpoint: string | null; -let hostProjectName: string | null = null; // Will be set during initialization -let enabled: boolean | null = null; -const CONFIG_FILE_NAME = "analyticsrc"; -let region: string | null; -let address: string | null; +let ar_anonymous_id: string | null +let user: User | null = null +let apiKey: string | null = null +let analyticsLogEndpoint: string | null +let analyticsTagEndpoint: string | null +let hostProjectName: string | null = null // Will be set during initialization +let enabled: boolean | null = null +const CONFIG_FILE_NAME = "analyticsrc" +let region: string | null +let address: string | null let coordinates: { - latitude: number - longitude: number + latitude: number + longitude: number } let runtime: string | null = null -let CONFIG: ConfigType | null | undefined; -let environment: "node" | "browser" | "unknown"; +let CONFIG: ConfigType | null | undefined +let environment: "node" | "browser" | "unknown" async function isTypeScriptProject(): Promise { + // Check if tsconfig.json file exists + if ( + fs && + "existsSync" in fs && + !(fs.existsSync(tsConfigPath) && fs.existsSync(packageJsonPath)) + ) { + return false + } + let tsConfig, packageJson + try { + tsConfig = await import(tsConfigPath) + // Check if "compilerOptions" property exists in tsconfig.json + if (tsConfig && tsConfig.compilerOptions) { + return true + } + } catch (error) { + console.error("Error loading tsconfig.json:", error) + } - // Check if tsconfig.json file exists - if (fs && "existsSync" in fs && !(fs.existsSync(tsConfigPath) && fs.existsSync(packageJsonPath))) { - return false; - } - let tsConfig, packageJson; - try { - tsConfig = await import(tsConfigPath); - // Check if "compilerOptions" property exists in tsconfig.json - if (tsConfig && tsConfig.compilerOptions) { - return true; - } - } catch (error) { - console.error('Error loading tsconfig.json:', error); - } + // Check if package.json file has TypeScript as a devDependency + try { + packageJson = await import(tsConfigPath) + if ( + packageJson && + packageJson.devDependencies && + packageJson.devDependencies.typescript + ) { + return true + } + } catch (error) { + console.error("Error loading tsconfig.json:", error) + } + if ( + packageJson && + packageJson.devDependencies && + packageJson.devDependencies.typescript + ) { + return true + } - // Check if package.json file has TypeScript as a devDependency - try { - packageJson = await import(tsConfigPath); - if ( - packageJson && - packageJson.devDependencies && - packageJson.devDependencies.typescript - ) { - return true; - } - } catch (error) { - console.error('Error loading tsconfig.json:', error); - } - if ( - packageJson && - packageJson.devDependencies && - packageJson.devDependencies.typescript - ) { - return true; - } - - return false; + return false } async function loadConfiguration() { - // Path of this module when deployed as NPM lib in development will be /node_modules/@armco/analytics/dist/analytics.js, - // hence need to move 3 steps up to reach project root, but lib name is @armco/analytics, this is considered as - // additional path, hence we move 4 steps up, for production we search two steps up, this function should only be called when - // the user fails to provide a configuration manually. - const ROOT = (runtime || getEnvironment()) === "production" ? "../../" : "../../../../" - let configFilePath = `${ROOT}${CONFIG_FILE_NAME}.json`; - try { - const config = await import(configFilePath); - return config; // Return the configuration if successful - } catch (error) { - console.error(`[ANALYTICS] Failed to load configuration from ${configFilePath}.`); - } - const isTS = await isTypeScriptProject(); - configFilePath = `${ROOT}${CONFIG_FILE_NAME}.${isTS ? "ts" : "js"}`; - try { - const config = await import(configFilePath); - console.log(`[ANALYTICS] Configuration loaded from ${configFilePath} successfully.`); - return config.default; // Return the configuration if successful - } catch (error) { - console.error(`[ANALYTICS] Failed to load configuration from ${configFilePath}.`); - } + // Path of this module when deployed as NPM lib in development will be /node_modules/@armco/analytics/dist/analytics.js, + // hence need to move 3 steps up to reach project root, but lib name is @armco/analytics, this is considered as + // additional path, hence we move 4 steps up, for production we search two steps up, this function should only be called when + // the user fails to provide a configuration manually. + const ROOT = + (runtime || getEnvironment()) === "production" ? "../../" : "../../../../" + let configFilePath = `${ROOT}${CONFIG_FILE_NAME}.json` + try { + const config = await import(configFilePath) + return config // Return the configuration if successful + } catch (error) { + console.error( + `[ANALYTICS] Failed to load configuration from ${configFilePath}.`, + ) + } + const isTS = await isTypeScriptProject() + configFilePath = `${ROOT}${CONFIG_FILE_NAME}.${isTS ? "ts" : "js"}` + try { + const config = await import(configFilePath) + console.log( + `[ANALYTICS] Configuration loaded from ${configFilePath} successfully.`, + ) + return config.default // Return the configuration if successful + } catch (error) { + console.error( + `[ANALYTICS] Failed to load configuration from ${configFilePath}.`, + ) + } - console.error(`[ANALYTICS] No valid configuration file found. Expected one of ${CONFIG_FILE_NAME}.js, ${CONFIG_FILE_NAME}.json or ${CONFIG_FILE_NAME}.ts`); - return null; + console.error( + `[ANALYTICS] No valid configuration file found. Expected one of ${CONFIG_FILE_NAME}.js, ${CONFIG_FILE_NAME}.json or ${CONFIG_FILE_NAME}.ts`, + ) + return null } function getEnvironment(): string { - // Check if 'process' object is available (Webpack) - if (typeof process !== 'undefined' && process.env && process.env.NODE_ENV) { - return process.env.NODE_ENV; - } + // Check if 'process' object is available (Webpack) + if (typeof process !== "undefined" && process.env && process.env.NODE_ENV) { + return process.env.NODE_ENV + } - // Check if 'import.meta' object is available (Vite) - if ((import.meta as any).env && (import.meta as any).env.MODE) { - return (import.meta as any).env.MODE; - } + // Check if 'import.meta' object is available (Vite) + if ((import.meta as any).env && (import.meta as any).env.MODE) { + return (import.meta as any).env.MODE + } - // If no environment is found, return a default value - return 'development'; + // If no environment is found, return a default value + return "development" } -function getEnvironmentType(): 'browser' | 'node' | 'unknown' { - if (typeof window !== 'undefined' && typeof window.document !== 'undefined') { - return 'browser'; - } else if ( - typeof process !== 'undefined' && - process.versions != null && - process.versions.node != null - ) { - return 'node'; - } else { - return 'unknown'; - } +function getEnvironmentType(): "browser" | "node" | "unknown" { + if (typeof window !== "undefined" && typeof window.document !== "undefined") { + return "browser" + } else if ( + typeof process !== "undefined" && + process.versions != null && + process.versions.node != null + ) { + return "node" + } else { + return "unknown" + } } // Function to get the name of the host project async function getHostProjectName(): Promise { - if (!hostProjectName) { - try { - const packageJson = await import(packageJsonPath); // Adjust the path if necessary - hostProjectName = packageJson.name || null; - console.log("[ANALYTICS] Fetched project name from package details: ", hostProjectName); - enabled = isEnabled(); - } catch (e) { - console.warn("[ANALYTICS] Failed to fetch project name, continuing without") - } - } - return hostProjectName; + if (!hostProjectName) { + try { + const packageJson = await import(packageJsonPath) // Adjust the path if necessary + hostProjectName = packageJson.name || null + console.log( + "[ANALYTICS] Fetched project name from package details: ", + hostProjectName, + ) + enabled = isEnabled() + } catch (e) { + console.warn( + "[ANALYTICS] Failed to fetch project name, continuing without", + ) + } + } + return hostProjectName } function enrichEventData(data: objType) { - if (data) { - data.eventId = uuidv4(); - data.client = hostProjectName; - data.sessionId = getSessionId(); - data.url = window.location.href; - region && (data.region = region); - address && !data.address && (data.address = address); - coordinates && !data.coordinates && (data.coordinates = coordinates); - !data.timestamp && (data.timestamp = new Date()); - !data.userId && (data.userId = (user ? user.email : ar_anonymous_id)); - !data.email && user && user.email && (data.email = user.email); - user && (data.user = user); - } + if (data) { + data.eventId = uuidv4() + data.client = hostProjectName + data.sessionId = getSessionId() + data.url = window.location.href + region && (data.region = region) + address && !data.address && (data.address = address) + coordinates && !data.coordinates && (data.coordinates = coordinates) + !data.timestamp && (data.timestamp = new Date()) + !data.userId && (data.userId = user ? user.email : ar_anonymous_id) + !data.email && user && user.email && (data.email = user.email) + user && (data.user = user) + } } function isMalformedEvent(data: objType) { - return !data || !data.eventType; + return !data || !data.eventType } // Function to send bulk events to the analaytics server -export async function sendBulkData(data: Event[], callback?: Function): Promise { - const promises = data && data.map((event: Event) => sendAnalyticsData(event)); - Promise.all(promises).then(() => callback && callback()) +export async function sendBulkData( + data: Event[], + callback?: Function, +): Promise { + const promises = data && data.map((event: Event) => sendAnalyticsData(event)) + Promise.all(promises).then(() => callback && callback()) } // Function to send data to the analytics server async function sendAnalyticsData(data: objType): Promise { - try { - if (!apiKey && !analyticsLogEndpoint) { - console.error('Neither of API key and Analytics server configured. Data not sent.'); - return; - } + try { + if (!apiKey && !analyticsLogEndpoint) { + console.error( + "Neither of API key and Analytics server configured. Data not sent.", + ) + return + } - const options: { - method: string, - headers: { - "Content-Type": string - Authorization?: string - }, - body: any - } = { - method: "POST", - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ event: data }), - }; - if (apiKey) { - options.headers.Authorization = `Bearer ${apiKey}`; - } - const logEndpoint = apiKey ? ARMCO_SERVER + ADD : analyticsLogEndpoint as string; - try { - const response = await fetch(logEndpoint, options); - console.log('Analytics data sent to server:', logEndpoint, data, response.status, response.statusText); - } catch (e) { - console.warn("Failed attempt to log event"); - } - } catch (error) { - console.error('Failed to send analytics data:', error); - } + const options: { + method: string + headers: { + "Content-Type": string + Authorization?: string + } + body: any + } = { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ event: data }), + } + if (apiKey) { + options.headers.Authorization = `Bearer ${apiKey}` + } + const logEndpoint = apiKey + ? ARMCO_SERVER + ADD + : (analyticsLogEndpoint as string) + try { + const response = await fetch(logEndpoint, options) + if (response.status === 200) { + console.log( + "Analytics data sent to server:", + logEndpoint, + data, + response.status, + response.statusText, + ) + } else { + console.warn( + "Failed to send analytics payload to:", + logEndpoint, + data, + response.status, + response.statusText, + ) + } + } catch (e) { + console.warn("Failed attempt to log event") + } + } catch (error) { + console.error("Failed to send analytics data:", error) + } } async function tagEvents(email: string) { - try { - if (!apiKey && !analyticsTagEndpoint) { - console.error('Neither of API key and Analytics server configured. Tagging won\'t be attempted.'); - return; - } + try { + if (!apiKey && !analyticsTagEndpoint) { + console.error( + "Neither of API key and Analytics server configured. Tagging won't be attempted.", + ) + return + } - const options: { - method: string, - headers: { - "Content-Type": string - Authorization?: string - }, - body: any - } = { - method: "POST", - headers: { - 'Content-Type': 'application/json', - }, - body: { event: JSON.stringify({ email, anonymousId: ar_anonymous_id }) }, - }; - if (apiKey) { - options.headers.Authorization = `Bearer ${apiKey}`; - } - const tagEndpoint = apiKey ? ARMCO_SERVER + ADD : analyticsTagEndpoint as string; - const response = await fetch(tagEndpoint, options); - console.log('Analytics data sent to server:', tagEndpoint, response.status, response.statusText); - } catch (error) { - console.error('Failed to send analytics data:', error); - } + const options: { + method: string + headers: { + "Content-Type": string + Authorization?: string + } + body: any + } = { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: { event: JSON.stringify({ email, anonymousId: ar_anonymous_id }) }, + } + if (apiKey) { + options.headers.Authorization = `Bearer ${apiKey}` + } + const tagEndpoint = apiKey + ? ARMCO_SERVER + ADD + : (analyticsTagEndpoint as string) + const response = await fetch(tagEndpoint, options) + console.log( + "Analytics data sent to server:", + tagEndpoint, + response.status, + response.statusText, + ) + } catch (error) { + console.error("Failed to send analytics data:", error) + } } // Core function to track a generic event function trackEvent(event: string | Event, data?: any): void { - const tracker = user && user.email ? user.email : ar_anonymous_id; - if (enabled && tracker) { - if (typeof event === "string") { - data = data || {} - if (data) { - data.eventType = event; - } - } else { - data = event; - } - - if (isMalformedEvent(data)) { - console.error("Attempting to send empty event, or event missing eventType, failing send..."); - return; - } - enrichEventData(data); - CONFIG?.submissionStrategy === "DEFER" ? queueEvent(data) : sendAnalyticsData(data); - } else { - console.log('Analytics disabled or user not identified. Event data not sent.'); - } + const tracker = user && user.email ? user.email : ar_anonymous_id + if (enabled && tracker) { + if (typeof event === "string") { + data = data || {} + if (data) { + data.eventType = event + } + } else { + data = event + } + if (isMalformedEvent(data)) { + console.error( + "Attempting to send empty event, or event missing eventType, failing send...", + ) + return + } + enrichEventData(data) + CONFIG?.submissionStrategy === "DEFER" + ? queueEvent(data) + : sendAnalyticsData(data) + } else { + console.log( + "Analytics disabled or user not identified. Event data not sent.", + ) + } } // Wrapper function to track page views function trackPageView(pageName: string, data?: { [key: string]: any }): void { - const pageViewEvent: Event = { - eventType: "Page View", - pageName, - ...data - // Other page-specific properties... - }; + const pageViewEvent: Event = { + eventType: "Page View", + pageName, + ...data, + // Other page-specific properties... + } - trackEvent('Page View', pageViewEvent); + trackEvent("Page View", pageViewEvent) } // Wrapper function to track errors function trackError(errorMessage: string): void { - const errorEvent: Event = { - eventType: 'Error', - timestamp: new Date(), - errorMessage, - // Other error-specific properties... - }; + const errorEvent: Event = { + eventType: "Error", + timestamp: new Date(), + errorMessage, + // Other error-specific properties... + } - trackEvent(errorEvent); + trackEvent(errorEvent) } // Function to determine if tracking is enabled or disabled function isEnabled(): boolean { - // Check if navigator and navigator.doNotTrack are available - if (typeof navigator !== 'undefined' && 'doNotTrack' in navigator) { - // Check if the "doNotTrack" property is "1" (meaning "yes, do not track") - // or "yes" (some older browsers use this instead of "1") - const doNotTrackValue = navigator.doNotTrack; - if (doNotTrackValue === '1' || doNotTrackValue === 'yes') { - console.warn("[ANALYTICS] Tracking disabled in client, events will not be logged!") - // Tracking is disabled, so return false - return isArClient(hostProjectName) || false; - } - } + // Check if navigator and navigator.doNotTrack are available + if (typeof navigator !== "undefined" && "doNotTrack" in navigator) { + // Check if the "doNotTrack" property is "1" (meaning "yes, do not track") + // or "yes" (some older browsers use this instead of "1") + const doNotTrackValue = navigator.doNotTrack + if (doNotTrackValue === "1" || doNotTrackValue === "yes") { + console.warn( + "[ANALYTICS] Tracking disabled in client, events will not be logged!", + ) + // Tracking is disabled, so return false + return isArClient(hostProjectName) || false + } + } - // If the navigator.doNotTrack property is not available or its value is not "1" or "yes", - // then return true (i.e., tracking is enabled by default). - return true; + // If the navigator.doNotTrack property is not available or its value is not "1" or "yes", + // then return true (i.e., tracking is enabled by default). + return true } // Function to enable or disable tracking function enableTracking(enable: boolean): void { - enabled = isEnabled() && enable; + enabled = isEnabled() && enable } // Function to generate an anonymous ID for users who haven't logged in function generateAnonymousId(): string { - ar_anonymous_id = uuidv4(); - return ar_anonymous_id; + ar_anonymous_id = uuidv4() + return ar_anonymous_id } function populateLocationDetails() { - if (!navigator.geolocation) { - console.warn("Geolocation is not supported by your browser"); - ipLookup(); - } else { - navigator.geolocation.getCurrentPosition((position: { coords: { latitude: number, longitude: number } }) => { - coordinates = { latitude: position.coords.latitude, longitude: position.coords.longitude } - success(position, (reverseGeocodingResponse: any) => { - address = reverseGeocodingResponse.results[0].formatted_address - }); - }, error); - } - region = localTimeRegion; + if (!navigator.geolocation) { + console.warn("Geolocation is not supported by your browser") + ipLookup() + } else { + navigator.geolocation.getCurrentPosition( + (position: { coords: { latitude: number; longitude: number } }) => { + coordinates = { + latitude: position.coords.latitude, + longitude: position.coords.longitude, + } + success(position, (reverseGeocodingResponse: any) => { + address = reverseGeocodingResponse.results[0].formatted_address + }) + }, + error, + ) + } + region = localTimeRegion } const trackedItems = [ @@ -354,128 +421,146 @@ const trackedItems = [ "[role='menuitem']", "[role='menuitemcheckbox']", "[role='menuitemradio']", - "[data-track='true']" -]; + "[data-track='true']", +] function isClickable(element: HTMLElement) { - return element.matches(trackedItems.join(", ")) || - (element as HTMLButtonElement).onclick != null || - window.getComputedStyle(element).cursor == "pointer"; + return ( + element.matches(trackedItems.join(", ")) || + (element as HTMLButtonElement).onclick != null || + window.getComputedStyle(element).cursor === "pointer" + ) } function handleTrackedItemClick(e: MouseEvent) { - const clickedElement = e.target as HTMLElement; - if (isClickable(clickedElement)) { - const dataAttributes = { ...clickedElement.dataset }; - const id = clickedElement.id || null; - const name = clickedElement.getAttribute('name') || null; - const classes = Array.from(clickedElement.classList); - const elementType = clickedElement.tagName.toLowerCase(); - const textContent = clickedElement.textContent; - const href = 'href' in clickedElement ? (clickedElement as HTMLAnchorElement).href : null; - const role = clickedElement.getAttribute('role') || null; - const parentElementId = clickedElement.parentElement?.id || null; - const value = - 'value' in clickedElement && (clickedElement as HTMLInputElement).value - ? (clickedElement as HTMLInputElement).value - : null; + const clickedElement = e.target as HTMLElement + if (isClickable(clickedElement)) { + const dataAttributes = { ...clickedElement.dataset } + const id = clickedElement.id || null + const name = clickedElement.getAttribute("name") || null + const classes = Array.from(clickedElement.classList) + const elementType = clickedElement.tagName.toLowerCase() + const textContent = clickedElement.textContent + const href = + "href" in clickedElement + ? (clickedElement as HTMLAnchorElement).href + : null + const role = clickedElement.getAttribute("role") || null + const parentElementId = clickedElement.parentElement?.id || null + const value = + "value" in clickedElement && (clickedElement as HTMLInputElement).value + ? (clickedElement as HTMLInputElement).value + : null - // Merge dataAttributes (if present) with identified properties - const mergedData = { - ...dataAttributes, - id, - name, - classes, - textContent, - href, - tagName: elementType, - role, - parentElementId, - value, - } + // Merge dataAttributes (if present) with identified properties + const mergedData = { + ...dataAttributes, + id, + name, + classes, + textContent, + href, + tagName: elementType, + role, + parentElementId, + value, + } - trackEvent("CLICK", {element: mergedData}); - } + trackEvent("CLICK", { element: mergedData }) + } } - function hookTrackers() { - if (environment === "browser") { - const TRACK_EVENTS = CONFIG?.trackEvents || ["click", "submit", "select-change"]; - if (TRACK_EVENTS.indexOf("click") > -1) { - console.log("[ANALYTICS] Attaching Click handlers") - const clickables = Array.from(document.querySelectorAll('*')) - console.log("[ANALYTICS] Found " + clickables.length + " items that can be clicked!") - console.log("[ANALYTICS] Dynamically added elements will be added to this list.") - document.addEventListener("click", handleTrackedItemClick) - console.log("[ANALYTICS] Click handlers Attached") - } - if (TRACK_EVENTS.indexOf("submit") > -1) { - console.log("[ANALYTICS] Attaching Submit handlers") - // Add a global event listener for the "submit" event on the document - document.addEventListener('submit', event => { - // Get the form element that triggered the submit event - const formElement = (event.target as HTMLFormElement); + if (environment === "browser") { + const TRACK_EVENTS = CONFIG?.trackEvents || [ + "click", + "submit", + "select-change", + ] + if (TRACK_EVENTS.indexOf("click") > -1) { + console.log("[ANALYTICS] Attaching Click handlers") + const clickables = Array.from(document.querySelectorAll("*")) + console.log( + "[ANALYTICS] Found " + + clickables.length + + " items that can be clicked!", + ) + console.log( + "[ANALYTICS] Dynamically added elements will be added to this list.", + ) + document.addEventListener("click", handleTrackedItemClick) + console.log("[ANALYTICS] Click handlers Attached") + } + if (TRACK_EVENTS.indexOf("submit") > -1) { + console.log("[ANALYTICS] Attaching Submit handlers") + // Add a global event listener for the "submit" event on the document + document.addEventListener("submit", (event) => { + // Get the form element that triggered the submit event + const formElement = event.target as HTMLFormElement - // Get additional information from the form, if needed - // For example, you can get form data, form ID, or any other relevant information + // Get additional information from the form, if needed + // For example, you can get form data, form ID, or any other relevant information - // Track the submit event - trackEvent('SUBMIT', { - submit: { - formId: formElement.id, - formData: new FormData(formElement) - } - }); - }); - console.log("[ANALYTICS] Submit handlers attached") - } - if (TRACK_EVENTS.indexOf("select-change") > -1) { - // Add a global event listener for the "change" event on the document - console.log("[ANALYTICS] Attaching Select OnChange handlers") - document.addEventListener('change', (event) => { - const target = event.target as HTMLElement; + // Track the submit event + trackEvent("SUBMIT", { + submit: { + formId: formElement.id, + formData: new FormData(formElement), + }, + }) + }) + console.log("[ANALYTICS] Submit handlers attached") + } + if (TRACK_EVENTS.indexOf("select-change") > -1) { + // Add a global event listener for the "change" event on the document + console.log("[ANALYTICS] Attaching Select OnChange handlers") + document.addEventListener("change", (event) => { + const target = event.target as HTMLElement - // Check if the target element is a select element - if (target.tagName === 'SELECT') { - // Get the selected option's value and label - const selectedOptionValue = (target as HTMLSelectElement).value; - const selectedOptionLabel = (target as HTMLSelectElement).selectedOptions[0].label; + // Check if the target element is a select element + if (target.tagName === "SELECT") { + // Get the selected option's value and label + const selectedOptionValue = (target as HTMLSelectElement).value + const selectedOptionLabel = (target as HTMLSelectElement) + .selectedOptions[0].label - // Track the select change event - trackEvent('SELECT_CHANGE', { value: selectedOptionValue, label: selectedOptionLabel }); - } - }); - console.log("[ANALYTICS] Select OnChange handlers attached") - } - } + // Track the select change event + trackEvent("SELECT_CHANGE", { + value: selectedOptionValue, + label: selectedOptionLabel, + }) + } + }) + console.log("[ANALYTICS] Select OnChange handlers attached") + } + } } // Function to identify the user with an email before login function identify(appUser: User): void { - if (user === null) { - // If the user is not already identified, create a new user object - user = appUser; - } else { - // If the user is already identified, update the email - user.email = appUser.email; - } - tagEvents(user.email); - ar_anonymous_id = null; + if (user === null) { + // If the user is not already identified, create a new user object + user = appUser + } else { + // If the user is already identified, update the email + user.email = appUser.email + } + tagEvents(user.email) + ar_anonymous_id = null } // Function to send the host project name to the analytics server function sendHostProjectName(): void { - if (hostProjectName) { - const data = { - hostProjectName, - }; - sendAnalyticsData(data); - } + if (hostProjectName) { + const data = { + hostProjectName, + } + sendAnalyticsData(data) + } } function showTrackingPopup() { - const popupContent = ` + const popupContent = `
Got it!
- `; + ` - $('body').append(popupContent); + document.body.insertAdjacentHTML("beforeend", popupContent) - // Add event listeners to the buttons - $('.btn-accept').on('click', function () { - // Handle user consent (e.g., start tracking events) - $('.tracking-popup').remove(); // Remove the popup after user consent - }); + // Add event listeners to the buttons + document.querySelector(".btn-accept")?.addEventListener("click", function () { + // Handle user consent (e.g., start tracking events) + document.querySelector(".tracking-popup")?.remove() // Remove the popup after user consent + }) - $('.btn-decline').on('click', function () { - // Handle user decline (e.g., stop tracking events) - enableTracking(false); - $('.tracking-popup').remove(); // Remove the popup after user decline - }); + document + .querySelector(".btn-decline") + ?.addEventListener("click", function () { + // Handle user decline (e.g., stop tracking events) + enableTracking(false) + document.querySelector(".tracking-popup")?.remove() // Remove the popup after user decline + }) } function loadAnalytics(config?: ConfigType) { - console.log("[ANALYTICS] Loading Configuration"); - CONFIG = config; - if (CONFIG) { - // update foundational config - console.log("[ANALYTICS] Configuration loaded") - apiKey = CONFIG.apiKey || null; - analyticsLogEndpoint = CONFIG.analyticsLogEndpoint || null; - analyticsTagEndpoint = CONFIG.analyticsTagEndpoint || null; - hostProjectName = CONFIG.hostProjectName || hostProjectName || null; // Automatically get the host project name - console.log("[ANALYTICS] Check if Analytics enabled"); - enabled = isEnabled() - if (enabled) { - console.log("[ANALYTICS] Display Tracker Popup") - // Show the tracking popup - CONFIG.showPopUp && showTrackingPopup(); - console.log("[ANALYTICS] Hook Event Trackers") - // Attach tracking to specified events - hookTrackers(); - console.log("[ANALYTICS] Hook Handlers to flush events (use when submissionStrategy is configured as \"DEFER\"") - // Establish event submission strategy - hookFlushHandlers(CONFIG.submissionStrategy); - console.log("[ANALYTICS] Find User Location Details") - // save location details - populateLocationDetails(); - console.log("[ANALYTICS] Initiate Session and Anonymous User ID") - // Start Session - const anonId = generateAnonymousId() - console.log("Tracking User as", anonId); - startSession(); - window.addEventListener('load', function() { - console.log("[ANALYTICS] Logging page load"); - trackEvent("PAGE"); - }); - window.addEventListener("popstate", (event) => { - const url = window.location.href; - trackEvent("NAV", { url }); - }); - } else { - console.warn("[ANALYTICS] Analytics blocked by client, or disabled by configuration!") - } - } + console.log("[ANALYTICS] Loading Configuration") + CONFIG = config + if (CONFIG) { + // update foundational config + console.log("[ANALYTICS] Configuration loaded") + apiKey = CONFIG.apiKey || null + analyticsLogEndpoint = CONFIG.analyticsLogEndpoint || null + analyticsTagEndpoint = CONFIG.analyticsTagEndpoint || null + hostProjectName = CONFIG.hostProjectName || hostProjectName || null // Automatically get the host project name + console.log("[ANALYTICS] Check if Analytics enabled") + enabled = isEnabled() + if (enabled) { + console.log("[ANALYTICS] Display Tracker Popup") + // Show the tracking popup + CONFIG.showPopUp && showTrackingPopup() + console.log("[ANALYTICS] Hook Event Trackers") + // Attach tracking to specified events + hookTrackers() + console.log( + '[ANALYTICS] Hook Handlers to flush events (use when submissionStrategy is configured as "DEFER"', + ) + // Establish event submission strategy + hookFlushHandlers(CONFIG.submissionStrategy) + console.log("[ANALYTICS] Find User Location Details") + // save location details + populateLocationDetails() + console.log("[ANALYTICS] Initiate Session and Anonymous User ID") + // Start Session + const anonId = generateAnonymousId() + console.log("Tracking User as", anonId) + startSession() + window.addEventListener("load", function () { + console.log("[ANALYTICS] Logging page load") + trackEvent("PAGE") + }) + window.addEventListener("popstate", (event) => { + const url = window.location.href + trackEvent("NAV", { url }) + }) + } else { + console.warn( + "[ANALYTICS] Analytics blocked by client, or disabled by configuration!", + ) + } + } } // Initialize the library with the configuration function init(config?: ConfigType): void { - try { - environment = getEnvironmentType(); - runtime = getEnvironment(); - const projectName = config && config.hostProjectName; - if (projectName) { - hostProjectName = projectName; - loadAnalytics(config); - } else { - getHostProjectName() - .then((projectName) => { - hostProjectName = projectName - if (config) { - loadAnalytics(config); - } else { - loadConfiguration() - .then(config => { - loadAnalytics(config); - }); - } - }) - .catch((err) => { - console.error("[ANALYTICS] Couldn't determine host project name, no events will be logged!") - }); - } - } catch (e) { - console.log("[ANALYTICS]", e); - } + try { + environment = getEnvironmentType() + runtime = getEnvironment() + const projectName = config && config.hostProjectName + if (projectName) { + hostProjectName = projectName + loadAnalytics(config) + } else { + getHostProjectName() + .then((projectName) => { + hostProjectName = projectName + if (config) { + loadAnalytics(config) + } else { + loadConfiguration().then((config) => { + loadAnalytics(config) + }) + } + }) + .catch((err) => { + console.error( + "[ANALYTICS] Couldn't determine host project name, no events will be logged!", + ) + }) + } + } catch (e) { + console.log("[ANALYTICS]", e) + } } // Function to logout the current session function logout(): void { - if (user) { - user = null; - generateAnonymousId(); - } - terminateSession(); + if (user) { + user = null + generateAnonymousId() + } + terminateSession() } // Export the utility functions for use in your analytics library export { - enabled as analyticsEnabled, - init, - identify, - getEnvironmentType, - getEnvironment, - loadConfiguration, - logout, - trackEvent, - trackPageView, - trackError, - enableTracking, - generateAnonymousId, - sendHostProjectName, - getSessionId, - startSession, -}; + enabled as analyticsEnabled, + init, + identify, + getEnvironmentType, + getEnvironment, + loadConfiguration, + logout, + trackEvent, + trackPageView, + trackError, + enableTracking, + generateAnonymousId, + sendHostProjectName, + getSessionId, + startSession, +} diff --git a/constants.ts b/constants.ts index 96c8dc3..0278dc1 100644 --- a/constants.ts +++ b/constants.ts @@ -1,3 +1,3 @@ -export const ARMCO_SERVER = "https://telemetry.armco.tech/events/"; -export const ADD = "add"; +export const ARMCO_SERVER = "https://telemetry.armco.tech/events/" +export const ADD = "add" export const TAG = "tag" diff --git a/flush.ts b/flush.ts index 405dab8..5fd012e 100644 --- a/flush.ts +++ b/flush.ts @@ -1,16 +1,16 @@ -import { sendBulkData } from "./analytics"; -import { Event, SubmissionStrategy } from "./index.interface"; +import { sendBulkData } from "./analytics" +import { Event, SubmissionStrategy } from "./index.interface" -const MAX_EVENTS = 100; // Maximum number of events to collect before flushing -const FLUSH_INTERVAL = 15000; // 60 seconds (in milliseconds) +const MAX_EVENTS = 100 // Maximum number of events to collect before flushing +const FLUSH_INTERVAL = 15000 // 60 seconds (in milliseconds) -let events: Event[] = []; +let events: Event[] = [] // Function to add an event to the collection export function queueEvent(event: Event) { - events.push(event); + events.push(event) if (events.length >= MAX_EVENTS) { - flushEvents(); + flushEvents() } } @@ -19,37 +19,38 @@ function flushEvents() { // Implement the logic to send the events to your analytics server here sendBulkData(events, () => { // Clear the events array after flushing - events = []; + events = [] }) +} +// Function to flush events before navigating away +function handleBeforeUnload() { + if (events.length > 0) { + flushEvents() + } +} + +// Function to flush events when changing tabs +function handleVisibilityChange() { + if (document.visibilityState === "hidden" && events.length > 0) { + flushEvents() + } } // Attach event listeners (if running in a browser environment) -export function hookFlushHandlers(submissionStrategy: SubmissionStrategy = "ONEVENT") { - if (typeof window !== 'undefined' && submissionStrategy === "DEFER") { - // Function to flush events before navigating away - function handleBeforeUnload() { - if (events.length > 0) { - flushEvents(); - } - } - - // Function to flush events when changing tabs - function handleVisibilityChange() { - if (document.visibilityState === 'hidden' && events.length > 0) { - flushEvents(); - } - } - +export function hookFlushHandlers( + submissionStrategy: SubmissionStrategy = "ONEVENT", +) { + if (typeof window !== "undefined" && submissionStrategy === "DEFER") { // Attach event listeners - window.addEventListener('beforeunload', handleBeforeUnload); - document.addEventListener('visibilitychange', handleVisibilityChange); + window.addEventListener("beforeunload", handleBeforeUnload) + document.addEventListener("visibilitychange", handleVisibilityChange) // Flush events on a regular interval setInterval(() => { if (events.length > 0) { - flushEvents(); + flushEvents() } - }, FLUSH_INTERVAL); + }, FLUSH_INTERVAL) } -} \ No newline at end of file +} diff --git a/global-modules.d.ts b/global-modules.d.ts index 98d75ed..ecbe0c7 100644 --- a/global-modules.d.ts +++ b/global-modules.d.ts @@ -1,14 +1,14 @@ -import analytics from './index'; +import analytics from "./index" declare global { namespace NodeJS { interface Global { - analytics: analytics; + analytics: analytics } } interface Window { - analytics: analytics; + analytics: analytics } } -export {}; \ No newline at end of file +export {} diff --git a/helper.ts b/helper.ts index 2b58240..7cc4453 100644 --- a/helper.ts +++ b/helper.ts @@ -1,3 +1,3 @@ export function isArClient(name: string | null): boolean { - return !!(name && name.startsWith("@armco")); -} \ No newline at end of file + return !!(name && name.startsWith("@armco")) +} diff --git a/index.interface.ts b/index.interface.ts index 7b1c1d2..c8e5da5 100644 --- a/index.interface.ts +++ b/index.interface.ts @@ -1,32 +1,30 @@ export interface User { - email: string; - // Other user properties... + email: string + // Other user properties... } export interface Event { - eventType: string; - timestamp?: Date; - region?: string, - address?: string, - coordinates?: { - latitude: number - longitude: number - } - [key: string]: any; // Index signature to accept any other properties with their associated types + eventType: string + timestamp?: Date + region?: string + address?: string + coordinates?: { + latitude: number + longitude: number + } + [key: string]: any // Index signature to accept any other properties with their associated types } - export interface ConfigType { - apiKey?: string; - analyticsLogEndpoint?: string; - analyticsTagEndpoint?: string; - hostProjectName?: string; - trackEvents?: Array; - submissionStrategy?: SubmissionStrategy - showPopUp?: boolean + apiKey?: string + analyticsLogEndpoint?: string + analyticsTagEndpoint?: string + hostProjectName?: string + trackEvents?: Array + submissionStrategy?: SubmissionStrategy + showPopUp?: boolean } export type SubmissionStrategy = "ONEVENT" | "DEFER" export type objType = { [key: string]: any } export type EVENT_TYPES = "CLICK" | "SUBMIT" | "SCROLL" | string - diff --git a/index.ts b/index.ts index ec69781..afec575 100644 --- a/index.ts +++ b/index.ts @@ -11,18 +11,22 @@ import { sendHostProjectName, getSessionId, startSession, -} from "./analytics"; +} from "./analytics" document.addEventListener("DOMContentLoaded", function (event) { - const environment = getEnvironment(); + const environment = getEnvironment() console.info("[ANALYTICS] Idenfitied environment: ", environment) if (!environment || environment === "development") { - console.info("[ANALYTICS] Attempting to auto-load analytics configurations from environment...") - init(); + console.info( + "[ANALYTICS] Attempting to auto-load analytics configurations from environment...", + ) + init() } else { - console.info("[ANALYTICS] Identified non-dev environment, analytics auto load wouldn't be attempted.") + console.info( + "[ANALYTICS] Identified non-dev environment, analytics auto load wouldn't be attempted.", + ) } -}); +}) export { init, @@ -37,4 +41,4 @@ export { sendHostProjectName, getSessionId, startSession, -}; +} diff --git a/location.ts b/location.ts index afb7239..9a775af 100644 --- a/location.ts +++ b/location.ts @@ -1,48 +1,54 @@ -import jstz from "jstz"; +import jstz from "jstz" export function ipLookup() { - fetch('https://extreme-ip-lookup.com/json/') - .then(res => res.json()) - .then(response => { - fallbackProcess(response) - }) - .catch(() => { - console.log('We could not find your location'); - }) + fetch("https://extreme-ip-lookup.com/json/") + .then((res) => res.json()) + .then((response) => { + fallbackProcess(response) + }) + .catch(() => { + console.log("We could not find your location") + }) } export function success(position: any, callback: Function) { - const latitude = position.coords.latitude; - const longitude = position.coords.longitude; - reverseGeocodingWithGoogle(latitude, longitude, callback) + const latitude = position.coords.latitude + const longitude = position.coords.longitude + reverseGeocodingWithGoogle(latitude, longitude, callback) } export function error() { - console.log("Unable to retrieve your location"); + console.log("Unable to retrieve your location") } -function reverseGeocodingWithGoogle(latitude: string, longitude: string, callback: Function) { - fetch(`https://maps.googleapis.com/maps/api/geocode/json? +function reverseGeocodingWithGoogle( + latitude: string, + longitude: string, + callback: Function, +) { + fetch(`https://maps.googleapis.com/maps/api/geocode/json? latlng=${latitude},${longitude}&key={GOOGLE_MAP_KEY}`) - .then(res => res.json()) - .then(response => { - callback ? callback(response) : processUserData(response) - }) - .catch(status => { - ipLookup() - }) + .then((res) => res.json()) + .then((response) => { + callback ? callback(response) : processUserData(response) + }) + .catch((status) => { + ipLookup() + }) } function processUserData(response: any) { - console.log(response.results[0].formatted_address); + console.log(response.results[0].formatted_address) } function fallbackProcess(response: any) { - const address: any = document.querySelector('.address') - address.innerText = `${response.city}, ${response.country}` + const address: any = document.querySelector(".address") + address.innerText = `${response.city}, ${response.country}` } -const localTimeRegion = jstz.determine().name(); -const localTime = new Date().toLocaleString("en-US", { timeZone: localTimeRegion }); +const localTimeRegion = jstz.determine().name() +const localTime = new Date().toLocaleString("en-US", { + timeZone: localTimeRegion, +}) -export { localTimeRegion, localTime }; \ No newline at end of file +export { localTimeRegion, localTime } diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 186c7ea..0000000 --- a/package-lock.json +++ /dev/null @@ -1,309 +0,0 @@ -{ - "name": "@armco/analytics", - "version": "0.1.6", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "@armco/analytics", - "version": "0.1.6", - "license": "ISC", - "dependencies": { - "jet-logger": "^1.3.1", - "jquery": "^3.7.0", - "js-cookie": "^3.0.5", - "jstz": "^2.1.1", - "uuid": "^9.0.0" - }, - "devDependencies": { - "@types/fs-extra": "^11.0.1", - "@types/jquery": "^3.5.16", - "@types/js-cookie": "^3.0.3", - "@types/node": "^20.4.2", - "@types/uuid": "^9.0.2", - "fs-extra": "^11.1.1", - "typescript": "^5.1.6" - }, - "peerDependencies": { - "jquery": "^3.7.0" - } - }, - "node_modules/@types/fs-extra": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.1.tgz", - "integrity": "sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==", - "dev": true, - "dependencies": { - "@types/jsonfile": "*", - "@types/node": "*" - } - }, - "node_modules/@types/jquery": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.16.tgz", - "integrity": "sha512-bsI7y4ZgeMkmpG9OM710RRzDFp+w4P1RGiIt30C1mSBT+ExCleeh4HObwgArnDFELmRrOpXgSYN9VF1hj+f1lw==", - "dev": true, - "dependencies": { - "@types/sizzle": "*" - } - }, - "node_modules/@types/js-cookie": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.3.tgz", - "integrity": "sha512-Xe7IImK09HP1sv2M/aI+48a20VX+TdRJucfq4vfRVy6nWN8PYPOEnlMRSgxJAgYQIXJVL8dZ4/ilAM7dWNaOww==", - "dev": true - }, - "node_modules/@types/jsonfile": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.1.tgz", - "integrity": "sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "20.4.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.2.tgz", - "integrity": "sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==", - "dev": true - }, - "node_modules/@types/sizzle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", - "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", - "dev": true - }, - "node_modules/@types/uuid": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.2.tgz", - "integrity": "sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==", - "dev": true - }, - "node_modules/colors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.0.tgz", - "integrity": "sha512-EDpX3a7wHMWFA7PUHWPHNWqOxIIRSJetuwl0AS5Oi/5FMV8kWm69RTlgm00GKjBO1xFHMtBbL49yRtMMdticBw==", - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/fs-extra": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", - "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/jet-logger": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jet-logger/-/jet-logger-1.3.1.tgz", - "integrity": "sha512-BSsTm88Y7a+XtXKpZM71qm0ulH+bNI13rR+BzeQStfjpE/6n3fX3FZpKF/WZh52h1e6gEAOjuFlkmdzGBQnwPg==", - "dependencies": { - "colors": "1.3.0" - } - }, - "node_modules/jquery": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz", - "integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ==" - }, - "node_modules/js-cookie": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", - "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", - "engines": { - "node": ">=14" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jstz": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/jstz/-/jstz-2.1.1.tgz", - "integrity": "sha512-8hfl5RD6P7rEeIbzStBz3h4f+BQHfq/ABtoU6gXKQv5OcZhnmrIpG7e1pYaZ8hS9e0mp+bxUj08fnDUbKctYyA==", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" - } - } - }, - "dependencies": { - "@types/fs-extra": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.1.tgz", - "integrity": "sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==", - "dev": true, - "requires": { - "@types/jsonfile": "*", - "@types/node": "*" - } - }, - "@types/jquery": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.16.tgz", - "integrity": "sha512-bsI7y4ZgeMkmpG9OM710RRzDFp+w4P1RGiIt30C1mSBT+ExCleeh4HObwgArnDFELmRrOpXgSYN9VF1hj+f1lw==", - "dev": true, - "requires": { - "@types/sizzle": "*" - } - }, - "@types/js-cookie": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.3.tgz", - "integrity": "sha512-Xe7IImK09HP1sv2M/aI+48a20VX+TdRJucfq4vfRVy6nWN8PYPOEnlMRSgxJAgYQIXJVL8dZ4/ilAM7dWNaOww==", - "dev": true - }, - "@types/jsonfile": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.1.tgz", - "integrity": "sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/node": { - "version": "20.4.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.2.tgz", - "integrity": "sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==", - "dev": true - }, - "@types/sizzle": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", - "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", - "dev": true - }, - "@types/uuid": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.2.tgz", - "integrity": "sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==", - "dev": true - }, - "colors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.0.tgz", - "integrity": "sha512-EDpX3a7wHMWFA7PUHWPHNWqOxIIRSJetuwl0AS5Oi/5FMV8kWm69RTlgm00GKjBO1xFHMtBbL49yRtMMdticBw==" - }, - "fs-extra": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", - "integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "jet-logger": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jet-logger/-/jet-logger-1.3.1.tgz", - "integrity": "sha512-BSsTm88Y7a+XtXKpZM71qm0ulH+bNI13rR+BzeQStfjpE/6n3fX3FZpKF/WZh52h1e6gEAOjuFlkmdzGBQnwPg==", - "requires": { - "colors": "1.3.0" - } - }, - "jquery": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz", - "integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ==" - }, - "js-cookie": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", - "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==" - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "jstz": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/jstz/-/jstz-2.1.1.tgz", - "integrity": "sha512-8hfl5RD6P7rEeIbzStBz3h4f+BQHfq/ABtoU6gXKQv5OcZhnmrIpG7e1pYaZ8hS9e0mp+bxUj08fnDUbKctYyA==" - }, - "typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", - "dev": true - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - }, - "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" - } - } -} diff --git a/package.json b/package.json index 998ed8f..6b592d3 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ ], "devDependencies": { "@types/fs-extra": "^11.0.1", - "@types/jquery": "^3.5.16", "@types/js-cookie": "^3.0.3", "@types/node": "^20.4.2", "@types/uuid": "^9.0.2", @@ -46,13 +45,8 @@ "typescript": "^5.1.6" }, "dependencies": { - "jet-logger": "^1.3.1", - "jquery": "^3.7.0", "js-cookie": "^3.0.5", "jstz": "^2.1.1", "uuid": "^9.0.0" - }, - "peerDependencies": { - "jquery": "^3.7.0" } } diff --git a/session.ts b/session.ts index d27bba4..137c537 100644 --- a/session.ts +++ b/session.ts @@ -1,121 +1,126 @@ -import Cookies from 'js-cookie'; -import {v4 as uuidv4} from "uuid"; +import Cookies from "js-cookie" +import { v4 as uuidv4 } from "uuid" -const SESSION_COOKIE_NAME = 'ar-session-id'; -const SESSION_EXPIRATION_TIME = 30; // In minutes -let localStorageTimeout: NodeJS.Timeout; +const SESSION_COOKIE_NAME = "ar-session-id" +const SESSION_EXPIRATION_TIME = 30 // In minutes +let localStorageTimeout: NodeJS.Timeout // Function to generate a new session ID function generateSessionId() { - return uuidv4(); + return uuidv4() } // Function to start a new session and generate a session ID export function startSession() { - const sessionId = generateSessionId(); - const expirationDate = new Date(); - + const sessionId = generateSessionId() + const expirationDate = new Date() + // Generate a unique identifier for this tab if it doesn't already have one - let tabId = sessionStorage.getItem('tabId'); + let tabId = sessionStorage.getItem("tabId") if (!tabId) { - const timestamp = expirationDate.getTime(); - tabId = `${uuidv4()}-${timestamp}`; - sessionStorage.setItem('tabId', tabId); + const timestamp = expirationDate.getTime() + tabId = `${uuidv4()}-${timestamp}` + sessionStorage.setItem("tabId", tabId) } - + // Combine the tab ID and session ID to create a unique cookie name for this tab - const cookieName = `${SESSION_COOKIE_NAME}-${tabId}`; + const cookieName = `${SESSION_COOKIE_NAME}-${tabId}` - refreshSessionId(sessionId, cookieName); + refreshSessionId(sessionId, cookieName) - return sessionId; + return sessionId } // Function to refresh the session ID expiration time function refreshSessionId(sessionId: string, cookieName: string) { - const expirationDate = new Date(); - expirationDate.setMinutes(expirationDate.getMinutes() + SESSION_EXPIRATION_TIME); + const expirationDate = new Date() + expirationDate.setMinutes( + expirationDate.getMinutes() + SESSION_EXPIRATION_TIME, + ) // Refresh the session ID expiration time in cookies or localStorage try { - Cookies.set(cookieName, sessionId, { expires: expirationDate }); + Cookies.set(cookieName, sessionId, { expires: expirationDate }) } catch (error) { - clearTimeout(localStorageTimeout); - localStorageTimeout = setTimeout(() => localStorage.removeItem(cookieName), SESSION_EXPIRATION_TIME * 1000); + clearTimeout(localStorageTimeout) + localStorageTimeout = setTimeout( + () => localStorage.removeItem(cookieName), + SESSION_EXPIRATION_TIME * 1000, + ) } } // Function to get the current session ID (if it exists) and refresh the cookie expiration time export function getSessionId() { - let sessionId: string | null | undefined; + let sessionId: string | null | undefined // Get the tab ID from sessionStorage - const tabId = sessionStorage.getItem('tabId'); + const tabId = sessionStorage.getItem("tabId") if (!tabId) { // If there's no tab ID, start a new session - return startSession(); + return startSession() } // Combine the tab ID and session ID to create the cookie name - const cookieName = `${SESSION_COOKIE_NAME}-${tabId}`; + const cookieName = `${SESSION_COOKIE_NAME}-${tabId}` - sessionId = Cookies.get(cookieName); + sessionId = Cookies.get(cookieName) // If the session ID is not found in cookies, check localStorage if (!sessionId) { - sessionId = localStorage.getItem(cookieName); + sessionId = localStorage.getItem(cookieName) } if (!sessionId) { - return startSession(); + return startSession() } - refreshSessionId(sessionId, cookieName); + refreshSessionId(sessionId, cookieName) - return sessionId; + return sessionId } export function extendSession() { // Get the tab ID from sessionStorage - const tabId = sessionStorage.getItem('tabId'); + const tabId = sessionStorage.getItem("tabId") if (!tabId) { // If there's no tab ID, there's no session to extend - return; + return } // Combine the tab ID and session ID to create the cookie name - const cookieName = `${SESSION_COOKIE_NAME}-${tabId}`; + const cookieName = `${SESSION_COOKIE_NAME}-${tabId}` - let sessionId: string | null | undefined = Cookies.get(cookieName); + let sessionId: string | null | undefined = Cookies.get(cookieName) // If the session ID is not found in cookies, check localStorage if (!sessionId) { - sessionId = localStorage.getItem(cookieName); + sessionId = localStorage.getItem(cookieName) } if (sessionId) { - refreshSessionId(sessionId, cookieName); + refreshSessionId(sessionId, cookieName) } } // Helper function to remove the session cookie export function terminateSession(): void { // Get the tab ID from sessionStorage - const tabId = sessionStorage.getItem('tabId'); + const tabId = sessionStorage.getItem("tabId") if (!tabId) { // If there's no tab ID, there's nothing to remove - return; + return } // Combine the tab ID and session ID to create the cookie name - const cookieName = `${SESSION_COOKIE_NAME}-${tabId}`; + const cookieName = `${SESSION_COOKIE_NAME}-${tabId}` // Use Cookies if available - if (typeof window !== 'undefined' && window.Cookies) { - Cookies.remove(cookieName); + if (typeof window !== "undefined" && window.Cookies) { + Cookies.remove(cookieName) } else { // Fallback to localStorage (blocked Cookies) - localStorage.removeItem(cookieName); + localStorage.removeItem(cookieName) } }