a013fcacd8
modified: main_dc/nginx/nginx-ssl.conf new file: main_dc/valitovgaziz/analytics/Dockerfile new file: main_dc/valitovgaziz/analytics/package.json new file: main_dc/valitovgaziz/analytics/server.js new file: main_dc/valitovgaziz/html/JavaScript/analytics.js modified: main_dc/valitovgaziz/html/index.html add nginx settings for api logs for valitovgaziz.ru site, add container for metrica container, add metrica scripts on site valitovgaziz.ru
270 lines
9.3 KiB
JavaScript
270 lines
9.3 KiB
JavaScript
// analytics.js - собственный счетчик аналитики для браузера
|
|
class CustomAnalytics {
|
|
constructor() {
|
|
this.endpoint = 'https://valitovgaziz.ru/api/analytics'; // Ваш endpoint для сбора данных
|
|
this.queue = [];
|
|
this.isOnline = navigator.onLine;
|
|
this.sessionId = this.getSessionId();
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
// Загружаем сохраненные данные из localStorage
|
|
this.loadFromStorage();
|
|
|
|
// Отслеживание событий
|
|
this.trackPageView();
|
|
this.setupEventListeners();
|
|
|
|
// Периодическая отправка данных
|
|
setInterval(() => this.flushQueue(), 30000);
|
|
|
|
// Отслеживание онлайн/офлайн статуса
|
|
window.addEventListener('online', () => {
|
|
this.isOnline = true;
|
|
this.flushQueue();
|
|
});
|
|
window.addEventListener('offline', () => {
|
|
this.isOnline = false;
|
|
});
|
|
|
|
// Отправка данных перед закрытием страницы
|
|
window.addEventListener('beforeunload', () => {
|
|
this.trackEvent('page', 'unload');
|
|
this.flushQueueSync();
|
|
});
|
|
}
|
|
|
|
trackPageView() {
|
|
const data = {
|
|
type: 'pageview',
|
|
url: window.location.href,
|
|
referrer: document.referrer,
|
|
timestamp: new Date().toISOString(),
|
|
userAgent: navigator.userAgent,
|
|
screen: `${screen.width}x${screen.height}`,
|
|
language: navigator.language,
|
|
sessionId: this.sessionId
|
|
};
|
|
this.addToQueue(data);
|
|
}
|
|
|
|
trackEvent(category, action, label = null, value = null) {
|
|
const data = {
|
|
type: 'event',
|
|
category,
|
|
action,
|
|
label,
|
|
value,
|
|
timestamp: new Date().toISOString(),
|
|
url: window.location.href,
|
|
sessionId: this.sessionId
|
|
};
|
|
this.addToQueue(data);
|
|
}
|
|
|
|
trackClick(element, context = 'unknown') {
|
|
const data = {
|
|
type: 'click',
|
|
element: element.tagName,
|
|
text: element.textContent?.substring(0, 100),
|
|
context,
|
|
timestamp: new Date().toISOString(),
|
|
url: window.location.href,
|
|
sessionId: this.sessionId
|
|
};
|
|
this.addToQueue(data);
|
|
}
|
|
|
|
addToQueue(data) {
|
|
this.queue.push(data);
|
|
|
|
// Сохраняем в localStorage
|
|
this.saveToStorage();
|
|
|
|
// Отправляем сразу если онлайн и очередь большая
|
|
if (this.isOnline && this.queue.length >= 3) {
|
|
this.flushQueue();
|
|
}
|
|
}
|
|
|
|
async flushQueue() {
|
|
if (!this.isOnline || this.queue.length === 0) return;
|
|
|
|
const batch = [...this.queue];
|
|
|
|
try {
|
|
const response = await fetch(this.endpoint, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
events: batch,
|
|
sessionId: this.sessionId
|
|
}),
|
|
keepalive: true // Позволяет отправлять данные даже при закрытии страницы
|
|
});
|
|
|
|
if (response.ok) {
|
|
// Удаляем отправленные данные из очереди
|
|
this.queue = this.queue.filter(item => !batch.includes(item));
|
|
this.saveToStorage();
|
|
}
|
|
} catch (error) {
|
|
console.log('Analytics offline, storing locally');
|
|
}
|
|
}
|
|
|
|
flushQueueSync() {
|
|
if (this.queue.length === 0) return;
|
|
|
|
// Используем sendBeacon для надежной отправки при закрытии страницы
|
|
const data = JSON.stringify({
|
|
events: this.queue,
|
|
sessionId: this.sessionId
|
|
});
|
|
|
|
if (navigator.sendBeacon) {
|
|
navigator.sendBeacon(this.endpoint, data);
|
|
}
|
|
}
|
|
|
|
getSessionId() {
|
|
let sessionId = localStorage.getItem('ga_session_id');
|
|
const now = Date.now();
|
|
|
|
if (!sessionId) {
|
|
sessionId = 'sess_' + now + '_' + Math.random().toString(36).substr(2, 9);
|
|
localStorage.setItem('ga_session_id', sessionId);
|
|
localStorage.setItem('ga_session_start', now);
|
|
}
|
|
|
|
// Обновляем время последней активности
|
|
localStorage.setItem('ga_last_activity', now);
|
|
|
|
return sessionId;
|
|
}
|
|
|
|
saveToStorage() {
|
|
try {
|
|
localStorage.setItem('ga_queue', JSON.stringify(this.queue));
|
|
} catch (e) {
|
|
console.warn('Cannot save analytics to localStorage');
|
|
}
|
|
}
|
|
|
|
loadFromStorage() {
|
|
try {
|
|
const stored = localStorage.getItem('ga_queue');
|
|
if (stored) {
|
|
const parsed = JSON.parse(stored);
|
|
if (Array.isArray(parsed)) {
|
|
this.queue = parsed;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.warn('Cannot load analytics from localStorage');
|
|
}
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Отслеживание кликов по кнопкам
|
|
document.addEventListener('click', (e) => {
|
|
if (e.target.matches('button, .btn, a[href]')) {
|
|
const context = e.target.closest('.section') ?
|
|
e.target.closest('.section').querySelector('h2')?.textContent || 'unknown' :
|
|
'global';
|
|
this.trackClick(e.target, context);
|
|
|
|
// Специальные события для кнопок сотрудничества
|
|
if (e.target.textContent.includes('сотрудничество') || e.target.textContent.includes('Написать')) {
|
|
this.trackEvent('conversion', 'contact_click', e.target.textContent.trim());
|
|
}
|
|
}
|
|
});
|
|
|
|
// Отслеживание отправки форм
|
|
document.addEventListener('submit', (e) => {
|
|
this.trackEvent('form', 'submit', e.target.id || 'unknown');
|
|
});
|
|
|
|
// Отслеживание видимости секций
|
|
this.setupSectionTracking();
|
|
|
|
// Отслеживание внешних ссылок
|
|
document.addEventListener('click', (e) => {
|
|
const link = e.target.closest('a[href]');
|
|
if (link && link.hostname !== window.location.hostname) {
|
|
this.trackEvent('outbound', 'click', link.href);
|
|
}
|
|
});
|
|
}
|
|
|
|
setupSectionTracking() {
|
|
const sections = document.querySelectorAll('.section');
|
|
const observedSections = new Set();
|
|
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
|
|
const sectionId = entry.target.id ||
|
|
entry.target.querySelector('h2')?.textContent?.substring(0, 50) ||
|
|
'unknown_section';
|
|
|
|
if (!observedSections.has(sectionId)) {
|
|
observedSections.add(sectionId);
|
|
this.trackEvent('content', 'section_view', sectionId);
|
|
}
|
|
}
|
|
});
|
|
}, {
|
|
threshold: [0.5],
|
|
rootMargin: '0px 0px -10% 0px'
|
|
});
|
|
|
|
sections.forEach(section => {
|
|
observer.observe(section);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Инициализация при полной загрузке DOM
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.analytics = new CustomAnalytics();
|
|
|
|
// Глобальные функции для ручного отслеживания
|
|
window.trackEvent = (category, action, label, value) => {
|
|
if (window.analytics) {
|
|
window.analytics.trackEvent(category, action, label, value);
|
|
}
|
|
};
|
|
|
|
// Отслеживание специальных событий для вашего сайта
|
|
const specialButtons = document.querySelectorAll('[onclick*="sendMessageTelegram"]');
|
|
specialButtons.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
trackEvent('business', 'telegram_click', btn.textContent.trim());
|
|
});
|
|
});
|
|
|
|
// Отслеживание просмотра ключевых элементов
|
|
const keyElements = document.querySelectorAll('.hero, .team-section, .yalarba-section');
|
|
const elementObserver = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
const elementType = entry.target.className.split(' ')[0];
|
|
trackEvent('engagement', `${elementType}_viewed`);
|
|
elementObserver.unobserve(entry.target);
|
|
}
|
|
});
|
|
}, { threshold: 0.3 });
|
|
|
|
keyElements.forEach(el => elementObserver.observe(el));
|
|
});
|
|
|
|
// Fallback для старых браузеров
|
|
if (!window.Promise) {
|
|
console.warn('Custom analytics requires Promise support');
|
|
window.trackEvent = function () { };
|
|
} |