@deriv-com/analytics
Version:
Comprehensive analytics package for Deriv applications. Provides unified event tracking, A/B testing, and user analytics through RudderStack, PostHog and GrowthBook integrations with built-in caching and offline support.
1 lines • 15.7 kB
Source Map (JSON)
{"version":3,"sources":["../src/utils/urls.ts","../node_modules/js-cookie/dist/js.cookie.mjs","../src/utils/helpers.ts"],"sourcesContent":["export const cloudflareTrace = 'https://deriv.com/cdn-cgi/trace'\nexport const growthbookApi = 'https://cdn.growthbook.io'\nexport const rudderstackDataplane = 'https://deriv-dataplane.rudderstack.com'\nexport const posthogApiHost = 'https://ph.deriv.com'\nexport const posthogUiHost = 'https://us.posthog.com'\n\nexport const getPosthogApiHost = (): string => {\n if (typeof window === 'undefined') return posthogApiHost\n const hostname = window.location.hostname\n if (hostname.includes('.deriv.me')) return 'https://ph.deriv.me'\n if (hostname.includes('.deriv.be')) return 'https://ph.deriv.be'\n if (hostname.includes('.deriv.ae')) return 'https://ph.deriv.ae'\n return posthogApiHost\n}\n\nexport const allowedDomains = ['deriv.com', 'deriv.be', 'deriv.me', 'deriv.team', 'deriv.ae'] as const\n\nexport const internalEmailDomains = [\n 'deriv.com',\n 'derivcrypto.com',\n 'besquare.my',\n 'besquare.com.my',\n 'ewallet.exchange',\n 'champion-fx.com',\n 'opalstraits.com',\n 'binary.com',\n 'binary.marketing',\n 'championgbs.com',\n '4x.my',\n 're-work.dev',\n 'regentmarkets.com',\n '4x.com',\n 'binary.me',\n 'deriv.team',\n 'firstsource.io',\n 'firstsource.tech',\n 'deriv.hr',\n 'vmgbpo.net',\n 'mailisk.net',\n 'mailosaur.net',\n 'mobileapps.mailisk.net',\n 'w3e180zd.mailosaur.net',\n] as const\n\nexport const getAllowedDomain = (): string => {\n if (typeof window === 'undefined') return '.deriv.com'\n const hostname = window.location.hostname\n\n if (hostname === 'localhost') return ''\n\n const matched = allowedDomains.find(d => hostname.includes(d))\n return matched ? `.${matched}` : '.deriv.com'\n}\n","/*! js-cookie v3.0.5 | MIT */\n/* eslint-disable no-var */\nfunction assign (target) {\n for (var i = 1; i < arguments.length; i++) {\n var source = arguments[i];\n for (var key in source) {\n target[key] = source[key];\n }\n }\n return target\n}\n/* eslint-enable no-var */\n\n/* eslint-disable no-var */\nvar defaultConverter = {\n read: function (value) {\n if (value[0] === '\"') {\n value = value.slice(1, -1);\n }\n return value.replace(/(%[\\dA-F]{2})+/gi, decodeURIComponent)\n },\n write: function (value) {\n return encodeURIComponent(value).replace(\n /%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g,\n decodeURIComponent\n )\n }\n};\n/* eslint-enable no-var */\n\n/* eslint-disable no-var */\n\nfunction init (converter, defaultAttributes) {\n function set (name, value, attributes) {\n if (typeof document === 'undefined') {\n return\n }\n\n attributes = assign({}, defaultAttributes, attributes);\n\n if (typeof attributes.expires === 'number') {\n attributes.expires = new Date(Date.now() + attributes.expires * 864e5);\n }\n if (attributes.expires) {\n attributes.expires = attributes.expires.toUTCString();\n }\n\n name = encodeURIComponent(name)\n .replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent)\n .replace(/[()]/g, escape);\n\n var stringifiedAttributes = '';\n for (var attributeName in attributes) {\n if (!attributes[attributeName]) {\n continue\n }\n\n stringifiedAttributes += '; ' + attributeName;\n\n if (attributes[attributeName] === true) {\n continue\n }\n\n // Considers RFC 6265 section 5.2:\n // ...\n // 3. If the remaining unparsed-attributes contains a %x3B (\";\")\n // character:\n // Consume the characters of the unparsed-attributes up to,\n // not including, the first %x3B (\";\") character.\n // ...\n stringifiedAttributes += '=' + attributes[attributeName].split(';')[0];\n }\n\n return (document.cookie =\n name + '=' + converter.write(value, name) + stringifiedAttributes)\n }\n\n function get (name) {\n if (typeof document === 'undefined' || (arguments.length && !name)) {\n return\n }\n\n // To prevent the for loop in the first place assign an empty array\n // in case there are no cookies at all.\n var cookies = document.cookie ? document.cookie.split('; ') : [];\n var jar = {};\n for (var i = 0; i < cookies.length; i++) {\n var parts = cookies[i].split('=');\n var value = parts.slice(1).join('=');\n\n try {\n var found = decodeURIComponent(parts[0]);\n jar[found] = converter.read(value, found);\n\n if (name === found) {\n break\n }\n } catch (e) {}\n }\n\n return name ? jar[name] : jar\n }\n\n return Object.create(\n {\n set,\n get,\n remove: function (name, attributes) {\n set(\n name,\n '',\n assign({}, attributes, {\n expires: -1\n })\n );\n },\n withAttributes: function (attributes) {\n return init(this.converter, assign({}, this.attributes, attributes))\n },\n withConverter: function (converter) {\n return init(assign({}, this.converter, converter), this.attributes)\n }\n },\n {\n attributes: { value: Object.freeze(defaultAttributes) },\n converter: { value: Object.freeze(converter) }\n }\n )\n}\n\nvar api = init(defaultConverter, { path: '/' });\n/* eslint-enable no-var */\n\nexport { api as default };\n","import Cookies from 'js-cookie'\nimport { cloudflareTrace, internalEmailDomains } from './urls'\n\n/**\n * Creates a prefixed logger that only outputs when debug mode is enabled.\n * Pass a getter function so the logger always reads the latest debug state.\n *\n * @param prefix - Optional provider name appended after [ANALYTIC], e.g. '[RudderStack]'\n * @param isDebugEnabled - A function that returns the current debug flag value\n * @returns A log function with the same signature as console.log\n *\n * @example\n * // In a class\n * private log = createLogger('[RudderStack]', () => this.debug)\n *\n * // In a closure\n * const log = createLogger('', () => _debug)\n */\nexport const createLogger =\n (prefix: string, isDebugEnabled: () => boolean) =>\n (...args: any[]): void => {\n if (isDebugEnabled()) console.log(`[ANALYTIC]${prefix}`, ...args)\n }\n\nexport const isInternalEmail = (email: string): boolean => {\n const domain = email.split('@')[1]?.toLowerCase()\n if (!domain) return false\n return (internalEmailDomains as readonly string[]).includes(domain)\n}\n\nexport const isUUID = (str: string): boolean => {\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n return uuidRegex.test(str)\n}\n\ntype TraceData = {\n loc?: string\n}\n\nlet countryPromise: Promise<string> | null = null\n\n/**\n * Fetches the country information based on Cloudflare's trace data or a fallback from cookies.\n * This function attempts to retrieve the country location by first fetching trace data from Cloudflare\n * and then falling back to the location stored in the cookies if the fetch fails.\n *\n * @returns {Promise<string>} A Promise that resolves to a string representing the country code in lowercase.\n * Returns an empty string if no country data is available or if an error occurs.\n *\n * @example\n * // Returns the country code in lowercase based on Cloudflare's trace data or cookies.\n * getCountry().then(country => console.log(country));\n */\nexport const getCountry = async (): Promise<string> => {\n if (countryPromise) return countryPromise\n\n const cookieCountry = JSON.parse(Cookies.get('website_status') || '{}')?.clients_country\n\n countryPromise = (async () => {\n try {\n const response = await fetch(cloudflareTrace).catch(() => null)\n if (!response) return cookieCountry || ''\n\n const text = await response.text().catch(() => '')\n if (!text) return cookieCountry || ''\n\n const data: TraceData = Object.fromEntries(text.split('\\n').map(v => v.split('=', 2)))\n return data.loc?.toLowerCase() || cookieCountry || ''\n } catch {\n return cookieCountry || ''\n }\n })()\n\n return countryPromise\n}\n\n/**\n * Recursively cleans an object by removing undefined, null, empty strings, empty objects, and empty arrays\n * Used to sanitize event properties before sending to analytics providers\n *\n * @param obj - The object to clean\n * @returns The cleaned object, or undefined if the result would be empty\n */\nexport const cleanObject = (obj: any): any => {\n if (obj == null || typeof obj !== 'object') return obj\n\n if (Array.isArray(obj)) {\n const out: any[] = []\n for (let i = 0; i < obj.length; i++) {\n const v = cleanObject(obj[i])\n if (v !== undefined && v !== null) out.push(v)\n }\n return out.length ? out : undefined\n }\n\n const cleaned: Record<string, any> = {}\n let hasKeys = false\n for (const key in obj) {\n if (!Object.prototype.hasOwnProperty.call(obj, key)) continue\n const v = cleanObject(obj[key])\n if (\n v === undefined ||\n v === null ||\n v === '' ||\n (typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length === 0) ||\n (Array.isArray(v) && v.length === 0)\n )\n continue\n cleaned[key] = v\n hasKeys = true\n }\n return hasKeys ? cleaned : undefined\n}\n\n/**\n * Flattens a nested object structure into a single-level object\n * Lifts all nested properties to the top level without prefixing\n *\n * @param obj - The object to flatten\n * @returns A flattened object with all nested properties at the top level\n *\n * @example\n * flattenObject({ action: 'click', event_metadata: { version: 2, user_language: 'en' } })\n * // Returns: { action: 'click', version: 2, user_language: 'en' }\n *\n * flattenObject({ form_name: 'signup', cta_information: { cta_name: 'signup', section_name: 'header' } })\n * // Returns: { form_name: 'signup', cta_name: 'signup', section_name: 'header' }\n */\nconst _flattenInto = (obj: Record<string, any>, target: Record<string, any>): void => {\n for (const key in obj) {\n if (!Object.prototype.hasOwnProperty.call(obj, key)) continue\n const value = obj[key]\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n _flattenInto(value, target)\n } else {\n target[key] = value\n }\n }\n}\n\nexport const flattenObject = (obj: any): Record<string, any> => {\n if (obj == null || typeof obj !== 'object' || Array.isArray(obj)) {\n return obj\n }\n const target: Record<string, any> = {}\n _flattenInto(obj, target)\n return target\n}\n"],"mappings":"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","names":["cloudflareTrace","growthbookApi","rudderstackDataplane","posthogApiHost","posthogUiHost","getPosthogApiHost","hostname","allowedDomains","internalEmailDomains","assign","target","i","source","key","defaultConverter","value","init","converter","defaultAttributes","set","name","attributes","stringifiedAttributes","attributeName","get","cookies","jar","parts","found","api","createLogger","prefix","isDebugEnabled","args","isInternalEmail","email","domain","internalEmailDomains","isUUID","str","countryPromise","getCountry","cookieCountry","api","response","cloudflareTrace","text","v","cleanObject","obj","out","i","cleaned","hasKeys","key","_flattenInto","target","value","flattenObject"]}