connectbase-client 0.14.0 → 0.15.1
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/dist/cli.js +181 -156
- package/dist/connect-base.umd.js +3 -3
- package/dist/index.d.mts +163 -4
- package/dist/index.d.ts +163 -4
- package/dist/index.js +650 -2
- package/dist/index.mjs +649 -2
- package/package.json +4 -2
package/dist/index.mjs
CHANGED
|
@@ -16,11 +16,14 @@ var AuthError = class extends Error {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
// src/core/http.ts
|
|
19
|
+
var TOKEN_STORAGE_KEY = "cb_auth_tokens";
|
|
19
20
|
var HttpClient = class {
|
|
20
21
|
constructor(config) {
|
|
21
22
|
this.isRefreshing = false;
|
|
22
23
|
this.refreshPromise = null;
|
|
23
24
|
this.config = { ...config };
|
|
25
|
+
this.storageKey = this.buildStorageKey();
|
|
26
|
+
this.restoreTokens();
|
|
24
27
|
}
|
|
25
28
|
updateConfig(config) {
|
|
26
29
|
this.config = { ...this.config, ...config };
|
|
@@ -28,10 +31,64 @@ var HttpClient = class {
|
|
|
28
31
|
setTokens(accessToken, refreshToken) {
|
|
29
32
|
this.config.accessToken = accessToken;
|
|
30
33
|
this.config.refreshToken = refreshToken;
|
|
34
|
+
this.persistTokens();
|
|
31
35
|
}
|
|
32
36
|
clearTokens() {
|
|
33
37
|
this.config.accessToken = void 0;
|
|
34
38
|
this.config.refreshToken = void 0;
|
|
39
|
+
this.removePersistedTokens();
|
|
40
|
+
}
|
|
41
|
+
// ===== Token Persistence =====
|
|
42
|
+
get persistence() {
|
|
43
|
+
return this.config.persistence ?? "localStorage";
|
|
44
|
+
}
|
|
45
|
+
getStorage() {
|
|
46
|
+
if (typeof window === "undefined") return null;
|
|
47
|
+
if (this.persistence === "localStorage" && typeof localStorage !== "undefined") return localStorage;
|
|
48
|
+
if (this.persistence === "sessionStorage" && typeof sessionStorage !== "undefined") return sessionStorage;
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
buildStorageKey() {
|
|
52
|
+
const credential = this.config.publicKey ?? this.config.secretKey;
|
|
53
|
+
if (!credential) return TOKEN_STORAGE_KEY;
|
|
54
|
+
let hash = 0;
|
|
55
|
+
for (let i = 0; i < credential.length; i++) {
|
|
56
|
+
hash = (hash << 5) - hash + credential.charCodeAt(i);
|
|
57
|
+
hash = hash & hash;
|
|
58
|
+
}
|
|
59
|
+
return `${TOKEN_STORAGE_KEY}_${Math.abs(hash).toString(36)}`;
|
|
60
|
+
}
|
|
61
|
+
persistTokens() {
|
|
62
|
+
if (this.persistence === "none") return;
|
|
63
|
+
const storage = this.getStorage();
|
|
64
|
+
if (!storage || !this.config.accessToken || !this.config.refreshToken) return;
|
|
65
|
+
storage.setItem(this.storageKey, JSON.stringify({
|
|
66
|
+
accessToken: this.config.accessToken,
|
|
67
|
+
refreshToken: this.config.refreshToken
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
restoreTokens() {
|
|
71
|
+
if (this.persistence === "none") return;
|
|
72
|
+
if (this.config.accessToken && this.config.refreshToken) return;
|
|
73
|
+
const storage = this.getStorage();
|
|
74
|
+
if (!storage) return;
|
|
75
|
+
const stored = storage.getItem(this.storageKey);
|
|
76
|
+
if (!stored) return;
|
|
77
|
+
try {
|
|
78
|
+
const { accessToken, refreshToken } = JSON.parse(stored);
|
|
79
|
+
if (accessToken && refreshToken) {
|
|
80
|
+
this.config.accessToken = accessToken;
|
|
81
|
+
this.config.refreshToken = refreshToken;
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
storage.removeItem(this.storageKey);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
removePersistedTokens() {
|
|
88
|
+
if (this.persistence === "none") return;
|
|
89
|
+
const storage = this.getStorage();
|
|
90
|
+
if (!storage) return;
|
|
91
|
+
storage.removeItem(this.storageKey);
|
|
35
92
|
}
|
|
36
93
|
/**
|
|
37
94
|
* Public Key 가 설정되어 있는지 확인
|
|
@@ -100,8 +157,7 @@ var HttpClient = class {
|
|
|
100
157
|
throw new Error("Token refresh failed");
|
|
101
158
|
}
|
|
102
159
|
const data = await response.json();
|
|
103
|
-
this.
|
|
104
|
-
this.config.refreshToken = data.refresh_token;
|
|
160
|
+
this.setTokens(data.access_token, data.refresh_token);
|
|
105
161
|
this.config.onTokenRefresh?.({
|
|
106
162
|
accessToken: data.access_token,
|
|
107
163
|
refreshToken: data.refresh_token
|
|
@@ -7422,6 +7478,594 @@ var QueueAPI = class {
|
|
|
7422
7478
|
}
|
|
7423
7479
|
};
|
|
7424
7480
|
|
|
7481
|
+
// src/api/analytics.ts
|
|
7482
|
+
var SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
7483
|
+
var SESSION_KEY = "__cb_session";
|
|
7484
|
+
var VISITOR_KEY = "__cb_visitor_uid";
|
|
7485
|
+
var LAST_ACTIVITY_KEY = "__cb_last_activity";
|
|
7486
|
+
function generateId() {
|
|
7487
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
7488
|
+
return crypto.randomUUID();
|
|
7489
|
+
}
|
|
7490
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
7491
|
+
const r = Math.random() * 16 | 0;
|
|
7492
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
7493
|
+
return v.toString(16);
|
|
7494
|
+
});
|
|
7495
|
+
}
|
|
7496
|
+
var SessionManager = class {
|
|
7497
|
+
constructor() {
|
|
7498
|
+
this._sessionId = null;
|
|
7499
|
+
this._visitorUid = null;
|
|
7500
|
+
this._lastActivity = 0;
|
|
7501
|
+
this._isNewSession = false;
|
|
7502
|
+
}
|
|
7503
|
+
get sessionId() {
|
|
7504
|
+
this.ensureSession();
|
|
7505
|
+
return this._sessionId;
|
|
7506
|
+
}
|
|
7507
|
+
get visitorUid() {
|
|
7508
|
+
if (!this._visitorUid) {
|
|
7509
|
+
this._visitorUid = this.loadOrCreateVisitorUid();
|
|
7510
|
+
}
|
|
7511
|
+
return this._visitorUid;
|
|
7512
|
+
}
|
|
7513
|
+
get isNewSession() {
|
|
7514
|
+
return this._isNewSession;
|
|
7515
|
+
}
|
|
7516
|
+
/** 활동 기록 — 세션 타임아웃 리셋 */
|
|
7517
|
+
touch() {
|
|
7518
|
+
this._lastActivity = Date.now();
|
|
7519
|
+
this._isNewSession = false;
|
|
7520
|
+
try {
|
|
7521
|
+
if (typeof sessionStorage !== "undefined") {
|
|
7522
|
+
sessionStorage.setItem(LAST_ACTIVITY_KEY, String(this._lastActivity));
|
|
7523
|
+
}
|
|
7524
|
+
} catch {
|
|
7525
|
+
}
|
|
7526
|
+
}
|
|
7527
|
+
/** 세션 강제 리셋 */
|
|
7528
|
+
reset() {
|
|
7529
|
+
this._sessionId = null;
|
|
7530
|
+
this._isNewSession = false;
|
|
7531
|
+
try {
|
|
7532
|
+
if (typeof sessionStorage !== "undefined") {
|
|
7533
|
+
sessionStorage.removeItem(SESSION_KEY);
|
|
7534
|
+
sessionStorage.removeItem(LAST_ACTIVITY_KEY);
|
|
7535
|
+
}
|
|
7536
|
+
} catch {
|
|
7537
|
+
}
|
|
7538
|
+
}
|
|
7539
|
+
ensureSession() {
|
|
7540
|
+
const now = Date.now();
|
|
7541
|
+
if (!this._sessionId) {
|
|
7542
|
+
try {
|
|
7543
|
+
if (typeof sessionStorage !== "undefined") {
|
|
7544
|
+
this._sessionId = sessionStorage.getItem(SESSION_KEY);
|
|
7545
|
+
const lastStr = sessionStorage.getItem(LAST_ACTIVITY_KEY);
|
|
7546
|
+
this._lastActivity = lastStr ? parseInt(lastStr, 10) : 0;
|
|
7547
|
+
}
|
|
7548
|
+
} catch {
|
|
7549
|
+
}
|
|
7550
|
+
}
|
|
7551
|
+
if (!this._sessionId || this._lastActivity > 0 && now - this._lastActivity > SESSION_TIMEOUT_MS) {
|
|
7552
|
+
this._sessionId = generateId();
|
|
7553
|
+
this._isNewSession = true;
|
|
7554
|
+
this._lastActivity = now;
|
|
7555
|
+
try {
|
|
7556
|
+
if (typeof sessionStorage !== "undefined") {
|
|
7557
|
+
sessionStorage.setItem(SESSION_KEY, this._sessionId);
|
|
7558
|
+
sessionStorage.setItem(LAST_ACTIVITY_KEY, String(now));
|
|
7559
|
+
}
|
|
7560
|
+
} catch {
|
|
7561
|
+
}
|
|
7562
|
+
}
|
|
7563
|
+
}
|
|
7564
|
+
loadOrCreateVisitorUid() {
|
|
7565
|
+
try {
|
|
7566
|
+
if (typeof localStorage !== "undefined") {
|
|
7567
|
+
const existing = localStorage.getItem(VISITOR_KEY);
|
|
7568
|
+
if (existing) return existing;
|
|
7569
|
+
const uid = generateId();
|
|
7570
|
+
localStorage.setItem(VISITOR_KEY, uid);
|
|
7571
|
+
return uid;
|
|
7572
|
+
}
|
|
7573
|
+
} catch {
|
|
7574
|
+
}
|
|
7575
|
+
return generateId();
|
|
7576
|
+
}
|
|
7577
|
+
};
|
|
7578
|
+
function parseUTM() {
|
|
7579
|
+
if (typeof window === "undefined") return {};
|
|
7580
|
+
try {
|
|
7581
|
+
const params = new URLSearchParams(window.location.search);
|
|
7582
|
+
const utm = {};
|
|
7583
|
+
for (const key of ["utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"]) {
|
|
7584
|
+
const val = params.get(key);
|
|
7585
|
+
if (val) utm[key] = val;
|
|
7586
|
+
}
|
|
7587
|
+
return utm;
|
|
7588
|
+
} catch {
|
|
7589
|
+
return {};
|
|
7590
|
+
}
|
|
7591
|
+
}
|
|
7592
|
+
var AnalyticsAPI = class {
|
|
7593
|
+
constructor(http) {
|
|
7594
|
+
this.storageWebId = null;
|
|
7595
|
+
this.eventQueue = [];
|
|
7596
|
+
this.batchTimer = null;
|
|
7597
|
+
this.isInitialized = false;
|
|
7598
|
+
this.heartbeatTimer = null;
|
|
7599
|
+
this.visibilityHandler = null;
|
|
7600
|
+
this.unloadHeartbeatHandler = null;
|
|
7601
|
+
this.popstateHandler = null;
|
|
7602
|
+
this.beforeUnloadHandler = null;
|
|
7603
|
+
this.origPushState = null;
|
|
7604
|
+
this.origReplaceState = null;
|
|
7605
|
+
this.heatmapClickHandler = null;
|
|
7606
|
+
this.heatmapScrollHandler = null;
|
|
7607
|
+
this.utm = {};
|
|
7608
|
+
this.handleHeatmapClick = (e) => {
|
|
7609
|
+
const xPercent = e.clientX / window.innerWidth * 100;
|
|
7610
|
+
const yPercent = e.clientY / window.innerHeight * 100;
|
|
7611
|
+
this.recordHeatmapEvent("click", xPercent, yPercent);
|
|
7612
|
+
};
|
|
7613
|
+
this.heatmapQueue = [];
|
|
7614
|
+
this.http = http;
|
|
7615
|
+
this.config = {
|
|
7616
|
+
trackPageViews: true,
|
|
7617
|
+
trackEvents: true,
|
|
7618
|
+
trackSessions: true,
|
|
7619
|
+
heatmap: false,
|
|
7620
|
+
recording: false,
|
|
7621
|
+
batchSize: 10,
|
|
7622
|
+
flushInterval: 5e3,
|
|
7623
|
+
respectDoNotTrack: true,
|
|
7624
|
+
debug: false
|
|
7625
|
+
};
|
|
7626
|
+
this.consent = {
|
|
7627
|
+
analytics: true,
|
|
7628
|
+
heatmap: false,
|
|
7629
|
+
recording: false
|
|
7630
|
+
};
|
|
7631
|
+
this.session = new SessionManager();
|
|
7632
|
+
}
|
|
7633
|
+
/**
|
|
7634
|
+
* Analytics 초기화
|
|
7635
|
+
* @param storageWebId 웹 스토리지 ID
|
|
7636
|
+
* @param config 설정 (선택)
|
|
7637
|
+
*/
|
|
7638
|
+
init(storageWebId, config) {
|
|
7639
|
+
if (this.isInitialized) {
|
|
7640
|
+
this.log("Analytics already initialized");
|
|
7641
|
+
return;
|
|
7642
|
+
}
|
|
7643
|
+
if (typeof window === "undefined") {
|
|
7644
|
+
this.log("Analytics only works in browser environment");
|
|
7645
|
+
return;
|
|
7646
|
+
}
|
|
7647
|
+
if (config?.respectDoNotTrack !== false && this.isDNT()) {
|
|
7648
|
+
this.log("Do Not Track enabled, analytics disabled");
|
|
7649
|
+
return;
|
|
7650
|
+
}
|
|
7651
|
+
this.storageWebId = storageWebId;
|
|
7652
|
+
Object.assign(this.config, config);
|
|
7653
|
+
this.isInitialized = true;
|
|
7654
|
+
this.utm = parseUTM();
|
|
7655
|
+
if (this.config.trackSessions) {
|
|
7656
|
+
this.trackSessionStart();
|
|
7657
|
+
this.startHeartbeat();
|
|
7658
|
+
}
|
|
7659
|
+
if (this.config.trackPageViews) {
|
|
7660
|
+
this.trackPageView();
|
|
7661
|
+
this.setupAutoPageView();
|
|
7662
|
+
}
|
|
7663
|
+
this.startBatchTimer();
|
|
7664
|
+
this.beforeUnloadHandler = () => this.flushSync();
|
|
7665
|
+
window.addEventListener("beforeunload", this.beforeUnloadHandler);
|
|
7666
|
+
this.log("Analytics initialized", { storageWebId });
|
|
7667
|
+
}
|
|
7668
|
+
/** Analytics 정리 */
|
|
7669
|
+
destroy() {
|
|
7670
|
+
if (!this.isInitialized) return;
|
|
7671
|
+
this.stopBatchTimer();
|
|
7672
|
+
this.stopHeartbeat();
|
|
7673
|
+
this.removeAutoPageView();
|
|
7674
|
+
this.removeHeatmapListeners();
|
|
7675
|
+
if (this.beforeUnloadHandler) {
|
|
7676
|
+
window.removeEventListener("beforeunload", this.beforeUnloadHandler);
|
|
7677
|
+
this.beforeUnloadHandler = null;
|
|
7678
|
+
}
|
|
7679
|
+
this.flush();
|
|
7680
|
+
this.isInitialized = false;
|
|
7681
|
+
this.log("Analytics destroyed");
|
|
7682
|
+
}
|
|
7683
|
+
/**
|
|
7684
|
+
* 동의 설정 변경
|
|
7685
|
+
*/
|
|
7686
|
+
setConsent(consent) {
|
|
7687
|
+
Object.assign(this.consent, consent);
|
|
7688
|
+
this.log("Consent updated", consent);
|
|
7689
|
+
if (consent.analytics === false && this.isInitialized) {
|
|
7690
|
+
this.destroy();
|
|
7691
|
+
}
|
|
7692
|
+
}
|
|
7693
|
+
/** 현재 동의 상태 조회 */
|
|
7694
|
+
getConsent() {
|
|
7695
|
+
return { ...this.consent };
|
|
7696
|
+
}
|
|
7697
|
+
/**
|
|
7698
|
+
* 페이지뷰 수동 추적
|
|
7699
|
+
*/
|
|
7700
|
+
trackPageView(path) {
|
|
7701
|
+
if (!this.canTrack()) return;
|
|
7702
|
+
this.session.touch();
|
|
7703
|
+
const event = this.createBaseEvent("page_view");
|
|
7704
|
+
event.page_path = path || window.location.pathname;
|
|
7705
|
+
event.page_url = window.location.href;
|
|
7706
|
+
event.page_title = document.title;
|
|
7707
|
+
event.referrer = document.referrer || void 0;
|
|
7708
|
+
event.screen_width = window.screen.width;
|
|
7709
|
+
event.screen_height = window.screen.height;
|
|
7710
|
+
Object.assign(event, this.utm);
|
|
7711
|
+
this.enqueue(event);
|
|
7712
|
+
}
|
|
7713
|
+
/**
|
|
7714
|
+
* 커스텀 이벤트 추적
|
|
7715
|
+
*/
|
|
7716
|
+
trackEvent(name, properties) {
|
|
7717
|
+
if (!this.canTrack() || !this.config.trackEvents) return;
|
|
7718
|
+
this.session.touch();
|
|
7719
|
+
const event = this.createBaseEvent("event");
|
|
7720
|
+
event.event_name = name;
|
|
7721
|
+
event.event_properties = properties;
|
|
7722
|
+
event.page_path = window.location.pathname;
|
|
7723
|
+
event.page_url = window.location.href;
|
|
7724
|
+
this.enqueue(event);
|
|
7725
|
+
}
|
|
7726
|
+
/**
|
|
7727
|
+
* 사용자 식별 (로그인 시)
|
|
7728
|
+
*/
|
|
7729
|
+
identify(memberId) {
|
|
7730
|
+
if (!this.canTrack()) return;
|
|
7731
|
+
if (typeof window.__cbSetMember === "function") {
|
|
7732
|
+
window.__cbSetMember(memberId);
|
|
7733
|
+
}
|
|
7734
|
+
this.log("User identified", { memberId });
|
|
7735
|
+
}
|
|
7736
|
+
/**
|
|
7737
|
+
* 히트맵 수집 활성화 (opt-in)
|
|
7738
|
+
*/
|
|
7739
|
+
enableHeatmap(options) {
|
|
7740
|
+
if (!this.canTrack() || !this.consent.heatmap) return;
|
|
7741
|
+
const trackClick = options?.click ?? true;
|
|
7742
|
+
const trackScroll = options?.scroll ?? true;
|
|
7743
|
+
if (trackClick) {
|
|
7744
|
+
this.heatmapClickHandler = this.handleHeatmapClick;
|
|
7745
|
+
document.addEventListener("click", this.heatmapClickHandler);
|
|
7746
|
+
}
|
|
7747
|
+
if (trackScroll) {
|
|
7748
|
+
let scrollTimer = null;
|
|
7749
|
+
let maxScrollDepth = 0;
|
|
7750
|
+
this.heatmapScrollHandler = () => {
|
|
7751
|
+
const depth = Math.round(
|
|
7752
|
+
(window.scrollY + window.innerHeight) / document.documentElement.scrollHeight * 100
|
|
7753
|
+
);
|
|
7754
|
+
maxScrollDepth = Math.max(maxScrollDepth, depth);
|
|
7755
|
+
if (scrollTimer) clearTimeout(scrollTimer);
|
|
7756
|
+
scrollTimer = setTimeout(() => {
|
|
7757
|
+
this.recordHeatmapEvent("scroll", 50, maxScrollDepth * (window.innerHeight / 100), maxScrollDepth);
|
|
7758
|
+
}, 500);
|
|
7759
|
+
};
|
|
7760
|
+
window.addEventListener("scroll", this.heatmapScrollHandler, { passive: true });
|
|
7761
|
+
}
|
|
7762
|
+
this.log("Heatmap enabled", options);
|
|
7763
|
+
}
|
|
7764
|
+
/**
|
|
7765
|
+
* 세션 heartbeat 자동 전송 시작 (30초 간격)
|
|
7766
|
+
*/
|
|
7767
|
+
enableHeartbeat() {
|
|
7768
|
+
if (!this.canTrack()) return;
|
|
7769
|
+
if (this.heartbeatTimer) return;
|
|
7770
|
+
const sendHeartbeat = () => {
|
|
7771
|
+
if (!this.canTrack()) return;
|
|
7772
|
+
const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
|
|
7773
|
+
this.http.post(`${prefix}/storages/web/${this.storageWebId}/sessions/heartbeat`, {
|
|
7774
|
+
visitor_uid: this.session.visitorUid,
|
|
7775
|
+
session_id: this.session.sessionId
|
|
7776
|
+
}).catch(() => {
|
|
7777
|
+
});
|
|
7778
|
+
};
|
|
7779
|
+
this.heartbeatTimer = setInterval(sendHeartbeat, 3e4);
|
|
7780
|
+
document.addEventListener("visibilitychange", () => {
|
|
7781
|
+
if (document.hidden) {
|
|
7782
|
+
if (this.heartbeatTimer) {
|
|
7783
|
+
clearInterval(this.heartbeatTimer);
|
|
7784
|
+
this.heartbeatTimer = null;
|
|
7785
|
+
}
|
|
7786
|
+
} else {
|
|
7787
|
+
if (!this.heartbeatTimer) {
|
|
7788
|
+
this.heartbeatTimer = setInterval(sendHeartbeat, 3e4);
|
|
7789
|
+
sendHeartbeat();
|
|
7790
|
+
}
|
|
7791
|
+
}
|
|
7792
|
+
});
|
|
7793
|
+
window.addEventListener("beforeunload", () => {
|
|
7794
|
+
if (typeof navigator !== "undefined" && navigator.sendBeacon && this.storageWebId) {
|
|
7795
|
+
const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
|
|
7796
|
+
const url = `${this.http.getBaseUrl()}${prefix}/storages/web/${this.storageWebId}/sessions/heartbeat`;
|
|
7797
|
+
navigator.sendBeacon(url, JSON.stringify({
|
|
7798
|
+
visitor_uid: this.session.visitorUid,
|
|
7799
|
+
session_id: this.session.sessionId
|
|
7800
|
+
}));
|
|
7801
|
+
}
|
|
7802
|
+
});
|
|
7803
|
+
sendHeartbeat();
|
|
7804
|
+
this.log("Heartbeat enabled (30s interval)");
|
|
7805
|
+
}
|
|
7806
|
+
/** 큐에 있는 이벤트 즉시 전송 */
|
|
7807
|
+
async flush() {
|
|
7808
|
+
await this.flushQueue();
|
|
7809
|
+
}
|
|
7810
|
+
/** 세션 매니저 접근 (고급) */
|
|
7811
|
+
getSession() {
|
|
7812
|
+
return this.session;
|
|
7813
|
+
}
|
|
7814
|
+
// ── Private ────────────────────────────────────────────────────
|
|
7815
|
+
canTrack() {
|
|
7816
|
+
if (!this.isInitialized || !this.storageWebId) return false;
|
|
7817
|
+
if (!this.consent.analytics) return false;
|
|
7818
|
+
return true;
|
|
7819
|
+
}
|
|
7820
|
+
isDNT() {
|
|
7821
|
+
if (typeof navigator === "undefined") return false;
|
|
7822
|
+
return navigator.doNotTrack === "1" || navigator.globalPrivacyControl === true;
|
|
7823
|
+
}
|
|
7824
|
+
createBaseEvent(type) {
|
|
7825
|
+
return {
|
|
7826
|
+
type,
|
|
7827
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7828
|
+
session_id: this.session.sessionId,
|
|
7829
|
+
visitor_uid: this.session.visitorUid
|
|
7830
|
+
};
|
|
7831
|
+
}
|
|
7832
|
+
enqueue(event) {
|
|
7833
|
+
this.eventQueue.push(event);
|
|
7834
|
+
if (this.eventQueue.length >= this.config.batchSize) {
|
|
7835
|
+
this.flushQueue();
|
|
7836
|
+
}
|
|
7837
|
+
}
|
|
7838
|
+
async flushQueue() {
|
|
7839
|
+
if (this.eventQueue.length === 0 || !this.storageWebId) return;
|
|
7840
|
+
const events = this.eventQueue.splice(0);
|
|
7841
|
+
const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
|
|
7842
|
+
try {
|
|
7843
|
+
await this.http.post(
|
|
7844
|
+
`${prefix}/storages/web/${this.storageWebId}/visitors/batch`,
|
|
7845
|
+
{
|
|
7846
|
+
visitor_uid: this.session.visitorUid,
|
|
7847
|
+
events: events.map((e) => ({
|
|
7848
|
+
timestamp: e.timestamp,
|
|
7849
|
+
page_path: e.page_path || "",
|
|
7850
|
+
page_url: e.page_url || "",
|
|
7851
|
+
page_title: e.page_title || "",
|
|
7852
|
+
referrer: e.referrer || "",
|
|
7853
|
+
user_agent: typeof navigator !== "undefined" ? navigator.userAgent : "",
|
|
7854
|
+
screen_width: e.screen_width || 0,
|
|
7855
|
+
screen_height: e.screen_height || 0,
|
|
7856
|
+
session_id: e.session_id,
|
|
7857
|
+
session_start: e.type === "session_start",
|
|
7858
|
+
is_page_view: e.type === "page_view",
|
|
7859
|
+
event_name: e.event_name,
|
|
7860
|
+
event_properties: e.event_properties,
|
|
7861
|
+
utm_source: e.utm_source,
|
|
7862
|
+
utm_medium: e.utm_medium,
|
|
7863
|
+
utm_campaign: e.utm_campaign,
|
|
7864
|
+
utm_content: e.utm_content,
|
|
7865
|
+
utm_term: e.utm_term
|
|
7866
|
+
}))
|
|
7867
|
+
}
|
|
7868
|
+
);
|
|
7869
|
+
this.log(`Flushed ${events.length} events`);
|
|
7870
|
+
} catch (err) {
|
|
7871
|
+
if (this.eventQueue.length < this.config.batchSize * 3) {
|
|
7872
|
+
this.eventQueue.unshift(...events);
|
|
7873
|
+
}
|
|
7874
|
+
this.log("Flush failed", err);
|
|
7875
|
+
}
|
|
7876
|
+
}
|
|
7877
|
+
/** sendBeacon으로 동기 flush (beforeunload용) */
|
|
7878
|
+
flushSync() {
|
|
7879
|
+
if (this.eventQueue.length === 0 || !this.storageWebId) return;
|
|
7880
|
+
if (typeof navigator === "undefined" || !navigator.sendBeacon) return;
|
|
7881
|
+
const events = this.eventQueue.splice(0);
|
|
7882
|
+
const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
|
|
7883
|
+
const baseUrl = this.http.getBaseUrl();
|
|
7884
|
+
const url = `${baseUrl}${prefix}/storages/web/${this.storageWebId}/visitors/batch`;
|
|
7885
|
+
const body = JSON.stringify({
|
|
7886
|
+
visitor_uid: this.session.visitorUid,
|
|
7887
|
+
events: events.map((e) => ({
|
|
7888
|
+
timestamp: e.timestamp,
|
|
7889
|
+
page_path: e.page_path || "",
|
|
7890
|
+
page_url: e.page_url || "",
|
|
7891
|
+
page_title: e.page_title || "",
|
|
7892
|
+
referrer: e.referrer || "",
|
|
7893
|
+
session_id: e.session_id,
|
|
7894
|
+
session_start: e.type === "session_start",
|
|
7895
|
+
is_page_view: e.type === "page_view",
|
|
7896
|
+
event_name: e.event_name,
|
|
7897
|
+
event_properties: e.event_properties
|
|
7898
|
+
}))
|
|
7899
|
+
});
|
|
7900
|
+
try {
|
|
7901
|
+
navigator.sendBeacon(url, new Blob([body], { type: "application/json" }));
|
|
7902
|
+
} catch {
|
|
7903
|
+
}
|
|
7904
|
+
}
|
|
7905
|
+
trackSessionStart() {
|
|
7906
|
+
const event = this.createBaseEvent("session_start");
|
|
7907
|
+
event.page_path = window.location.pathname;
|
|
7908
|
+
event.page_url = window.location.href;
|
|
7909
|
+
event.referrer = document.referrer || void 0;
|
|
7910
|
+
event.screen_width = window.screen.width;
|
|
7911
|
+
event.screen_height = window.screen.height;
|
|
7912
|
+
Object.assign(event, this.utm);
|
|
7913
|
+
this.enqueue(event);
|
|
7914
|
+
}
|
|
7915
|
+
startBatchTimer() {
|
|
7916
|
+
this.batchTimer = setInterval(() => this.flushQueue(), this.config.flushInterval);
|
|
7917
|
+
}
|
|
7918
|
+
stopBatchTimer() {
|
|
7919
|
+
if (this.batchTimer) {
|
|
7920
|
+
clearInterval(this.batchTimer);
|
|
7921
|
+
this.batchTimer = null;
|
|
7922
|
+
}
|
|
7923
|
+
}
|
|
7924
|
+
startHeartbeat() {
|
|
7925
|
+
this.heartbeatTimer = setInterval(() => {
|
|
7926
|
+
if (!this.canTrack()) return;
|
|
7927
|
+
this.sendHeartbeat();
|
|
7928
|
+
}, 30 * 1e3);
|
|
7929
|
+
if (typeof document !== "undefined") {
|
|
7930
|
+
this.visibilityHandler = () => {
|
|
7931
|
+
if (document.visibilityState === "hidden") {
|
|
7932
|
+
this.sendHeartbeatBeacon();
|
|
7933
|
+
if (this.heartbeatTimer) {
|
|
7934
|
+
clearInterval(this.heartbeatTimer);
|
|
7935
|
+
this.heartbeatTimer = null;
|
|
7936
|
+
}
|
|
7937
|
+
} else if (document.visibilityState === "visible") {
|
|
7938
|
+
if (!this.heartbeatTimer) {
|
|
7939
|
+
this.sendHeartbeat();
|
|
7940
|
+
this.heartbeatTimer = setInterval(() => {
|
|
7941
|
+
if (!this.canTrack()) return;
|
|
7942
|
+
this.sendHeartbeat();
|
|
7943
|
+
}, 30 * 1e3);
|
|
7944
|
+
}
|
|
7945
|
+
}
|
|
7946
|
+
};
|
|
7947
|
+
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
7948
|
+
}
|
|
7949
|
+
if (typeof window !== "undefined") {
|
|
7950
|
+
this.unloadHeartbeatHandler = () => this.sendHeartbeatBeacon();
|
|
7951
|
+
window.addEventListener("beforeunload", this.unloadHeartbeatHandler);
|
|
7952
|
+
}
|
|
7953
|
+
}
|
|
7954
|
+
stopHeartbeat() {
|
|
7955
|
+
if (this.heartbeatTimer) {
|
|
7956
|
+
clearInterval(this.heartbeatTimer);
|
|
7957
|
+
this.heartbeatTimer = null;
|
|
7958
|
+
}
|
|
7959
|
+
if (typeof document !== "undefined" && this.visibilityHandler) {
|
|
7960
|
+
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
7961
|
+
this.visibilityHandler = null;
|
|
7962
|
+
}
|
|
7963
|
+
if (typeof window !== "undefined" && this.unloadHeartbeatHandler) {
|
|
7964
|
+
window.removeEventListener("beforeunload", this.unloadHeartbeatHandler);
|
|
7965
|
+
this.unloadHeartbeatHandler = null;
|
|
7966
|
+
}
|
|
7967
|
+
}
|
|
7968
|
+
/** 하트비트를 큐에 넣어 배치 전송 */
|
|
7969
|
+
sendHeartbeat() {
|
|
7970
|
+
const event = this.createBaseEvent("heartbeat");
|
|
7971
|
+
event.page_path = window.location.pathname;
|
|
7972
|
+
this.enqueue(event);
|
|
7973
|
+
if (this.storageWebId) {
|
|
7974
|
+
const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
|
|
7975
|
+
this.http.post(
|
|
7976
|
+
`${prefix}/storages/web/${this.storageWebId}/sessions/heartbeat`,
|
|
7977
|
+
{
|
|
7978
|
+
visitor_uid: this.session.visitorUid,
|
|
7979
|
+
session_id: this.session.sessionId
|
|
7980
|
+
}
|
|
7981
|
+
).catch(() => {
|
|
7982
|
+
});
|
|
7983
|
+
}
|
|
7984
|
+
}
|
|
7985
|
+
/** sendBeacon으로 하트비트 전송 (unload/visibility hidden용) */
|
|
7986
|
+
sendHeartbeatBeacon() {
|
|
7987
|
+
if (!this.storageWebId) return;
|
|
7988
|
+
if (typeof navigator === "undefined" || !navigator.sendBeacon) return;
|
|
7989
|
+
const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
|
|
7990
|
+
const baseUrl = this.http.getBaseUrl();
|
|
7991
|
+
const url = `${baseUrl}${prefix}/storages/web/${this.storageWebId}/sessions/heartbeat`;
|
|
7992
|
+
const body = JSON.stringify({
|
|
7993
|
+
visitor_uid: this.session.visitorUid,
|
|
7994
|
+
session_id: this.session.sessionId
|
|
7995
|
+
});
|
|
7996
|
+
try {
|
|
7997
|
+
navigator.sendBeacon(url, new Blob([body], { type: "application/json" }));
|
|
7998
|
+
} catch {
|
|
7999
|
+
}
|
|
8000
|
+
}
|
|
8001
|
+
setupAutoPageView() {
|
|
8002
|
+
this.popstateHandler = () => this.trackPageView();
|
|
8003
|
+
window.addEventListener("popstate", this.popstateHandler);
|
|
8004
|
+
this.origPushState = history.pushState.bind(history);
|
|
8005
|
+
this.origReplaceState = history.replaceState.bind(history);
|
|
8006
|
+
const self = this;
|
|
8007
|
+
history.pushState = function(...args) {
|
|
8008
|
+
self.origPushState(...args);
|
|
8009
|
+
self.trackPageView();
|
|
8010
|
+
};
|
|
8011
|
+
history.replaceState = function(...args) {
|
|
8012
|
+
self.origReplaceState(...args);
|
|
8013
|
+
self.trackPageView();
|
|
8014
|
+
};
|
|
8015
|
+
}
|
|
8016
|
+
removeAutoPageView() {
|
|
8017
|
+
if (this.popstateHandler) {
|
|
8018
|
+
window.removeEventListener("popstate", this.popstateHandler);
|
|
8019
|
+
this.popstateHandler = null;
|
|
8020
|
+
}
|
|
8021
|
+
if (this.origPushState) {
|
|
8022
|
+
history.pushState = this.origPushState;
|
|
8023
|
+
this.origPushState = null;
|
|
8024
|
+
}
|
|
8025
|
+
if (this.origReplaceState) {
|
|
8026
|
+
history.replaceState = this.origReplaceState;
|
|
8027
|
+
this.origReplaceState = null;
|
|
8028
|
+
}
|
|
8029
|
+
}
|
|
8030
|
+
removeHeatmapListeners() {
|
|
8031
|
+
if (this.heatmapClickHandler) {
|
|
8032
|
+
document.removeEventListener("click", this.heatmapClickHandler);
|
|
8033
|
+
this.heatmapClickHandler = null;
|
|
8034
|
+
}
|
|
8035
|
+
if (this.heatmapScrollHandler) {
|
|
8036
|
+
window.removeEventListener("scroll", this.heatmapScrollHandler);
|
|
8037
|
+
this.heatmapScrollHandler = null;
|
|
8038
|
+
}
|
|
8039
|
+
}
|
|
8040
|
+
recordHeatmapEvent(eventType, xPercent, yPercent, scrollDepth) {
|
|
8041
|
+
if (!this.canTrack() || !this.storageWebId) return;
|
|
8042
|
+
const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
|
|
8043
|
+
this.heatmapQueue.push({
|
|
8044
|
+
page_path: window.location.pathname,
|
|
8045
|
+
event_type: eventType,
|
|
8046
|
+
x_percent: Math.round(xPercent * 100) / 100,
|
|
8047
|
+
y_percent: Math.round(yPercent * 100) / 100,
|
|
8048
|
+
viewport_width: window.innerWidth,
|
|
8049
|
+
viewport_height: window.innerHeight,
|
|
8050
|
+
scroll_depth_percent: scrollDepth,
|
|
8051
|
+
session_id: this.session.sessionId
|
|
8052
|
+
});
|
|
8053
|
+
if (this.heatmapQueue.length >= 50) {
|
|
8054
|
+
const events = this.heatmapQueue.splice(0);
|
|
8055
|
+
this.http.post(`${prefix}/storages/web/${this.storageWebId}/heatmap/batch`, {
|
|
8056
|
+
visitor_uid: this.session.visitorUid,
|
|
8057
|
+
events
|
|
8058
|
+
}).catch(() => {
|
|
8059
|
+
});
|
|
8060
|
+
}
|
|
8061
|
+
}
|
|
8062
|
+
log(...args) {
|
|
8063
|
+
if (this.config.debug) {
|
|
8064
|
+
console.log("[Analytics]", ...args);
|
|
8065
|
+
}
|
|
8066
|
+
}
|
|
8067
|
+
};
|
|
8068
|
+
|
|
7425
8069
|
// src/api/game-transport.ts
|
|
7426
8070
|
var WebTransportTransport = class {
|
|
7427
8071
|
constructor(config, onMessage, onClose, onError) {
|
|
@@ -8077,6 +8721,7 @@ var ConnectBase = class {
|
|
|
8077
8721
|
baseUrl: config.baseUrl || DEFAULT_BASE_URL,
|
|
8078
8722
|
publicKey: config.publicKey,
|
|
8079
8723
|
secretKey: config.secretKey,
|
|
8724
|
+
persistence: config.persistence,
|
|
8080
8725
|
onTokenRefresh: config.onTokenRefresh,
|
|
8081
8726
|
onAuthError: config.onAuthError,
|
|
8082
8727
|
onTokenExpired: config.onTokenExpired
|
|
@@ -8101,6 +8746,7 @@ var ConnectBase = class {
|
|
|
8101
8746
|
this.knowledge = new KnowledgeAPI(this.http);
|
|
8102
8747
|
this.ai = new AIAPI(this.http);
|
|
8103
8748
|
this.queue = new QueueAPI(this.http);
|
|
8749
|
+
this.analytics = new AnalyticsAPI(this.http);
|
|
8104
8750
|
}
|
|
8105
8751
|
/**
|
|
8106
8752
|
* 수동으로 토큰 설정 (기존 토큰으로 세션 복원 시)
|
|
@@ -8132,6 +8778,7 @@ export {
|
|
|
8132
8778
|
GameRoom,
|
|
8133
8779
|
GameRoomTransport,
|
|
8134
8780
|
NativeAPI,
|
|
8781
|
+
SessionManager,
|
|
8135
8782
|
VideoProcessingError,
|
|
8136
8783
|
index_default as default,
|
|
8137
8784
|
isWebTransportSupported
|