a072325149
metrika.ts: читает id из <meta name=metrika-id>, один раз инжектит tag.js + init webvisor:true (defer), hit(url) шлёт заход. Тесты tests/Frontend/metrika.spec.ts: no-op без meta, идемпотентная загрузка, hit вызывает ym с id+url. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
/**
|
|
* Ленивый загрузчик Яндекс.Метрики для портала.
|
|
* Грузится ТОЛЬКО из роутера при входе в кабинет (meta.layout==='app') — см. router/index.ts.
|
|
* id счётчика читается из <meta name="metrika-id"> (проставляется welcome.blade.php по config).
|
|
* Вебвизор пишет с маскировкой ПДн (классы ym-hide-content в компонентах кабинета).
|
|
*/
|
|
declare global {
|
|
interface Window {
|
|
ym?: (...args: unknown[]) => void;
|
|
}
|
|
}
|
|
|
|
let loaded = false;
|
|
|
|
function counterId(): number | null {
|
|
const meta = document.querySelector('meta[name="metrika-id"]');
|
|
const raw = meta?.getAttribute('content');
|
|
const id = raw ? Number(raw) : NaN;
|
|
return Number.isFinite(id) && id > 0 ? id : null;
|
|
}
|
|
|
|
/** Один раз инжектит tag.js и инициализирует Вебвизор. Идемпотентно. No-op без id. */
|
|
export function ensureLoaded(): void {
|
|
if (loaded) return;
|
|
const id = counterId();
|
|
if (id === null) return;
|
|
loaded = true;
|
|
|
|
// стандартный bootstrap-стаб ym до загрузки tag.js
|
|
const w = window as Window & { ym?: { (...a: unknown[]): void; a?: unknown[]; l?: number } };
|
|
w.ym =
|
|
w.ym ||
|
|
function (...args: unknown[]) {
|
|
(w.ym as { a?: unknown[] }).a = (w.ym as { a?: unknown[] }).a || [];
|
|
(w.ym as { a: unknown[] }).a.push(args);
|
|
};
|
|
(w.ym as { l?: number }).l = Number(new Date());
|
|
|
|
const s = document.createElement('script');
|
|
s.async = true;
|
|
s.src = `https://mc.yandex.ru/metrika/tag.js?id=${id}`;
|
|
document.head.appendChild(s);
|
|
|
|
// defer:true — без авто-первого-хита; первый хит шлёт роутер вручную
|
|
w.ym(id, 'init', {
|
|
webvisor: true,
|
|
clickmap: true,
|
|
trackLinks: true,
|
|
accurateTrackBounce: true,
|
|
defer: true,
|
|
});
|
|
}
|
|
|
|
/** Отправить заход (SPA-переход). No-op без id/ym. */
|
|
export function hit(url: string): void {
|
|
const id = counterId();
|
|
if (id === null || typeof window.ym !== 'function') return;
|
|
window.ym(id, 'hit', url);
|
|
}
|