@vutolabs/analytics
Version:
Vuto Analytics - instant, Stripe-native analytics for SaaS apps built with Next.js or React.
1 lines • 10.6 kB
Source Map (JSON)
{"version":3,"sources":["../src/utils.ts","../src/generic.ts"],"sourcesContent":["import type { AllowedPropertyValues, AnalyticsProps, Mode } from \"./types\";\n\nexport function isBrowser(): boolean {\n return typeof window !== \"undefined\";\n}\n\nfunction detectEnvironment(): \"development\" | \"production\" {\n try {\n const env = process.env.NODE_ENV;\n if (env === \"development\" || env === \"test\") {\n return \"development\";\n }\n } catch (e) {\n // do nothing, this is okay\n }\n return \"production\";\n}\n\nexport function setMode(mode: Mode = \"auto\"): void {\n if (mode === \"auto\") {\n window.vam = detectEnvironment();\n return;\n }\n\n window.vam = mode;\n}\n\nexport function getMode(): Mode {\n const mode = isBrowser() ? window.vam : detectEnvironment();\n return mode || \"production\";\n}\n\nexport function isProduction(): boolean {\n return getMode() === \"production\";\n}\n\nexport function isDevelopment(): boolean {\n return getMode() === \"development\";\n}\n\nfunction removeKey(key: string, { [key]: _, ...rest }): Record<string, unknown> {\n return rest;\n}\n\nexport function parseProperties(\n properties: Record<string, unknown> | undefined,\n options: {\n strip?: boolean;\n }\n): Error | Record<string, AllowedPropertyValues> | undefined {\n if (!properties) return undefined;\n let props = properties;\n const errorProperties: string[] = [];\n for (const [key, value] of Object.entries(properties)) {\n if (typeof value === \"object\" && value !== null) {\n if (options.strip) {\n props = removeKey(key, props);\n } else {\n errorProperties.push(key);\n }\n }\n }\n\n if (errorProperties.length > 0 && !options.strip) {\n throw Error(\n `The following properties are not valid: ${errorProperties.join(\n \", \"\n )}. Only strings, numbers, booleans, and null are allowed.`\n );\n }\n return props as Record<string, AllowedPropertyValues>;\n}\n\nexport function computeRoute(\n pathname: string | null,\n pathParams: Record<string, string | string[]> | null\n): string | null {\n if (!pathname || !pathParams) {\n return pathname;\n }\n\n let result = pathname;\n try {\n const entries = Object.entries(pathParams);\n // simple keys must be handled first\n for (const [key, value] of entries) {\n if (!Array.isArray(value)) {\n const matcher = turnValueToRegExp(value);\n if (matcher.test(result)) {\n result = result.replace(matcher, `/[${key}]`);\n }\n }\n }\n // array values next\n for (const [key, value] of entries) {\n if (Array.isArray(value)) {\n const matcher = turnValueToRegExp(value.join(\"/\"));\n if (matcher.test(result)) {\n result = result.replace(matcher, `/[...${key}]`);\n }\n }\n }\n return result;\n } catch (e) {\n return pathname;\n }\n}\n\nfunction turnValueToRegExp(value: string): RegExp {\n return new RegExp(`/${escapeRegExp(value)}(?=[/?#]|$)`);\n}\n\nfunction escapeRegExp(string: string): string {\n return string.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nexport function getScriptSrc(props: AnalyticsProps & { basePath?: string }): string {\n if (isDevelopment()) {\n // return \"https://vuto-backend-production.up.railway.app/analytics/script.dev.js\";\n return \"http://localhost:3000/analytics/script.js\";\n }\n\n return \"https://vuto-backend-production.up.railway.app/analytics/script.js\";\n}\n","import { name as packageName, version } from \"../package.json\";\nimport { initQueue } from \"./queue\";\nimport type { AllowedPropertyValues, AnalyticsProps, FlagsDataInput } from \"./types\";\nimport {\n isBrowser,\n parseProperties,\n setMode,\n isDevelopment,\n isProduction,\n computeRoute,\n getScriptSrc,\n} from \"./utils\";\n\n/**\n * Injects the Vuto Web Analytics script into the page head and starts tracking page views. Read more in our [documentation](https://vercel.com/docs/concepts/analytics/package).\n * @param [props] - Analytics options.\n * @param [props.mode] - The mode to use for the analytics script. Defaults to `auto`.\n * - `auto` - Automatically detect the environment. Uses `production` if the environment cannot be determined.\n * - `production` - Always use the production script. (Sends events to the server)\n * - `development` - Always use the development script. (Logs events to the console)\n */\nfunction inject(\n props: AnalyticsProps & {\n framework?: string;\n basePath?: string;\n }\n): void {\n if (!isBrowser()) return;\n\n setMode(props.mode);\n\n initQueue();\n\n const src = getScriptSrc(props);\n\n if (document.head.querySelector(`script[src*=\"${src}\"]`)) return;\n\n const script = document.createElement(\"script\");\n script.src = src;\n script.defer = true;\n script.dataset.sdkn = packageName + (props.framework ? `/${props.framework}` : \"\");\n script.dataset.sdkv = version;\n\n // Définir l'endpoint de collecte des données avec l'URL complète\n const scriptOrigin = new URL(src).origin;\n script.dataset.endpoint = `${scriptOrigin}/api/analytics`;\n script.dataset.projectId = props.projectId;\n\n script.onerror = (): void => {\n const errorMessage = isDevelopment()\n ? \"Please check if any ad blockers are enabled and try again.\"\n : \"Please check if the project is enabled in the Vuto Web Analytics dashboard.\";\n\n // eslint-disable-next-line no-console -- Logging to console is intentional\n console.log(`[Vuto Web Analytics] Failed to load script from ${src}. ${errorMessage}`);\n };\n\n document.head.appendChild(script);\n}\n\n/**\n * Tracks a custom event. Please refer to the [documentation](https://vercel.com/docs/concepts/analytics/custom-events) for more information on custom events.\n * @param name - The name of the event.\n * * Examples: `Purchase`, `Click Button`, or `Play Video`.\n * @param [properties] - Additional properties of the event. Nested objects are not supported. Allowed values are `string`, `number`, `boolean`, and `null`.\n */\nfunction track(\n name: string,\n properties?: Record<string, AllowedPropertyValues>,\n options?: {\n flags?: FlagsDataInput;\n }\n): void {\n if (!isBrowser()) {\n const msg =\n \"[Vuto Web Analytics] Please import `track` from `@vutolabs/analytics/server` when using this function in a server environment\";\n\n if (isProduction()) {\n // eslint-disable-next-line no-console -- Show warning in production\n console.warn(msg);\n } else {\n throw new Error(msg);\n }\n\n return;\n }\n\n if (!properties) {\n (window.va as ((event: string, properties?: unknown) => void) | undefined)?.(\"event\", {\n name,\n options,\n });\n return;\n }\n\n try {\n const props = parseProperties(properties, {\n strip: isProduction(),\n });\n\n (window.va as ((event: string, properties?: unknown) => void) | undefined)?.(\"event\", {\n name,\n data: props,\n options,\n });\n } catch (err) {\n if (err instanceof Error && isDevelopment()) {\n // eslint-disable-next-line no-console -- Logging to console is intentional\n console.error(err);\n }\n }\n}\n\n/**\n * Identifies a user with their email address. This links the current visitor ID to the provided email.\n * @param email - The email address of the user to identify.\n */\nfunction identify(email: string): void {\n if (!isBrowser()) {\n const msg =\n \"[Vuto Web Analytics] Please import `identify` from `@vutolabs/analytics/server` when using this function in a server environment\";\n\n if (isProduction()) {\n // eslint-disable-next-line no-console -- Show warning in production\n console.warn(msg);\n } else {\n throw new Error(msg);\n }\n\n return;\n }\n\n if (!email || typeof email !== \"string\") {\n if (isDevelopment()) {\n // eslint-disable-next-line no-console -- Logging to console is intentional\n console.error(\n \"[Vuto Web Analytics] Invalid email provided to identify(). Email must be a non-empty string.\"\n );\n }\n return;\n }\n\n // Get the visitor ID from localStorage\n const vid = localStorage.getItem(\"analytics_vid\");\n\n // Using type assertion to ensure compatibility with the va function\n (window.va as ((event: string, properties?: unknown) => void) | undefined)?.(\"identify\", {\n email,\n vid,\n });\n}\n\nexport { inject, track, identify, computeRoute };\nexport type { AnalyticsProps };\n\n// eslint-disable-next-line import/no-default-export -- Default export is intentional\nexport default {\n inject,\n track,\n identify,\n computeRoute,\n};\n"],"mappings":";AAEO,SAAS,YAAqB;AACnC,SAAO,OAAO,WAAW;AAC3B;AAEA,SAAS,oBAAkD;AACzD,MAAI;AACF,UAAM,MAAM,QAAQ,IAAI;AACxB,QAAI,QAAQ,iBAAiB,QAAQ,QAAQ;AAC3C,aAAO;AAAA,IACT;AAAA,EACF,SAAS,GAAG;AAAA,EAEZ;AACA,SAAO;AACT;AAWO,SAAS,UAAgB;AAC9B,QAAM,OAAO,UAAU,IAAI,OAAO,MAAM,kBAAkB;AAC1D,SAAO,QAAQ;AACjB;AAEO,SAAS,eAAwB;AACtC,SAAO,QAAQ,MAAM;AACvB;AAEO,SAAS,gBAAyB;AACvC,SAAO,QAAQ,MAAM;AACvB;AAEA,SAAS,UAAU,KAAa,EAAE,CAAC,GAAG,GAAG,GAAG,GAAG,KAAK,GAA4B;AAC9E,SAAO;AACT;AAEO,SAAS,gBACd,YACA,SAG2D;AAC3D,MAAI,CAAC,WAAY,QAAO;AACxB,MAAI,QAAQ;AACZ,QAAM,kBAA4B,CAAC;AACnC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,UAAU,GAAG;AACrD,QAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,UAAI,QAAQ,OAAO;AACjB,gBAAQ,UAAU,KAAK,KAAK;AAAA,MAC9B,OAAO;AACL,wBAAgB,KAAK,GAAG;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AAEA,MAAI,gBAAgB,SAAS,KAAK,CAAC,QAAQ,OAAO;AAChD,UAAM;AAAA,MACJ,2CAA2C,gBAAgB;AAAA,QACzD;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AACT;;;ACLA,SAAS,MACP,MACA,YACA,SAGM;AAxER;AAyEE,MAAI,CAAC,UAAU,GAAG;AAChB,UAAM,MACJ;AAEF,QAAI,aAAa,GAAG;AAElB,cAAQ,KAAK,GAAG;AAAA,IAClB,OAAO;AACL,YAAM,IAAI,MAAM,GAAG;AAAA,IACrB;AAEA;AAAA,EACF;AAEA,MAAI,CAAC,YAAY;AACf,KAAC,YAAO,OAAP,gCAA4E,SAAS;AAAA,MACpF;AAAA,MACA;AAAA,IACF;AACA;AAAA,EACF;AAEA,MAAI;AACF,UAAM,QAAQ,gBAAgB,YAAY;AAAA,MACxC,OAAO,aAAa;AAAA,IACtB,CAAC;AAED,KAAC,YAAO,OAAP,gCAA4E,SAAS;AAAA,MACpF;AAAA,MACA,MAAM;AAAA,MACN;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AACZ,QAAI,eAAe,SAAS,cAAc,GAAG;AAE3C,cAAQ,MAAM,GAAG;AAAA,IACnB;AAAA,EACF;AACF;AAMA,SAAS,SAAS,OAAqB;AArHvC;AAsHE,MAAI,CAAC,UAAU,GAAG;AAChB,UAAM,MACJ;AAEF,QAAI,aAAa,GAAG;AAElB,cAAQ,KAAK,GAAG;AAAA,IAClB,OAAO;AACL,YAAM,IAAI,MAAM,GAAG;AAAA,IACrB;AAEA;AAAA,EACF;AAEA,MAAI,CAAC,SAAS,OAAO,UAAU,UAAU;AACvC,QAAI,cAAc,GAAG;AAEnB,cAAQ;AAAA,QACN;AAAA,MACF;AAAA,IACF;AACA;AAAA,EACF;AAGA,QAAM,MAAM,aAAa,QAAQ,eAAe;AAGhD,GAAC,YAAO,OAAP,gCAA4E,YAAY;AAAA,IACvF;AAAA,IACA;AAAA,EACF;AACF;","names":[]}