@alexsab-ru/scripts 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/analytics.js CHANGED
@@ -1,7 +1,9 @@
1
+ import { sendToCallTouch } from './calltouch';
2
+
1
3
  window.dataLayer = window.dataLayer || [];
2
4
 
3
5
  export function reachGoal(eventAction, t = {}) {
4
- t.eventAction = eventAction;
6
+ t.eventAction = eventAction;
5
7
  if(isGTMInstalled()) {
6
8
  dl("reachGoal-"+eventAction, {
7
9
  ...t
@@ -12,6 +14,20 @@ export function reachGoal(eventAction, t = {}) {
12
14
  ...t
13
15
  });
14
16
  }
17
+ if(window.calltouch_params) {
18
+ t.siteId = window.calltouch_params.site_id;
19
+ // console.log("sendToCallTouch:", t);
20
+ sendToCallTouch(t)
21
+ .then(result => {
22
+ console.log('Данные успешно отправлены в колтач:', eventAction, result);
23
+ })
24
+ .catch(error => {
25
+ console.error('Ошибка при отправке данных в колтач:', eventAction, error);
26
+ });
27
+ if(window.ct) {
28
+ window.ct(window.calltouch_params.mod_id, 'goal', eventAction);
29
+ }
30
+ }
15
31
  ymGoal(eventAction, {
16
32
  ...t
17
33
  });
@@ -19,7 +35,7 @@ export function reachGoal(eventAction, t = {}) {
19
35
  }
20
36
 
21
37
  export function pageView(eventAction, t = {}) {
22
- t.eventAction = eventAction;
38
+ t.eventAction = eventAction;
23
39
  if(isGTMInstalled()) {
24
40
  dl("pageView-"+eventAction, {
25
41
  ...t
@@ -37,172 +53,172 @@ export function pageView(eventAction, t = {}) {
37
53
  }
38
54
 
39
55
  export function dl(event, t = {}) {
40
- // console.log(event, t);
41
- void 0 !== window.dataLayer && window.dataLayer.push({
42
- event: event,
43
- ...t
44
- })
45
- }
56
+ // console.log(event, t);
57
+ void 0 !== window.dataLayer && window.dataLayer.push({
58
+ event: event,
59
+ ...t
60
+ })
61
+ }
46
62
 
47
63
  export function getFormDataObject(formData, form_id) {
48
- let eventProperties = {};
49
- formData.forEach((value, key) => (eventProperties[key] = value));
50
- eventProperties['formID'] = form_id;
51
- return {
52
- "eventProperties": eventProperties,
53
- "eventCategory": "Lead",
54
- "sourceName": "page",
55
- };
56
- }
64
+ let eventProperties = {};
65
+ formData.forEach((value, key) => (eventProperties[key] = value));
66
+ eventProperties['formID'] = form_id;
67
+ return {
68
+ "eventProperties": eventProperties,
69
+ "eventCategory": "Lead",
70
+ "sourceName": "page",
71
+ };
72
+ }
57
73
 
58
74
  export function gGoal(goalName,goalParams){
59
- try {
60
- dl(goalName, goalParams)
61
- } catch (err) {
62
- console.error(goalName + ' - error send goal to Google Analytics');
63
- }
64
- }
65
-
75
+ try {
76
+ dl(goalName, goalParams)
77
+ } catch (err) {
78
+ console.error(goalName + ' - error send goal to Google Analytics');
79
+ }
80
+ }
81
+
66
82
 
67
83
  export function ymGoal(goalName,goalParams) {
68
84
  // console.log("ymGoal:", goalName,goalParams);
69
- try {
70
- Ya._metrika.getCounters().forEach((me)=>{
71
- ym(me.id, "reachGoal", goalName, goalParams||{})
72
- })
73
- } catch (err) {
74
- console.error(goalName + ' - error send goal to Metrika');
75
- }
85
+ try {
86
+ Ya._metrika.getCounters().forEach((me)=>{
87
+ ym(me.id, "reachGoal", goalName, goalParams||{})
88
+ })
89
+ } catch (err) {
90
+ console.error(goalName + ' - error send goal to Metrika');
76
91
  }
92
+ }
77
93
 
78
94
  export function ymPage(pageName,goalParams) {
79
- try {
80
- Ya._metrika.getCounters().forEach((me)=>{
81
- ym(me.id, "hit", pageName, goalParams||{})
82
- })
83
- } catch (err) {
84
- console.error(pageName + ' - error send page to Metrika');
85
- }
95
+ try {
96
+ Ya._metrika.getCounters().forEach((me)=>{
97
+ ym(me.id, "hit", pageName, goalParams||{})
98
+ })
99
+ } catch (err) {
100
+ console.error(pageName + ' - error send page to Metrika');
86
101
  }
102
+ }
87
103
 
88
104
  export function addClickCopyContextmenuGoals(item, prefix) {
89
- item.addEventListener('click', function(evt) {
90
- reachGoal(prefix + '_click');
91
- });
92
- item.addEventListener('copy', function(evt) {
93
- reachGoal(prefix + '_copy');
94
- });
95
- item.addEventListener('contextmenu', function(evt) {
96
- reachGoal(prefix + '_contextmenu');
97
- });
98
- }
99
-
100
- document.querySelectorAll('a[href^\="tel:"]').forEach((tel)=>{
101
- addClickCopyContextmenuGoals(tel, "phone");
105
+ item.addEventListener('click', function(evt) {
106
+ reachGoal(prefix + '_click');
102
107
  });
103
- document.querySelectorAll('a[href^\="mailto:"]').forEach((tel)=>{
104
- addClickCopyContextmenuGoals(tel, "email");
108
+ item.addEventListener('copy', function(evt) {
109
+ reachGoal(prefix + '_copy');
105
110
  });
111
+ item.addEventListener('contextmenu', function(evt) {
112
+ reachGoal(prefix + '_contextmenu');
113
+ });
114
+ }
106
115
 
107
- let goals = [
108
- {
109
- selector: 'form input',
110
- action: 'click',
111
- goal: 'form_click',
112
- title: 'Клик в поле любой формы',
113
- },
114
- {
115
- selector: 'form input',
116
- action: 'change',
117
- goal: 'form_change',
118
- title: 'Изменения полей любой формы',
119
- },
120
-
121
- ];
122
-
123
- goals.forEach(function(value, index, array){
124
- if(value.goal != null) {
125
- document.querySelectorAll(value.selector).forEach(function(element) {
126
- // console.log("Set \"" + value.goal + "\" goal", element);
127
- element.addEventListener(value.action, function(){
128
- reachGoal(value.goal, {
129
- title: value.title,
130
- });
116
+ document.querySelectorAll('a[href^\="tel:"]').forEach((tel)=>{
117
+ addClickCopyContextmenuGoals(tel, "phone");
118
+ });
119
+ document.querySelectorAll('a[href^\="mailto:"]').forEach((tel)=>{
120
+ addClickCopyContextmenuGoals(tel, "email");
121
+ });
122
+
123
+ let goals = [
124
+ {
125
+ selector: 'form input',
126
+ action: 'click',
127
+ goal: 'form_click',
128
+ title: 'Клик в поле любой формы',
129
+ },
130
+ {
131
+ selector: 'form input',
132
+ action: 'change',
133
+ goal: 'form_change',
134
+ title: 'Изменения полей любой формы',
135
+ },
136
+
137
+ ];
138
+
139
+ goals.forEach(function(value, index, array){
140
+ if(value.goal != null) {
141
+ document.querySelectorAll(value.selector).forEach(function(element) {
142
+ // console.log("Set \"" + value.goal + "\" goal", element);
143
+ element.addEventListener(value.action, function(){
144
+ reachGoal(value.goal, {
145
+ title: value.title,
131
146
  });
132
147
  });
133
- } else if(value.hit != null) {
134
- document.querySelectorAll(value.selector).forEach(function(element) {
135
- // console.log("Set \"" + value.goal + "\" hit", element);
136
- element.addEventListener(value.action, function(){
137
- pageView(value.hit, {
138
- title: value.title,
139
- });
148
+ });
149
+ } else if(value.hit != null) {
150
+ document.querySelectorAll(value.selector).forEach(function(element) {
151
+ // console.log("Set \"" + value.goal + "\" hit", element);
152
+ element.addEventListener(value.action, function(){
153
+ pageView(value.hit, {
154
+ title: value.title,
140
155
  });
141
156
  });
142
- } else {
143
- console.warn("Ошибка в списке целей", value);
144
- }
145
- });
157
+ });
158
+ } else {
159
+ console.warn("Ошибка в списке целей", value);
160
+ }
161
+ });
162
+
163
+ function isGTMInstalled() {
164
+ // console.log("gtm.js:", window.dataLayer.length > 0 && dataLayer[0].event == "gtm.js");
165
+ // console.log("window.isGTMInstalled:", window.isGTMInstalled);
166
+ if (window.dataLayer.length > 0 && dataLayer[0].event == "gtm.js") return true;
167
+ switch(window.isGTMInstalled) {
168
+ case true:
169
+ return true;
170
+ break;
171
+ case false:
172
+ return false;
173
+ break;
174
+ default:
175
+ }
146
176
 
147
- function isGTMInstalled() {
148
- // console.log("gtm.js:", window.dataLayer.length > 0 && dataLayer[0].event == "gtm.js");
149
- // console.log("window.isGTMInstalled:", window.isGTMInstalled);
150
- if (window.dataLayer.length > 0 && dataLayer[0].event == "gtm.js") return true;
151
- switch(window.isGTMInstalled) {
152
- case true:
153
- return true;
154
- break;
155
- case false:
156
- return false;
157
- break;
158
- default:
177
+ let scripts = document.querySelectorAll('script');
178
+ for (let script of scripts) {
179
+ if (script.src && script.src.includes('gtm.js')) {
180
+ window.isGTMInstalled = true;
181
+ return true;
159
182
  }
160
-
161
- let scripts = document.querySelectorAll('script');
162
- for (let script of scripts) {
163
- if (script.src && script.src.includes('gtm.js')) {
164
- window.isGTMInstalled = true;
183
+ }
184
+ window.isGTMInstalled = false;
185
+ return false;
186
+ }
187
+
188
+ function checkGA4InDataLayer() {
189
+ // console.log("window.ga4Installed:", window.ga4Installed);
190
+ switch(window.ga4Installed) {
191
+ case true:
165
192
  return true;
166
- }
167
- }
168
- window.isGTMInstalled = false;
169
- return false;
193
+ break;
194
+ case false:
195
+ return false;
196
+ break;
197
+ default:
170
198
  }
171
199
 
172
- function checkGA4InDataLayer() {
173
- // console.log("window.ga4Installed:", window.ga4Installed);
174
- switch(window.ga4Installed) {
175
- case true:
176
- return true;
177
- break;
178
- case false:
179
- return false;
180
- break;
181
- default:
200
+ if (typeof window.dataLayer !== 'undefined') {
201
+ // Просмотр событий в dataLayer для поиска конфигурации GA4
202
+ const ga4Config = window.dataLayer.find(item =>
203
+ item && item[0] === 'config' && item[1] && item[1].startsWith('G-')
204
+ );
205
+
206
+ if (ga4Config) {
207
+ // console.log('GA4 настройка найдена в dataLayer:', ga4Config[1]);
208
+ window.ga4Installed = true;
209
+ return true;
182
210
  }
211
+ }
183
212
 
184
- if (typeof window.dataLayer !== 'undefined') {
185
- // Просмотр событий в dataLayer для поиска конфигурации GA4
186
- const ga4Config = window.dataLayer.find(item =>
187
- item && item[0] === 'config' && item[1] && item[1].startsWith('G-')
188
- );
189
-
190
- if (ga4Config) {
191
- // console.log('GA4 настройка найдена в dataLayer:', ga4Config[1]);
192
- window.ga4Installed = true;
193
- return true;
194
- }
213
+ const scripts = document.querySelectorAll('script');
214
+ scripts.forEach(script => {
215
+ if (script.src && script.src.includes('gtag.js')) {
216
+ window.ga4Installed = true;
217
+ return true;
195
218
  }
219
+ });
196
220
 
197
- const scripts = document.querySelectorAll('script');
198
- scripts.forEach(script => {
199
- if (script.src && script.src.includes('gtag.js')) {
200
- window.ga4Installed = true;
201
- return true;
202
- }
203
- });
204
-
205
- // console.log('GA4 конфигурация не найдена в dataLayer');
206
- window.ga4Installed = false;
207
- return false;
208
- }
221
+ // console.log('GA4 конфигурация не найдена в dataLayer');
222
+ window.ga4Installed = false;
223
+ return false;
224
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Модуль для отправки данных в CallTouch API
3
+ * @module calltouch-integration
4
+ */
5
+
6
+ /**
7
+ * Проверяет значение и возвращает пустую строку, если значение пустое или undefined
8
+ * @param {*} value - Проверяемое значение
9
+ * @return {string} Проверенное значение или пустая строка
10
+ */
11
+ const getValueOrEmpty = (value) => {
12
+ if (value === undefined || value === null || value === 'undefined') {
13
+ return '';
14
+ }
15
+ return String(value);
16
+ };
17
+
18
+ /**
19
+ * Обрабатывает данные события и формирует объект для отправки в CallTouch
20
+ * @param {Object} eventData - Данные о событии
21
+ * @param {string} sessionId - ID сессии
22
+ * @return {Object} Объект с данными и комментарием для отправки
23
+ */
24
+ function processEventData(eventData, sessionId) {
25
+ // Основные поля для передачи в API
26
+ const basicFields = {
27
+ name: { key: 'fio', label: null },
28
+ phone: { key: 'phoneNumber', label: null },
29
+ email: { key: 'email', label: null },
30
+ form: { key: 'subject', label: null }
31
+ };
32
+
33
+ // Поля для включения в комментарий
34
+ const commentFields = {
35
+ dealer: { label: 'Дилер' },
36
+ dealershipName: { label: 'ДЦ' },
37
+ salon: { label: 'Салон' },
38
+ vehicleNameplate: { label: 'Авто' },
39
+ priceDealership: { label: 'Цена' },
40
+ vehicleModel: { label: 'Модель' },
41
+ vehicleBrand: { label: 'Марка' },
42
+ service: { label: 'Услуга' },
43
+ source: { label: 'Источник' },
44
+ medium: { label: 'Канал' },
45
+ campaign: { label: 'Кампания' }
46
+ // Можно добавить любые другие поля по мере необходимости
47
+ };
48
+
49
+ // Собираем основные данные
50
+ const ct_data = {
51
+ requestUrl: location.href,
52
+ sessionId: sessionId
53
+ };
54
+
55
+ // Обрабатываем основные поля
56
+ Object.entries(basicFields).forEach(([fieldName, config]) => {
57
+ const value = getValueOrEmpty(eventData[fieldName]);
58
+ if (value) {
59
+ ct_data[config.key] = value;
60
+ }
61
+ });
62
+
63
+ // Формируем комментарий из всех доступных полей
64
+ const ct_comment = [];
65
+
66
+ Object.entries(commentFields).forEach(([fieldName, config]) => {
67
+ const value = getValueOrEmpty(eventData[fieldName]);
68
+ if (value) {
69
+ ct_comment.push(`${config.label}: ${value}`);
70
+ }
71
+ });
72
+
73
+ // Добавляем комментарий в объект данных, если он не пустой
74
+ if (ct_comment.length > 0) {
75
+ ct_data.comment = ct_comment.join(', ');
76
+ }
77
+
78
+ return ct_data;
79
+ }
80
+
81
+ /**
82
+ * Отправляет данные лида в CallTouch API
83
+ * @param {Object} options - Параметры для отправки
84
+ * @param {string} options.siteId - ID сайта в CallTouch
85
+ * @param {string} options.eventCategory - Категория события (например, 'Lead')
86
+ * @param {Object} options.eventProperties - Данные о событии
87
+ * @param {string} [options.eventProperties.name] - Имя клиента
88
+ * @param {string} options.eventProperties.phone - Телефон клиента
89
+ * @param {string} [options.eventProperties.email] - Email клиента
90
+ * @param {string} [options.eventProperties.form] - Название формы
91
+ * @param {string} [options.eventProperties.dealershipName] - Название дилерского центра
92
+ * @param {string} [options.eventProperties.salon] - Название салона
93
+ * @param {string} [options.eventProperties.vehicleNameplate] - Название автомобиля
94
+ * @param {string} [options.eventProperties.priceDealership] - Цена автомобиля
95
+ * @param {string} [options.sessionId] - ID сессии (опционально, по умолчанию window.call_value)
96
+ * @return {Promise} Promise с результатом запроса
97
+ */
98
+ function sendToCallTouch(options) {
99
+ return new Promise((resolve, reject) => {
100
+ try {
101
+ const { siteId, eventCategory, eventProperties, sessionId = window.call_value } = options;
102
+
103
+ // Проверяем, что категория события - Lead
104
+ if (eventCategory !== 'Lead') {
105
+ return resolve({ status: 'skipped', message: 'Not a Lead event' });
106
+ }
107
+
108
+ // Проверяем обязательные параметры
109
+ if (!siteId) {
110
+ return reject(new Error('siteId is required'));
111
+ }
112
+
113
+ // Обрабатываем данные с помощью оптимизированной функции
114
+ const ct_data = processEventData(eventProperties, sessionId);
115
+
116
+ // Проверяем наличие телефона
117
+ if (!ct_data.phoneNumber) {
118
+ return resolve({ status: 'error', message: 'Phone number is required' });
119
+ }
120
+
121
+ // Формируем строку параметров для POST-запроса
122
+ const post_data = Object.keys(ct_data)
123
+ .filter(key => ct_data[key]) // Исключаем пустые поля
124
+ .map(key => key + '=' + encodeURIComponent(ct_data[key]))
125
+ .join('&');
126
+
127
+ // URL для запроса к API CallTouch
128
+ const CT_URL = 'https://api.calltouch.ru/calls-service/RestAPI/requests/' + siteId + '/register/';
129
+
130
+ // Проверяем, не отправляется ли уже запрос
131
+ if (window.ct_snd_flag) {
132
+ return resolve({ status: 'skipped', message: 'Another request is in progress' });
133
+ }
134
+
135
+ // Устанавливаем флаг для предотвращения дублирования запросов
136
+ window.ct_snd_flag = 1;
137
+ setTimeout(function() {
138
+ window.ct_snd_flag = 0;
139
+ }, 20000);
140
+
141
+ // Отправляем запрос
142
+ const request = new XMLHttpRequest();
143
+ request.open("POST", CT_URL, true);
144
+ request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
145
+
146
+ request.onload = function() {
147
+ if (this.status >= 200 && this.status < 400) {
148
+ resolve({
149
+ status: 'success',
150
+ response: this.responseText,
151
+ data: ct_data
152
+ });
153
+ } else {
154
+ reject(new Error('API returned status: ' + this.status));
155
+ }
156
+ };
157
+
158
+ request.onerror = function() {
159
+ reject(new Error('Connection error'));
160
+ };
161
+
162
+ request.send(post_data);
163
+ } catch (error) {
164
+ console.error('CallTouch error:', error);
165
+ reject(error);
166
+ }
167
+ });
168
+ }
169
+
170
+ // Экспорт функций для использования в других модулях
171
+ export { sendToCallTouch, getValueOrEmpty };
172
+
173
+ // Для совместимости с CommonJS
174
+ if (typeof module !== 'undefined' && module.exports) {
175
+ module.exports = {
176
+ sendToCallTouch,
177
+ getValueOrEmpty
178
+ };
179
+ }
180
+
181
+ // Для использования через тег <script>
182
+ if (typeof window !== 'undefined') {
183
+ window.CallTouchAPI = {
184
+ sendToCallTouch,
185
+ getValueOrEmpty
186
+ };
187
+ }
@@ -0,0 +1,76 @@
1
+ export const createRequest = (routeKey, phoneValue, nameValue, verbose = false) => {
2
+ return new Promise((resolve, reject) => {
3
+ let errorText = '';
4
+ if(window.ctw) {
5
+ window.ctw.getRouteKeyData(routeKey, function(success, data){
6
+ verbose && console.log(success, data);
7
+ if (success) {
8
+ if (data.widgetFound) {
9
+ if (data.widgetData.callCenterWorkingMode == 'working_hours') {
10
+ verbose && console.log('колл-центр работает, отображение виджета');
11
+ } else {
12
+ if (data.widgetData.collectNonWorkingRequests) {
13
+ verbose && console.log('колл-центр не работает, но можем отобразить форму нерабочего времени');
14
+ } else {
15
+ verbose && console.log('колл-центр не работает, заявки в нерабочее время не собираем');
16
+ }
17
+ }
18
+
19
+ var phone_ct = phoneValue.replace(/[^0-9]/gim, '');
20
+ if (phone_ct[0] == '8') { phone_ct = phone_ct.substring(1); }
21
+ if (phone_ct[0] == '7') { phone_ct = phone_ct.substring(1); }
22
+ phone_ct = '7' + phone_ct;
23
+
24
+ window.ctw.createRequest(
25
+ routeKey,
26
+ phone_ct,
27
+ ( nameValue.length > 0 ? [
28
+ {"name": "Name", "value": nameValue}
29
+ ] : []),
30
+ function (success, data) {
31
+ verbose && console.log(success, data)
32
+ if (success) {
33
+ verbose && console.log('Создана заявка на колбек, идентификатор: ' + data.callbackRequestId);
34
+ resolve(data); // Разрешаем Promise данными от createRequest
35
+ }
36
+ else {
37
+ errorText = 'Error in createRequest';
38
+ switch (data.type) {
39
+ case "request_throttle_timeout":
40
+ case "request_throttle_count":
41
+ errorText = 'Достигнут лимит создания заявок, попробуйте позже';
42
+ verbose && console.log(errorText);
43
+ break;
44
+ case "request_phone_blacklisted":
45
+ errorText = 'номер телефона находится в черном списке';
46
+ verbose && console.log(errorText);
47
+ break;
48
+ case "validation_error":
49
+ errorText = 'были переданы некорректные данные';
50
+ verbose && console.log(errorText);
51
+ break;
52
+ default:
53
+ errorText = 'Во время выполнения запроса произошла ошибка: ' + data.type;
54
+ verbose && console.log(errorText);
55
+ }
56
+ reject(errorText); // Отклоняем Promise в случае ошибки
57
+ }
58
+ }
59
+ );
60
+ } else {
61
+ errorText = 'не найден включенный виджет '+routeKey+', либо услуга обратного звонка не активна';
62
+ verbose && console.log(errorText);
63
+ reject(errorText);
64
+ }
65
+ } else {
66
+ errorText = 'во время обработки произошла ошибка';
67
+ verbose && console.log(errorText);
68
+ verbose && console.log(data)
69
+ reject(errorText);
70
+ }
71
+ });
72
+ } else {
73
+ reject("window.ctw is not defined");
74
+ }
75
+ });
76
+ };