feat(frontend): تحلیل رفتار با OpenPanel به‌جای اجرای نمونهٔ دوم Umami

نمونهٔ OpenPanel موجود (stats.*) با یک پروژهٔ دوم برای این سایت استفاده می‌شود؛
VITE_OPENPANEL_URL/VITE_OPENPANEL_CLIENT_ID در زمان بیلد تزریق می‌شوند و در
نبودشان همه‌چیز بی‌اثر است. API عمومی track() تغییر نکرده و مسیر Umami هم
برای عقب‌نشینی دست‌نخورده مانده.
This commit is contained in:
aramesh-site deploy
2026-09-26 21:43:59 +00:00
parent 8600d2d4a3
commit 9d0015808c
2 changed files with 56 additions and 5 deletions
+53 -5
View File
@@ -1,17 +1,65 @@
// لایهٔ نازک تحلیل رفتار (Umami) — اگر اسکریپت ردیاب بارگیری نشده باشد // لایهٔ نازک تحلیل رفتار — OpenPanel.
// (VITE_UMAMI_SRC خالی)، همهٔ فراخوانی‌ها بی‌اثر و ارزان‌اند. //
// نمونهٔ OpenPanel از قبل روی این سرور بالا است (stats.bennu-api-bahrestan.ir) و
// پروژهٔ ERP هم از همان استفاده می‌کند؛ به‌جای اجرای یک ابزار تحلیل دوم (Umami)،
// همین یک نمونه با یک «پروژهٔ» دوم برای این سایت به کار می‌رود.
//
// اگر VITE_OPENPANEL_URL و VITE_OPENPANEL_CLIENT_ID در زمان بیلد تنظیم نشده باشند،
// همهٔ فراخوانی‌ها بی‌اثر و ارزان‌اند (مثل حالت فعلی Umami).
//
// مسیر Umami دست‌نخورده مانده تا اگر اسکریپت قدیمی جایی فعال بود از کار نیفتد.
declare global { declare global {
interface Window { interface Window {
op?: (...args: unknown[]) => void
umami?: { track: (event?: string, data?: Record<string, unknown>) => void } umami?: { track: (event?: string, data?: Record<string, unknown>) => void }
} }
} }
/** رخداد سفارشی — نام رخداد در داشبورد Umami نمایش داده می‌شود. */ const OP_URL = (import.meta.env?.VITE_OPENPANEL_URL as string | undefined)?.trim()
export function track(event: string, data?: Record<string, unknown>) { 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 { 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 { } catch {
// تحلیل رفتار هرگز نباید مسیر اصلی را بشکند // تحلیل رفتار هرگز نباید مسیر اصلی را بشکند
} }
} }
/** رخداد سفارشی — در داشبورد OpenPanel نمایش داده می‌شود. */
export function track(event: string, data?: Record<string, unknown>) {
try {
window.op?.('track', event, data ?? {})
} catch {
/* ignore */
}
try {
window.umami?.track(event, data)
} catch {
/* ignore */
}
}
+3
View File
@@ -2,6 +2,9 @@ import "./index.css";
import React from "react"; import React from "react";
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import { App } from "./App"; import { App } from "./App";
import { initAnalytics } from "./analytics";
initAnalytics();
const rootEl = document.getElementById("root"); const rootEl = document.getElementById("root");
if (rootEl) { if (rootEl) {