tracking-lib-ott 1.0.13 → 1.0.15
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/README.md +257 -464
- package/dist/index.cjs +186 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +359 -16
- package/dist/index.d.ts +359 -16
- package/dist/index.js +175 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -39,8 +39,19 @@ __export(index_exports, {
|
|
|
39
39
|
sendSessionProgress: () => sendSessionProgress,
|
|
40
40
|
sendSessionStart: () => sendSessionStart,
|
|
41
41
|
setCookie: () => setCookie,
|
|
42
|
+
setUserInfo: () => setUserInfo,
|
|
43
|
+
trackBufferEnded: () => trackBufferEnded,
|
|
44
|
+
trackBufferStarted: () => trackBufferStarted,
|
|
42
45
|
trackEvent: () => trackEvent,
|
|
43
46
|
trackPageView: () => trackPageView,
|
|
47
|
+
trackPlaybackCompleted: () => trackPlaybackCompleted,
|
|
48
|
+
trackPlaybackError: () => trackPlaybackError,
|
|
49
|
+
trackPlaybackPaused: () => trackPlaybackPaused,
|
|
50
|
+
trackPlaybackProgress: () => trackPlaybackProgress,
|
|
51
|
+
trackPlaybackResumed: () => trackPlaybackResumed,
|
|
52
|
+
trackPlaybackStarted: () => trackPlaybackStarted,
|
|
53
|
+
trackQualityChanged: () => trackQualityChanged,
|
|
54
|
+
trackViewContentDetails: () => trackViewContentDetails,
|
|
44
55
|
updateSessionConfig: () => updateSessionConfig,
|
|
45
56
|
usePageViewTracking: () => usePageViewTracking
|
|
46
57
|
});
|
|
@@ -49,29 +60,19 @@ module.exports = __toCommonJS(index_exports);
|
|
|
49
60
|
// src/constants.ts
|
|
50
61
|
var defaultConfig = {
|
|
51
62
|
baseURL: "",
|
|
52
|
-
appName: "on_plus",
|
|
53
63
|
appId: "",
|
|
54
64
|
packageId: "com.vtvcab.onsportstv",
|
|
55
|
-
platform: "
|
|
65
|
+
platform: "WebPC",
|
|
56
66
|
debug: false,
|
|
57
67
|
// Required properties
|
|
58
68
|
sessionId: "",
|
|
59
69
|
sessionSign: "",
|
|
60
|
-
userId: -1,
|
|
61
70
|
deviceId: "",
|
|
62
71
|
deviceModel: "",
|
|
63
72
|
deviceBrand: "",
|
|
64
|
-
ipAddress: "",
|
|
65
73
|
// Optional properties
|
|
66
74
|
adId: "",
|
|
67
|
-
osVersion: "",
|
|
68
|
-
netType: "",
|
|
69
|
-
carrierNet: "",
|
|
70
75
|
sdkVersion: "",
|
|
71
|
-
appVersion: "",
|
|
72
|
-
buildNumber: "",
|
|
73
|
-
language: "",
|
|
74
|
-
country: "",
|
|
75
76
|
events: []
|
|
76
77
|
};
|
|
77
78
|
var defaultPageMap = {
|
|
@@ -156,8 +157,8 @@ var clearSessionCookie = (options = {}) => {
|
|
|
156
157
|
};
|
|
157
158
|
|
|
158
159
|
// src/tracking.ts
|
|
159
|
-
var import_uuid2 = require("uuid");
|
|
160
160
|
var config = { ...defaultConfig };
|
|
161
|
+
var userId = config.userId ?? -1;
|
|
161
162
|
var previousPageId = null;
|
|
162
163
|
var pageMap = { ...defaultPageMap };
|
|
163
164
|
var initTracking = (options = {}) => {
|
|
@@ -183,6 +184,12 @@ var initTracking = (options = {}) => {
|
|
|
183
184
|
console.log("[Tracking] Initialized with config:", config);
|
|
184
185
|
}
|
|
185
186
|
};
|
|
187
|
+
var setUserInfo = (userInfo) => {
|
|
188
|
+
userId = userInfo.userId;
|
|
189
|
+
if (config.debug) {
|
|
190
|
+
console.log("[Tracking] User info updated, userId:", userId);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
186
193
|
var getPageInfo = (url) => {
|
|
187
194
|
const path = (url || "").split("?")[0];
|
|
188
195
|
const pathSegment = path.split("/").filter(Boolean)[0] || "home";
|
|
@@ -199,35 +206,30 @@ var sendEvent = async (eventData) => {
|
|
|
199
206
|
try {
|
|
200
207
|
const { event_name, ...eventParams } = eventData;
|
|
201
208
|
const eventObject = {
|
|
202
|
-
|
|
209
|
+
// Required fields
|
|
210
|
+
user_id: userId,
|
|
211
|
+
// userId được set từ setUserInfo, -1 nếu chưa set
|
|
203
212
|
event_name: event_name || "unknown",
|
|
204
|
-
event_params: JSON.stringify(eventParams),
|
|
205
213
|
event_time: Math.floor(Date.now() / 1e3),
|
|
206
|
-
|
|
214
|
+
// Optional fields
|
|
215
|
+
event_params: JSON.stringify(eventParams)
|
|
216
|
+
// JSON string ≤ 1024
|
|
207
217
|
};
|
|
208
218
|
const payload = {
|
|
209
219
|
// Required properties
|
|
210
|
-
app_name: config.appName,
|
|
211
220
|
app_id: config.appId,
|
|
212
221
|
package_id: config.packageId,
|
|
213
222
|
platform: config.platform,
|
|
214
223
|
session_id: config.sessionId,
|
|
215
224
|
session_sign: config.sessionSign,
|
|
216
|
-
user_id: config.userId,
|
|
217
225
|
device_id: config.deviceId,
|
|
218
226
|
device_model: config.deviceModel,
|
|
219
227
|
device_brand: config.deviceBrand,
|
|
220
|
-
ip_address: config.ipAddress,
|
|
221
228
|
// Optional properties
|
|
222
229
|
ad_id: config.adId,
|
|
223
|
-
|
|
224
|
-
net_type: config.netType,
|
|
225
|
-
carrier_net: config.carrierNet,
|
|
230
|
+
device_os_version: config.deviceOsVersion,
|
|
226
231
|
sdk_version: config.sdkVersion,
|
|
227
|
-
|
|
228
|
-
build_number: config.buildNumber,
|
|
229
|
-
language: config.language,
|
|
230
|
-
country: config.country,
|
|
232
|
+
// Note: ip_address và carrier_net - Server tự lấy từ header
|
|
231
233
|
// Events batch - tự động thêm event vào mảng
|
|
232
234
|
events: [...config.events ?? [], eventObject]
|
|
233
235
|
};
|
|
@@ -267,6 +269,143 @@ var trackEvent = (eventName, params = {}) => {
|
|
|
267
269
|
...params
|
|
268
270
|
});
|
|
269
271
|
};
|
|
272
|
+
var trackViewContentDetails = (contentId, contentTitle, contentType, channelId, eventTime) => {
|
|
273
|
+
sendEvent({
|
|
274
|
+
event_name: "view_content_details",
|
|
275
|
+
content_id: contentId,
|
|
276
|
+
content_title: contentTitle,
|
|
277
|
+
content_type: contentType,
|
|
278
|
+
channel_id: channelId,
|
|
279
|
+
event_time: eventTime
|
|
280
|
+
});
|
|
281
|
+
};
|
|
282
|
+
var trackPlaybackStarted = (params) => {
|
|
283
|
+
sendEvent({
|
|
284
|
+
event_name: "playback_started",
|
|
285
|
+
content_id: params.contentId,
|
|
286
|
+
content_title: params.contentTitle,
|
|
287
|
+
content_type: params.contentType,
|
|
288
|
+
playback_session: params.playbackSession,
|
|
289
|
+
video_quality: params.videoQuality,
|
|
290
|
+
start_timestamp: params.startTimestamp,
|
|
291
|
+
start_position: params.startPosition,
|
|
292
|
+
duration: params.duration,
|
|
293
|
+
channel_id: params.channelId,
|
|
294
|
+
sub_content_id: params.subContentId,
|
|
295
|
+
sub_content_title: params.subContentTitle,
|
|
296
|
+
event_time: params.eventTime
|
|
297
|
+
});
|
|
298
|
+
};
|
|
299
|
+
var trackPlaybackProgress = (params) => {
|
|
300
|
+
sendEvent({
|
|
301
|
+
event_name: "playback_progress",
|
|
302
|
+
content_id: params.contentId,
|
|
303
|
+
content_title: params.contentTitle,
|
|
304
|
+
content_type: params.contentType,
|
|
305
|
+
playback_session: params.playbackSession,
|
|
306
|
+
total_watch_time: params.totalWatchTime,
|
|
307
|
+
channel_id: params.channelId,
|
|
308
|
+
sub_content_id: params.subContentId,
|
|
309
|
+
sub_content_title: params.subContentTitle,
|
|
310
|
+
event_time: params.eventTime
|
|
311
|
+
});
|
|
312
|
+
};
|
|
313
|
+
var trackPlaybackCompleted = (params) => {
|
|
314
|
+
sendEvent({
|
|
315
|
+
event_name: "playback_completed",
|
|
316
|
+
content_id: params.contentId,
|
|
317
|
+
content_title: params.contentTitle,
|
|
318
|
+
content_type: params.contentType,
|
|
319
|
+
playback_session: params.playbackSession,
|
|
320
|
+
total_watch_time: params.totalWatchTime,
|
|
321
|
+
channel_id: params.channelId,
|
|
322
|
+
sub_content_id: params.subContentId,
|
|
323
|
+
sub_content_title: params.subContentTitle,
|
|
324
|
+
event_time: params.eventTime
|
|
325
|
+
});
|
|
326
|
+
};
|
|
327
|
+
var trackPlaybackError = (params) => {
|
|
328
|
+
sendEvent({
|
|
329
|
+
event_name: "playback_error",
|
|
330
|
+
content_id: params.contentId,
|
|
331
|
+
content_title: params.contentTitle,
|
|
332
|
+
content_type: params.contentType,
|
|
333
|
+
playback_session: params.playbackSession,
|
|
334
|
+
channel_id: params.channelId,
|
|
335
|
+
error_code: params.errorCode,
|
|
336
|
+
error_message: params.errorMessage,
|
|
337
|
+
error_type: params.errorType,
|
|
338
|
+
event_time: params.eventTime
|
|
339
|
+
});
|
|
340
|
+
};
|
|
341
|
+
var trackQualityChanged = (params) => {
|
|
342
|
+
sendEvent({
|
|
343
|
+
event_name: "quality_changed",
|
|
344
|
+
content_id: params.contentId,
|
|
345
|
+
content_title: params.contentTitle,
|
|
346
|
+
content_type: params.contentType,
|
|
347
|
+
playback_session: params.playbackSession,
|
|
348
|
+
from_quality: params.fromQuality,
|
|
349
|
+
to_quality: params.toQuality,
|
|
350
|
+
position_sec: params.positionSec,
|
|
351
|
+
channel_id: params.channelId,
|
|
352
|
+
reason: params.reason,
|
|
353
|
+
event_time: params.eventTime
|
|
354
|
+
});
|
|
355
|
+
};
|
|
356
|
+
var trackBufferStarted = (params) => {
|
|
357
|
+
sendEvent({
|
|
358
|
+
event_name: "buffer_started",
|
|
359
|
+
content_id: params.contentId,
|
|
360
|
+
content_title: params.contentTitle,
|
|
361
|
+
content_type: params.contentType,
|
|
362
|
+
playback_session: params.playbackSession,
|
|
363
|
+
video_quality: params.videoQuality,
|
|
364
|
+
position_sec: params.positionSec,
|
|
365
|
+
channel_id: params.channelId,
|
|
366
|
+
reason: params.reason,
|
|
367
|
+
event_time: params.eventTime
|
|
368
|
+
});
|
|
369
|
+
};
|
|
370
|
+
var trackBufferEnded = (params) => {
|
|
371
|
+
sendEvent({
|
|
372
|
+
event_name: "buffer_ended",
|
|
373
|
+
content_id: params.contentId,
|
|
374
|
+
content_title: params.contentTitle,
|
|
375
|
+
content_type: params.contentType,
|
|
376
|
+
playback_session: params.playbackSession,
|
|
377
|
+
video_quality: params.videoQuality,
|
|
378
|
+
position_sec: params.positionSec,
|
|
379
|
+
buffer_duration_sec: params.bufferDurationSec,
|
|
380
|
+
channel_id: params.channelId,
|
|
381
|
+
reason: params.reason,
|
|
382
|
+
event_time: params.eventTime
|
|
383
|
+
});
|
|
384
|
+
};
|
|
385
|
+
var trackPlaybackPaused = (params) => {
|
|
386
|
+
sendEvent({
|
|
387
|
+
event_name: "playback_paused",
|
|
388
|
+
content_id: params.contentId,
|
|
389
|
+
content_title: params.contentTitle,
|
|
390
|
+
content_type: params.contentType,
|
|
391
|
+
playback_session: params.playbackSession,
|
|
392
|
+
position_sec: params.positionSec,
|
|
393
|
+
channel_id: params.channelId,
|
|
394
|
+
event_time: params.eventTime
|
|
395
|
+
});
|
|
396
|
+
};
|
|
397
|
+
var trackPlaybackResumed = (params) => {
|
|
398
|
+
sendEvent({
|
|
399
|
+
event_name: "playback_resumed",
|
|
400
|
+
content_id: params.contentId,
|
|
401
|
+
content_title: params.contentTitle,
|
|
402
|
+
content_type: params.contentType,
|
|
403
|
+
playback_session: params.playbackSession,
|
|
404
|
+
position_sec: params.positionSec,
|
|
405
|
+
channel_id: params.channelId,
|
|
406
|
+
event_time: params.eventTime
|
|
407
|
+
});
|
|
408
|
+
};
|
|
270
409
|
var usePageViewTracking = (router) => {
|
|
271
410
|
if (typeof window === "undefined") return;
|
|
272
411
|
if (!router) return;
|
|
@@ -426,8 +565,19 @@ var renewSessionIdCookie = () => {
|
|
|
426
565
|
// src/index.ts
|
|
427
566
|
var tracking = {
|
|
428
567
|
init: initTracking,
|
|
568
|
+
setUserInfo,
|
|
429
569
|
trackPageView,
|
|
430
570
|
trackEvent,
|
|
571
|
+
trackViewContentDetails,
|
|
572
|
+
trackPlaybackStarted,
|
|
573
|
+
trackPlaybackProgress,
|
|
574
|
+
trackPlaybackCompleted,
|
|
575
|
+
trackPlaybackError,
|
|
576
|
+
trackQualityChanged,
|
|
577
|
+
trackBufferStarted,
|
|
578
|
+
trackBufferEnded,
|
|
579
|
+
trackPlaybackPaused,
|
|
580
|
+
trackPlaybackResumed,
|
|
431
581
|
sendEvent,
|
|
432
582
|
getPageInfo,
|
|
433
583
|
usePageViewTracking,
|
|
@@ -459,8 +609,19 @@ var index_default = tracking;
|
|
|
459
609
|
sendSessionProgress,
|
|
460
610
|
sendSessionStart,
|
|
461
611
|
setCookie,
|
|
612
|
+
setUserInfo,
|
|
613
|
+
trackBufferEnded,
|
|
614
|
+
trackBufferStarted,
|
|
462
615
|
trackEvent,
|
|
463
616
|
trackPageView,
|
|
617
|
+
trackPlaybackCompleted,
|
|
618
|
+
trackPlaybackError,
|
|
619
|
+
trackPlaybackPaused,
|
|
620
|
+
trackPlaybackProgress,
|
|
621
|
+
trackPlaybackResumed,
|
|
622
|
+
trackPlaybackStarted,
|
|
623
|
+
trackQualityChanged,
|
|
624
|
+
trackViewContentDetails,
|
|
464
625
|
updateSessionConfig,
|
|
465
626
|
usePageViewTracking
|
|
466
627
|
});
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/cookie-utils.ts","../src/tracking.ts","../src/session.ts"],"sourcesContent":["/**\r\n * Tracking Library v2 (Fetch + TypeScript)\r\n * Thư viện tracking events có thể dùng cho nhiều project\r\n */\r\n\r\n// Re-export types\r\nexport type {\r\n TrackingConfig,\r\n PageInfo,\r\n PageMap,\r\n EventData,\r\n NextRouterLike,\r\n} from \"./types\";\r\n\r\nexport type { SessionConfig } from \"./session\";\r\nexport type { SessionCookieConfig } from \"./cookie-utils\";\r\n\r\n// Re-export constants\r\nexport { defaultConfig, defaultPageMap } from \"./constants\";\r\n\r\n// Re-export functions\r\nexport {\r\n initTracking,\r\n getPageInfo,\r\n sendEvent,\r\n trackPageView,\r\n trackEvent,\r\n usePageViewTracking,\r\n} from \"./tracking\";\r\n\r\n// Re-export session functions\r\nexport {\r\n initSessionTracking,\r\n cleanupSessionTracking,\r\n sendSessionStart,\r\n sendSessionProgress,\r\n sendSessionEnd,\r\n updateSessionConfig,\r\n isSessionStarted,\r\n getSessionDuration,\r\n} from \"./session\";\r\n\r\n// Re-export cookie utilities\r\nexport {\r\n generateSessionId,\r\n getCookie,\r\n setCookie,\r\n getOrCreateSessionId,\r\n renewSessionCookie,\r\n clearSessionCookie,\r\n} from \"./cookie-utils\";\r\n\r\n// Import for default export\r\nimport {\r\n initTracking,\r\n getPageInfo,\r\n sendEvent,\r\n trackPageView,\r\n trackEvent,\r\n usePageViewTracking,\r\n} from \"./tracking\";\r\n\r\nimport {\r\n initSessionTracking,\r\n cleanupSessionTracking,\r\n sendSessionStart,\r\n sendSessionProgress,\r\n sendSessionEnd,\r\n} from \"./session\";\r\n\r\n// Export default object\r\nconst tracking = {\r\n init: initTracking,\r\n trackPageView,\r\n trackEvent,\r\n sendEvent,\r\n getPageInfo,\r\n usePageViewTracking,\r\n // Session lifecycle\r\n initSession: initSessionTracking,\r\n cleanupSession: cleanupSessionTracking,\r\n sessionStart: sendSessionStart,\r\n sessionProgress: sendSessionProgress,\r\n sessionEnd: sendSessionEnd,\r\n};\r\n\r\nexport default tracking;\r\n","/**\r\n * Constants for Tracking Library\r\n */\r\n\r\nimport type { TrackingConfig, PageMap } from \"./types\";\r\n\r\nexport const defaultConfig: TrackingConfig = {\r\n baseURL: \"\",\r\n appName: \"on_plus\",\r\n appId: \"\",\r\n packageId: \"com.vtvcab.onsportstv\",\r\n platform: \"Web\",\r\n debug: false,\r\n\r\n // Required properties\r\n sessionId: \"\",\r\n sessionSign: \"\",\r\n userId: -1,\r\n deviceId: \"\",\r\n deviceModel: \"\",\r\n deviceBrand: \"\",\r\n ipAddress: \"\",\r\n\r\n // Optional properties\r\n adId: \"\",\r\n osVersion: \"\",\r\n netType: \"\",\r\n carrierNet: \"\",\r\n sdkVersion: \"\",\r\n appVersion: \"\",\r\n buildNumber: \"\",\r\n language: \"\",\r\n country: \"\",\r\n\r\n events: [],\r\n};\r\n\r\nexport const defaultPageMap: PageMap = {\r\n \"\": { name: \"Home\", id: \"home\" },\r\n home: { name: \"Home\", id: \"home\" },\r\n search: { name: \"Search\", id: \"search\" },\r\n detail: { name: \"Detail\", id: \"detail\" },\r\n player: { name: \"Player\", id: \"player\" },\r\n video: { name: \"Video\", id: \"video\" },\r\n live: { name: \"Live\", id: \"live\" },\r\n category: { name: \"Category\", id: \"category\" },\r\n profile: { name: \"Profile\", id: \"profile\" },\r\n login: { name: \"Login\", id: \"login\" },\r\n};\r\n","/**\r\n * Cookie Utilities for Session Management\r\n */\r\n\r\nimport { v4 as uuidv4 } from \"uuid\";\r\n\r\nexport type SessionCookieConfig = {\r\n /** Tên cookie lưu session_id. Default: '_tracking_session_id' */\r\n cookieName?: string;\r\n /** Thời gian hết hạn cookie (phút). Default: 30 */\r\n expirationMinutes?: number;\r\n /** Domain cho cookie (hỗ trợ group domain như .example.com) */\r\n domain?: string;\r\n /** Path cho cookie. Default: '/' */\r\n path?: string;\r\n /** SameSite attribute. Default: 'Lax' */\r\n sameSite?: \"Strict\" | \"Lax\" | \"None\";\r\n};\r\n\r\nconst defaultCookieConfig: Required<SessionCookieConfig> = {\r\n cookieName: \"_tracking_session_id\",\r\n expirationMinutes: 30,\r\n domain: \"\",\r\n path: \"/\",\r\n sameSite: \"Lax\",\r\n};\r\n\r\n/**\r\n * Tạo session ID theo format UUID v4\r\n */\r\nexport const generateSessionId = (): string => {\r\n return uuidv4();\r\n};\r\n\r\n/**\r\n * Lấy giá trị cookie theo tên\r\n */\r\nexport const getCookie = (name: string): string | null => {\r\n if (typeof document === \"undefined\") return null;\r\n\r\n const nameEQ = name + \"=\";\r\n const cookies = document.cookie.split(\";\");\r\n\r\n for (let i = 0; i < cookies.length; i++) {\r\n let cookie = cookies[i];\r\n while (cookie.charAt(0) === \" \") {\r\n cookie = cookie.substring(1);\r\n }\r\n if (cookie.indexOf(nameEQ) === 0) {\r\n return cookie.substring(nameEQ.length);\r\n }\r\n }\r\n return null;\r\n};\r\n\r\n/**\r\n * Set cookie với các options\r\n */\r\nexport const setCookie = (\r\n name: string,\r\n value: string,\r\n options: SessionCookieConfig = {},\r\n): void => {\r\n if (typeof document === \"undefined\") return;\r\n\r\n const config = { ...defaultCookieConfig, ...options };\r\n const expirationMs = config.expirationMinutes * 60 * 1000;\r\n const expires = new Date(Date.now() + expirationMs);\r\n\r\n let cookieString = `${name}=${value}; expires=${expires.toUTCString()}; path=${config.path}`;\r\n\r\n if (config.domain) {\r\n cookieString += `; domain=${config.domain}`;\r\n }\r\n\r\n if (config.sameSite) {\r\n cookieString += `; SameSite=${config.sameSite}`;\r\n }\r\n\r\n // Nếu SameSite=None thì phải có Secure\r\n if (config.sameSite === \"None\") {\r\n cookieString += \"; Secure\";\r\n }\r\n\r\n document.cookie = cookieString;\r\n};\r\n\r\n/**\r\n * Lấy hoặc tạo mới session_id từ cookie\r\n * Nếu cookie tồn tại và chưa hết hạn -> trả về giá trị cũ\r\n * Nếu không -> tạo mới và lưu vào cookie\r\n */\r\nexport const getOrCreateSessionId = (\r\n options: SessionCookieConfig = {},\r\n): string => {\r\n const config = { ...defaultCookieConfig, ...options };\r\n const existingSessionId = getCookie(config.cookieName);\r\n\r\n if (existingSessionId) {\r\n return existingSessionId;\r\n }\r\n\r\n // Tạo mới session_id\r\n const newSessionId = generateSessionId();\r\n setCookie(config.cookieName, newSessionId, options);\r\n\r\n return newSessionId;\r\n};\r\n\r\n/**\r\n * Gia hạn cookie session_id\r\n * Gọi hàm này khi có event session_start để extend thời gian hết hạn\r\n */\r\nexport const renewSessionCookie = (\r\n sessionId: string,\r\n options: SessionCookieConfig = {},\r\n): void => {\r\n const config = { ...defaultCookieConfig, ...options };\r\n setCookie(config.cookieName, sessionId, options);\r\n};\r\n\r\n/**\r\n * Xóa session cookie\r\n */\r\nexport const clearSessionCookie = (options: SessionCookieConfig = {}): void => {\r\n if (typeof document === \"undefined\") return;\r\n\r\n const config = { ...defaultCookieConfig, ...options };\r\n let cookieString = `${config.cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${config.path}`;\r\n\r\n if (config.domain) {\r\n cookieString += `; domain=${config.domain}`;\r\n }\r\n\r\n document.cookie = cookieString;\r\n};\r\n","/**\r\n * Core Tracking Functions\r\n */\r\n\r\nimport type {\r\n TrackingConfig,\r\n PageMap,\r\n PageInfo,\r\n EventData,\r\n NextRouterLike,\r\n} from \"./types\";\r\nimport { defaultConfig, defaultPageMap } from \"./constants\";\r\nimport { getOrCreateSessionId } from \"./cookie-utils\";\r\nimport { v4 as uuidv4 } from \"uuid\";\r\n\r\n// Config runtime\r\nlet config: TrackingConfig = { ...defaultConfig };\r\n\r\n// State\r\nlet previousPageId: string | null = null;\r\n\r\n// Page mapping\r\nlet pageMap: any = { ...defaultPageMap };\r\n\r\n/**\r\n * Khởi tạo tracking với config\r\n */\r\nexport const initTracking = (\r\n options: Partial<TrackingConfig> & { pageMap?: Partial<PageMap> } = {},\r\n) => {\r\n config = { ...config, ...options };\r\n\r\n // Auto-generate sessionId from cookie if not provided\r\n if (!config.sessionId) {\r\n config.sessionId = getOrCreateSessionId(config.sessionCookieConfig);\r\n if (config.debug) {\r\n console.log(\r\n \"[Tracking] Auto-generated session_id from cookie:\",\r\n config.sessionId,\r\n );\r\n }\r\n } else if (config.debug) {\r\n console.log(\"[Tracking] Using user-provided session_id:\", config.sessionId);\r\n }\r\n\r\n if (options.pageMap) {\r\n pageMap = { ...pageMap, ...options.pageMap };\r\n }\r\n\r\n if (!config.baseURL) {\r\n console.warn(\"[Tracking] baseURL is required.\");\r\n }\r\n\r\n if (config.debug) {\r\n console.log(\"[Tracking] Initialized with config:\", config);\r\n }\r\n};\r\n\r\n/**\r\n * Lấy thông tin page từ URL\r\n */\r\nexport const getPageInfo = (url: string): PageInfo => {\r\n const path = (url || \"\").split(\"?\")[0];\r\n const pathSegment = path.split(\"/\").filter(Boolean)[0] || \"home\";\r\n\r\n return (\r\n pageMap[pathSegment] || {\r\n name: pathSegment.charAt(0).toUpperCase() + pathSegment.slice(1),\r\n id: pathSegment,\r\n }\r\n );\r\n};\r\n\r\n/**\r\n * Send Event -> POST JSON\r\n */\r\nexport const sendEvent = async (\r\n eventData: EventData,\r\n): Promise<Response | void> => {\r\n if (!config.baseURL) {\r\n console.warn(\"[Tracking] Not initialized. Call initTracking() first.\");\r\n return;\r\n }\r\n\r\n try {\r\n // Tách event_name ra khỏi eventData để tạo event object\r\n const { event_name, ...eventParams } = eventData;\r\n\r\n // Tạo event object với format chuẩn\r\n const eventObject = {\r\n event_id: uuidv4(),\r\n event_name: event_name || \"unknown\",\r\n event_params: JSON.stringify(eventParams),\r\n event_time: Math.floor(Date.now() / 1000),\r\n user_id: config.userId,\r\n };\r\n\r\n const payload = {\r\n // Required properties\r\n app_name: config.appName,\r\n app_id: config.appId,\r\n package_id: config.packageId,\r\n platform: config.platform,\r\n\r\n session_id: config.sessionId,\r\n session_sign: config.sessionSign,\r\n user_id: config.userId,\r\n\r\n device_id: config.deviceId,\r\n device_model: config.deviceModel,\r\n device_brand: config.deviceBrand,\r\n ip_address: config.ipAddress,\r\n\r\n // Optional properties\r\n ad_id: config.adId,\r\n os_version: config.osVersion,\r\n net_type: config.netType,\r\n carrier_net: config.carrierNet,\r\n sdk_version: config.sdkVersion,\r\n app_version: config.appVersion,\r\n build_number: config.buildNumber,\r\n language: config.language,\r\n country: config.country,\r\n\r\n // Events batch - tự động thêm event vào mảng\r\n events: [...(config.events ?? []), eventObject],\r\n };\r\n\r\n if (config.debug) {\r\n console.log(\"[Tracking] Sending event:\", payload);\r\n }\r\n\r\n const res = await fetch(config.baseURL, {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify(payload),\r\n });\r\n\r\n if (!res.ok && config.debug) {\r\n const text = await res.text().catch(() => \"\");\r\n console.error(\"[Tracking] HTTP Error:\", res.status, text);\r\n }\r\n\r\n return res;\r\n } catch (error) {\r\n if (config.debug) {\r\n console.error(\"[Tracking] Error:\", error);\r\n }\r\n }\r\n};\r\n\r\n/**\r\n * Track page view event\r\n */\r\nexport const trackPageView = (url: string) => {\r\n const pageInfo = getPageInfo(url);\r\n\r\n sendEvent({\r\n event_name: \"page_view\",\r\n page_name: pageInfo.name,\r\n page_id: pageInfo.id,\r\n page_path: url,\r\n referrer_page_id: previousPageId || \"\",\r\n });\r\n\r\n previousPageId = pageInfo.id;\r\n};\r\n\r\n/**\r\n * Track custom event\r\n */\r\nexport const trackEvent = (eventName: string, params: EventData = {}) => {\r\n sendEvent({\r\n event_name: eventName,\r\n ...params,\r\n });\r\n};\r\n\r\n/**\r\n * Hook cho Next.js - auto track page views\r\n * Dùng cho Next.js Pages Router (next/router)\r\n */\r\nexport const usePageViewTracking = (router: NextRouterLike) => {\r\n if (typeof window === \"undefined\") return;\r\n if (!router) return;\r\n\r\n // Track initial page\r\n trackPageView(router.asPath);\r\n\r\n const handleRouteChange = (url: string) => {\r\n trackPageView(url);\r\n };\r\n\r\n router.events.on(\"routeChangeComplete\", handleRouteChange);\r\n\r\n return () => {\r\n router.events.off(\"routeChangeComplete\", handleRouteChange);\r\n };\r\n};\r\n","/**\r\n * Session Lifecycle Tracking\r\n * - session_start: Khi user mở app\r\n * - session_progress: Định kỳ khi user đang active (foreground)\r\n * - session_end: Khi user thoát app\r\n */\r\n\r\nimport { sendEvent } from \"./tracking\";\r\nimport { renewSessionCookie } from \"./cookie-utils\";\r\nimport type { SessionCookieConfig } from \"./cookie-utils\";\r\n\r\nexport type SessionConfig = {\r\n /** Khoảng thời gian gửi session_progress (ms). Default: 5 phút */\r\n progressInterval?: number;\r\n /** Có tự động start session khi init không. Default: true */\r\n autoStart?: boolean;\r\n /** Debug mode */\r\n debug?: boolean;\r\n /** User ID */\r\n userId?: string;\r\n /** Session cookie configuration */\r\n sessionCookieConfig?: SessionCookieConfig;\r\n};\r\n\r\nconst defaultSessionConfig: SessionConfig = {\r\n progressInterval: 5 * 60 * 1000, // 5 phút\r\n autoStart: true,\r\n debug: false,\r\n};\r\n\r\n// State\r\nlet sessionConfig: SessionConfig = { ...defaultSessionConfig };\r\nlet progressTimer: ReturnType<typeof setInterval> | null = null;\r\nlet isSessionActive = false;\r\nlet sessionStartTime: number = 0;\r\n\r\n/**\r\n * Log helper\r\n */\r\nconst log = (...args: any[]) => {\r\n if (sessionConfig.debug) {\r\n console.log(\"[Session]\", ...args);\r\n }\r\n};\r\n\r\n/**\r\n * Gửi event session_start\r\n */\r\nexport const sendSessionStart = () => {\r\n if (isSessionActive) {\r\n log(\"Session already started, skipping...\");\r\n return;\r\n }\r\n\r\n sessionStartTime = Date.now();\r\n isSessionActive = true;\r\n\r\n sendEvent({\r\n event_name: \"session_start\",\r\n event_time: sessionStartTime,\r\n });\r\n\r\n log(\"Session started at:\", new Date(sessionStartTime).toISOString());\r\n\r\n // Gia hạn cookie session_id (extend thêm 30 phút)\r\n renewSessionIdCookie();\r\n\r\n // Bắt đầu timer cho session_progress\r\n startProgressTimer();\r\n};\r\n\r\n/**\r\n * Gửi event session_progress\r\n */\r\nexport const sendSessionProgress = () => {\r\n if (!isSessionActive) return;\r\n\r\n const now = Math.floor(Date.now() / 1000);\r\n const duration = now - sessionStartTime;\r\n\r\n sendEvent({\r\n event_name: \"session_progress\",\r\n session_duration: duration,\r\n });\r\n\r\n log(\"Session progress - Duration:\", Math.round(duration / 1000), \"seconds\");\r\n\r\n // Gia hạn cookie session_id mỗi khi user còn active\r\n renewSessionIdCookie();\r\n};\r\n\r\n/**\r\n * Gửi event session_end\r\n */\r\nexport const sendSessionEnd = () => {\r\n if (!isSessionActive) return;\r\n\r\n const now = Math.floor(Date.now() / 1000);\r\n const duration = now - sessionStartTime;\r\n\r\n // Dùng sendBeacon để đảm bảo gửi được khi page unload\r\n // Fallback về sendEvent nếu không support\r\n sendEvent({\r\n event_name: \"session_end\",\r\n session_duration: duration,\r\n });\r\n\r\n log(\r\n \"Session ended - Total duration:\",\r\n Math.round(duration / 1000),\r\n \"seconds\",\r\n );\r\n\r\n stopProgressTimer();\r\n isSessionActive = false;\r\n};\r\n\r\n/**\r\n * Start progress timer\r\n */\r\nconst startProgressTimer = () => {\r\n stopProgressTimer(); // Clear existing timer\r\n\r\n if (sessionConfig.progressInterval && sessionConfig.progressInterval > 0) {\r\n progressTimer = setInterval(() => {\r\n if (document.visibilityState === \"visible\") {\r\n sendSessionProgress();\r\n }\r\n }, sessionConfig.progressInterval);\r\n\r\n log(\r\n \"Progress timer started, interval:\",\r\n sessionConfig.progressInterval,\r\n \"ms\",\r\n );\r\n }\r\n};\r\n\r\n/**\r\n * Stop progress timer\r\n */\r\nconst stopProgressTimer = () => {\r\n if (progressTimer) {\r\n clearInterval(progressTimer);\r\n progressTimer = null;\r\n log(\"Progress timer stopped\");\r\n }\r\n};\r\n\r\n/**\r\n * Handle visibility change\r\n * Pause/resume tracking khi user switch tab hoặc minimize\r\n */\r\nconst handleVisibilityChange = () => {\r\n if (document.visibilityState === \"hidden\") {\r\n // User rời khỏi tab - có thể gửi session_progress cuối\r\n log(\"Tab hidden - pausing progress\");\r\n stopProgressTimer();\r\n } else if (document.visibilityState === \"visible\") {\r\n // User quay lại tab - resume timer\r\n log(\"Tab visible - resuming progress\");\r\n if (isSessionActive) {\r\n startProgressTimer();\r\n }\r\n }\r\n};\r\n\r\n/**\r\n * Handle page unload (user đóng tab/browser)\r\n */\r\nconst handleBeforeUnload = () => {\r\n sendSessionEnd();\r\n};\r\n\r\n/**\r\n * Handle page hide (mobile: switch app, close tab)\r\n * Dùng pagehide thay vì beforeunload cho mobile\r\n */\r\nconst handlePageHide = (event: PageTransitionEvent) => {\r\n if (event.persisted) {\r\n // Page được cache (bfcache) - không phải thực sự đóng\r\n return;\r\n }\r\n sendSessionEnd();\r\n};\r\n\r\n/**\r\n * Khởi tạo session tracking\r\n */\r\nexport const initSessionTracking = (options: SessionConfig = {}) => {\r\n if (typeof window === \"undefined\") return;\r\n\r\n sessionConfig = { ...defaultSessionConfig, ...options };\r\n\r\n log(\"Initializing with config:\", sessionConfig);\r\n\r\n // Đăng ký event listeners\r\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\r\n window.addEventListener(\"beforeunload\", handleBeforeUnload);\r\n window.addEventListener(\"pagehide\", handlePageHide);\r\n\r\n // Auto start session\r\n if (sessionConfig.autoStart) {\r\n sendSessionStart();\r\n }\r\n};\r\n\r\n/**\r\n * Cleanup - gọi khi unmount component\r\n */\r\nexport const cleanupSessionTracking = () => {\r\n if (typeof window === \"undefined\") return;\r\n\r\n log(\"Cleaning up...\");\r\n\r\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\r\n window.removeEventListener(\"beforeunload\", handleBeforeUnload);\r\n window.removeEventListener(\"pagehide\", handlePageHide);\r\n\r\n stopProgressTimer();\r\n isSessionActive = false;\r\n};\r\n\r\n/**\r\n * Cập nhật config (ví dụ: thay đổi interval)\r\n */\r\nexport const updateSessionConfig = (options: Partial<SessionConfig>) => {\r\n sessionConfig = { ...sessionConfig, ...options };\r\n\r\n // Restart timer nếu interval thay đổi\r\n if (options.progressInterval !== undefined && isSessionActive) {\r\n startProgressTimer();\r\n }\r\n\r\n log(\"Config updated:\", sessionConfig);\r\n};\r\n\r\n/**\r\n * Kiểm tra session đang active không\r\n */\r\nexport const isSessionStarted = () => isSessionActive;\r\n\r\n/**\r\n * Lấy thời gian session hiện tại (ms)\r\n */\r\nexport const getSessionDuration = () => {\r\n if (!isSessionActive) return 0;\r\n return Date.now() - sessionStartTime;\r\n};\r\n\r\n/**\r\n * Gia hạn cookie session_id\r\n * Gọi khi có event session_start để extend thời gian hết hạn\r\n */\r\nconst renewSessionIdCookie = () => {\r\n // Lấy sessionId và config từ tracking module\r\n // Note: Cần export getter function từ tracking.ts để lấy config\r\n if (sessionConfig.sessionCookieConfig) {\r\n // Tạm thời sử dụng cookie name mặc định để lấy sessionId\r\n const cookieName =\r\n sessionConfig.sessionCookieConfig.cookieName || \"_tracking_session_id\";\r\n const sessionId = document.cookie\r\n .split(\"; \")\r\n .find((row) => row.startsWith(cookieName + \"=\"))\r\n ?.split(\"=\")[1];\r\n\r\n if (sessionId) {\r\n renewSessionCookie(sessionId, sessionConfig.sessionCookieConfig);\r\n log(\"Session cookie renewed for:\", sessionId);\r\n }\r\n }\r\n};\r\n"],"mappings":"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","names":["uuidv4","config","import_uuid","uuidv4"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/cookie-utils.ts","../src/tracking.ts","../src/session.ts"],"sourcesContent":["/**\r\n * Tracking Library v2 (Fetch + TypeScript)\r\n * Thư viện tracking events có thể dùng cho nhiều project\r\n */\r\n\r\n// Re-export types\r\nexport type {\r\n TrackingConfig,\r\n PageInfo,\r\n PageMap,\r\n EventData,\r\n NextRouterLike,\r\n UserInfo,\r\n} from \"./types\";\r\n\r\nexport type { SessionConfig } from \"./session\";\r\nexport type { SessionCookieConfig } from \"./cookie-utils\";\r\n\r\n// Re-export constants\r\nexport { defaultConfig, defaultPageMap } from \"./constants\";\r\n\r\n// Re-export functions\r\nexport {\r\n initTracking,\r\n setUserInfo,\r\n getPageInfo,\r\n sendEvent,\r\n trackPageView,\r\n trackEvent,\r\n trackViewContentDetails,\r\n trackPlaybackStarted,\r\n trackPlaybackProgress,\r\n trackPlaybackCompleted,\r\n trackPlaybackError,\r\n trackQualityChanged,\r\n trackBufferStarted,\r\n trackBufferEnded,\r\n trackPlaybackPaused,\r\n trackPlaybackResumed,\r\n usePageViewTracking,\r\n} from \"./tracking\";\r\n\r\n// Re-export session functions\r\nexport {\r\n initSessionTracking,\r\n cleanupSessionTracking,\r\n sendSessionStart,\r\n sendSessionProgress,\r\n sendSessionEnd,\r\n updateSessionConfig,\r\n isSessionStarted,\r\n getSessionDuration,\r\n} from \"./session\";\r\n\r\n// Re-export cookie utilities\r\nexport {\r\n generateSessionId,\r\n getCookie,\r\n setCookie,\r\n getOrCreateSessionId,\r\n renewSessionCookie,\r\n clearSessionCookie,\r\n} from \"./cookie-utils\";\r\n\r\n// Import for default export\r\nimport {\r\n initTracking,\r\n setUserInfo,\r\n getPageInfo,\r\n sendEvent,\r\n trackPageView,\r\n trackEvent,\r\n trackViewContentDetails,\r\n trackPlaybackStarted,\r\n trackPlaybackProgress,\r\n trackPlaybackCompleted,\r\n trackPlaybackError,\r\n trackQualityChanged,\r\n trackBufferStarted,\r\n trackBufferEnded,\r\n trackPlaybackPaused,\r\n trackPlaybackResumed,\r\n usePageViewTracking,\r\n} from \"./tracking\";\r\n\r\nimport {\r\n initSessionTracking,\r\n cleanupSessionTracking,\r\n sendSessionStart,\r\n sendSessionProgress,\r\n sendSessionEnd,\r\n} from \"./session\";\r\n\r\n// Export default object\r\nconst tracking = {\r\n init: initTracking,\r\n setUserInfo,\r\n trackPageView,\r\n trackEvent,\r\n trackViewContentDetails,\r\n trackPlaybackStarted,\r\n trackPlaybackProgress,\r\n trackPlaybackCompleted,\r\n trackPlaybackError,\r\n trackQualityChanged,\r\n trackBufferStarted,\r\n trackBufferEnded,\r\n trackPlaybackPaused,\r\n trackPlaybackResumed,\r\n sendEvent,\r\n getPageInfo,\r\n usePageViewTracking,\r\n // Session lifecycle\r\n initSession: initSessionTracking,\r\n cleanupSession: cleanupSessionTracking,\r\n sessionStart: sendSessionStart,\r\n sessionProgress: sendSessionProgress,\r\n sessionEnd: sendSessionEnd,\r\n};\r\n\r\nexport default tracking;\r\n","/**\r\n * Constants for Tracking Library\r\n */\r\n\r\nimport type { TrackingConfig, PageMap } from \"./types\";\r\n\r\nexport const defaultConfig: TrackingConfig = {\r\n baseURL: \"\",\r\n appId: \"\",\r\n packageId: \"com.vtvcab.onsportstv\",\r\n platform: \"WebPC\",\r\n debug: false,\r\n\r\n // Required properties\r\n sessionId: \"\",\r\n sessionSign: \"\",\r\n deviceId: \"\",\r\n deviceModel: \"\",\r\n deviceBrand: \"\",\r\n\r\n // Optional properties\r\n adId: \"\",\r\n sdkVersion: \"\",\r\n\r\n events: [],\r\n};\r\n\r\nexport const defaultPageMap: PageMap = {\r\n \"\": { name: \"Home\", id: \"home\" },\r\n home: { name: \"Home\", id: \"home\" },\r\n search: { name: \"Search\", id: \"search\" },\r\n detail: { name: \"Detail\", id: \"detail\" },\r\n player: { name: \"Player\", id: \"player\" },\r\n video: { name: \"Video\", id: \"video\" },\r\n live: { name: \"Live\", id: \"live\" },\r\n category: { name: \"Category\", id: \"category\" },\r\n profile: { name: \"Profile\", id: \"profile\" },\r\n login: { name: \"Login\", id: \"login\" },\r\n};\r\n","/**\r\n * Cookie Utilities for Session Management\r\n */\r\n\r\nimport { v4 as uuidv4 } from \"uuid\";\r\n\r\nexport type SessionCookieConfig = {\r\n /** Tên cookie lưu session_id. Default: '_tracking_session_id' */\r\n cookieName?: string;\r\n /** Thời gian hết hạn cookie (phút). Default: 30 */\r\n expirationMinutes?: number;\r\n /** Domain cho cookie (hỗ trợ group domain như .example.com) */\r\n domain?: string;\r\n /** Path cho cookie. Default: '/' */\r\n path?: string;\r\n /** SameSite attribute. Default: 'Lax' */\r\n sameSite?: \"Strict\" | \"Lax\" | \"None\";\r\n};\r\n\r\nconst defaultCookieConfig: Required<SessionCookieConfig> = {\r\n cookieName: \"_tracking_session_id\",\r\n expirationMinutes: 30,\r\n domain: \"\",\r\n path: \"/\",\r\n sameSite: \"Lax\",\r\n};\r\n\r\n/**\r\n * Tạo session ID theo format UUID v4\r\n */\r\nexport const generateSessionId = (): string => {\r\n return uuidv4();\r\n};\r\n\r\n/**\r\n * Lấy giá trị cookie theo tên\r\n */\r\nexport const getCookie = (name: string): string | null => {\r\n if (typeof document === \"undefined\") return null;\r\n\r\n const nameEQ = name + \"=\";\r\n const cookies = document.cookie.split(\";\");\r\n\r\n for (let i = 0; i < cookies.length; i++) {\r\n let cookie = cookies[i];\r\n while (cookie.charAt(0) === \" \") {\r\n cookie = cookie.substring(1);\r\n }\r\n if (cookie.indexOf(nameEQ) === 0) {\r\n return cookie.substring(nameEQ.length);\r\n }\r\n }\r\n return null;\r\n};\r\n\r\n/**\r\n * Set cookie với các options\r\n */\r\nexport const setCookie = (\r\n name: string,\r\n value: string,\r\n options: SessionCookieConfig = {},\r\n): void => {\r\n if (typeof document === \"undefined\") return;\r\n\r\n const config = { ...defaultCookieConfig, ...options };\r\n const expirationMs = config.expirationMinutes * 60 * 1000;\r\n const expires = new Date(Date.now() + expirationMs);\r\n\r\n let cookieString = `${name}=${value}; expires=${expires.toUTCString()}; path=${config.path}`;\r\n\r\n if (config.domain) {\r\n cookieString += `; domain=${config.domain}`;\r\n }\r\n\r\n if (config.sameSite) {\r\n cookieString += `; SameSite=${config.sameSite}`;\r\n }\r\n\r\n // Nếu SameSite=None thì phải có Secure\r\n if (config.sameSite === \"None\") {\r\n cookieString += \"; Secure\";\r\n }\r\n\r\n document.cookie = cookieString;\r\n};\r\n\r\n/**\r\n * Lấy hoặc tạo mới session_id từ cookie\r\n * Nếu cookie tồn tại và chưa hết hạn -> trả về giá trị cũ\r\n * Nếu không -> tạo mới và lưu vào cookie\r\n */\r\nexport const getOrCreateSessionId = (\r\n options: SessionCookieConfig = {},\r\n): string => {\r\n const config = { ...defaultCookieConfig, ...options };\r\n const existingSessionId = getCookie(config.cookieName);\r\n\r\n if (existingSessionId) {\r\n return existingSessionId;\r\n }\r\n\r\n // Tạo mới session_id\r\n const newSessionId = generateSessionId();\r\n setCookie(config.cookieName, newSessionId, options);\r\n\r\n return newSessionId;\r\n};\r\n\r\n/**\r\n * Gia hạn cookie session_id\r\n * Gọi hàm này khi có event session_start để extend thời gian hết hạn\r\n */\r\nexport const renewSessionCookie = (\r\n sessionId: string,\r\n options: SessionCookieConfig = {},\r\n): void => {\r\n const config = { ...defaultCookieConfig, ...options };\r\n setCookie(config.cookieName, sessionId, options);\r\n};\r\n\r\n/**\r\n * Xóa session cookie\r\n */\r\nexport const clearSessionCookie = (options: SessionCookieConfig = {}): void => {\r\n if (typeof document === \"undefined\") return;\r\n\r\n const config = { ...defaultCookieConfig, ...options };\r\n let cookieString = `${config.cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${config.path}`;\r\n\r\n if (config.domain) {\r\n cookieString += `; domain=${config.domain}`;\r\n }\r\n\r\n document.cookie = cookieString;\r\n};\r\n","/**\r\n * Core Tracking Functions\r\n */\r\n\r\nimport type {\r\n TrackingConfig,\r\n PageMap,\r\n PageInfo,\r\n EventData,\r\n NextRouterLike,\r\n UserInfo,\r\n} from \"./types\";\r\nimport { defaultConfig, defaultPageMap } from \"./constants\";\r\nimport { getOrCreateSessionId } from \"./cookie-utils\";\r\n\r\n// Config runtime\r\nlet config: TrackingConfig = { ...defaultConfig };\r\n\r\n// User info state\r\nlet userId: number = config.userId ?? -1;\r\n\r\n// State\r\nlet previousPageId: string | null = null;\r\n\r\n// Page mapping\r\nlet pageMap: any = { ...defaultPageMap };\r\n\r\n/**\r\n * Khởi tạo tracking với config\r\n */\r\nexport const initTracking = (\r\n options: Partial<TrackingConfig> & { pageMap?: Partial<PageMap> } = {},\r\n) => {\r\n config = { ...config, ...options };\r\n\r\n // Auto-generate sessionId from cookie if not provided\r\n if (!config.sessionId) {\r\n config.sessionId = getOrCreateSessionId(config.sessionCookieConfig);\r\n if (config.debug) {\r\n console.log(\r\n \"[Tracking] Auto-generated session_id from cookie:\",\r\n config.sessionId,\r\n );\r\n }\r\n } else if (config.debug) {\r\n console.log(\"[Tracking] Using user-provided session_id:\", config.sessionId);\r\n }\r\n\r\n if (options.pageMap) {\r\n pageMap = { ...pageMap, ...options.pageMap };\r\n }\r\n\r\n if (!config.baseURL) {\r\n console.warn(\"[Tracking] baseURL is required.\");\r\n }\r\n\r\n if (config.debug) {\r\n console.log(\"[Tracking] Initialized with config:\", config);\r\n }\r\n};\r\n\r\n/**\r\n * Cập nhật thông tin người dùng (userId)\r\n * userId sẽ được tự động thêm vào mỗi event trong events array\r\n */\r\nexport const setUserInfo = (userInfo: UserInfo) => {\r\n userId = userInfo.userId;\r\n\r\n if (config.debug) {\r\n console.log(\"[Tracking] User info updated, userId:\", userId);\r\n }\r\n};\r\n\r\n/**\r\n * Lấy thông tin page từ URL\r\n */\r\nexport const getPageInfo = (url: string): PageInfo => {\r\n const path = (url || \"\").split(\"?\")[0];\r\n const pathSegment = path.split(\"/\").filter(Boolean)[0] || \"home\";\r\n\r\n return (\r\n pageMap[pathSegment] || {\r\n name: pathSegment.charAt(0).toUpperCase() + pathSegment.slice(1),\r\n id: pathSegment,\r\n }\r\n );\r\n};\r\n\r\n/**\r\n * Send Event -> POST JSON\r\n */\r\nexport const sendEvent = async (\r\n eventData: EventData,\r\n): Promise<Response | void> => {\r\n if (!config.baseURL) {\r\n console.warn(\"[Tracking] Not initialized. Call initTracking() first.\");\r\n return;\r\n }\r\n\r\n try {\r\n // Tách event_name ra khỏi eventData để tạo event object\r\n const { event_name, ...eventParams } = eventData;\r\n\r\n // Tạo event object với format chuẩn theo API spec\r\n const eventObject = {\r\n // Required fields\r\n user_id: userId, // userId được set từ setUserInfo, -1 nếu chưa set\r\n event_name: event_name || \"unknown\",\r\n event_time: Math.floor(Date.now() / 1000),\r\n // Optional fields\r\n event_params: JSON.stringify(eventParams), // JSON string ≤ 1024\r\n };\r\n\r\n const payload = {\r\n // Required properties\r\n app_id: config.appId,\r\n package_id: config.packageId,\r\n platform: config.platform,\r\n\r\n session_id: config.sessionId,\r\n session_sign: config.sessionSign,\r\n\r\n device_id: config.deviceId,\r\n device_model: config.deviceModel,\r\n device_brand: config.deviceBrand,\r\n\r\n // Optional properties\r\n ad_id: config.adId,\r\n device_os_version: config.deviceOsVersion,\r\n sdk_version: config.sdkVersion,\r\n\r\n // Note: ip_address và carrier_net - Server tự lấy từ header\r\n\r\n // Events batch - tự động thêm event vào mảng\r\n events: [...(config.events ?? []), eventObject],\r\n };\r\n\r\n if (config.debug) {\r\n console.log(\"[Tracking] Sending event:\", payload);\r\n }\r\n\r\n const res = await fetch(config.baseURL, {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify(payload),\r\n });\r\n\r\n if (!res.ok && config.debug) {\r\n const text = await res.text().catch(() => \"\");\r\n console.error(\"[Tracking] HTTP Error:\", res.status, text);\r\n }\r\n\r\n return res;\r\n } catch (error) {\r\n if (config.debug) {\r\n console.error(\"[Tracking] Error:\", error);\r\n }\r\n }\r\n};\r\n\r\n/**\r\n * Track page view event\r\n */\r\nexport const trackPageView = (url: string) => {\r\n const pageInfo = getPageInfo(url);\r\n\r\n sendEvent({\r\n event_name: \"page_view\",\r\n page_name: pageInfo.name,\r\n page_id: pageInfo.id,\r\n page_path: url,\r\n referrer_page_id: previousPageId || \"\",\r\n });\r\n\r\n previousPageId = pageInfo.id;\r\n};\r\n\r\n/**\r\n * Track custom event\r\n */\r\nexport const trackEvent = (eventName: string, params: EventData = {}) => {\r\n sendEvent({\r\n event_name: eventName,\r\n ...params,\r\n });\r\n};\r\n\r\n/**\r\n * Track view content details event\r\n * @param contentId - ID của content (required)\r\n * @param contentTitle - Tiêu đề content (required)\r\n * @param contentType - Loại content: 0=VOD, 1=Event, 2=Channel (required)\r\n * @param channelId - Mã kênh EPG (optional)\r\n * @param eventTime - Timestamp Unix (optional, mặc định lấy time hiện tại)\r\n */\r\nexport const trackViewContentDetails = (\r\n contentId: string,\r\n contentTitle: string,\r\n contentType: 0 | 1 | 2,\r\n channelId?: string,\r\n eventTime?: number,\r\n) => {\r\n sendEvent({\r\n event_name: \"view_content_details\",\r\n content_id: contentId,\r\n content_title: contentTitle,\r\n content_type: contentType,\r\n channel_id: channelId,\r\n event_time: eventTime,\r\n });\r\n};\r\n\r\n/**\r\n * Track playback started event\r\n * @param params.contentId - ID của content (required)\r\n * @param params.contentTitle - Tiêu đề content (required)\r\n * @param params.contentType - Loại content: 0=VOD, 1=Live, 2=Channel (required)\r\n * @param params.playbackSession - Session ID: MD5(content_id + start_timestamp) (required)\r\n * @param params.videoQuality - Chất lượng video: 360p, 480p, 720p, 1080p, 2k, 4k (required)\r\n * @param params.startTimestamp - Unix timestamp khi bắt đầu phát (required)\r\n * @param params.startPosition - Vị trí bắt đầu: Live=0, VOD: 0 nếu mới, >0 nếu xem lại (required)\r\n * @param params.duration - Thời lượng: Live=-1, VOD=số giây (required)\r\n * @param params.channelId - Mã kênh EPG (optional)\r\n * @param params.subContentId - ID chương trình trong kênh live (optional)\r\n * @param params.subContentTitle - Title chương trình trong kênh live (optional)\r\n * @param params.eventTime - Unix timestamp (optional)\r\n */\r\nexport const trackPlaybackStarted = (params: {\r\n contentId: string;\r\n contentTitle: string;\r\n contentType: 0 | 1 | 2;\r\n playbackSession: string;\r\n videoQuality: \"360p\" | \"480p\" | \"720p\" | \"1080p\" | \"2k\" | \"4k\";\r\n startTimestamp: number;\r\n startPosition: number;\r\n duration: number;\r\n channelId?: string;\r\n subContentId?: string;\r\n subContentTitle?: string;\r\n eventTime?: number;\r\n}) => {\r\n sendEvent({\r\n event_name: \"playback_started\",\r\n content_id: params.contentId,\r\n content_title: params.contentTitle,\r\n content_type: params.contentType,\r\n playback_session: params.playbackSession,\r\n video_quality: params.videoQuality,\r\n start_timestamp: params.startTimestamp,\r\n start_position: params.startPosition,\r\n duration: params.duration,\r\n channel_id: params.channelId,\r\n sub_content_id: params.subContentId,\r\n sub_content_title: params.subContentTitle,\r\n event_time: params.eventTime,\r\n });\r\n};\r\n\r\n/**\r\n * Track playback progress event\r\n * Gửi định kỳ: loop 1 = 5s, loop 2 = 25s để track thời lượng user xem nội dung\r\n * Lưu ý: total_watch_time là tổng thời gian xem được (không tính pause)\r\n *\r\n * @param params.contentId - ID của content (required)\r\n * @param params.contentTitle - Tiêu đề content (required)\r\n * @param params.contentType - Loại content: 0=VOD, 1=Live, 2=Channel (required)\r\n * @param params.playbackSession - Session ID từ playback_started (required)\r\n * @param params.totalWatchTime - Tổng thời gian đã xem tính bằng giây (required)\r\n * @param params.channelId - Mã kênh EPG (optional)\r\n * @param params.subContentId - ID chương trình trong kênh live (optional)\r\n * @param params.subContentTitle - Title chương trình trong kênh live (optional)\r\n * @param params.eventTime - Unix timestamp (optional)\r\n */\r\nexport const trackPlaybackProgress = (params: {\r\n contentId: string;\r\n contentTitle: string;\r\n contentType: 0 | 1 | 2;\r\n playbackSession: string;\r\n totalWatchTime: number;\r\n channelId?: string;\r\n subContentId?: string;\r\n subContentTitle?: string;\r\n eventTime?: number;\r\n}) => {\r\n sendEvent({\r\n event_name: \"playback_progress\",\r\n content_id: params.contentId,\r\n content_title: params.contentTitle,\r\n content_type: params.contentType,\r\n playback_session: params.playbackSession,\r\n total_watch_time: params.totalWatchTime,\r\n channel_id: params.channelId,\r\n sub_content_id: params.subContentId,\r\n sub_content_title: params.subContentTitle,\r\n event_time: params.eventTime,\r\n });\r\n};\r\n\r\n/**\r\n * Track playback completed event\r\n * Tương tự playback_progress nhưng là event cuối cùng khi kết thúc xem\r\n * Với Live có thể thoát chủ động thì tracking event này\r\n *\r\n * @param params.contentId - ID của content (required)\r\n * @param params.contentTitle - Tiêu đề content (required)\r\n * @param params.contentType - Loại content: 0=VOD, 1=Live, 2=Channel (required)\r\n * @param params.playbackSession - Session ID từ playback_started (required)\r\n * @param params.totalWatchTime - Tổng thời gian đã xem tính bằng giây (required)\r\n * @param params.channelId - Mã kênh EPG (optional)\r\n * @param params.subContentId - ID chương trình trong kênh live (optional)\r\n * @param params.subContentTitle - Title chương trình trong kênh live (optional)\r\n * @param params.eventTime - Unix timestamp (optional)\r\n */\r\nexport const trackPlaybackCompleted = (params: {\r\n contentId: string;\r\n contentTitle: string;\r\n contentType: 0 | 1 | 2;\r\n playbackSession: string;\r\n totalWatchTime: number;\r\n channelId?: string;\r\n subContentId?: string;\r\n subContentTitle?: string;\r\n eventTime?: number;\r\n}) => {\r\n sendEvent({\r\n event_name: \"playback_completed\",\r\n content_id: params.contentId,\r\n content_title: params.contentTitle,\r\n content_type: params.contentType,\r\n playback_session: params.playbackSession,\r\n total_watch_time: params.totalWatchTime,\r\n channel_id: params.channelId,\r\n sub_content_id: params.subContentId,\r\n sub_content_title: params.subContentTitle,\r\n event_time: params.eventTime,\r\n });\r\n};\r\n\r\n/**\r\n * Track playback error event\r\n * Trigger khi:\r\n * - Video không load/play được (network timeout, source 404)\r\n * - Player error (format không hỗ trợ, DRM error)\r\n * - Source error (media source and manifest-related errors)\r\n *\r\n * @param params.contentId - ID của content (required)\r\n * @param params.contentTitle - Tiêu đề content (required)\r\n * @param params.contentType - Loại content: 0=VOD, 1=Live (required)\r\n * @param params.playbackSession - Session ID từ playback_started (required)\r\n * @param params.channelId - Mã kênh EPG (optional)\r\n * @param params.errorCode - Mã lỗi (optional)\r\n * @param params.errorMessage - Thông báo lỗi (optional)\r\n * @param params.errorType - Loại lỗi: video_playback_error, network_error... (optional)\r\n * @param params.eventTime - Unix timestamp (optional)\r\n */\r\nexport const trackPlaybackError = (params: {\r\n contentId: string;\r\n contentTitle: string;\r\n contentType: 0 | 1;\r\n playbackSession: string;\r\n channelId?: string;\r\n errorCode?: number;\r\n errorMessage?: string;\r\n errorType?: string;\r\n eventTime?: number;\r\n}) => {\r\n sendEvent({\r\n event_name: \"playback_error\",\r\n content_id: params.contentId,\r\n content_title: params.contentTitle,\r\n content_type: params.contentType,\r\n playback_session: params.playbackSession,\r\n channel_id: params.channelId,\r\n error_code: params.errorCode,\r\n error_message: params.errorMessage,\r\n error_type: params.errorType,\r\n event_time: params.eventTime,\r\n });\r\n};\r\n\r\n/**\r\n * Track quality changed event\r\n * Trigger khi:\r\n * - Tự động thay đổi do thuật toán ABR\r\n * - Thay đổi chủ động bởi người dùng\r\n *\r\n * @param params.contentId - ID của content (required)\r\n * @param params.contentTitle - Tiêu đề content (required)\r\n * @param params.contentType - Loại content: 0=VOD, 1=Live, 2=Channel (required)\r\n * @param params.playbackSession - Session ID từ playback_started (required)\r\n * @param params.fromQuality - Chất lượng trước khi đổi (required)\r\n * @param params.toQuality - Chất lượng sau khi đổi (required)\r\n * @param params.positionSec - VOD: vị trí hiện tại, Live: số giây từ lúc bắt đầu xem (required)\r\n * @param params.channelId - Mã kênh EPG (optional)\r\n * @param params.reason - Lý do: automatic_abr hoặc user_selected (optional)\r\n * @param params.eventTime - Unix timestamp (optional)\r\n */\r\nexport const trackQualityChanged = (params: {\r\n contentId: string;\r\n contentTitle: string;\r\n contentType: 0 | 1 | 2;\r\n playbackSession: string;\r\n fromQuality: \"360p\" | \"480p\" | \"720p\" | \"1080p\" | \"2k\" | \"4k\";\r\n toQuality: \"360p\" | \"480p\" | \"720p\" | \"1080p\" | \"2k\" | \"4k\";\r\n positionSec: number;\r\n channelId?: string;\r\n reason?: \"automatic_abr\" | \"user_selected\";\r\n eventTime?: number;\r\n}) => {\r\n sendEvent({\r\n event_name: \"quality_changed\",\r\n content_id: params.contentId,\r\n content_title: params.contentTitle,\r\n content_type: params.contentType,\r\n playback_session: params.playbackSession,\r\n from_quality: params.fromQuality,\r\n to_quality: params.toQuality,\r\n position_sec: params.positionSec,\r\n channel_id: params.channelId,\r\n reason: params.reason,\r\n event_time: params.eventTime,\r\n });\r\n};\r\n\r\n/**\r\n * Track buffer started event\r\n * Buffering xảy ra khi video không còn segment kế tiếp để phát, video sẽ pause để tải thêm segment\r\n *\r\n * @param params.contentId - ID của content (required)\r\n * @param params.contentTitle - Tiêu đề content (required)\r\n * @param params.contentType - Loại content: 0=VOD, 1=Live, 2=Channel (required)\r\n * @param params.playbackSession - Session ID từ playback_started (required)\r\n * @param params.videoQuality - Chất lượng video hiện tại (required)\r\n * @param params.positionSec - VOD: vị trí hiện tại, Live: số giây từ lúc bắt đầu xem (required)\r\n * @param params.channelId - Mã kênh EPG (optional)\r\n * @param params.reason - Lý do: seek_operation, network_congestion (optional)\r\n * @param params.eventTime - Unix timestamp (optional)\r\n */\r\nexport const trackBufferStarted = (params: {\r\n contentId: string;\r\n contentTitle: string;\r\n contentType: 0 | 1 | 2;\r\n playbackSession: string;\r\n videoQuality: \"360p\" | \"480p\" | \"720p\" | \"1080p\" | \"2k\" | \"4k\";\r\n positionSec: number;\r\n channelId?: string;\r\n reason?: \"seek_operation\" | \"network_congestion\";\r\n eventTime?: number;\r\n}) => {\r\n sendEvent({\r\n event_name: \"buffer_started\",\r\n content_id: params.contentId,\r\n content_title: params.contentTitle,\r\n content_type: params.contentType,\r\n playback_session: params.playbackSession,\r\n video_quality: params.videoQuality,\r\n position_sec: params.positionSec,\r\n channel_id: params.channelId,\r\n reason: params.reason,\r\n event_time: params.eventTime,\r\n });\r\n};\r\n\r\n/**\r\n * Track buffer ended event\r\n * Khi buffering kết thúc và video tiếp tục phát\r\n *\r\n * @param params.contentId - ID của content (required)\r\n * @param params.contentTitle - Tiêu đề content (required)\r\n * @param params.contentType - Loại content: 0=VOD, 1=Live, 2=Channel (required)\r\n * @param params.playbackSession - Session ID từ playback_started (required)\r\n * @param params.videoQuality - Chất lượng video hiện tại (required)\r\n * @param params.positionSec - VOD: vị trí hiện tại, Live: số giây từ lúc bắt đầu xem (required)\r\n * @param params.bufferDurationSec - Tổng số giây mà buffer xảy ra (required)\r\n * @param params.channelId - Mã kênh EPG (optional)\r\n * @param params.reason - Lý do: seek_operation, network_congestion (optional)\r\n * @param params.eventTime - Unix timestamp (optional)\r\n */\r\nexport const trackBufferEnded = (params: {\r\n contentId: string;\r\n contentTitle: string;\r\n contentType: 0 | 1 | 2;\r\n playbackSession: string;\r\n videoQuality: \"360p\" | \"480p\" | \"720p\" | \"1080p\" | \"2k\" | \"4k\";\r\n positionSec: number;\r\n bufferDurationSec: number;\r\n channelId?: string;\r\n reason?: \"seek_operation\" | \"network_congestion\";\r\n eventTime?: number;\r\n}) => {\r\n sendEvent({\r\n event_name: \"buffer_ended\",\r\n content_id: params.contentId,\r\n content_title: params.contentTitle,\r\n content_type: params.contentType,\r\n playback_session: params.playbackSession,\r\n video_quality: params.videoQuality,\r\n position_sec: params.positionSec,\r\n buffer_duration_sec: params.bufferDurationSec,\r\n channel_id: params.channelId,\r\n reason: params.reason,\r\n event_time: params.eventTime,\r\n });\r\n};\r\n\r\n/**\r\n * Track playback paused event\r\n * Khi user pause video\r\n *\r\n * @param params.contentId - ID của content (required)\r\n * @param params.contentTitle - Tiêu đề content (required)\r\n * @param params.contentType - Loại content: 0=VOD, 1=Live, 2=Channel (required)\r\n * @param params.playbackSession - Session ID từ playback_started (required)\r\n * @param params.positionSec - VOD: vị trí hiện tại, Live: số giây từ lúc bắt đầu xem (required)\r\n * @param params.channelId - Mã kênh EPG (optional)\r\n * @param params.eventTime - Unix timestamp (optional)\r\n */\r\nexport const trackPlaybackPaused = (params: {\r\n contentId: string;\r\n contentTitle: string;\r\n contentType: 0 | 1 | 2;\r\n playbackSession: string;\r\n positionSec: number;\r\n channelId?: string;\r\n eventTime?: number;\r\n}) => {\r\n sendEvent({\r\n event_name: \"playback_paused\",\r\n content_id: params.contentId,\r\n content_title: params.contentTitle,\r\n content_type: params.contentType,\r\n playback_session: params.playbackSession,\r\n position_sec: params.positionSec,\r\n channel_id: params.channelId,\r\n event_time: params.eventTime,\r\n });\r\n};\r\n\r\n/**\r\n * Track playback resumed event\r\n * Khi user resume video sau khi pause\r\n *\r\n * @param params.contentId - ID của content (required)\r\n * @param params.contentTitle - Tiêu đề content (required)\r\n * @param params.contentType - Loại content: 0=VOD, 1=Live, 2=Channel (required)\r\n * @param params.playbackSession - Session ID từ playback_started (required)\r\n * @param params.positionSec - VOD: vị trí hiện tại, Live: số giây từ lúc bắt đầu xem (required)\r\n * @param params.channelId - Mã kênh EPG (optional)\r\n * @param params.eventTime - Unix timestamp (optional)\r\n */\r\nexport const trackPlaybackResumed = (params: {\r\n contentId: string;\r\n contentTitle: string;\r\n contentType: 0 | 1 | 2;\r\n playbackSession: string;\r\n positionSec: number;\r\n channelId?: string;\r\n eventTime?: number;\r\n}) => {\r\n sendEvent({\r\n event_name: \"playback_resumed\",\r\n content_id: params.contentId,\r\n content_title: params.contentTitle,\r\n content_type: params.contentType,\r\n playback_session: params.playbackSession,\r\n position_sec: params.positionSec,\r\n channel_id: params.channelId,\r\n event_time: params.eventTime,\r\n });\r\n};\r\n\r\n/**\r\n * Hook cho Next.js - auto track page views\r\n * Dùng cho Next.js Pages Router (next/router)\r\n */\r\nexport const usePageViewTracking = (router: NextRouterLike) => {\r\n if (typeof window === \"undefined\") return;\r\n if (!router) return;\r\n\r\n // Track initial page\r\n trackPageView(router.asPath);\r\n\r\n const handleRouteChange = (url: string) => {\r\n trackPageView(url);\r\n };\r\n\r\n router.events.on(\"routeChangeComplete\", handleRouteChange);\r\n\r\n return () => {\r\n router.events.off(\"routeChangeComplete\", handleRouteChange);\r\n };\r\n};\r\n","/**\r\n * Session Lifecycle Tracking\r\n * - session_start: Khi user mở app\r\n * - session_progress: Định kỳ khi user đang active (foreground)\r\n * - session_end: Khi user thoát app\r\n */\r\n\r\nimport { sendEvent } from \"./tracking\";\r\nimport { renewSessionCookie } from \"./cookie-utils\";\r\nimport type { SessionCookieConfig } from \"./cookie-utils\";\r\n\r\nexport type SessionConfig = {\r\n /** Khoảng thời gian gửi session_progress (ms). Default: 5 phút */\r\n progressInterval?: number;\r\n /** Có tự động start session khi init không. Default: true */\r\n autoStart?: boolean;\r\n /** Debug mode */\r\n debug?: boolean;\r\n /** User ID */\r\n userId?: string;\r\n /** Session cookie configuration */\r\n sessionCookieConfig?: SessionCookieConfig;\r\n};\r\n\r\nconst defaultSessionConfig: SessionConfig = {\r\n progressInterval: 5 * 60 * 1000, // 5 phút\r\n autoStart: true,\r\n debug: false,\r\n};\r\n\r\n// State\r\nlet sessionConfig: SessionConfig = { ...defaultSessionConfig };\r\nlet progressTimer: ReturnType<typeof setInterval> | null = null;\r\nlet isSessionActive = false;\r\nlet sessionStartTime: number = 0;\r\n\r\n/**\r\n * Log helper\r\n */\r\nconst log = (...args: any[]) => {\r\n if (sessionConfig.debug) {\r\n console.log(\"[Session]\", ...args);\r\n }\r\n};\r\n\r\n/**\r\n * Gửi event session_start\r\n */\r\nexport const sendSessionStart = () => {\r\n if (isSessionActive) {\r\n log(\"Session already started, skipping...\");\r\n return;\r\n }\r\n\r\n sessionStartTime = Date.now();\r\n isSessionActive = true;\r\n\r\n sendEvent({\r\n event_name: \"session_start\",\r\n event_time: sessionStartTime,\r\n });\r\n\r\n log(\"Session started at:\", new Date(sessionStartTime).toISOString());\r\n\r\n // Gia hạn cookie session_id (extend thêm 30 phút)\r\n renewSessionIdCookie();\r\n\r\n // Bắt đầu timer cho session_progress\r\n startProgressTimer();\r\n};\r\n\r\n/**\r\n * Gửi event session_progress\r\n */\r\nexport const sendSessionProgress = () => {\r\n if (!isSessionActive) return;\r\n\r\n const now = Math.floor(Date.now() / 1000);\r\n const duration = now - sessionStartTime;\r\n\r\n sendEvent({\r\n event_name: \"session_progress\",\r\n session_duration: duration,\r\n });\r\n\r\n log(\"Session progress - Duration:\", Math.round(duration / 1000), \"seconds\");\r\n\r\n // Gia hạn cookie session_id mỗi khi user còn active\r\n renewSessionIdCookie();\r\n};\r\n\r\n/**\r\n * Gửi event session_end\r\n */\r\nexport const sendSessionEnd = () => {\r\n if (!isSessionActive) return;\r\n\r\n const now = Math.floor(Date.now() / 1000);\r\n const duration = now - sessionStartTime;\r\n\r\n // Dùng sendBeacon để đảm bảo gửi được khi page unload\r\n // Fallback về sendEvent nếu không support\r\n sendEvent({\r\n event_name: \"session_end\",\r\n session_duration: duration,\r\n });\r\n\r\n log(\r\n \"Session ended - Total duration:\",\r\n Math.round(duration / 1000),\r\n \"seconds\",\r\n );\r\n\r\n stopProgressTimer();\r\n isSessionActive = false;\r\n};\r\n\r\n/**\r\n * Start progress timer\r\n */\r\nconst startProgressTimer = () => {\r\n stopProgressTimer(); // Clear existing timer\r\n\r\n if (sessionConfig.progressInterval && sessionConfig.progressInterval > 0) {\r\n progressTimer = setInterval(() => {\r\n if (document.visibilityState === \"visible\") {\r\n sendSessionProgress();\r\n }\r\n }, sessionConfig.progressInterval);\r\n\r\n log(\r\n \"Progress timer started, interval:\",\r\n sessionConfig.progressInterval,\r\n \"ms\",\r\n );\r\n }\r\n};\r\n\r\n/**\r\n * Stop progress timer\r\n */\r\nconst stopProgressTimer = () => {\r\n if (progressTimer) {\r\n clearInterval(progressTimer);\r\n progressTimer = null;\r\n log(\"Progress timer stopped\");\r\n }\r\n};\r\n\r\n/**\r\n * Handle visibility change\r\n * Pause/resume tracking khi user switch tab hoặc minimize\r\n */\r\nconst handleVisibilityChange = () => {\r\n if (document.visibilityState === \"hidden\") {\r\n // User rời khỏi tab - có thể gửi session_progress cuối\r\n log(\"Tab hidden - pausing progress\");\r\n stopProgressTimer();\r\n } else if (document.visibilityState === \"visible\") {\r\n // User quay lại tab - resume timer\r\n log(\"Tab visible - resuming progress\");\r\n if (isSessionActive) {\r\n startProgressTimer();\r\n }\r\n }\r\n};\r\n\r\n/**\r\n * Handle page unload (user đóng tab/browser)\r\n */\r\nconst handleBeforeUnload = () => {\r\n sendSessionEnd();\r\n};\r\n\r\n/**\r\n * Handle page hide (mobile: switch app, close tab)\r\n * Dùng pagehide thay vì beforeunload cho mobile\r\n */\r\nconst handlePageHide = (event: PageTransitionEvent) => {\r\n if (event.persisted) {\r\n // Page được cache (bfcache) - không phải thực sự đóng\r\n return;\r\n }\r\n sendSessionEnd();\r\n};\r\n\r\n/**\r\n * Khởi tạo session tracking\r\n */\r\nexport const initSessionTracking = (options: SessionConfig = {}) => {\r\n if (typeof window === \"undefined\") return;\r\n\r\n sessionConfig = { ...defaultSessionConfig, ...options };\r\n\r\n log(\"Initializing with config:\", sessionConfig);\r\n\r\n // Đăng ký event listeners\r\n document.addEventListener(\"visibilitychange\", handleVisibilityChange);\r\n window.addEventListener(\"beforeunload\", handleBeforeUnload);\r\n window.addEventListener(\"pagehide\", handlePageHide);\r\n\r\n // Auto start session\r\n if (sessionConfig.autoStart) {\r\n sendSessionStart();\r\n }\r\n};\r\n\r\n/**\r\n * Cleanup - gọi khi unmount component\r\n */\r\nexport const cleanupSessionTracking = () => {\r\n if (typeof window === \"undefined\") return;\r\n\r\n log(\"Cleaning up...\");\r\n\r\n document.removeEventListener(\"visibilitychange\", handleVisibilityChange);\r\n window.removeEventListener(\"beforeunload\", handleBeforeUnload);\r\n window.removeEventListener(\"pagehide\", handlePageHide);\r\n\r\n stopProgressTimer();\r\n isSessionActive = false;\r\n};\r\n\r\n/**\r\n * Cập nhật config (ví dụ: thay đổi interval)\r\n */\r\nexport const updateSessionConfig = (options: Partial<SessionConfig>) => {\r\n sessionConfig = { ...sessionConfig, ...options };\r\n\r\n // Restart timer nếu interval thay đổi\r\n if (options.progressInterval !== undefined && isSessionActive) {\r\n startProgressTimer();\r\n }\r\n\r\n log(\"Config updated:\", sessionConfig);\r\n};\r\n\r\n/**\r\n * Kiểm tra session đang active không\r\n */\r\nexport const isSessionStarted = () => isSessionActive;\r\n\r\n/**\r\n * Lấy thời gian session hiện tại (ms)\r\n */\r\nexport const getSessionDuration = () => {\r\n if (!isSessionActive) return 0;\r\n return Date.now() - sessionStartTime;\r\n};\r\n\r\n/**\r\n * Gia hạn cookie session_id\r\n * Gọi khi có event session_start để extend thời gian hết hạn\r\n */\r\nconst renewSessionIdCookie = () => {\r\n // Lấy sessionId và config từ tracking module\r\n // Note: Cần export getter function từ tracking.ts để lấy config\r\n if (sessionConfig.sessionCookieConfig) {\r\n // Tạm thời sử dụng cookie name mặc định để lấy sessionId\r\n const cookieName =\r\n sessionConfig.sessionCookieConfig.cookieName || \"_tracking_session_id\";\r\n const sessionId = document.cookie\r\n .split(\"; \")\r\n .find((row) => row.startsWith(cookieName + \"=\"))\r\n ?.split(\"=\")[1];\r\n\r\n if (sessionId) {\r\n renewSessionCookie(sessionId, sessionConfig.sessionCookieConfig);\r\n log(\"Session cookie renewed for:\", sessionId);\r\n }\r\n }\r\n};\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACMO,IAAM,gBAAgC;AAAA,EAC3C,SAAS;AAAA,EACT,OAAO;AAAA,EACP,WAAW;AAAA,EACX,UAAU;AAAA,EACV,OAAO;AAAA;AAAA,EAGP,WAAW;AAAA,EACX,aAAa;AAAA,EACb,UAAU;AAAA,EACV,aAAa;AAAA,EACb,aAAa;AAAA;AAAA,EAGb,MAAM;AAAA,EACN,YAAY;AAAA,EAEZ,QAAQ,CAAC;AACX;AAEO,IAAM,iBAA0B;AAAA,EACrC,IAAI,EAAE,MAAM,QAAQ,IAAI,OAAO;AAAA,EAC/B,MAAM,EAAE,MAAM,QAAQ,IAAI,OAAO;AAAA,EACjC,QAAQ,EAAE,MAAM,UAAU,IAAI,SAAS;AAAA,EACvC,QAAQ,EAAE,MAAM,UAAU,IAAI,SAAS;AAAA,EACvC,QAAQ,EAAE,MAAM,UAAU,IAAI,SAAS;AAAA,EACvC,OAAO,EAAE,MAAM,SAAS,IAAI,QAAQ;AAAA,EACpC,MAAM,EAAE,MAAM,QAAQ,IAAI,OAAO;AAAA,EACjC,UAAU,EAAE,MAAM,YAAY,IAAI,WAAW;AAAA,EAC7C,SAAS,EAAE,MAAM,WAAW,IAAI,UAAU;AAAA,EAC1C,OAAO,EAAE,MAAM,SAAS,IAAI,QAAQ;AACtC;;;AClCA,kBAA6B;AAe7B,IAAM,sBAAqD;AAAA,EACzD,YAAY;AAAA,EACZ,mBAAmB;AAAA,EACnB,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AACZ;AAKO,IAAM,oBAAoB,MAAc;AAC7C,aAAO,YAAAA,IAAO;AAChB;AAKO,IAAM,YAAY,CAAC,SAAgC;AACxD,MAAI,OAAO,aAAa,YAAa,QAAO;AAE5C,QAAM,SAAS,OAAO;AACtB,QAAM,UAAU,SAAS,OAAO,MAAM,GAAG;AAEzC,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,QAAI,SAAS,QAAQ,CAAC;AACtB,WAAO,OAAO,OAAO,CAAC,MAAM,KAAK;AAC/B,eAAS,OAAO,UAAU,CAAC;AAAA,IAC7B;AACA,QAAI,OAAO,QAAQ,MAAM,MAAM,GAAG;AAChC,aAAO,OAAO,UAAU,OAAO,MAAM;AAAA,IACvC;AAAA,EACF;AACA,SAAO;AACT;AAKO,IAAM,YAAY,CACvB,MACA,OACA,UAA+B,CAAC,MACvB;AACT,MAAI,OAAO,aAAa,YAAa;AAErC,QAAMC,UAAS,EAAE,GAAG,qBAAqB,GAAG,QAAQ;AACpD,QAAM,eAAeA,QAAO,oBAAoB,KAAK;AACrD,QAAM,UAAU,IAAI,KAAK,KAAK,IAAI,IAAI,YAAY;AAElD,MAAI,eAAe,GAAG,IAAI,IAAI,KAAK,aAAa,QAAQ,YAAY,CAAC,UAAUA,QAAO,IAAI;AAE1F,MAAIA,QAAO,QAAQ;AACjB,oBAAgB,YAAYA,QAAO,MAAM;AAAA,EAC3C;AAEA,MAAIA,QAAO,UAAU;AACnB,oBAAgB,cAAcA,QAAO,QAAQ;AAAA,EAC/C;AAGA,MAAIA,QAAO,aAAa,QAAQ;AAC9B,oBAAgB;AAAA,EAClB;AAEA,WAAS,SAAS;AACpB;AAOO,IAAM,uBAAuB,CAClC,UAA+B,CAAC,MACrB;AACX,QAAMA,UAAS,EAAE,GAAG,qBAAqB,GAAG,QAAQ;AACpD,QAAM,oBAAoB,UAAUA,QAAO,UAAU;AAErD,MAAI,mBAAmB;AACrB,WAAO;AAAA,EACT;AAGA,QAAM,eAAe,kBAAkB;AACvC,YAAUA,QAAO,YAAY,cAAc,OAAO;AAElD,SAAO;AACT;AAMO,IAAM,qBAAqB,CAChC,WACA,UAA+B,CAAC,MACvB;AACT,QAAMA,UAAS,EAAE,GAAG,qBAAqB,GAAG,QAAQ;AACpD,YAAUA,QAAO,YAAY,WAAW,OAAO;AACjD;AAKO,IAAM,qBAAqB,CAAC,UAA+B,CAAC,MAAY;AAC7E,MAAI,OAAO,aAAa,YAAa;AAErC,QAAMA,UAAS,EAAE,GAAG,qBAAqB,GAAG,QAAQ;AACpD,MAAI,eAAe,GAAGA,QAAO,UAAU,kDAAkDA,QAAO,IAAI;AAEpG,MAAIA,QAAO,QAAQ;AACjB,oBAAgB,YAAYA,QAAO,MAAM;AAAA,EAC3C;AAEA,WAAS,SAAS;AACpB;;;ACvHA,IAAI,SAAyB,EAAE,GAAG,cAAc;AAGhD,IAAI,SAAiB,OAAO,UAAU;AAGtC,IAAI,iBAAgC;AAGpC,IAAI,UAAe,EAAE,GAAG,eAAe;AAKhC,IAAM,eAAe,CAC1B,UAAoE,CAAC,MAClE;AACH,WAAS,EAAE,GAAG,QAAQ,GAAG,QAAQ;AAGjC,MAAI,CAAC,OAAO,WAAW;AACrB,WAAO,YAAY,qBAAqB,OAAO,mBAAmB;AAClE,QAAI,OAAO,OAAO;AAChB,cAAQ;AAAA,QACN;AAAA,QACA,OAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF,WAAW,OAAO,OAAO;AACvB,YAAQ,IAAI,8CAA8C,OAAO,SAAS;AAAA,EAC5E;AAEA,MAAI,QAAQ,SAAS;AACnB,cAAU,EAAE,GAAG,SAAS,GAAG,QAAQ,QAAQ;AAAA,EAC7C;AAEA,MAAI,CAAC,OAAO,SAAS;AACnB,YAAQ,KAAK,iCAAiC;AAAA,EAChD;AAEA,MAAI,OAAO,OAAO;AAChB,YAAQ,IAAI,uCAAuC,MAAM;AAAA,EAC3D;AACF;AAMO,IAAM,cAAc,CAAC,aAAuB;AACjD,WAAS,SAAS;AAElB,MAAI,OAAO,OAAO;AAChB,YAAQ,IAAI,yCAAyC,MAAM;AAAA,EAC7D;AACF;AAKO,IAAM,cAAc,CAAC,QAA0B;AACpD,QAAM,QAAQ,OAAO,IAAI,MAAM,GAAG,EAAE,CAAC;AACrC,QAAM,cAAc,KAAK,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,CAAC,KAAK;AAE1D,SACE,QAAQ,WAAW,KAAK;AAAA,IACtB,MAAM,YAAY,OAAO,CAAC,EAAE,YAAY,IAAI,YAAY,MAAM,CAAC;AAAA,IAC/D,IAAI;AAAA,EACN;AAEJ;AAKO,IAAM,YAAY,OACvB,cAC6B;AAC7B,MAAI,CAAC,OAAO,SAAS;AACnB,YAAQ,KAAK,wDAAwD;AACrE;AAAA,EACF;AAEA,MAAI;AAEF,UAAM,EAAE,YAAY,GAAG,YAAY,IAAI;AAGvC,UAAM,cAAc;AAAA;AAAA,MAElB,SAAS;AAAA;AAAA,MACT,YAAY,cAAc;AAAA,MAC1B,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA;AAAA,MAExC,cAAc,KAAK,UAAU,WAAW;AAAA;AAAA,IAC1C;AAEA,UAAM,UAAU;AAAA;AAAA,MAEd,QAAQ,OAAO;AAAA,MACf,YAAY,OAAO;AAAA,MACnB,UAAU,OAAO;AAAA,MAEjB,YAAY,OAAO;AAAA,MACnB,cAAc,OAAO;AAAA,MAErB,WAAW,OAAO;AAAA,MAClB,cAAc,OAAO;AAAA,MACrB,cAAc,OAAO;AAAA;AAAA,MAGrB,OAAO,OAAO;AAAA,MACd,mBAAmB,OAAO;AAAA,MAC1B,aAAa,OAAO;AAAA;AAAA;AAAA,MAKpB,QAAQ,CAAC,GAAI,OAAO,UAAU,CAAC,GAAI,WAAW;AAAA,IAChD;AAEA,QAAI,OAAO,OAAO;AAChB,cAAQ,IAAI,6BAA6B,OAAO;AAAA,IAClD;AAEA,UAAM,MAAM,MAAM,MAAM,OAAO,SAAS;AAAA,MACtC,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,OAAO;AAAA,IAC9B,CAAC;AAED,QAAI,CAAC,IAAI,MAAM,OAAO,OAAO;AAC3B,YAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,cAAQ,MAAM,0BAA0B,IAAI,QAAQ,IAAI;AAAA,IAC1D;AAEA,WAAO;AAAA,EACT,SAAS,OAAO;AACd,QAAI,OAAO,OAAO;AAChB,cAAQ,MAAM,qBAAqB,KAAK;AAAA,IAC1C;AAAA,EACF;AACF;AAKO,IAAM,gBAAgB,CAAC,QAAgB;AAC5C,QAAM,WAAW,YAAY,GAAG;AAEhC,YAAU;AAAA,IACR,YAAY;AAAA,IACZ,WAAW,SAAS;AAAA,IACpB,SAAS,SAAS;AAAA,IAClB,WAAW;AAAA,IACX,kBAAkB,kBAAkB;AAAA,EACtC,CAAC;AAED,mBAAiB,SAAS;AAC5B;AAKO,IAAM,aAAa,CAAC,WAAmB,SAAoB,CAAC,MAAM;AACvE,YAAU;AAAA,IACR,YAAY;AAAA,IACZ,GAAG;AAAA,EACL,CAAC;AACH;AAUO,IAAM,0BAA0B,CACrC,WACA,cACA,aACA,WACA,cACG;AACH,YAAU;AAAA,IACR,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,YAAY;AAAA,EACd,CAAC;AACH;AAiBO,IAAM,uBAAuB,CAAC,WAa/B;AACJ,YAAU;AAAA,IACR,YAAY;AAAA,IACZ,YAAY,OAAO;AAAA,IACnB,eAAe,OAAO;AAAA,IACtB,cAAc,OAAO;AAAA,IACrB,kBAAkB,OAAO;AAAA,IACzB,eAAe,OAAO;AAAA,IACtB,iBAAiB,OAAO;AAAA,IACxB,gBAAgB,OAAO;AAAA,IACvB,UAAU,OAAO;AAAA,IACjB,YAAY,OAAO;AAAA,IACnB,gBAAgB,OAAO;AAAA,IACvB,mBAAmB,OAAO;AAAA,IAC1B,YAAY,OAAO;AAAA,EACrB,CAAC;AACH;AAiBO,IAAM,wBAAwB,CAAC,WAUhC;AACJ,YAAU;AAAA,IACR,YAAY;AAAA,IACZ,YAAY,OAAO;AAAA,IACnB,eAAe,OAAO;AAAA,IACtB,cAAc,OAAO;AAAA,IACrB,kBAAkB,OAAO;AAAA,IACzB,kBAAkB,OAAO;AAAA,IACzB,YAAY,OAAO;AAAA,IACnB,gBAAgB,OAAO;AAAA,IACvB,mBAAmB,OAAO;AAAA,IAC1B,YAAY,OAAO;AAAA,EACrB,CAAC;AACH;AAiBO,IAAM,yBAAyB,CAAC,WAUjC;AACJ,YAAU;AAAA,IACR,YAAY;AAAA,IACZ,YAAY,OAAO;AAAA,IACnB,eAAe,OAAO;AAAA,IACtB,cAAc,OAAO;AAAA,IACrB,kBAAkB,OAAO;AAAA,IACzB,kBAAkB,OAAO;AAAA,IACzB,YAAY,OAAO;AAAA,IACnB,gBAAgB,OAAO;AAAA,IACvB,mBAAmB,OAAO;AAAA,IAC1B,YAAY,OAAO;AAAA,EACrB,CAAC;AACH;AAmBO,IAAM,qBAAqB,CAAC,WAU7B;AACJ,YAAU;AAAA,IACR,YAAY;AAAA,IACZ,YAAY,OAAO;AAAA,IACnB,eAAe,OAAO;AAAA,IACtB,cAAc,OAAO;AAAA,IACrB,kBAAkB,OAAO;AAAA,IACzB,YAAY,OAAO;AAAA,IACnB,YAAY,OAAO;AAAA,IACnB,eAAe,OAAO;AAAA,IACtB,YAAY,OAAO;AAAA,IACnB,YAAY,OAAO;AAAA,EACrB,CAAC;AACH;AAmBO,IAAM,sBAAsB,CAAC,WAW9B;AACJ,YAAU;AAAA,IACR,YAAY;AAAA,IACZ,YAAY,OAAO;AAAA,IACnB,eAAe,OAAO;AAAA,IACtB,cAAc,OAAO;AAAA,IACrB,kBAAkB,OAAO;AAAA,IACzB,cAAc,OAAO;AAAA,IACrB,YAAY,OAAO;AAAA,IACnB,cAAc,OAAO;AAAA,IACrB,YAAY,OAAO;AAAA,IACnB,QAAQ,OAAO;AAAA,IACf,YAAY,OAAO;AAAA,EACrB,CAAC;AACH;AAgBO,IAAM,qBAAqB,CAAC,WAU7B;AACJ,YAAU;AAAA,IACR,YAAY;AAAA,IACZ,YAAY,OAAO;AAAA,IACnB,eAAe,OAAO;AAAA,IACtB,cAAc,OAAO;AAAA,IACrB,kBAAkB,OAAO;AAAA,IACzB,eAAe,OAAO;AAAA,IACtB,cAAc,OAAO;AAAA,IACrB,YAAY,OAAO;AAAA,IACnB,QAAQ,OAAO;AAAA,IACf,YAAY,OAAO;AAAA,EACrB,CAAC;AACH;AAiBO,IAAM,mBAAmB,CAAC,WAW3B;AACJ,YAAU;AAAA,IACR,YAAY;AAAA,IACZ,YAAY,OAAO;AAAA,IACnB,eAAe,OAAO;AAAA,IACtB,cAAc,OAAO;AAAA,IACrB,kBAAkB,OAAO;AAAA,IACzB,eAAe,OAAO;AAAA,IACtB,cAAc,OAAO;AAAA,IACrB,qBAAqB,OAAO;AAAA,IAC5B,YAAY,OAAO;AAAA,IACnB,QAAQ,OAAO;AAAA,IACf,YAAY,OAAO;AAAA,EACrB,CAAC;AACH;AAcO,IAAM,sBAAsB,CAAC,WAQ9B;AACJ,YAAU;AAAA,IACR,YAAY;AAAA,IACZ,YAAY,OAAO;AAAA,IACnB,eAAe,OAAO;AAAA,IACtB,cAAc,OAAO;AAAA,IACrB,kBAAkB,OAAO;AAAA,IACzB,cAAc,OAAO;AAAA,IACrB,YAAY,OAAO;AAAA,IACnB,YAAY,OAAO;AAAA,EACrB,CAAC;AACH;AAcO,IAAM,uBAAuB,CAAC,WAQ/B;AACJ,YAAU;AAAA,IACR,YAAY;AAAA,IACZ,YAAY,OAAO;AAAA,IACnB,eAAe,OAAO;AAAA,IACtB,cAAc,OAAO;AAAA,IACrB,kBAAkB,OAAO;AAAA,IACzB,cAAc,OAAO;AAAA,IACrB,YAAY,OAAO;AAAA,IACnB,YAAY,OAAO;AAAA,EACrB,CAAC;AACH;AAMO,IAAM,sBAAsB,CAAC,WAA2B;AAC7D,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI,CAAC,OAAQ;AAGb,gBAAc,OAAO,MAAM;AAE3B,QAAM,oBAAoB,CAAC,QAAgB;AACzC,kBAAc,GAAG;AAAA,EACnB;AAEA,SAAO,OAAO,GAAG,uBAAuB,iBAAiB;AAEzD,SAAO,MAAM;AACX,WAAO,OAAO,IAAI,uBAAuB,iBAAiB;AAAA,EAC5D;AACF;;;ACvjBA,IAAM,uBAAsC;AAAA,EAC1C,kBAAkB,IAAI,KAAK;AAAA;AAAA,EAC3B,WAAW;AAAA,EACX,OAAO;AACT;AAGA,IAAI,gBAA+B,EAAE,GAAG,qBAAqB;AAC7D,IAAI,gBAAuD;AAC3D,IAAI,kBAAkB;AACtB,IAAI,mBAA2B;AAK/B,IAAM,MAAM,IAAI,SAAgB;AAC9B,MAAI,cAAc,OAAO;AACvB,YAAQ,IAAI,aAAa,GAAG,IAAI;AAAA,EAClC;AACF;AAKO,IAAM,mBAAmB,MAAM;AACpC,MAAI,iBAAiB;AACnB,QAAI,sCAAsC;AAC1C;AAAA,EACF;AAEA,qBAAmB,KAAK,IAAI;AAC5B,oBAAkB;AAElB,YAAU;AAAA,IACR,YAAY;AAAA,IACZ,YAAY;AAAA,EACd,CAAC;AAED,MAAI,uBAAuB,IAAI,KAAK,gBAAgB,EAAE,YAAY,CAAC;AAGnE,uBAAqB;AAGrB,qBAAmB;AACrB;AAKO,IAAM,sBAAsB,MAAM;AACvC,MAAI,CAAC,gBAAiB;AAEtB,QAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,QAAM,WAAW,MAAM;AAEvB,YAAU;AAAA,IACR,YAAY;AAAA,IACZ,kBAAkB;AAAA,EACpB,CAAC;AAED,MAAI,gCAAgC,KAAK,MAAM,WAAW,GAAI,GAAG,SAAS;AAG1E,uBAAqB;AACvB;AAKO,IAAM,iBAAiB,MAAM;AAClC,MAAI,CAAC,gBAAiB;AAEtB,QAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,QAAM,WAAW,MAAM;AAIvB,YAAU;AAAA,IACR,YAAY;AAAA,IACZ,kBAAkB;AAAA,EACpB,CAAC;AAED;AAAA,IACE;AAAA,IACA,KAAK,MAAM,WAAW,GAAI;AAAA,IAC1B;AAAA,EACF;AAEA,oBAAkB;AAClB,oBAAkB;AACpB;AAKA,IAAM,qBAAqB,MAAM;AAC/B,oBAAkB;AAElB,MAAI,cAAc,oBAAoB,cAAc,mBAAmB,GAAG;AACxE,oBAAgB,YAAY,MAAM;AAChC,UAAI,SAAS,oBAAoB,WAAW;AAC1C,4BAAoB;AAAA,MACtB;AAAA,IACF,GAAG,cAAc,gBAAgB;AAEjC;AAAA,MACE;AAAA,MACA,cAAc;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACF;AAKA,IAAM,oBAAoB,MAAM;AAC9B,MAAI,eAAe;AACjB,kBAAc,aAAa;AAC3B,oBAAgB;AAChB,QAAI,wBAAwB;AAAA,EAC9B;AACF;AAMA,IAAM,yBAAyB,MAAM;AACnC,MAAI,SAAS,oBAAoB,UAAU;AAEzC,QAAI,+BAA+B;AACnC,sBAAkB;AAAA,EACpB,WAAW,SAAS,oBAAoB,WAAW;AAEjD,QAAI,iCAAiC;AACrC,QAAI,iBAAiB;AACnB,yBAAmB;AAAA,IACrB;AAAA,EACF;AACF;AAKA,IAAM,qBAAqB,MAAM;AAC/B,iBAAe;AACjB;AAMA,IAAM,iBAAiB,CAAC,UAA+B;AACrD,MAAI,MAAM,WAAW;AAEnB;AAAA,EACF;AACA,iBAAe;AACjB;AAKO,IAAM,sBAAsB,CAAC,UAAyB,CAAC,MAAM;AAClE,MAAI,OAAO,WAAW,YAAa;AAEnC,kBAAgB,EAAE,GAAG,sBAAsB,GAAG,QAAQ;AAEtD,MAAI,6BAA6B,aAAa;AAG9C,WAAS,iBAAiB,oBAAoB,sBAAsB;AACpE,SAAO,iBAAiB,gBAAgB,kBAAkB;AAC1D,SAAO,iBAAiB,YAAY,cAAc;AAGlD,MAAI,cAAc,WAAW;AAC3B,qBAAiB;AAAA,EACnB;AACF;AAKO,IAAM,yBAAyB,MAAM;AAC1C,MAAI,OAAO,WAAW,YAAa;AAEnC,MAAI,gBAAgB;AAEpB,WAAS,oBAAoB,oBAAoB,sBAAsB;AACvE,SAAO,oBAAoB,gBAAgB,kBAAkB;AAC7D,SAAO,oBAAoB,YAAY,cAAc;AAErD,oBAAkB;AAClB,oBAAkB;AACpB;AAKO,IAAM,sBAAsB,CAAC,YAAoC;AACtE,kBAAgB,EAAE,GAAG,eAAe,GAAG,QAAQ;AAG/C,MAAI,QAAQ,qBAAqB,UAAa,iBAAiB;AAC7D,uBAAmB;AAAA,EACrB;AAEA,MAAI,mBAAmB,aAAa;AACtC;AAKO,IAAM,mBAAmB,MAAM;AAK/B,IAAM,qBAAqB,MAAM;AACtC,MAAI,CAAC,gBAAiB,QAAO;AAC7B,SAAO,KAAK,IAAI,IAAI;AACtB;AAMA,IAAM,uBAAuB,MAAM;AAGjC,MAAI,cAAc,qBAAqB;AAErC,UAAM,aACJ,cAAc,oBAAoB,cAAc;AAClD,UAAM,YAAY,SAAS,OACxB,MAAM,IAAI,EACV,KAAK,CAAC,QAAQ,IAAI,WAAW,aAAa,GAAG,CAAC,GAC7C,MAAM,GAAG,EAAE,CAAC;AAEhB,QAAI,WAAW;AACb,yBAAmB,WAAW,cAAc,mBAAmB;AAC/D,UAAI,+BAA+B,SAAS;AAAA,IAC9C;AAAA,EACF;AACF;;;AJjLA,IAAM,WAAW;AAAA,EACf,MAAM;AAAA,EACN;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,YAAY;AACd;AAEA,IAAO,gBAAQ;","names":["uuidv4","config"]}
|