connectbase-client 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +203 -93
- package/dist/connect-base.umd.js +3 -3
- package/dist/index.d.mts +137 -1
- package/dist/index.d.ts +137 -1
- package/dist/index.js +591 -0
- package/dist/index.mjs +590 -0
- package/package.json +4 -2
package/dist/index.mjs
CHANGED
|
@@ -7422,6 +7422,594 @@ var QueueAPI = class {
|
|
|
7422
7422
|
}
|
|
7423
7423
|
};
|
|
7424
7424
|
|
|
7425
|
+
// src/api/analytics.ts
|
|
7426
|
+
var SESSION_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
7427
|
+
var SESSION_KEY = "__cb_session";
|
|
7428
|
+
var VISITOR_KEY = "__cb_visitor_uid";
|
|
7429
|
+
var LAST_ACTIVITY_KEY = "__cb_last_activity";
|
|
7430
|
+
function generateId() {
|
|
7431
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
7432
|
+
return crypto.randomUUID();
|
|
7433
|
+
}
|
|
7434
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
7435
|
+
const r = Math.random() * 16 | 0;
|
|
7436
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
7437
|
+
return v.toString(16);
|
|
7438
|
+
});
|
|
7439
|
+
}
|
|
7440
|
+
var SessionManager = class {
|
|
7441
|
+
constructor() {
|
|
7442
|
+
this._sessionId = null;
|
|
7443
|
+
this._visitorUid = null;
|
|
7444
|
+
this._lastActivity = 0;
|
|
7445
|
+
this._isNewSession = false;
|
|
7446
|
+
}
|
|
7447
|
+
get sessionId() {
|
|
7448
|
+
this.ensureSession();
|
|
7449
|
+
return this._sessionId;
|
|
7450
|
+
}
|
|
7451
|
+
get visitorUid() {
|
|
7452
|
+
if (!this._visitorUid) {
|
|
7453
|
+
this._visitorUid = this.loadOrCreateVisitorUid();
|
|
7454
|
+
}
|
|
7455
|
+
return this._visitorUid;
|
|
7456
|
+
}
|
|
7457
|
+
get isNewSession() {
|
|
7458
|
+
return this._isNewSession;
|
|
7459
|
+
}
|
|
7460
|
+
/** 활동 기록 — 세션 타임아웃 리셋 */
|
|
7461
|
+
touch() {
|
|
7462
|
+
this._lastActivity = Date.now();
|
|
7463
|
+
this._isNewSession = false;
|
|
7464
|
+
try {
|
|
7465
|
+
if (typeof sessionStorage !== "undefined") {
|
|
7466
|
+
sessionStorage.setItem(LAST_ACTIVITY_KEY, String(this._lastActivity));
|
|
7467
|
+
}
|
|
7468
|
+
} catch {
|
|
7469
|
+
}
|
|
7470
|
+
}
|
|
7471
|
+
/** 세션 강제 리셋 */
|
|
7472
|
+
reset() {
|
|
7473
|
+
this._sessionId = null;
|
|
7474
|
+
this._isNewSession = false;
|
|
7475
|
+
try {
|
|
7476
|
+
if (typeof sessionStorage !== "undefined") {
|
|
7477
|
+
sessionStorage.removeItem(SESSION_KEY);
|
|
7478
|
+
sessionStorage.removeItem(LAST_ACTIVITY_KEY);
|
|
7479
|
+
}
|
|
7480
|
+
} catch {
|
|
7481
|
+
}
|
|
7482
|
+
}
|
|
7483
|
+
ensureSession() {
|
|
7484
|
+
const now = Date.now();
|
|
7485
|
+
if (!this._sessionId) {
|
|
7486
|
+
try {
|
|
7487
|
+
if (typeof sessionStorage !== "undefined") {
|
|
7488
|
+
this._sessionId = sessionStorage.getItem(SESSION_KEY);
|
|
7489
|
+
const lastStr = sessionStorage.getItem(LAST_ACTIVITY_KEY);
|
|
7490
|
+
this._lastActivity = lastStr ? parseInt(lastStr, 10) : 0;
|
|
7491
|
+
}
|
|
7492
|
+
} catch {
|
|
7493
|
+
}
|
|
7494
|
+
}
|
|
7495
|
+
if (!this._sessionId || this._lastActivity > 0 && now - this._lastActivity > SESSION_TIMEOUT_MS) {
|
|
7496
|
+
this._sessionId = generateId();
|
|
7497
|
+
this._isNewSession = true;
|
|
7498
|
+
this._lastActivity = now;
|
|
7499
|
+
try {
|
|
7500
|
+
if (typeof sessionStorage !== "undefined") {
|
|
7501
|
+
sessionStorage.setItem(SESSION_KEY, this._sessionId);
|
|
7502
|
+
sessionStorage.setItem(LAST_ACTIVITY_KEY, String(now));
|
|
7503
|
+
}
|
|
7504
|
+
} catch {
|
|
7505
|
+
}
|
|
7506
|
+
}
|
|
7507
|
+
}
|
|
7508
|
+
loadOrCreateVisitorUid() {
|
|
7509
|
+
try {
|
|
7510
|
+
if (typeof localStorage !== "undefined") {
|
|
7511
|
+
const existing = localStorage.getItem(VISITOR_KEY);
|
|
7512
|
+
if (existing) return existing;
|
|
7513
|
+
const uid = generateId();
|
|
7514
|
+
localStorage.setItem(VISITOR_KEY, uid);
|
|
7515
|
+
return uid;
|
|
7516
|
+
}
|
|
7517
|
+
} catch {
|
|
7518
|
+
}
|
|
7519
|
+
return generateId();
|
|
7520
|
+
}
|
|
7521
|
+
};
|
|
7522
|
+
function parseUTM() {
|
|
7523
|
+
if (typeof window === "undefined") return {};
|
|
7524
|
+
try {
|
|
7525
|
+
const params = new URLSearchParams(window.location.search);
|
|
7526
|
+
const utm = {};
|
|
7527
|
+
for (const key of ["utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"]) {
|
|
7528
|
+
const val = params.get(key);
|
|
7529
|
+
if (val) utm[key] = val;
|
|
7530
|
+
}
|
|
7531
|
+
return utm;
|
|
7532
|
+
} catch {
|
|
7533
|
+
return {};
|
|
7534
|
+
}
|
|
7535
|
+
}
|
|
7536
|
+
var AnalyticsAPI = class {
|
|
7537
|
+
constructor(http) {
|
|
7538
|
+
this.storageWebId = null;
|
|
7539
|
+
this.eventQueue = [];
|
|
7540
|
+
this.batchTimer = null;
|
|
7541
|
+
this.isInitialized = false;
|
|
7542
|
+
this.heartbeatTimer = null;
|
|
7543
|
+
this.visibilityHandler = null;
|
|
7544
|
+
this.unloadHeartbeatHandler = null;
|
|
7545
|
+
this.popstateHandler = null;
|
|
7546
|
+
this.beforeUnloadHandler = null;
|
|
7547
|
+
this.origPushState = null;
|
|
7548
|
+
this.origReplaceState = null;
|
|
7549
|
+
this.heatmapClickHandler = null;
|
|
7550
|
+
this.heatmapScrollHandler = null;
|
|
7551
|
+
this.utm = {};
|
|
7552
|
+
this.handleHeatmapClick = (e) => {
|
|
7553
|
+
const xPercent = e.clientX / window.innerWidth * 100;
|
|
7554
|
+
const yPercent = e.clientY / window.innerHeight * 100;
|
|
7555
|
+
this.recordHeatmapEvent("click", xPercent, yPercent);
|
|
7556
|
+
};
|
|
7557
|
+
this.heatmapQueue = [];
|
|
7558
|
+
this.http = http;
|
|
7559
|
+
this.config = {
|
|
7560
|
+
trackPageViews: true,
|
|
7561
|
+
trackEvents: true,
|
|
7562
|
+
trackSessions: true,
|
|
7563
|
+
heatmap: false,
|
|
7564
|
+
recording: false,
|
|
7565
|
+
batchSize: 10,
|
|
7566
|
+
flushInterval: 5e3,
|
|
7567
|
+
respectDoNotTrack: true,
|
|
7568
|
+
debug: false
|
|
7569
|
+
};
|
|
7570
|
+
this.consent = {
|
|
7571
|
+
analytics: true,
|
|
7572
|
+
heatmap: false,
|
|
7573
|
+
recording: false
|
|
7574
|
+
};
|
|
7575
|
+
this.session = new SessionManager();
|
|
7576
|
+
}
|
|
7577
|
+
/**
|
|
7578
|
+
* Analytics 초기화
|
|
7579
|
+
* @param storageWebId 웹 스토리지 ID
|
|
7580
|
+
* @param config 설정 (선택)
|
|
7581
|
+
*/
|
|
7582
|
+
init(storageWebId, config) {
|
|
7583
|
+
if (this.isInitialized) {
|
|
7584
|
+
this.log("Analytics already initialized");
|
|
7585
|
+
return;
|
|
7586
|
+
}
|
|
7587
|
+
if (typeof window === "undefined") {
|
|
7588
|
+
this.log("Analytics only works in browser environment");
|
|
7589
|
+
return;
|
|
7590
|
+
}
|
|
7591
|
+
if (config?.respectDoNotTrack !== false && this.isDNT()) {
|
|
7592
|
+
this.log("Do Not Track enabled, analytics disabled");
|
|
7593
|
+
return;
|
|
7594
|
+
}
|
|
7595
|
+
this.storageWebId = storageWebId;
|
|
7596
|
+
Object.assign(this.config, config);
|
|
7597
|
+
this.isInitialized = true;
|
|
7598
|
+
this.utm = parseUTM();
|
|
7599
|
+
if (this.config.trackSessions) {
|
|
7600
|
+
this.trackSessionStart();
|
|
7601
|
+
this.startHeartbeat();
|
|
7602
|
+
}
|
|
7603
|
+
if (this.config.trackPageViews) {
|
|
7604
|
+
this.trackPageView();
|
|
7605
|
+
this.setupAutoPageView();
|
|
7606
|
+
}
|
|
7607
|
+
this.startBatchTimer();
|
|
7608
|
+
this.beforeUnloadHandler = () => this.flushSync();
|
|
7609
|
+
window.addEventListener("beforeunload", this.beforeUnloadHandler);
|
|
7610
|
+
this.log("Analytics initialized", { storageWebId });
|
|
7611
|
+
}
|
|
7612
|
+
/** Analytics 정리 */
|
|
7613
|
+
destroy() {
|
|
7614
|
+
if (!this.isInitialized) return;
|
|
7615
|
+
this.stopBatchTimer();
|
|
7616
|
+
this.stopHeartbeat();
|
|
7617
|
+
this.removeAutoPageView();
|
|
7618
|
+
this.removeHeatmapListeners();
|
|
7619
|
+
if (this.beforeUnloadHandler) {
|
|
7620
|
+
window.removeEventListener("beforeunload", this.beforeUnloadHandler);
|
|
7621
|
+
this.beforeUnloadHandler = null;
|
|
7622
|
+
}
|
|
7623
|
+
this.flush();
|
|
7624
|
+
this.isInitialized = false;
|
|
7625
|
+
this.log("Analytics destroyed");
|
|
7626
|
+
}
|
|
7627
|
+
/**
|
|
7628
|
+
* 동의 설정 변경
|
|
7629
|
+
*/
|
|
7630
|
+
setConsent(consent) {
|
|
7631
|
+
Object.assign(this.consent, consent);
|
|
7632
|
+
this.log("Consent updated", consent);
|
|
7633
|
+
if (consent.analytics === false && this.isInitialized) {
|
|
7634
|
+
this.destroy();
|
|
7635
|
+
}
|
|
7636
|
+
}
|
|
7637
|
+
/** 현재 동의 상태 조회 */
|
|
7638
|
+
getConsent() {
|
|
7639
|
+
return { ...this.consent };
|
|
7640
|
+
}
|
|
7641
|
+
/**
|
|
7642
|
+
* 페이지뷰 수동 추적
|
|
7643
|
+
*/
|
|
7644
|
+
trackPageView(path) {
|
|
7645
|
+
if (!this.canTrack()) return;
|
|
7646
|
+
this.session.touch();
|
|
7647
|
+
const event = this.createBaseEvent("page_view");
|
|
7648
|
+
event.page_path = path || window.location.pathname;
|
|
7649
|
+
event.page_url = window.location.href;
|
|
7650
|
+
event.page_title = document.title;
|
|
7651
|
+
event.referrer = document.referrer || void 0;
|
|
7652
|
+
event.screen_width = window.screen.width;
|
|
7653
|
+
event.screen_height = window.screen.height;
|
|
7654
|
+
Object.assign(event, this.utm);
|
|
7655
|
+
this.enqueue(event);
|
|
7656
|
+
}
|
|
7657
|
+
/**
|
|
7658
|
+
* 커스텀 이벤트 추적
|
|
7659
|
+
*/
|
|
7660
|
+
trackEvent(name, properties) {
|
|
7661
|
+
if (!this.canTrack() || !this.config.trackEvents) return;
|
|
7662
|
+
this.session.touch();
|
|
7663
|
+
const event = this.createBaseEvent("event");
|
|
7664
|
+
event.event_name = name;
|
|
7665
|
+
event.event_properties = properties;
|
|
7666
|
+
event.page_path = window.location.pathname;
|
|
7667
|
+
event.page_url = window.location.href;
|
|
7668
|
+
this.enqueue(event);
|
|
7669
|
+
}
|
|
7670
|
+
/**
|
|
7671
|
+
* 사용자 식별 (로그인 시)
|
|
7672
|
+
*/
|
|
7673
|
+
identify(memberId) {
|
|
7674
|
+
if (!this.canTrack()) return;
|
|
7675
|
+
if (typeof window.__cbSetMember === "function") {
|
|
7676
|
+
window.__cbSetMember(memberId);
|
|
7677
|
+
}
|
|
7678
|
+
this.log("User identified", { memberId });
|
|
7679
|
+
}
|
|
7680
|
+
/**
|
|
7681
|
+
* 히트맵 수집 활성화 (opt-in)
|
|
7682
|
+
*/
|
|
7683
|
+
enableHeatmap(options) {
|
|
7684
|
+
if (!this.canTrack() || !this.consent.heatmap) return;
|
|
7685
|
+
const trackClick = options?.click ?? true;
|
|
7686
|
+
const trackScroll = options?.scroll ?? true;
|
|
7687
|
+
if (trackClick) {
|
|
7688
|
+
this.heatmapClickHandler = this.handleHeatmapClick;
|
|
7689
|
+
document.addEventListener("click", this.heatmapClickHandler);
|
|
7690
|
+
}
|
|
7691
|
+
if (trackScroll) {
|
|
7692
|
+
let scrollTimer = null;
|
|
7693
|
+
let maxScrollDepth = 0;
|
|
7694
|
+
this.heatmapScrollHandler = () => {
|
|
7695
|
+
const depth = Math.round(
|
|
7696
|
+
(window.scrollY + window.innerHeight) / document.documentElement.scrollHeight * 100
|
|
7697
|
+
);
|
|
7698
|
+
maxScrollDepth = Math.max(maxScrollDepth, depth);
|
|
7699
|
+
if (scrollTimer) clearTimeout(scrollTimer);
|
|
7700
|
+
scrollTimer = setTimeout(() => {
|
|
7701
|
+
this.recordHeatmapEvent("scroll", 50, maxScrollDepth * (window.innerHeight / 100), maxScrollDepth);
|
|
7702
|
+
}, 500);
|
|
7703
|
+
};
|
|
7704
|
+
window.addEventListener("scroll", this.heatmapScrollHandler, { passive: true });
|
|
7705
|
+
}
|
|
7706
|
+
this.log("Heatmap enabled", options);
|
|
7707
|
+
}
|
|
7708
|
+
/**
|
|
7709
|
+
* 세션 heartbeat 자동 전송 시작 (30초 간격)
|
|
7710
|
+
*/
|
|
7711
|
+
enableHeartbeat() {
|
|
7712
|
+
if (!this.canTrack()) return;
|
|
7713
|
+
if (this.heartbeatTimer) return;
|
|
7714
|
+
const sendHeartbeat = () => {
|
|
7715
|
+
if (!this.canTrack()) return;
|
|
7716
|
+
const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
|
|
7717
|
+
this.http.post(`${prefix}/storages/web/${this.storageWebId}/sessions/heartbeat`, {
|
|
7718
|
+
visitor_uid: this.session.visitorUid,
|
|
7719
|
+
session_id: this.session.sessionId
|
|
7720
|
+
}).catch(() => {
|
|
7721
|
+
});
|
|
7722
|
+
};
|
|
7723
|
+
this.heartbeatTimer = setInterval(sendHeartbeat, 3e4);
|
|
7724
|
+
document.addEventListener("visibilitychange", () => {
|
|
7725
|
+
if (document.hidden) {
|
|
7726
|
+
if (this.heartbeatTimer) {
|
|
7727
|
+
clearInterval(this.heartbeatTimer);
|
|
7728
|
+
this.heartbeatTimer = null;
|
|
7729
|
+
}
|
|
7730
|
+
} else {
|
|
7731
|
+
if (!this.heartbeatTimer) {
|
|
7732
|
+
this.heartbeatTimer = setInterval(sendHeartbeat, 3e4);
|
|
7733
|
+
sendHeartbeat();
|
|
7734
|
+
}
|
|
7735
|
+
}
|
|
7736
|
+
});
|
|
7737
|
+
window.addEventListener("beforeunload", () => {
|
|
7738
|
+
if (typeof navigator !== "undefined" && navigator.sendBeacon && this.storageWebId) {
|
|
7739
|
+
const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
|
|
7740
|
+
const url = `${this.http.getBaseUrl()}${prefix}/storages/web/${this.storageWebId}/sessions/heartbeat`;
|
|
7741
|
+
navigator.sendBeacon(url, JSON.stringify({
|
|
7742
|
+
visitor_uid: this.session.visitorUid,
|
|
7743
|
+
session_id: this.session.sessionId
|
|
7744
|
+
}));
|
|
7745
|
+
}
|
|
7746
|
+
});
|
|
7747
|
+
sendHeartbeat();
|
|
7748
|
+
this.log("Heartbeat enabled (30s interval)");
|
|
7749
|
+
}
|
|
7750
|
+
/** 큐에 있는 이벤트 즉시 전송 */
|
|
7751
|
+
async flush() {
|
|
7752
|
+
await this.flushQueue();
|
|
7753
|
+
}
|
|
7754
|
+
/** 세션 매니저 접근 (고급) */
|
|
7755
|
+
getSession() {
|
|
7756
|
+
return this.session;
|
|
7757
|
+
}
|
|
7758
|
+
// ── Private ────────────────────────────────────────────────────
|
|
7759
|
+
canTrack() {
|
|
7760
|
+
if (!this.isInitialized || !this.storageWebId) return false;
|
|
7761
|
+
if (!this.consent.analytics) return false;
|
|
7762
|
+
return true;
|
|
7763
|
+
}
|
|
7764
|
+
isDNT() {
|
|
7765
|
+
if (typeof navigator === "undefined") return false;
|
|
7766
|
+
return navigator.doNotTrack === "1" || navigator.globalPrivacyControl === true;
|
|
7767
|
+
}
|
|
7768
|
+
createBaseEvent(type) {
|
|
7769
|
+
return {
|
|
7770
|
+
type,
|
|
7771
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7772
|
+
session_id: this.session.sessionId,
|
|
7773
|
+
visitor_uid: this.session.visitorUid
|
|
7774
|
+
};
|
|
7775
|
+
}
|
|
7776
|
+
enqueue(event) {
|
|
7777
|
+
this.eventQueue.push(event);
|
|
7778
|
+
if (this.eventQueue.length >= this.config.batchSize) {
|
|
7779
|
+
this.flushQueue();
|
|
7780
|
+
}
|
|
7781
|
+
}
|
|
7782
|
+
async flushQueue() {
|
|
7783
|
+
if (this.eventQueue.length === 0 || !this.storageWebId) return;
|
|
7784
|
+
const events = this.eventQueue.splice(0);
|
|
7785
|
+
const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
|
|
7786
|
+
try {
|
|
7787
|
+
await this.http.post(
|
|
7788
|
+
`${prefix}/storages/web/${this.storageWebId}/visitors/batch`,
|
|
7789
|
+
{
|
|
7790
|
+
visitor_uid: this.session.visitorUid,
|
|
7791
|
+
events: events.map((e) => ({
|
|
7792
|
+
timestamp: e.timestamp,
|
|
7793
|
+
page_path: e.page_path || "",
|
|
7794
|
+
page_url: e.page_url || "",
|
|
7795
|
+
page_title: e.page_title || "",
|
|
7796
|
+
referrer: e.referrer || "",
|
|
7797
|
+
user_agent: typeof navigator !== "undefined" ? navigator.userAgent : "",
|
|
7798
|
+
screen_width: e.screen_width || 0,
|
|
7799
|
+
screen_height: e.screen_height || 0,
|
|
7800
|
+
session_id: e.session_id,
|
|
7801
|
+
session_start: e.type === "session_start",
|
|
7802
|
+
is_page_view: e.type === "page_view",
|
|
7803
|
+
event_name: e.event_name,
|
|
7804
|
+
event_properties: e.event_properties,
|
|
7805
|
+
utm_source: e.utm_source,
|
|
7806
|
+
utm_medium: e.utm_medium,
|
|
7807
|
+
utm_campaign: e.utm_campaign,
|
|
7808
|
+
utm_content: e.utm_content,
|
|
7809
|
+
utm_term: e.utm_term
|
|
7810
|
+
}))
|
|
7811
|
+
}
|
|
7812
|
+
);
|
|
7813
|
+
this.log(`Flushed ${events.length} events`);
|
|
7814
|
+
} catch (err) {
|
|
7815
|
+
if (this.eventQueue.length < this.config.batchSize * 3) {
|
|
7816
|
+
this.eventQueue.unshift(...events);
|
|
7817
|
+
}
|
|
7818
|
+
this.log("Flush failed", err);
|
|
7819
|
+
}
|
|
7820
|
+
}
|
|
7821
|
+
/** sendBeacon으로 동기 flush (beforeunload용) */
|
|
7822
|
+
flushSync() {
|
|
7823
|
+
if (this.eventQueue.length === 0 || !this.storageWebId) return;
|
|
7824
|
+
if (typeof navigator === "undefined" || !navigator.sendBeacon) return;
|
|
7825
|
+
const events = this.eventQueue.splice(0);
|
|
7826
|
+
const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
|
|
7827
|
+
const baseUrl = this.http.getBaseUrl();
|
|
7828
|
+
const url = `${baseUrl}${prefix}/storages/web/${this.storageWebId}/visitors/batch`;
|
|
7829
|
+
const body = JSON.stringify({
|
|
7830
|
+
visitor_uid: this.session.visitorUid,
|
|
7831
|
+
events: events.map((e) => ({
|
|
7832
|
+
timestamp: e.timestamp,
|
|
7833
|
+
page_path: e.page_path || "",
|
|
7834
|
+
page_url: e.page_url || "",
|
|
7835
|
+
page_title: e.page_title || "",
|
|
7836
|
+
referrer: e.referrer || "",
|
|
7837
|
+
session_id: e.session_id,
|
|
7838
|
+
session_start: e.type === "session_start",
|
|
7839
|
+
is_page_view: e.type === "page_view",
|
|
7840
|
+
event_name: e.event_name,
|
|
7841
|
+
event_properties: e.event_properties
|
|
7842
|
+
}))
|
|
7843
|
+
});
|
|
7844
|
+
try {
|
|
7845
|
+
navigator.sendBeacon(url, new Blob([body], { type: "application/json" }));
|
|
7846
|
+
} catch {
|
|
7847
|
+
}
|
|
7848
|
+
}
|
|
7849
|
+
trackSessionStart() {
|
|
7850
|
+
const event = this.createBaseEvent("session_start");
|
|
7851
|
+
event.page_path = window.location.pathname;
|
|
7852
|
+
event.page_url = window.location.href;
|
|
7853
|
+
event.referrer = document.referrer || void 0;
|
|
7854
|
+
event.screen_width = window.screen.width;
|
|
7855
|
+
event.screen_height = window.screen.height;
|
|
7856
|
+
Object.assign(event, this.utm);
|
|
7857
|
+
this.enqueue(event);
|
|
7858
|
+
}
|
|
7859
|
+
startBatchTimer() {
|
|
7860
|
+
this.batchTimer = setInterval(() => this.flushQueue(), this.config.flushInterval);
|
|
7861
|
+
}
|
|
7862
|
+
stopBatchTimer() {
|
|
7863
|
+
if (this.batchTimer) {
|
|
7864
|
+
clearInterval(this.batchTimer);
|
|
7865
|
+
this.batchTimer = null;
|
|
7866
|
+
}
|
|
7867
|
+
}
|
|
7868
|
+
startHeartbeat() {
|
|
7869
|
+
this.heartbeatTimer = setInterval(() => {
|
|
7870
|
+
if (!this.canTrack()) return;
|
|
7871
|
+
this.sendHeartbeat();
|
|
7872
|
+
}, 30 * 1e3);
|
|
7873
|
+
if (typeof document !== "undefined") {
|
|
7874
|
+
this.visibilityHandler = () => {
|
|
7875
|
+
if (document.visibilityState === "hidden") {
|
|
7876
|
+
this.sendHeartbeatBeacon();
|
|
7877
|
+
if (this.heartbeatTimer) {
|
|
7878
|
+
clearInterval(this.heartbeatTimer);
|
|
7879
|
+
this.heartbeatTimer = null;
|
|
7880
|
+
}
|
|
7881
|
+
} else if (document.visibilityState === "visible") {
|
|
7882
|
+
if (!this.heartbeatTimer) {
|
|
7883
|
+
this.sendHeartbeat();
|
|
7884
|
+
this.heartbeatTimer = setInterval(() => {
|
|
7885
|
+
if (!this.canTrack()) return;
|
|
7886
|
+
this.sendHeartbeat();
|
|
7887
|
+
}, 30 * 1e3);
|
|
7888
|
+
}
|
|
7889
|
+
}
|
|
7890
|
+
};
|
|
7891
|
+
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
7892
|
+
}
|
|
7893
|
+
if (typeof window !== "undefined") {
|
|
7894
|
+
this.unloadHeartbeatHandler = () => this.sendHeartbeatBeacon();
|
|
7895
|
+
window.addEventListener("beforeunload", this.unloadHeartbeatHandler);
|
|
7896
|
+
}
|
|
7897
|
+
}
|
|
7898
|
+
stopHeartbeat() {
|
|
7899
|
+
if (this.heartbeatTimer) {
|
|
7900
|
+
clearInterval(this.heartbeatTimer);
|
|
7901
|
+
this.heartbeatTimer = null;
|
|
7902
|
+
}
|
|
7903
|
+
if (typeof document !== "undefined" && this.visibilityHandler) {
|
|
7904
|
+
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
7905
|
+
this.visibilityHandler = null;
|
|
7906
|
+
}
|
|
7907
|
+
if (typeof window !== "undefined" && this.unloadHeartbeatHandler) {
|
|
7908
|
+
window.removeEventListener("beforeunload", this.unloadHeartbeatHandler);
|
|
7909
|
+
this.unloadHeartbeatHandler = null;
|
|
7910
|
+
}
|
|
7911
|
+
}
|
|
7912
|
+
/** 하트비트를 큐에 넣어 배치 전송 */
|
|
7913
|
+
sendHeartbeat() {
|
|
7914
|
+
const event = this.createBaseEvent("heartbeat");
|
|
7915
|
+
event.page_path = window.location.pathname;
|
|
7916
|
+
this.enqueue(event);
|
|
7917
|
+
if (this.storageWebId) {
|
|
7918
|
+
const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
|
|
7919
|
+
this.http.post(
|
|
7920
|
+
`${prefix}/storages/web/${this.storageWebId}/sessions/heartbeat`,
|
|
7921
|
+
{
|
|
7922
|
+
visitor_uid: this.session.visitorUid,
|
|
7923
|
+
session_id: this.session.sessionId
|
|
7924
|
+
}
|
|
7925
|
+
).catch(() => {
|
|
7926
|
+
});
|
|
7927
|
+
}
|
|
7928
|
+
}
|
|
7929
|
+
/** sendBeacon으로 하트비트 전송 (unload/visibility hidden용) */
|
|
7930
|
+
sendHeartbeatBeacon() {
|
|
7931
|
+
if (!this.storageWebId) return;
|
|
7932
|
+
if (typeof navigator === "undefined" || !navigator.sendBeacon) return;
|
|
7933
|
+
const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
|
|
7934
|
+
const baseUrl = this.http.getBaseUrl();
|
|
7935
|
+
const url = `${baseUrl}${prefix}/storages/web/${this.storageWebId}/sessions/heartbeat`;
|
|
7936
|
+
const body = JSON.stringify({
|
|
7937
|
+
visitor_uid: this.session.visitorUid,
|
|
7938
|
+
session_id: this.session.sessionId
|
|
7939
|
+
});
|
|
7940
|
+
try {
|
|
7941
|
+
navigator.sendBeacon(url, new Blob([body], { type: "application/json" }));
|
|
7942
|
+
} catch {
|
|
7943
|
+
}
|
|
7944
|
+
}
|
|
7945
|
+
setupAutoPageView() {
|
|
7946
|
+
this.popstateHandler = () => this.trackPageView();
|
|
7947
|
+
window.addEventListener("popstate", this.popstateHandler);
|
|
7948
|
+
this.origPushState = history.pushState.bind(history);
|
|
7949
|
+
this.origReplaceState = history.replaceState.bind(history);
|
|
7950
|
+
const self = this;
|
|
7951
|
+
history.pushState = function(...args) {
|
|
7952
|
+
self.origPushState(...args);
|
|
7953
|
+
self.trackPageView();
|
|
7954
|
+
};
|
|
7955
|
+
history.replaceState = function(...args) {
|
|
7956
|
+
self.origReplaceState(...args);
|
|
7957
|
+
self.trackPageView();
|
|
7958
|
+
};
|
|
7959
|
+
}
|
|
7960
|
+
removeAutoPageView() {
|
|
7961
|
+
if (this.popstateHandler) {
|
|
7962
|
+
window.removeEventListener("popstate", this.popstateHandler);
|
|
7963
|
+
this.popstateHandler = null;
|
|
7964
|
+
}
|
|
7965
|
+
if (this.origPushState) {
|
|
7966
|
+
history.pushState = this.origPushState;
|
|
7967
|
+
this.origPushState = null;
|
|
7968
|
+
}
|
|
7969
|
+
if (this.origReplaceState) {
|
|
7970
|
+
history.replaceState = this.origReplaceState;
|
|
7971
|
+
this.origReplaceState = null;
|
|
7972
|
+
}
|
|
7973
|
+
}
|
|
7974
|
+
removeHeatmapListeners() {
|
|
7975
|
+
if (this.heatmapClickHandler) {
|
|
7976
|
+
document.removeEventListener("click", this.heatmapClickHandler);
|
|
7977
|
+
this.heatmapClickHandler = null;
|
|
7978
|
+
}
|
|
7979
|
+
if (this.heatmapScrollHandler) {
|
|
7980
|
+
window.removeEventListener("scroll", this.heatmapScrollHandler);
|
|
7981
|
+
this.heatmapScrollHandler = null;
|
|
7982
|
+
}
|
|
7983
|
+
}
|
|
7984
|
+
recordHeatmapEvent(eventType, xPercent, yPercent, scrollDepth) {
|
|
7985
|
+
if (!this.canTrack() || !this.storageWebId) return;
|
|
7986
|
+
const prefix = this.http.hasPublicKey() ? "/v1/public" : "/v1";
|
|
7987
|
+
this.heatmapQueue.push({
|
|
7988
|
+
page_path: window.location.pathname,
|
|
7989
|
+
event_type: eventType,
|
|
7990
|
+
x_percent: Math.round(xPercent * 100) / 100,
|
|
7991
|
+
y_percent: Math.round(yPercent * 100) / 100,
|
|
7992
|
+
viewport_width: window.innerWidth,
|
|
7993
|
+
viewport_height: window.innerHeight,
|
|
7994
|
+
scroll_depth_percent: scrollDepth,
|
|
7995
|
+
session_id: this.session.sessionId
|
|
7996
|
+
});
|
|
7997
|
+
if (this.heatmapQueue.length >= 50) {
|
|
7998
|
+
const events = this.heatmapQueue.splice(0);
|
|
7999
|
+
this.http.post(`${prefix}/storages/web/${this.storageWebId}/heatmap/batch`, {
|
|
8000
|
+
visitor_uid: this.session.visitorUid,
|
|
8001
|
+
events
|
|
8002
|
+
}).catch(() => {
|
|
8003
|
+
});
|
|
8004
|
+
}
|
|
8005
|
+
}
|
|
8006
|
+
log(...args) {
|
|
8007
|
+
if (this.config.debug) {
|
|
8008
|
+
console.log("[Analytics]", ...args);
|
|
8009
|
+
}
|
|
8010
|
+
}
|
|
8011
|
+
};
|
|
8012
|
+
|
|
7425
8013
|
// src/api/game-transport.ts
|
|
7426
8014
|
var WebTransportTransport = class {
|
|
7427
8015
|
constructor(config, onMessage, onClose, onError) {
|
|
@@ -8101,6 +8689,7 @@ var ConnectBase = class {
|
|
|
8101
8689
|
this.knowledge = new KnowledgeAPI(this.http);
|
|
8102
8690
|
this.ai = new AIAPI(this.http);
|
|
8103
8691
|
this.queue = new QueueAPI(this.http);
|
|
8692
|
+
this.analytics = new AnalyticsAPI(this.http);
|
|
8104
8693
|
}
|
|
8105
8694
|
/**
|
|
8106
8695
|
* 수동으로 토큰 설정 (기존 토큰으로 세션 복원 시)
|
|
@@ -8132,6 +8721,7 @@ export {
|
|
|
8132
8721
|
GameRoom,
|
|
8133
8722
|
GameRoomTransport,
|
|
8134
8723
|
NativeAPI,
|
|
8724
|
+
SessionManager,
|
|
8135
8725
|
VideoProcessingError,
|
|
8136
8726
|
index_default as default,
|
|
8137
8727
|
isWebTransportSupported
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "connectbase-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Connect Base JavaScript/TypeScript SDK for browser and Node.js",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"build": "tsup",
|
|
34
34
|
"dev": "tsup --watch",
|
|
35
35
|
"typecheck": "tsc --noEmit",
|
|
36
|
+
"test": "vitest run",
|
|
36
37
|
"test:types": "tsc --noEmit -p test/tsconfig.json",
|
|
37
38
|
"release": "pnpm build && npm publish --access public"
|
|
38
39
|
},
|
|
@@ -55,7 +56,8 @@
|
|
|
55
56
|
"devDependencies": {
|
|
56
57
|
"@types/node": "^22.15.18",
|
|
57
58
|
"tsup": "^8.5.1",
|
|
58
|
-
"typescript": "^5.9.3"
|
|
59
|
+
"typescript": "^5.9.3",
|
|
60
|
+
"vitest": "^3.2.4"
|
|
59
61
|
},
|
|
60
62
|
"engines": {
|
|
61
63
|
"node": ">=16"
|