From 544f81c48d3dfe28b7bf10c6d7375a8c4f59e862 Mon Sep 17 00:00:00 2001 From: ashim-hq Date: Wed, 22 Apr 2026 19:07:50 +0800 Subject: [PATCH] feat: add frontend analytics wrapper and consent store --- apps/web/package.json | 2 + apps/web/src/lib/analytics.ts | 113 +++++++++ apps/web/src/stores/analytics-store.ts | 102 ++++++++ pnpm-lock.yaml | 311 +++++++++++++++++++++++++ 4 files changed, 528 insertions(+) create mode 100644 apps/web/src/lib/analytics.ts create mode 100644 apps/web/src/stores/analytics-store.ts diff --git a/apps/web/package.json b/apps/web/package.json index 82f25fff..5eb0e435 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -16,12 +16,14 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "@sentry/react": "^10.49.0", "@use-gesture/react": "^10.3.1", "clsx": "^2.1.0", "fflate": "^0.8.2", "jszip": "^3.10.1", "leaflet": "^1.9.4", "lucide-react": "^0.469.0", + "posthog-js": "^1.370.0", "qr-code-styling": "^1.9.2", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/apps/web/src/lib/analytics.ts b/apps/web/src/lib/analytics.ts new file mode 100644 index 00000000..04984148 --- /dev/null +++ b/apps/web/src/lib/analytics.ts @@ -0,0 +1,113 @@ +import type { AnalyticsConfig } from "@ashim/shared"; +import * as Sentry from "@sentry/react"; +import posthogJs from "posthog-js"; + +let posthog: import("posthog-js").PostHog | null = null; +let initialized = false; +let consentGranted = false; + +const FILE_EXT_PATTERN = + /\.(jpe?g|png|pdf|webp|gif|tiff?|bmp|svg|he[ic]f?|avif|raw|cr2|nef|arw|dng|psd|tga|exr|hdr)\b/gi; +const FILE_PATH_PATTERN = /\/(tmp\/workspace|data\/files|data\/ai|Users|home)\//g; + +function scrubString(str: string): string { + return str.replace(FILE_EXT_PATTERN, ".[REDACTED]").replace(FILE_PATH_PATTERN, "/[REDACTED]/"); +} + +export function initAnalytics(config: AnalyticsConfig): void { + if (initialized || !config.enabled) return; + initialized = true; + + try { + posthog = + posthogJs.init(config.posthogApiKey, { + api_host: config.posthogHost, + autocapture: false, + capture_pageview: true, + disable_session_recording: true, + session_recording: { + captureCanvas: { recordCanvas: false }, + maskAllInputs: true, + maskTextSelector: ".file-name, .file-path, [data-file-name]", + blockSelector: "[data-user-content]", + }, + ip: false, + persistence: "localStorage", + }) ?? null; + } catch { + // SDK blocked or unavailable — use null provider + } + + try { + if (config.sentryDsn) { + Sentry.init({ + dsn: config.sentryDsn, + sendDefaultPii: false, + beforeSend(event) { + if (!consentGranted) return null; + startErrorReplay(); + if (event.user) { + delete event.user.email; + delete event.user.username; + } + if (event.exception?.values) { + for (const ex of event.exception.values) { + if (ex.value) ex.value = scrubString(ex.value); + if (ex.stacktrace?.frames) { + for (const frame of ex.stacktrace.frames) { + if (frame.filename) frame.filename = scrubString(frame.filename); + if (frame.abs_path) frame.abs_path = scrubString(frame.abs_path); + } + } + } + } + return event; + }, + beforeBreadcrumb(breadcrumb) { + if (!consentGranted) return null; + if (breadcrumb.category === "ui.click") return null; + if (breadcrumb.category === "fetch" && breadcrumb.data?.url) { + if (FILE_EXT_PATTERN.test(breadcrumb.data.url as string)) return null; + } + if (breadcrumb.message) { + breadcrumb.message = scrubString(breadcrumb.message); + } + return breadcrumb; + }, + }); + } + } catch { + // Sentry blocked or unavailable + } +} + +export function setAnalyticsConsent(enabled: boolean): void { + consentGranted = enabled; +} + +export function identify(instanceId: string, properties: Record): void { + if (!posthog || !consentGranted) return; + try { + posthog.identify(instanceId, properties); + } catch { + // never throw + } +} + +export function track(event: string, properties?: Record): void { + if (!posthog || !consentGranted) return; + try { + posthog.capture(event, properties); + } catch { + // never throw + } +} + +export function startErrorReplay(): void { + if (!posthog || !consentGranted) return; + try { + posthog.startSessionRecording(); + } catch { + // never throw + } +} diff --git a/apps/web/src/stores/analytics-store.ts b/apps/web/src/stores/analytics-store.ts new file mode 100644 index 00000000..7aaf65f2 --- /dev/null +++ b/apps/web/src/stores/analytics-store.ts @@ -0,0 +1,102 @@ +import type { AnalyticsConfig, ConsentState } from "@ashim/shared"; +import { create } from "zustand"; +import { setAnalyticsConsent } from "@/lib/analytics"; +import { apiPut } from "@/lib/api"; + +interface AnalyticsState { + config: AnalyticsConfig | null; + consent: ConsentState; + configLoaded: boolean; + fetchConfig: () => Promise; + setConsent: (consent: ConsentState) => void; + acceptAnalytics: () => Promise; + declineAnalytics: () => Promise; + remindLater: () => Promise; + toggleAnalytics: (enabled: boolean) => Promise; +} + +export const useAnalyticsStore = create((set, get) => ({ + config: null, + consent: { + analyticsEnabled: null, + analyticsConsentShownAt: null, + analyticsConsentRemindAt: null, + }, + configLoaded: false, + + fetchConfig: async () => { + if (get().configLoaded) return; + try { + const res = await fetch("/api/v1/config/analytics"); + const config: AnalyticsConfig = await res.json(); + set({ config, configLoaded: true }); + } catch { + set({ configLoaded: true }); + } + }, + + setConsent: (consent: ConsentState) => { + set({ consent }); + setAnalyticsConsent(consent.analyticsEnabled === true); + }, + + acceptAnalytics: async () => { + try { + await apiPut("/v1/user/analytics", { enabled: true }); + } catch { + localStorage.setItem("ashim-analytics-consent", "true"); + } + const now = Date.now(); + const consent: ConsentState = { + analyticsEnabled: true, + analyticsConsentShownAt: now, + analyticsConsentRemindAt: null, + }; + set({ consent }); + setAnalyticsConsent(true); + }, + + declineAnalytics: async () => { + try { + await apiPut("/v1/user/analytics", { enabled: false }); + } catch { + localStorage.setItem("ashim-analytics-consent", "false"); + } + const now = Date.now(); + const consent: ConsentState = { + analyticsEnabled: false, + analyticsConsentShownAt: now, + analyticsConsentRemindAt: null, + }; + set({ consent }); + setAnalyticsConsent(false); + }, + + remindLater: async () => { + try { + await apiPut("/v1/user/analytics", { remindLater: true }); + } catch { + localStorage.setItem("ashim-analytics-consent", "remind"); + } + const now = Date.now(); + const consent: ConsentState = { + analyticsEnabled: null, + analyticsConsentShownAt: now, + analyticsConsentRemindAt: now + 7 * 24 * 60 * 60 * 1000, + }; + set({ consent }); + setAnalyticsConsent(false); + }, + + toggleAnalytics: async (enabled: boolean) => { + try { + await apiPut("/v1/user/analytics", { enabled }); + } catch { + localStorage.setItem("ashim-analytics-consent", enabled ? "true" : "false"); + } + set((state) => ({ + consent: { ...state.consent, analyticsEnabled: enabled }, + })); + setAnalyticsConsent(enabled); + }, +})); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7ede49c6..d4c177aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -223,6 +223,9 @@ importers: '@dnd-kit/utilities': specifier: ^3.2.2 version: 3.2.2(react@19.2.4) + '@sentry/react': + specifier: ^10.49.0 + version: 10.49.0(react@19.2.4) '@use-gesture/react': specifier: ^10.3.1 version: 10.3.1(react@19.2.4) @@ -241,6 +244,9 @@ importers: lucide-react: specifier: ^0.469.0 version: 0.469.0(react@19.2.4) + posthog-js: + specifier: ^1.370.0 + version: 1.370.0 qr-code-styling: specifier: ^1.9.2 version: 1.9.2 @@ -2185,6 +2191,10 @@ packages: resolution: {integrity: sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ==} engines: {node: '>=8.0.0'} + '@opentelemetry/api-logs@0.208.0': + resolution: {integrity: sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==} + engines: {node: '>=8.0.0'} + '@opentelemetry/api-logs@0.212.0': resolution: {integrity: sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==} engines: {node: '>=8.0.0'} @@ -2197,6 +2207,12 @@ packages: resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} engines: {node: '>=8.0.0'} + '@opentelemetry/core@2.2.0': + resolution: {integrity: sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/core@2.6.1': resolution: {integrity: sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==} engines: {node: ^18.19.0 || >=20.6.0} @@ -2209,6 +2225,12 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/exporter-logs-otlp-http@0.208.0': + resolution: {integrity: sha512-jOv40Bs9jy9bZVLo/i8FwUiuCvbjWDI+ZW13wimJm4LjnlwJxGgB+N/VWOZUTpM+ah/awXeQqKdNlpLf2EjvYg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/instrumentation-amqplib@0.61.0': resolution: {integrity: sha512-mCKoyTGfRNisge4br0NpOFSy2Z1NnEW8hbCJdUDdJFHrPqVzc4IIBPA/vX0U+LUcQqrQvJX+HMIU0dbDRe0i0Q==} engines: {node: ^18.19.0 || >=20.6.0} @@ -2353,16 +2375,52 @@ packages: peerDependencies: '@opentelemetry/api': ^1.3.0 + '@opentelemetry/otlp-exporter-base@0.208.0': + resolution: {integrity: sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.208.0': + resolution: {integrity: sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + '@opentelemetry/redis-common@0.38.3': resolution: {integrity: sha512-VCghU1JYs/4gP6Gqf/xro9MEsZ7LrMv2uONVsaESKL38ZOB9BqnI98FfS23wjMnHlpuE+TTaWSoAVNpTwYXzjw==} engines: {node: ^18.19.0 || >=20.6.0} + '@opentelemetry/resources@2.2.0': + resolution: {integrity: sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/resources@2.7.0': resolution: {integrity: sha512-K+oi0hNMv94EpZbnW3eyu2X6SGVpD3O5DhG2NIp65Hc7lhAj9brRXTAVzh3wB82+q3ThakEf7Zd7RsFUqcTc7A==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/sdk-logs@0.208.0': + resolution: {integrity: sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.2.0': + resolution: {integrity: sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.2.0': + resolution: {integrity: sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + '@opentelemetry/sdk-trace-base@2.7.0': resolution: {integrity: sha512-Yg9zEXJB50DLVLpsKPk7NmNqlPlS+OvqhJGh0A8oawIOTPOwlm4eXs9BMJV7L79lvEwI+dWtAj+YjTyddV336A==} engines: {node: ^18.19.0 || >=20.6.0} @@ -2409,11 +2467,44 @@ packages: '@posthog/core@1.26.0': resolution: {integrity: sha512-q6pSkW7V9RC3rIXjJBo7IBXOH2KPJEh8o+xIcSqw7QD4vq7OYVzTJfUIvkD38+sm54RG/61/3QaPdpQZkDnOyg==} + '@posthog/types@1.370.0': + resolution: {integrity: sha512-Hcan3j0L1JZwyaoi8SvbZk41phzHpj5u3/BRn0dFVBlfkbvfhkgXHPETKIZP5CPzjrotOXRLYI4LNRDHp28VsA==} + '@prisma/instrumentation@7.6.0': resolution: {integrity: sha512-ZPW2gRiwpPzEfgeZgaekhqXrbW+Y2RJKHVqUmlhZhKzRNCcvR6DykzylDrynpArKKRQtLxoZy36fK7U0p3pdgQ==} peerDependencies: '@opentelemetry/api': ^1.8 + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.4': + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.0': + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.0': + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -2627,6 +2718,26 @@ packages: peerDependencies: semantic-release: '>=20.1.0' + '@sentry-internal/browser-utils@10.49.0': + resolution: {integrity: sha512-n0QRx0Ysx6mPfIydTkz7VP0FmwM+/EqMZiRqdsU3aTYsngE9GmEDV0OL1bAy6a8N/C1xf9vntkuAtj6N/8Z51w==} + engines: {node: '>=18'} + + '@sentry-internal/feedback@10.49.0': + resolution: {integrity: sha512-JNsUBGv0faCFE7MeZUH99Y9lU9qq3LBALbLxpE1x7ngNrQnVYRlcFgdqaD/btNBKr8awjYL8gmcSkHBWskGqLQ==} + engines: {node: '>=18'} + + '@sentry-internal/replay-canvas@10.49.0': + resolution: {integrity: sha512-7D/NrgH1Qwx5trDYaaTSSJmCb1yVQQLqFG4G/S9x2ltzl9876lSGJL8UeW8ReNQgF3CDAcwbmm/9aXaVSBUNZA==} + engines: {node: '>=18'} + + '@sentry-internal/replay@10.49.0': + resolution: {integrity: sha512-IEy4lwHVMiRE3JAcn+kFKjsTgalDOCSTf20SoFd+nkt6rN/k1RDyr4xpdfF//Kj3UdeTmbuibYjK5H/FLhhnGg==} + engines: {node: '>=18'} + + '@sentry/browser@10.49.0': + resolution: {integrity: sha512-bGCHc+wK2Dx67YoSbmtlt04alqWfQ+dasD/GVipVOq50gvw/BBIDHTEWRJEjACl+LrvszeY54V+24p8z4IgysA==} + engines: {node: '>=18'} + '@sentry/core@10.49.0': resolution: {integrity: sha512-UaFeum3LUM1mB0d67jvKnqId1yWQjyqmaDV6kWngG03x+jqXb08tJdGpSoxjXZe13jFBbiBL/wKDDYIK7rCK4g==} engines: {node: '>=18'} @@ -2668,6 +2779,12 @@ packages: '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 '@opentelemetry/semantic-conventions': ^1.39.0 + '@sentry/react@10.49.0': + resolution: {integrity: sha512-WdfJve0orTiumr25Ozgs2p2KaJR9xV82Z5V9IYBi0TadsurSWK6xI6SAFjw84tQht9Fp8q4UCn3QYCnApF4BfA==} + engines: {node: '>=18'} + peerDependencies: + react: ^16.14.0 || 17.x || 18.x || 19.x + '@shikijs/core@2.5.0': resolution: {integrity: sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==} @@ -2966,6 +3083,9 @@ packages: '@types/tedious@4.0.14': resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -3568,6 +3688,9 @@ packages: resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} engines: {node: '>=18'} + core-js@3.49.0: + resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} + core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -3676,6 +3799,9 @@ packages: dom-walk@0.1.2: resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==} + dompurify@3.4.1: + resolution: {integrity: sha512-JahakDAIg1gyOm7dlgWSDjV4n7Ip2PKR55NIT6jrMfIgLFgWo81vdr1/QGqWtFNRqXP9UV71oVePtjqS2ebnPw==} + dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} @@ -3993,6 +4119,9 @@ packages: picomatch: optional: true + fflate@0.4.8: + resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==} + fflate@0.8.2: resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} @@ -4613,6 +4742,9 @@ packages: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -5282,6 +5414,9 @@ packages: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} + posthog-js@1.370.0: + resolution: {integrity: sha512-0PQCUO6EGbXaOPgXYrcafrXa9B13nDaGc91xCiuwNPK80oovqTmeHfNUNCv1r+xCpEoxw3L1FyBuuLCt5Zz7rg==} + posthog-node@5.29.5: resolution: {integrity: sha512-mxM8+mXHHMgBKp8f4EhR5TR96tGJ9IqgYa4bPzKgSUW2uxL2vu9AVEz8dusbc913BXPRrDjw4fp+q/PoSsuQFQ==} engines: {node: ^20.20.0 || >=22.22.0} @@ -5334,6 +5469,10 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + protobufjs@7.5.5: + resolution: {integrity: sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==} + engines: {node: '>=12.0.0'} + pump@3.0.4: resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} @@ -5357,6 +5496,9 @@ packages: engines: {node: '>=10.13.0'} hasBin: true + query-selector-shadow-dom@1.0.1: + resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==} + quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} @@ -6215,6 +6357,9 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + web-vitals@5.2.0: + resolution: {integrity: sha512-i2z98bEmaCqSDiHEDu+gHl/dmR4Q+TxFmG3/13KkMO+o8UxQzCqWaDRCiLgEa41nlO4VpXSI0ASa1xWmO9sBlA==} + web-worker@1.5.0: resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==} @@ -8038,6 +8183,10 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs@0.208.0': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs@0.212.0': dependencies: '@opentelemetry/api': 1.9.1 @@ -8048,6 +8197,11 @@ snapshots: '@opentelemetry/api@1.9.1': {} + '@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 @@ -8058,6 +8212,15 @@ snapshots: '@opentelemetry/api': 1.9.1 '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/exporter-logs-otlp-http@0.208.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.208.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-amqplib@0.61.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 @@ -8267,14 +8430,57 @@ snapshots: transitivePeerDependencies: - supports-color + '@opentelemetry/otlp-exporter-base@0.208.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/otlp-transformer@0.208.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.1) + protobufjs: 7.5.5 + '@opentelemetry/redis-common@0.38.3': {} + '@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/resources@2.7.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/sdk-logs@0.208.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/sdk-metrics@2.2.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1) + + '@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.40.0 + '@opentelemetry/sdk-trace-base@2.7.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 @@ -8314,6 +8520,8 @@ snapshots: '@posthog/core@1.26.0': {} + '@posthog/types@1.370.0': {} + '@prisma/instrumentation@7.6.0(@opentelemetry/api@1.9.1)': dependencies: '@opentelemetry/api': 1.9.1 @@ -8321,6 +8529,29 @@ snapshots: transitivePeerDependencies: - supports-color + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/rollup-android-arm-eabi@4.59.1': @@ -8558,6 +8789,32 @@ snapshots: transitivePeerDependencies: - supports-color + '@sentry-internal/browser-utils@10.49.0': + dependencies: + '@sentry/core': 10.49.0 + + '@sentry-internal/feedback@10.49.0': + dependencies: + '@sentry/core': 10.49.0 + + '@sentry-internal/replay-canvas@10.49.0': + dependencies: + '@sentry-internal/replay': 10.49.0 + '@sentry/core': 10.49.0 + + '@sentry-internal/replay@10.49.0': + dependencies: + '@sentry-internal/browser-utils': 10.49.0 + '@sentry/core': 10.49.0 + + '@sentry/browser@10.49.0': + dependencies: + '@sentry-internal/browser-utils': 10.49.0 + '@sentry-internal/feedback': 10.49.0 + '@sentry-internal/replay': 10.49.0 + '@sentry-internal/replay-canvas': 10.49.0 + '@sentry/core': 10.49.0 + '@sentry/core@10.49.0': {} '@sentry/node-core@10.49.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.7.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.40.0)': @@ -8618,6 +8875,12 @@ snapshots: '@opentelemetry/semantic-conventions': 1.40.0 '@sentry/core': 10.49.0 + '@sentry/react@10.49.0(react@19.2.4)': + dependencies: + '@sentry/browser': 10.49.0 + '@sentry/core': 10.49.0 + react: 19.2.4 + '@shikijs/core@2.5.0': dependencies: '@shikijs/engine-javascript': 2.5.0 @@ -8920,6 +9183,9 @@ snapshots: dependencies: '@types/node': 22.19.15 + '@types/trusted-types@2.0.7': + optional: true + '@types/unist@3.0.3': {} '@types/web-bluetooth@0.0.21': {} @@ -9552,6 +9818,8 @@ snapshots: dependencies: is-what: 5.5.0 + core-js@3.49.0: {} + core-util-is@1.0.3: {} cosmiconfig@9.0.1(typescript@5.9.3): @@ -9640,6 +9908,10 @@ snapshots: dom-walk@0.1.2: {} + dompurify@3.4.1: + optionalDependencies: + '@types/trusted-types': 2.0.7 + dot-prop@5.3.0: dependencies: is-obj: 2.0.0 @@ -9995,6 +10267,8 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fflate@0.4.8: {} + fflate@0.8.2: {} figures@2.0.0: @@ -10637,6 +10911,8 @@ snapshots: strip-ansi: 7.2.0 wrap-ansi: 9.0.2 + long@5.3.2: {} + longest-streak@3.1.0: {} loupe@3.2.1: {} @@ -11289,6 +11565,22 @@ snapshots: dependencies: xtend: 4.0.2 + posthog-js@1.370.0: + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/exporter-logs-otlp-http': 0.208.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.1) + '@posthog/core': 1.26.0 + '@posthog/types': 1.370.0 + core-js: 3.49.0 + dompurify: 3.4.1 + fflate: 0.4.8 + preact: 10.29.0 + query-selector-shadow-dom: 1.0.1 + web-vitals: 5.2.0 + posthog-node@5.29.5: dependencies: '@posthog/core': 1.26.0 @@ -11340,6 +11632,21 @@ snapshots: proto-list@1.2.4: {} + protobufjs@7.5.5: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 22.19.15 + long: 5.3.2 + pump@3.0.4: dependencies: end-of-stream: 1.4.5 @@ -11361,6 +11668,8 @@ snapshots: pngjs: 5.0.0 yargs: 15.4.1 + query-selector-shadow-dom@1.0.1: {} + quick-format-unescaped@4.0.4: {} rc@1.2.8: @@ -12354,6 +12663,8 @@ snapshots: dependencies: xml-name-validator: 5.0.0 + web-vitals@5.2.0: {} + web-worker@1.5.0: {} webidl-conversions@8.0.1: {}