commit 207224356163053761fe8485302b73eaaa5ea6db Author: mohiit1502 Date: Wed Sep 4 21:35:55 2024 +0530 First Commit diff --git a/.env b/.env new file mode 100644 index 0000000..995fca4 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +NODE_ENV=production \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de4d1f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +dist +node_modules diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..24e2427 --- /dev/null +++ b/.npmignore @@ -0,0 +1,6 @@ +tsconfig* +package-lock.json +build.ts +build.js +node_modules +index.ts \ No newline at end of file diff --git a/analytics.ts b/analytics.ts new file mode 100644 index 0000000..5609229 --- /dev/null +++ b/analytics.ts @@ -0,0 +1,618 @@ +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"; + +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 coordinates: { + latitude: number + longitude: number +} +let runtime: string | null = null +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 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; +} + +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}.`); + } + + 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 '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'; +} + +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; +} + +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); + } +} + +function isMalformedEvent(data: objType) { + 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()) +} + +// 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; + } + + 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); + } +} + +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; + } + + 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.'); + } + +} + +// 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... + }; + + 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... + }; + + 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; + } + } + + // 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; +} +// Function to generate an anonymous ID for users who haven't logged in +function generateAnonymousId(): string { + 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; +} + +const trackedItems = [ + "a[href]", + "button", + "input[type='button']", + "input[type='submit']", + "input[type='reset']", + "input[type='checkbox']", + "input[type='radio']", + "select", + "textarea", + "area", + "details", + "summary", + "iframe", + "object", + "embed", + "label", + "img", + "[role='button']", + "[role='checkbox']", + "[role='link']", + "[role='menuitem']", + "[role='menuitemcheckbox']", + "[role='menuitemradio']", + "[data-track='true']" +]; + +function isClickable(element: HTMLElement) { + 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; + + // Merge dataAttributes (if present) with identified properties + const mergedData = { + ...dataAttributes, + id, + name, + classes, + textContent, + href, + tagName: elementType, + role, + parentElementId, + value, + } + + 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); + + // 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; + + // 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") + } + } +} + +// 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; +} + +// Function to send the host project name to the analytics server +function sendHostProjectName(): void { + if (hostProjectName) { + const data = { + hostProjectName, + }; + sendAnalyticsData(data); + } +} + +function showTrackingPopup() { + const popupContent = ` +
+

We use cookies to collect and analyze data to improve our website. By clicking "Accept," you consent to the use of cookies.

