@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.
@@ -0,0 +1,336 @@
1
+ import { reachGoal } from '../analytics';
2
+
3
+ /**
4
+ * Настройки для NoBounce модуля
5
+ */
6
+ const defaultOptions = {
7
+ /**
8
+ * Задержка в секундах перед отправкой цели NoBounce
9
+ * @default 15
10
+ */
11
+ delay: 15,
12
+
13
+ /**
14
+ * Название цели для отправки в аналитику
15
+ * @default 'NoBounce'
16
+ */
17
+ goalName: 'NoBounce',
18
+
19
+ /**
20
+ * Дополнительные параметры для отправки с целью
21
+ * @default {}
22
+ */
23
+ goalParams: {},
24
+
25
+ /**
26
+ * Автоматически запускать при инициализации
27
+ * @default true
28
+ */
29
+ autoStart: true,
30
+
31
+ /**
32
+ * Отправлять цель только один раз за сессию
33
+ * @default true
34
+ */
35
+ oncePerSession: true,
36
+
37
+ /**
38
+ * Проверять видимость страницы (Page Visibility API)
39
+ * Если страница скрыта, таймер останавливается
40
+ * @default true
41
+ */
42
+ checkVisibility: true,
43
+
44
+ /**
45
+ * Логировать отправку цели в консоль
46
+ * @default false
47
+ */
48
+ debug: false
49
+ };
50
+
51
+ /**
52
+ * Класс для управления NoBounce целью
53
+ */
54
+ class NoBounceManager {
55
+ constructor(options = {}) {
56
+ this.config = { ...defaultOptions, ...options };
57
+ this.timerId = null;
58
+ this.isGoalSent = false;
59
+ this.startTime = Date.now();
60
+ this.visibilityTime = 0; // Время, проведенное на видимой странице
61
+ this.lastVisibilityChange = Date.now();
62
+
63
+ // Проверяем сессионное хранилище
64
+ if (this.config.oncePerSession) {
65
+ const sessionKey = `nobounce_sent_${this.config.goalName}`;
66
+ this.isGoalSent = sessionStorage.getItem(sessionKey) === 'true';
67
+ }
68
+
69
+ if (this.config.autoStart && !this.isGoalSent) {
70
+ this.start();
71
+ }
72
+
73
+ this._bindVisibilityEvents();
74
+ }
75
+
76
+ /**
77
+ * Запускает таймер для отправки NoBounce цели
78
+ */
79
+ start() {
80
+ if (this.isGoalSent) {
81
+ if (this.config.debug) {
82
+ console.log('NoBounce: цель уже была отправлена в этой сессии');
83
+ }
84
+ return;
85
+ }
86
+
87
+ if (this.timerId) {
88
+ this.stop();
89
+ }
90
+
91
+ const delayMs = this.config.delay * 1000;
92
+
93
+ if (this.config.debug) {
94
+ console.log(`NoBounce: запущен таймер на ${this.config.delay} секунд`);
95
+ }
96
+
97
+ this.timerId = setTimeout(() => {
98
+ this._sendGoal();
99
+ }, delayMs);
100
+ }
101
+
102
+ /**
103
+ * Останавливает таймер
104
+ */
105
+ stop() {
106
+ if (this.timerId) {
107
+ clearTimeout(this.timerId);
108
+ this.timerId = null;
109
+
110
+ if (this.config.debug) {
111
+ console.log('NoBounce: таймер остановлен');
112
+ }
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Принудительно отправляет цель NoBounce
118
+ */
119
+ sendGoalNow() {
120
+ this.stop();
121
+ this._sendGoal();
122
+ }
123
+
124
+ /**
125
+ * Сбрасывает состояние (для тестирования или повторного использования)
126
+ */
127
+ reset() {
128
+ this.stop();
129
+ this.isGoalSent = false;
130
+ this.startTime = Date.now();
131
+ this.visibilityTime = 0;
132
+ this.lastVisibilityChange = Date.now();
133
+
134
+ if (this.config.oncePerSession) {
135
+ const sessionKey = `nobounce_sent_${this.config.goalName}`;
136
+ sessionStorage.removeItem(sessionKey);
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Получает статистику времени на странице
142
+ */
143
+ getTimeStats() {
144
+ const now = Date.now();
145
+ const totalTime = Math.floor((now - this.startTime) / 1000);
146
+ const visibleTime = Math.floor((this.visibilityTime + (document.hidden ? 0 : now - this.lastVisibilityChange)) / 1000);
147
+
148
+ return {
149
+ totalTime,
150
+ visibleTime,
151
+ hiddenTime: totalTime - visibleTime,
152
+ goalSent: this.isGoalSent
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Отправляет цель в аналитику
158
+ * @private
159
+ */
160
+ _sendGoal() {
161
+ if (this.isGoalSent) {
162
+ return;
163
+ }
164
+
165
+ const stats = this.getTimeStats();
166
+ const goalParams = {
167
+ ...this.config.goalParams,
168
+ timeOnPage: stats.totalTime,
169
+ visibleTime: stats.visibleTime,
170
+ timestamp: new Date().toISOString()
171
+ };
172
+
173
+ try {
174
+ reachGoal(this.config.goalName, goalParams);
175
+ this.isGoalSent = true;
176
+
177
+ // Сохраняем в sessionStorage
178
+ if (this.config.oncePerSession) {
179
+ const sessionKey = `nobounce_sent_${this.config.goalName}`;
180
+ sessionStorage.setItem(sessionKey, 'true');
181
+ }
182
+
183
+ if (this.config.debug) {
184
+ console.log('NoBounce: цель отправлена', {
185
+ goal: this.config.goalName,
186
+ params: goalParams,
187
+ stats
188
+ });
189
+ }
190
+
191
+ } catch (error) {
192
+ console.error('NoBounce: ошибка при отправке цели', error);
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Привязывает события видимости страницы
198
+ * @private
199
+ */
200
+ _bindVisibilityEvents() {
201
+ if (!this.config.checkVisibility || typeof document.hidden === 'undefined') {
202
+ return;
203
+ }
204
+
205
+ const handleVisibilityChange = () => {
206
+ const now = Date.now();
207
+
208
+ if (document.hidden) {
209
+ // Страница скрыта - останавливаем таймер и сохраняем время
210
+ this.visibilityTime += now - this.lastVisibilityChange;
211
+ this.stop();
212
+
213
+ if (this.config.debug) {
214
+ console.log('NoBounce: страница скрыта, таймер остановлен');
215
+ }
216
+ } else {
217
+ // Страница показана - возобновляем таймер если цель не отправлена
218
+ this.lastVisibilityChange = now;
219
+
220
+ if (!this.isGoalSent && !this.timerId) {
221
+ const remainingTime = Math.max(0, this.config.delay - Math.floor(this.visibilityTime / 1000));
222
+
223
+ if (remainingTime > 0) {
224
+ if (this.config.debug) {
225
+ console.log(`NoBounce: страница показана, возобновляем таймер на ${remainingTime} секунд`);
226
+ }
227
+
228
+ this.timerId = setTimeout(() => {
229
+ this._sendGoal();
230
+ }, remainingTime * 1000);
231
+ } else {
232
+ this._sendGoal();
233
+ }
234
+ }
235
+ }
236
+ };
237
+
238
+ document.addEventListener('visibilitychange', handleVisibilityChange);
239
+
240
+ // Сохраняем время начала как видимое
241
+ if (!document.hidden) {
242
+ this.lastVisibilityChange = this.startTime;
243
+ }
244
+ }
245
+ }
246
+
247
+ // Глобальный экземпляр
248
+ let globalNoBounceManager = null;
249
+
250
+ /**
251
+ * Инициализирует NoBounce модуль
252
+ * @param {Object} options - Настройки модуля
253
+ * @returns {NoBounceManager} - Экземпляр менеджера
254
+ */
255
+ export function initNoBounce(options = {}) {
256
+ if (globalNoBounceManager) {
257
+ console.warn('NoBounce: модуль уже инициализирован');
258
+ return globalNoBounceManager;
259
+ }
260
+
261
+ globalNoBounceManager = new NoBounceManager(options);
262
+ return globalNoBounceManager;
263
+ }
264
+
265
+ /**
266
+ * Получает текущий экземпляр NoBounce менеджера
267
+ * @returns {NoBounceManager|null}
268
+ */
269
+ export function getNoBounceManager() {
270
+ return globalNoBounceManager;
271
+ }
272
+
273
+ /**
274
+ * Быстрый способ запуска NoBounce с настройками по умолчанию
275
+ * @param {number} delay - Задержка в секундах (по умолчанию 15)
276
+ * @param {string} goalName - Название цели (по умолчанию 'NoBounce')
277
+ */
278
+ export function startNoBounce(delay = 15, goalName = 'NoBounce') {
279
+ return initNoBounce({
280
+ delay,
281
+ goalName,
282
+ debug: false
283
+ });
284
+ }
285
+
286
+ /**
287
+ * Останавливает текущий NoBounce таймер
288
+ */
289
+ export function stopNoBounce() {
290
+ if (globalNoBounceManager) {
291
+ globalNoBounceManager.stop();
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Принудительно отправляет NoBounce цель
297
+ */
298
+ export function sendNoBounceNow() {
299
+ if (globalNoBounceManager) {
300
+ globalNoBounceManager.sendGoalNow();
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Получает статистику времени на странице
306
+ */
307
+ export function getNoBounceStats() {
308
+ if (globalNoBounceManager) {
309
+ return globalNoBounceManager.getTimeStats();
310
+ }
311
+ return null;
312
+ }
313
+
314
+ /**
315
+ * Сбрасывает NoBounce состояние
316
+ */
317
+ export function resetNoBounce() {
318
+ if (globalNoBounceManager) {
319
+ globalNoBounceManager.reset();
320
+ }
321
+ }
322
+
323
+ // Экспорт класса для продвинутого использования
324
+ export { NoBounceManager };
325
+
326
+ // Экспорт по умолчанию
327
+ export default {
328
+ initNoBounce,
329
+ startNoBounce,
330
+ stopNoBounce,
331
+ sendNoBounceNow,
332
+ getNoBounceStats,
333
+ resetNoBounce,
334
+ getNoBounceManager,
335
+ NoBounceManager
336
+ };
@@ -0,0 +1,255 @@
1
+ // TypeScript определения для nobounce модуля
2
+
3
+ /**
4
+ * Настройки для NoBounce модуля
5
+ */
6
+ export interface NoBounceOptions {
7
+ /**
8
+ * Задержка в секундах перед отправкой цели NoBounce
9
+ * @default 15
10
+ */
11
+ delay?: number;
12
+
13
+ /**
14
+ * Название цели для отправки в аналитику
15
+ * @default 'NoBounce'
16
+ */
17
+ goalName?: string;
18
+
19
+ /**
20
+ * Дополнительные параметры для отправки с целью
21
+ * @default {}
22
+ */
23
+ goalParams?: Record<string, any>;
24
+
25
+ /**
26
+ * Автоматически запускать при инициализации
27
+ * @default true
28
+ */
29
+ autoStart?: boolean;
30
+
31
+ /**
32
+ * Отправлять цель только один раз за сессию
33
+ * @default true
34
+ */
35
+ oncePerSession?: boolean;
36
+
37
+ /**
38
+ * Проверять видимость страницы (Page Visibility API)
39
+ * Если страница скрыта, таймер останавливается
40
+ * @default true
41
+ */
42
+ checkVisibility?: boolean;
43
+
44
+ /**
45
+ * Логировать отправку цели в консоль
46
+ * @default false
47
+ */
48
+ debug?: boolean;
49
+ }
50
+
51
+ /**
52
+ * Статистика времени проведенного на странице
53
+ */
54
+ export interface TimeStats {
55
+ /**
56
+ * Общее время на странице в секундах
57
+ */
58
+ totalTime: number;
59
+
60
+ /**
61
+ * Время когда страница была видима в секундах
62
+ */
63
+ visibleTime: number;
64
+
65
+ /**
66
+ * Время когда страница была скрыта в секундах
67
+ */
68
+ hiddenTime: number;
69
+
70
+ /**
71
+ * Была ли отправлена цель NoBounce
72
+ */
73
+ goalSent: boolean;
74
+ }
75
+
76
+ /**
77
+ * Менеджер для управления NoBounce целью
78
+ */
79
+ export declare class NoBounceManager {
80
+ /**
81
+ * Конфигурация менеджера
82
+ */
83
+ readonly config: Required<NoBounceOptions>;
84
+
85
+ /**
86
+ * Была ли цель уже отправлена
87
+ */
88
+ readonly isGoalSent: boolean;
89
+
90
+ /**
91
+ * Создает новый экземпляр NoBounceManager
92
+ * @param options - Настройки менеджера
93
+ */
94
+ constructor(options?: NoBounceOptions);
95
+
96
+ /**
97
+ * Запускает таймер для отправки NoBounce цели
98
+ */
99
+ start(): void;
100
+
101
+ /**
102
+ * Останавливает таймер
103
+ */
104
+ stop(): void;
105
+
106
+ /**
107
+ * Принудительно отправляет цель NoBounce
108
+ */
109
+ sendGoalNow(): void;
110
+
111
+ /**
112
+ * Сбрасывает состояние (для тестирования или повторного использования)
113
+ */
114
+ reset(): void;
115
+
116
+ /**
117
+ * Получает статистику времени на странице
118
+ */
119
+ getTimeStats(): TimeStats;
120
+ }
121
+
122
+ /**
123
+ * Инициализирует NoBounce модуль
124
+ * @param options - Настройки модуля
125
+ * @returns Экземпляр менеджера
126
+ */
127
+ export function initNoBounce(options?: NoBounceOptions): NoBounceManager;
128
+
129
+ /**
130
+ * Получает текущий экземпляр NoBounce менеджера
131
+ * @returns Экземпляр менеджера или null если не инициализирован
132
+ */
133
+ export function getNoBounceManager(): NoBounceManager | null;
134
+
135
+ /**
136
+ * Быстрый способ запуска NoBounce с настройками по умолчанию
137
+ * @param delay - Задержка в секундах (по умолчанию 15)
138
+ * @param goalName - Название цели (по умолчанию 'NoBounce')
139
+ * @returns Экземпляр менеджера
140
+ */
141
+ export function startNoBounce(delay?: number, goalName?: string): NoBounceManager;
142
+
143
+ /**
144
+ * Останавливает текущий NoBounce таймер
145
+ */
146
+ export function stopNoBounce(): void;
147
+
148
+ /**
149
+ * Принудительно отправляет NoBounce цель
150
+ */
151
+ export function sendNoBounceNow(): void;
152
+
153
+ /**
154
+ * Получает статистику времени на странице
155
+ * @returns Статистика или null если менеджер не инициализирован
156
+ */
157
+ export function getNoBounceStats(): TimeStats | null;
158
+
159
+ /**
160
+ * Сбрасывает NoBounce состояние
161
+ */
162
+ export function resetNoBounce(): void;
163
+
164
+ /**
165
+ * Объект с экспортированными функциями по умолчанию
166
+ */
167
+ declare const _default: {
168
+ initNoBounce: typeof initNoBounce;
169
+ startNoBounce: typeof startNoBounce;
170
+ stopNoBounce: typeof stopNoBounce;
171
+ sendNoBounceNow: typeof sendNoBounceNow;
172
+ getNoBounceStats: typeof getNoBounceStats;
173
+ resetNoBounce: typeof resetNoBounce;
174
+ getNoBounceManager: typeof getNoBounceManager;
175
+ NoBounceManager: typeof NoBounceManager;
176
+ };
177
+
178
+ export default _default;
179
+
180
+ /**
181
+ * React Hook для использования NoBounce
182
+ */
183
+ export interface UseNoBounceHook {
184
+ /**
185
+ * Менеджер NoBounce
186
+ */
187
+ manager: NoBounceManager | null;
188
+
189
+ /**
190
+ * Статистика времени на странице
191
+ */
192
+ stats: TimeStats | null;
193
+
194
+ /**
195
+ * Была ли цель отправлена
196
+ */
197
+ goalSent: boolean;
198
+
199
+ /**
200
+ * Принудительно отправить цель
201
+ */
202
+ sendNow: () => void;
203
+
204
+ /**
205
+ * Остановить таймер
206
+ */
207
+ stop: () => void;
208
+ }
209
+
210
+ /**
211
+ * Vue композабл для NoBounce
212
+ */
213
+ export interface VueNoBounceComposable {
214
+ /**
215
+ * Реактивный менеджер NoBounce
216
+ */
217
+ manager: Ref<NoBounceManager | null>;
218
+
219
+ /**
220
+ * Реактивная статистика
221
+ */
222
+ stats: Ref<TimeStats | null>;
223
+
224
+ /**
225
+ * Статус инициализации
226
+ */
227
+ isInitialized: Ref<boolean>;
228
+
229
+ /**
230
+ * Функция инициализации с кастомными опциями
231
+ */
232
+ init: (options?: NoBounceOptions) => void;
233
+ }
234
+
235
+ // Расширение глобального объекта Window для отладки
236
+ declare global {
237
+ interface Window {
238
+ /**
239
+ * Отладочные функции NoBounce
240
+ */
241
+ noBounceDebug?: {
242
+ getStats: () => TimeStats | null;
243
+ sendNow: () => void;
244
+ stop: () => void;
245
+ reset: () => void;
246
+ };
247
+
248
+ /**
249
+ * Флаги для предотвращения повторной отправки целей
250
+ */
251
+ oneMinuteSent?: boolean;
252
+ fiveMinutesSent?: boolean;
253
+ deepScrollSent?: boolean;
254
+ }
255
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexsab-ru/scripts",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "common libs for websites",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -25,5 +25,24 @@
25
25
  "publishConfig": {
26
26
  "access": "public"
27
27
  },
28
+ "exports": {
29
+ ".": "./index.js",
30
+ "./analytics": "./lib/analytics.js",
31
+ "./calltouch": "./lib/calltouch.js",
32
+ "./campaign": "./lib/campaign/index.js",
33
+ "./campaign/persist": "./lib/campaign/persist-campaign-data.js",
34
+ "./cookie": "./lib/cookie.js",
35
+ "./form": "./lib/form.js"
36
+ },
37
+ "typesVersions": {
38
+ "*": {
39
+ "campaign": [
40
+ "./lib/campaign/types.d.ts"
41
+ ],
42
+ "campaign/persist": [
43
+ "./lib/campaign/types.d.ts"
44
+ ]
45
+ }
46
+ },
28
47
  "homepage": "https://github.com/alexsab-ru/scripts#readme"
29
48
  }