diff --git a/frontend/src/analytics.ts b/frontend/src/analytics.ts index 2cf504c..fb9f688 100644 --- a/frontend/src/analytics.ts +++ b/frontend/src/analytics.ts @@ -1,17 +1,65 @@ -// لایهٔ نازک تحلیل رفتار (Umami) — اگر اسکریپت ردیاب بارگیری نشده باشد -// (VITE_UMAMI_SRC خالی)، همهٔ فراخوانی‌ها بی‌اثر و ارزان‌اند. +// لایهٔ نازک تحلیل رفتار — OpenPanel. +// +// نمونهٔ OpenPanel از قبل روی این سرور بالا است (stats.bennu-api-bahrestan.ir) و +// پروژهٔ ERP هم از همان استفاده می‌کند؛ به‌جای اجرای یک ابزار تحلیل دوم (Umami)، +// همین یک نمونه با یک «پروژهٔ» دوم برای این سایت به کار می‌رود. +// +// اگر VITE_OPENPANEL_URL و VITE_OPENPANEL_CLIENT_ID در زمان بیلد تنظیم نشده باشند، +// همهٔ فراخوانی‌ها بی‌اثر و ارزان‌اند (مثل حالت فعلی Umami). +// +// مسیر Umami دست‌نخورده مانده تا اگر اسکریپت قدیمی جایی فعال بود از کار نیفتد. declare global { interface Window { + op?: (...args: unknown[]) => void umami?: { track: (event?: string, data?: Record) => void } } } -/** رخداد سفارشی — نام رخداد در داشبورد Umami نمایش داده می‌شود. */ -export function track(event: string, data?: Record) { +const OP_URL = (import.meta.env?.VITE_OPENPANEL_URL as string | undefined)?.trim() +const OP_CLIENT_ID = (import.meta.env?.VITE_OPENPANEL_CLIENT_ID as string | undefined)?.trim() +const OP_SCRIPT_URL = (import.meta.env?.VITE_OPENPANEL_SCRIPT_URL as string | undefined)?.trim() + +// شیم صف OpenPanel — تا رسیدن op1.js، فراخوانی‌ها در صف می‌مانند و بعد تخلیه می‌شوند. +function loadOpenPanelScript(src: string): void { + if (document.querySelector('script[data-openpanel]')) return + const shim = document.createElement('script') + shim.textContent = + 'window.op=window.op||function(){var n=[];return new Proxy(function(){arguments.length&&n.push([].slice.call(arguments))},{get:function(t,r){return"q"===r?n:function(){n.push([r].concat([].slice.call(arguments)))}},has:function(t,r){return"q"===r}}) }();' + document.head.appendChild(shim) + const script = document.createElement('script') + script.src = src + script.async = true + script.defer = true + script.dataset.openpanel = '1' + document.head.appendChild(script) +} + +/** یک‌بار در شروع اپ صدا زده می‌شود؛ بدون تنظیمات، هیچ کاری نمی‌کند. */ +export function initAnalytics(): void { + if (!OP_URL || !OP_CLIENT_ID) return try { - window.umami?.track(event, data) + if (OP_SCRIPT_URL) loadOpenPanelScript(OP_SCRIPT_URL) + window.op?.('init', { + apiUrl: OP_URL, + clientId: OP_CLIENT_ID, + trackScreenViews: true, // بازدید صفحه → کدام صفحات دیده می‌شوند + }) } catch { // تحلیل رفتار هرگز نباید مسیر اصلی را بشکند } } + +/** رخداد سفارشی — در داشبورد OpenPanel نمایش داده می‌شود. */ +export function track(event: string, data?: Record) { + try { + window.op?.('track', event, data ?? {}) + } catch { + /* ignore */ + } + try { + window.umami?.track(event, data) + } catch { + /* ignore */ + } +} diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index cf28de2..af94cbd 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -2,6 +2,9 @@ import "./index.css"; import React from "react"; import ReactDOM from "react-dom/client"; import { App } from "./App"; +import { initAnalytics } from "./analytics"; + +initAnalytics(); const rootEl = document.getElementById("root"); if (rootEl) {