+ Got it! +
+ `; + + $('body').append(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 + }); + + $('.btn-decline').on('click', function () { + // Handle user decline (e.g., stop tracking events) + enableTracking(false); + $('.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!") + } + } +} + +// 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); + } +} + +// Function to logout the current session +function logout(): void { + 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, +}; diff --git a/build.js b/build.js new file mode 100644 index 0000000..d6788b7 --- /dev/null +++ b/build.js @@ -0,0 +1,57 @@ +/** + * Remove old files, copy front-end ones. + */ + +import fs from "fs-extra"; +import childProcess from "child_process"; +import pkg from "./package.json" assert {type: "json"}; + +/** + * Start + */ +(async () => { + try { + // Remove current + console.log("removing dist"); + await remove("./dist/"); + await exec("tsc --build tsconfig.prod.json", "./"); + pkg.scripts = {}; + pkg.devDependencies = {}; + if (pkg.main.startsWith("dist/")) { + pkg.main = pkg.main.slice(5); + } + fs.outputFileSync( + "./dist/package.json", + Buffer.from(JSON.stringify(pkg, null, 2), "utf-8") + ); + + fs.copyFileSync(".npmignore", "./dist/.npmignore"); + fs.copyFileSync("global-modules.d.ts", "./dist/global-modules.d.ts"); + console.log("Trigger build"); + } catch (err) { + console.log(err); + process.exit(1); + } +})(); + +/** + * Remove file + */ +function remove(loc) { + return new Promise((res, rej) => { + return fs.remove(loc, (err) => { + return !!err ? rej(err) : res(); + }); + }); +} + +/** + * Do command line command. + */ +function exec(cmd, loc) { + return new Promise((res, rej) => { + return childProcess.exec(cmd, {cwd: loc}, (err, stdout, stderr) => { + return !!err ? rej(err) : res(); + }); + }); +} diff --git a/constants.ts b/constants.ts new file mode 100644 index 0000000..96c8dc3 --- /dev/null +++ b/constants.ts @@ -0,0 +1,3 @@ +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 new file mode 100644 index 0000000..405dab8 --- /dev/null +++ b/flush.ts @@ -0,0 +1,55 @@ +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) + +let events: Event[] = []; + +// Function to add an event to the collection +export function queueEvent(event: Event) { + events.push(event); + if (events.length >= MAX_EVENTS) { + flushEvents(); + } +} + +// Function to flush the collected events +function flushEvents() { + // Implement the logic to send the events to your analytics server here + sendBulkData(events, () => { + // Clear the events array after flushing + events = []; + }) + +} + +// 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(); + } + } + + // Attach event listeners + window.addEventListener('beforeunload', handleBeforeUnload); + document.addEventListener('visibilitychange', handleVisibilityChange); + + // Flush events on a regular interval + setInterval(() => { + if (events.length > 0) { + flushEvents(); + } + }, FLUSH_INTERVAL); + } +} \ No newline at end of file diff --git a/global-modules.d.ts b/global-modules.d.ts new file mode 100644 index 0000000..98d75ed --- /dev/null +++ b/global-modules.d.ts @@ -0,0 +1,14 @@ +import analytics from './index'; + +declare global { + namespace NodeJS { + interface Global { + analytics: analytics; + } + } + interface Window { + analytics: analytics; + } +} + +export {}; \ No newline at end of file diff --git a/helper.ts b/helper.ts new file mode 100644 index 0000000..2b58240 --- /dev/null +++ b/helper.ts @@ -0,0 +1,3 @@ +export function isArClient(name: string | null): boolean { + return !!(name && name.startsWith("@armco")); +} \ No newline at end of file diff --git a/index.interface.ts b/index.interface.ts new file mode 100644 index 0000000..7b1c1d2 --- /dev/null +++ b/index.interface.ts @@ -0,0 +1,32 @@ +export interface User { + 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 +} + + +export interface ConfigType { + 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 new file mode 100644 index 0000000..ec69781 --- /dev/null +++ b/index.ts @@ -0,0 +1,40 @@ +import { + init, + identify, + getEnvironment, + getEnvironmentType, + trackEvent, + trackPageView, + trackError, + enableTracking, + generateAnonymousId, + sendHostProjectName, + getSessionId, + startSession, +} from "./analytics"; + +document.addEventListener("DOMContentLoaded", function (event) { + 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(); + } else { + console.info("[ANALYTICS] Identified non-dev environment, analytics auto load wouldn't be attempted.") + } +}); + +export { + init, + identify, + getEnvironment, + getEnvironmentType, + trackEvent, + trackPageView, + trackError, + enableTracking, + generateAnonymousId, + sendHostProjectName, + getSessionId, + startSession, +}; diff --git a/location.ts b/location.ts new file mode 100644 index 0000000..afb7239 --- /dev/null +++ b/location.ts @@ -0,0 +1,48 @@ +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'); + }) +} + +export function success(position: any, callback: Function) { + const latitude = position.coords.latitude; + const longitude = position.coords.longitude; + reverseGeocodingWithGoogle(latitude, longitude, callback) +} + +export function error() { + console.log("Unable to retrieve your location"); +} + +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() + }) +} + +function processUserData(response: any) { + console.log(response.results[0].formatted_address); +} + +function fallbackProcess(response: any) { + 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 }); + +export { localTimeRegion, localTime }; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..186c7ea --- /dev/null +++ b/package-lock.json @@ -0,0 +1,309 @@ +{ + "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 new file mode 100644 index 0000000..998ed8f --- /dev/null +++ b/package.json @@ -0,0 +1,58 @@ +{ + "name": "@armco/analytics", + "version": "0.2.8", + "description": "Browser Based Analytics interceptor for configured events", + "main": "index.js", + "type": "module", + "scripts": { + "build": "npx ts-node build.js", + "lint": "npx eslint --ext .ts src/", + "lint:tests": "npx eslint --ext .ts spec/", + "start": "node ./dist --env=production", + "dev": "nodemon", + "test": "nodemon --config ./spec/nodemon.json", + "test:no-reloading": "npx ts-node --files -r tsconfig-paths/register ./spec", + "publish:local": "./publish-local.sh", + "publish:sh": "./publish.sh", + "publish:sh:minor": "./publish.sh minor" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ReStruct-Corporate-Advantage/analytics.git" + }, + "keywords": [ + "analytics", + "browser", + "configurable", + "insights", + "automated" + ], + "author": "mohit.nagar@armco.tech", + "license": "ISC", + "bugs": { + "url": "https://github.com/ReStruct-Corporate-Advantage/analytics/issues" + }, + "homepage": "https://github.com/ReStruct-Corporate-Advantage/analytics#readme", + "files": [ + "*" + ], + "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" + }, + "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/publish-local.sh b/publish-local.sh new file mode 100755 index 0000000..daf915a --- /dev/null +++ b/publish-local.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +semver=${1:-patch} + +set -e +npm run build +cd dist +npm pack --pack-destination ~/__Projects__/Common \ No newline at end of file diff --git a/publish.sh b/publish.sh new file mode 100755 index 0000000..8687652 --- /dev/null +++ b/publish.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +set -e +semver=${1:-patch} + +npm run build +cd dist +npm --no-git-tag-version version ${semver} +npm publish --access public \ No newline at end of file diff --git a/session.ts b/session.ts new file mode 100644 index 0000000..d27bba4 --- /dev/null +++ b/session.ts @@ -0,0 +1,126 @@ +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; + +// Function to generate a new session ID +function generateSessionId() { + return uuidv4(); +} + +// Function to start a new session and generate a session ID +export function startSession() { + 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'); + if (!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}`; + + refreshSessionId(sessionId, cookieName); + + 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); + + // Refresh the session ID expiration time in cookies or localStorage + try { + Cookies.set(cookieName, sessionId, { expires: expirationDate }); + } catch (error) { + 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; + + // Get the tab ID from sessionStorage + const tabId = sessionStorage.getItem('tabId'); + if (!tabId) { + // If there's no tab ID, start a new session + return startSession(); + } + + // Combine the tab ID and session ID to create the cookie name + const cookieName = `${SESSION_COOKIE_NAME}-${tabId}`; + + sessionId = Cookies.get(cookieName); + + // If the session ID is not found in cookies, check localStorage + if (!sessionId) { + sessionId = localStorage.getItem(cookieName); + } + + if (!sessionId) { + return startSession(); + } + + refreshSessionId(sessionId, cookieName); + + return sessionId; +} + +export function extendSession() { + // Get the tab ID from sessionStorage + const tabId = sessionStorage.getItem('tabId'); + if (!tabId) { + // If there's no tab ID, there's no session to extend + return; + } + + // Combine the tab ID and session ID to create the cookie name + const cookieName = `${SESSION_COOKIE_NAME}-${tabId}`; + + 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); + } + + if (sessionId) { + 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'); + if (!tabId) { + // If there's no tab ID, there's nothing to remove + return; + } + + // Combine the tab ID and session ID to create the cookie name + const cookieName = `${SESSION_COOKIE_NAME}-${tabId}`; + + // Use Cookies if available + if (typeof window !== 'undefined' && window.Cookies) { + Cookies.remove(cookieName); + } else { + // Fallback to localStorage (blocked Cookies) + localStorage.removeItem(cookieName); + } +} + +// // Function to handle browser quit event +// window.addEventListener('beforeunload', () => { +// // Logout the session on browser quit +// logout(); +// }); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..4d0ba6f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationDir": "dist", + "target": "es6", + "module": "esnext", + "moduleResolution": "node", + "outDir": "./dist", + "strict": true, + "baseUrl": "./", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "paths": { + "@src/*": [ + "src/*" + ] + }, + "useUnknownInCatchVariables": false + }, + "include": [ + "./**/*.ts", + "build.js" + ], + "exclude": [ + "src/public/", + "dist" + ] +} \ No newline at end of file diff --git a/tsconfig.prod.json b/tsconfig.prod.json new file mode 100644 index 0000000..85dd1b8 --- /dev/null +++ b/tsconfig.prod.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "sourceMap": false, + "removeComments": true + }, + "exclude": [ + "spec", + "build.ts" + ] +}