(function () { 'use strict'; const CONFIG = { SDK_VERSION: '1.4.0', RETRY_DELAY: 500, MAX_RETRIES: 20, CLIENT_ID_FIELD: 'UF_CRM_1753275327', DEBUG: false, DEBOUNCE_DELAY: 300, STORAGE_PREFIX: 'ym_', COOKIE_MAX_AGE: 3600, EXCLUDE_SELECTORS: ['.no-tracking', '[data-no-ym]'], LOG_LEVEL: 'warn' }; let retryCount = 0; let injectTimeout; let pendingForms = new Set(); let dynamicFormsObserver = null; function log(level, ...args) { const LEVELS = { error: 0, warn: 1, info: 2, debug: 3 }; const currentLevel = LEVELS[CONFIG.LOG_LEVEL] ?? 1; if (LEVELS[level] <= currentLevel) { const prefix = `[YM-ClientID v${CONFIG.SDK_VERSION}${CONFIG.DEBUG ? '-DEBUG' : ''}]`; const logger = console[level] || console.log; logger(prefix, ...args); } } function shouldProcessForm(form) { if (CONFIG.EXCLUDE_SELECTORS.some(sel => form.matches(sel) || form.closest(sel))) { log('debug', 'Форма исключена из обработки', form); return false; } if (!form.getAttribute('action') && !form.hasAttribute('data-allow-no-action') && false) { log('debug', 'Пропущена форма без action', form); return false; } return true; } function createClientIdInput(value) { const input = document.createElement('input'); input.type = 'hidden'; input.name = CONFIG.CLIENT_ID_FIELD; input.value = value; return input; } function storeClientId(clientId) { if (!clientId) return; try { sessionStorage.setItem(CONFIG.STORAGE_PREFIX + 'client_id', clientId); log('debug', 'Client ID сохранён в sessionStorage'); } catch { try { document.cookie = `${CONFIG.STORAGE_PREFIX}client_id=${encodeURIComponent(clientId)}; path=/; max-age=${CONFIG.COOKIE_MAX_AGE}; Secure; SameSite=Lax`; window._ymClientIdFallback = clientId; log('debug', 'Client ID сохранён в cookie'); } catch { log('error', 'Не удалось сохранить client_id'); } } window.__YM_CLIENT_ID__ = clientId; } function getStoredClientId() { try { return sessionStorage.getItem(CONFIG.STORAGE_PREFIX + 'client_id'); } catch { const match = document.cookie.match(new RegExp(`${CONFIG.STORAGE_PREFIX}client_id=([^;]+)`)); return match ? decodeURIComponent(match[1]) : window._ymClientIdFallback || null; } } function addClientIdToForms(clientId) { if (!clientId || typeof clientId !== 'string') { log('warn', 'Некорректный client_id:', clientId); return; } const forms = Array.from(document.querySelectorAll('form.bitrix24')) .filter(form => shouldProcessForm(form)) .filter(form => !form.querySelector(`input[name="${CONFIG.CLIENT_ID_FIELD}"]`)); forms.forEach(form => { try { form.appendChild(createClientIdInput(clientId)); } catch (error) { log('error', 'Ошибка при добавлении client_id в форму:', error, form); } }); if (forms.length > 0) log('info', `Добавлен client_id в ${forms.length} форм`); } function addClientIdToSingleForm(form, clientId) { if (shouldProcessForm(form) && !form.querySelector(`input[name="${CONFIG.CLIENT_ID_FIELD}"]`)) { try { form.appendChild(createClientIdInput(clientId)); log('debug', 'Добавлен client_id в динамическую форму'); } catch (error) { log('error', 'Ошибка при добавлении client_id в динамическую форму:', error); } } } function getCounterId() { const script = document.currentScript; if (script?.dataset.metrikaId) { log('debug', 'Счётчик найден через data-атрибут:', script.dataset.metrikaId); return script.dataset.metrikaId; } if (window.YM_COUNTER_ID) { log('debug', 'Счётчик найден через глобальную переменную:', window.YM_COUNTER_ID); return window.YM_COUNTER_ID; } if (typeof Ya !== 'undefined' && Ya.Metrika && Ya.Metrika.counters) { const counters = Object.keys(Ya.Metrika.counters); if (counters.length > 0) { log('debug', 'Счётчик найден через Ya.Metrika.counters:', counters[0]); return counters[0]; } } if (typeof ym === 'function' && ym.getCounters) { const counters = ym.getCounters(); if (counters && counters[0]?.id) { log('debug', 'Счётчик найден через ym.getCounters():', counters[0].id); return counters[0].id; } } return findMetrikaCounter(); } function findMetrikaCounter() { if (window.ym?.a?.length > 0) { log('debug', 'Счётчик найден через ym.a:', window.ym.a[0][0]); return window.ym.a[0][0]; } if (window.Ya?.Metrika2?._metrika) { const counters = Object.values(window.Ya.Metrika2._metrika); if (counters.length > 0 && counters[0].id) { log('debug', 'Счётчик найден через Ya.Metrika2._metrika:', counters[0].id); return counters[0].id; } } log('debug', 'Счётчики Метрики не найдены'); return null; } function waitForMetrika(callback, maxWait = 5000) { const start = Date.now(); (function check() { if (typeof ym === 'function') callback(); else if (Date.now() - start < maxWait) setTimeout(check, 100); else { log('warn', 'Таймаут ожидания загрузки Метрики'); callback(); } })(); } function getClientId() { try { const counterId = getCounterId(); if (!counterId) { log('debug', 'ID счётчика не найден, повторная попытка'); return retryWithDelay(); } waitForMetrika(() => { try { ym(counterId, 'getClientID', function (clientID) { if (clientID) { log('debug', 'Получен client_id от Метрики'); addClientIdToForms(clientID); storeClientId(clientID); observeDynamicForms(clientID); } else { log('warn', 'Метрика вернула пустой clientID'); retryWithDelay(); } }); } catch (error) { log('error', 'Ошибка вызова ym.getClientID:', error); retryWithDelay(); } }); } catch (error) { log('error', 'Ошибка при получении client_id:', error); retryWithDelay(); } } function retryWithDelay() { retryCount++; if (retryCount < CONFIG.MAX_RETRIES) { log('debug', `Повторная попытка ${retryCount}/${CONFIG.MAX_RETRIES}`); setTimeout(getClientId, CONFIG.RETRY_DELAY); } else { log('error', 'Превышено количество попыток получения client_id'); const storedClientId = getStoredClientId(); if (storedClientId) { log('info', 'Используем сохранённый client_id'); addClientIdToForms(storedClientId); observeDynamicForms(storedClientId); } } } function observeDynamicForms(clientId) { if (!clientId) return; if (dynamicFormsObserver) { dynamicFormsObserver.disconnect(); pendingForms.clear(); } dynamicFormsObserver = new MutationObserver(mutations => { let hasNewForms = false; mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === 1) { if (node.tagName === 'FORM') { pendingForms.add(node); hasNewForms = true; } else if (node.querySelectorAll) { node.querySelectorAll('form.bitrix24').forEach(f => { pendingForms.add(f); hasNewForms = true; }); } } }); mutation.removedNodes.forEach(node => { if (node.nodeType === 1) { if (node.tagName === 'FORM') pendingForms.delete(node); node.querySelectorAll?.('form').forEach(f => pendingForms.delete(f)); } }); }); if (hasNewForms) { clearTimeout(injectTimeout); injectTimeout = setTimeout(() => { pendingForms.forEach(f => addClientIdToSingleForm(f, clientId)); pendingForms.clear(); }, CONFIG.DEBOUNCE_DELAY); } }); dynamicFormsObserver.observe(document.body || document.documentElement, { childList: true, subtree: true }); log('debug', 'Запущено наблюдение за динамическими формами'); } (function patchFormData() { const originalAppend = FormData.prototype.append; FormData.prototype.append = function (name, value, ...rest) { const storedId = getStoredClientId(); if (!this.__hasClientId && storedId) { originalAppend.call(this, CONFIG.CLIENT_ID_FIELD, storedId); this.__hasClientId = true; } return originalAppend.apply(this, arguments); }; log('debug', 'Патч FormData активирован'); })(); function init() { const storedClientId = getStoredClientId(); if (storedClientId) { log('debug', 'Найден сохранённый client_id'); addClientIdToForms(storedClientId); observeDynamicForms(storedClientId); window.__YM_CLIENT_ID__ = storedClientId; } else { log('debug', 'Начало получения client_id от Метрики'); getClientId(); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();