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