@blinkdotnew/sdk 0.7.1 → 0.8.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/README.md CHANGED
@@ -81,6 +81,22 @@ await blink.realtime.publish('chat-room', 'message', { text: 'Hello world!' })
81
81
  const users = await blink.realtime.presence('chat-room')
82
82
  console.log('Online users:', users.length)
83
83
 
84
+ // Analytics operations (automatic pageview tracking + custom events)
85
+ // Pageviews are tracked automatically on initialization and route changes
86
+ blink.analytics.log('button_clicked', {
87
+ button_id: 'signup',
88
+ page: '/pricing'
89
+ })
90
+
91
+ // Check if analytics is enabled
92
+ if (blink.analytics.isEnabled()) {
93
+ console.log('Analytics is active')
94
+ }
95
+
96
+ // Disable/enable analytics
97
+ blink.analytics.disable()
98
+ blink.analytics.enable()
99
+
84
100
  // Storage operations (instant - returns public URL directly)
85
101
  const { publicUrl } = await blink.storage.upload(
86
102
  file,
@@ -109,6 +125,7 @@ This SDK powers every Blink-generated app with:
109
125
  - **📁 Storage**: File upload, download, and management
110
126
  - **📧 Notifications**: Email sending with attachments, custom branding, and delivery tracking
111
127
  - **⚡ Realtime**: WebSocket-based pub/sub messaging, presence tracking, and live updates
128
+ - **📊 Analytics**: Automatic pageview tracking, custom event logging, session management, and privacy-first design
112
129
  - **🌐 Universal**: Works on client-side and server-side
113
130
  - **📱 Framework Agnostic**: React, Vue, Svelte, vanilla JS, Node.js, Deno
114
131
  - **🔄 Real-time**: Built-in auth state management and token refresh
@@ -550,6 +567,31 @@ try {
550
567
  }
551
568
  ```
552
569
 
570
+ ### Analytics Operations
571
+
572
+ ```typescript
573
+ // 🔥 Analytics (NEW!) - Automatic pageview tracking + custom events
574
+ // Pageviews are tracked automatically on initialization and route changes
575
+
576
+ // Log custom events - context data is added automatically
577
+ blink.analytics.log('button_clicked', {
578
+ button_id: 'signup',
579
+ campaign: 'summer_sale'
580
+ })
581
+
582
+ // All events automatically include:
583
+ // - timestamp, project_id, user_id, session_id
584
+ // - pathname (current page), referrer, screen_width
585
+ // - device/browser/OS info (parsed server-side)
586
+
587
+ // Control analytics
588
+ blink.analytics.disable()
589
+ blink.analytics.enable()
590
+ const isEnabled = blink.analytics.isEnabled()
591
+
592
+ // Features: Privacy-first, offline support, event batching, session management
593
+ ```
594
+
553
595
  ### Realtime Operations
554
596
 
555
597
  ```typescript
package/dist/index.d.mts CHANGED
@@ -248,6 +248,71 @@ declare class BlinkDataImpl implements BlinkData {
248
248
  }): Promise<SearchResponse>;
249
249
  }
250
250
 
251
+ /**
252
+ * Blink Analytics Module
253
+ * Provides automatic pageview tracking and custom event logging
254
+ */
255
+
256
+ interface AnalyticsEvent {
257
+ type: string;
258
+ timestamp?: string;
259
+ user_id?: string | null;
260
+ session_id?: string | null;
261
+ pathname?: string | null;
262
+ referrer?: string | null;
263
+ screen_width?: number | null;
264
+ [key: string]: any;
265
+ }
266
+ interface BlinkAnalytics {
267
+ log(eventName: string, data?: Record<string, any>): void;
268
+ disable(): void;
269
+ enable(): void;
270
+ isEnabled(): boolean;
271
+ setUserId(userId: string | null): void;
272
+ }
273
+ declare class BlinkAnalyticsImpl implements BlinkAnalytics {
274
+ private httpClient;
275
+ private projectId;
276
+ private queue;
277
+ private timer;
278
+ private enabled;
279
+ private userId;
280
+ private hasTrackedPageview;
281
+ constructor(httpClient: HttpClient, projectId: string);
282
+ /**
283
+ * Log a custom analytics event
284
+ */
285
+ log(eventName: string, data?: Record<string, any>): void;
286
+ /**
287
+ * Disable analytics tracking
288
+ */
289
+ disable(): void;
290
+ /**
291
+ * Enable analytics tracking
292
+ */
293
+ enable(): void;
294
+ /**
295
+ * Check if analytics is enabled
296
+ */
297
+ isEnabled(): boolean;
298
+ /**
299
+ * Set the user ID for analytics events
300
+ */
301
+ setUserId(userId: string | null): void;
302
+ private buildEvent;
303
+ private sanitizeData;
304
+ private enqueue;
305
+ private flush;
306
+ private clearTimer;
307
+ private getOrCreateSessionId;
308
+ private createNewSession;
309
+ private loadQueue;
310
+ private persistQueue;
311
+ private trackPageview;
312
+ private setupRouteChangeListener;
313
+ private setupUnloadListener;
314
+ }
315
+
251
316
  /**
252
317
  * Blink Client - Main SDK entry point
253
318
  * Factory function and client class for the Blink SDK
@@ -261,6 +326,7 @@ interface BlinkClient {
261
326
  data: BlinkData;
262
327
  realtime: BlinkRealtime;
263
328
  notifications: BlinkNotifications;
329
+ analytics: BlinkAnalytics;
264
330
  }
265
331
  /**
266
332
  * Create a new Blink client instance
@@ -720,4 +786,4 @@ declare class BlinkRealtimeImpl implements BlinkRealtime {
720
786
  onPresence(channelName: string, callback: (users: PresenceUser[]) => void): () => void;
721
787
  }
722
788
 
723
- export { type AuthStateChangeCallback, BlinkAIImpl, type BlinkClient, type BlinkData, BlinkDataImpl, BlinkDatabase, BlinkRealtimeChannel, BlinkRealtimeImpl, BlinkStorageImpl, BlinkTable, createClient };
789
+ export { type AnalyticsEvent, type AuthStateChangeCallback, BlinkAIImpl, type BlinkAnalytics, BlinkAnalyticsImpl, type BlinkClient, type BlinkData, BlinkDataImpl, BlinkDatabase, BlinkRealtimeChannel, BlinkRealtimeImpl, BlinkStorageImpl, BlinkTable, createClient };
package/dist/index.d.ts CHANGED
@@ -248,6 +248,71 @@ declare class BlinkDataImpl implements BlinkData {
248
248
  }): Promise<SearchResponse>;
249
249
  }
250
250
 
251
+ /**
252
+ * Blink Analytics Module
253
+ * Provides automatic pageview tracking and custom event logging
254
+ */
255
+
256
+ interface AnalyticsEvent {
257
+ type: string;
258
+ timestamp?: string;
259
+ user_id?: string | null;
260
+ session_id?: string | null;
261
+ pathname?: string | null;
262
+ referrer?: string | null;
263
+ screen_width?: number | null;
264
+ [key: string]: any;
265
+ }
266
+ interface BlinkAnalytics {
267
+ log(eventName: string, data?: Record<string, any>): void;
268
+ disable(): void;
269
+ enable(): void;
270
+ isEnabled(): boolean;
271
+ setUserId(userId: string | null): void;
272
+ }
273
+ declare class BlinkAnalyticsImpl implements BlinkAnalytics {
274
+ private httpClient;
275
+ private projectId;
276
+ private queue;
277
+ private timer;
278
+ private enabled;
279
+ private userId;
280
+ private hasTrackedPageview;
281
+ constructor(httpClient: HttpClient, projectId: string);
282
+ /**
283
+ * Log a custom analytics event
284
+ */
285
+ log(eventName: string, data?: Record<string, any>): void;
286
+ /**
287
+ * Disable analytics tracking
288
+ */
289
+ disable(): void;
290
+ /**
291
+ * Enable analytics tracking
292
+ */
293
+ enable(): void;
294
+ /**
295
+ * Check if analytics is enabled
296
+ */
297
+ isEnabled(): boolean;
298
+ /**
299
+ * Set the user ID for analytics events
300
+ */
301
+ setUserId(userId: string | null): void;
302
+ private buildEvent;
303
+ private sanitizeData;
304
+ private enqueue;
305
+ private flush;
306
+ private clearTimer;
307
+ private getOrCreateSessionId;
308
+ private createNewSession;
309
+ private loadQueue;
310
+ private persistQueue;
311
+ private trackPageview;
312
+ private setupRouteChangeListener;
313
+ private setupUnloadListener;
314
+ }
315
+
251
316
  /**
252
317
  * Blink Client - Main SDK entry point
253
318
  * Factory function and client class for the Blink SDK
@@ -261,6 +326,7 @@ interface BlinkClient {
261
326
  data: BlinkData;
262
327
  realtime: BlinkRealtime;
263
328
  notifications: BlinkNotifications;
329
+ analytics: BlinkAnalytics;
264
330
  }
265
331
  /**
266
332
  * Create a new Blink client instance
@@ -720,4 +786,4 @@ declare class BlinkRealtimeImpl implements BlinkRealtime {
720
786
  onPresence(channelName: string, callback: (users: PresenceUser[]) => void): () => void;
721
787
  }
722
788
 
723
- export { type AuthStateChangeCallback, BlinkAIImpl, type BlinkClient, type BlinkData, BlinkDataImpl, BlinkDatabase, BlinkRealtimeChannel, BlinkRealtimeImpl, BlinkStorageImpl, BlinkTable, createClient };
789
+ export { type AnalyticsEvent, type AuthStateChangeCallback, BlinkAIImpl, type BlinkAnalytics, BlinkAnalyticsImpl, type BlinkClient, type BlinkData, BlinkDataImpl, BlinkDatabase, BlinkRealtimeChannel, BlinkRealtimeImpl, BlinkStorageImpl, BlinkTable, createClient };
package/dist/index.js CHANGED
@@ -2880,6 +2880,219 @@ var BlinkNotificationsImpl = class {
2880
2880
  }
2881
2881
  };
2882
2882
 
2883
+ // src/analytics.ts
2884
+ var SESSION_DURATION = 30 * 60 * 1e3;
2885
+ var MAX_BATCH_SIZE = 10;
2886
+ var BATCH_TIMEOUT = 3e3;
2887
+ var MAX_STRING_LENGTH = 256;
2888
+ var STORAGE_KEY_QUEUE = "blinkAnalyticsQueue";
2889
+ var STORAGE_KEY_SESSION = "blinkAnalyticsSession";
2890
+ var BlinkAnalyticsImpl = class {
2891
+ httpClient;
2892
+ projectId;
2893
+ queue = [];
2894
+ timer = null;
2895
+ enabled = true;
2896
+ userId = null;
2897
+ hasTrackedPageview = false;
2898
+ constructor(httpClient, projectId) {
2899
+ this.httpClient = httpClient;
2900
+ this.projectId = projectId;
2901
+ if (typeof window === "undefined") {
2902
+ return;
2903
+ }
2904
+ if (navigator.doNotTrack === "1") {
2905
+ this.enabled = false;
2906
+ return;
2907
+ }
2908
+ this.loadQueue();
2909
+ this.trackPageview();
2910
+ this.setupRouteChangeListener();
2911
+ this.setupUnloadListener();
2912
+ }
2913
+ /**
2914
+ * Log a custom analytics event
2915
+ */
2916
+ log(eventName, data = {}) {
2917
+ if (!this.enabled || typeof window === "undefined") {
2918
+ return;
2919
+ }
2920
+ const event = this.buildEvent(eventName, data);
2921
+ this.enqueue(event);
2922
+ }
2923
+ /**
2924
+ * Disable analytics tracking
2925
+ */
2926
+ disable() {
2927
+ this.enabled = false;
2928
+ this.clearTimer();
2929
+ }
2930
+ /**
2931
+ * Enable analytics tracking
2932
+ */
2933
+ enable() {
2934
+ this.enabled = true;
2935
+ }
2936
+ /**
2937
+ * Check if analytics is enabled
2938
+ */
2939
+ isEnabled() {
2940
+ return this.enabled;
2941
+ }
2942
+ /**
2943
+ * Set the user ID for analytics events
2944
+ */
2945
+ setUserId(userId) {
2946
+ this.userId = userId;
2947
+ }
2948
+ // Private methods
2949
+ buildEvent(type, data = {}) {
2950
+ const sessionId = this.getOrCreateSessionId();
2951
+ return {
2952
+ type,
2953
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2954
+ project_id: this.projectId,
2955
+ user_id: this.userId,
2956
+ session_id: sessionId,
2957
+ pathname: window.location.pathname,
2958
+ referrer: document.referrer || null,
2959
+ screen_width: window.innerWidth,
2960
+ ...this.sanitizeData(data)
2961
+ };
2962
+ }
2963
+ sanitizeData(data) {
2964
+ if (typeof data === "string") {
2965
+ return data.length > MAX_STRING_LENGTH ? data.slice(0, MAX_STRING_LENGTH - 3) + "..." : data;
2966
+ }
2967
+ if (typeof data === "object" && data !== null) {
2968
+ const result = {};
2969
+ for (const key in data) {
2970
+ result[key] = this.sanitizeData(data[key]);
2971
+ }
2972
+ return result;
2973
+ }
2974
+ return data;
2975
+ }
2976
+ enqueue(event) {
2977
+ this.queue.push(event);
2978
+ this.persistQueue();
2979
+ if (this.queue.length >= MAX_BATCH_SIZE) {
2980
+ this.flush();
2981
+ } else if (!this.timer) {
2982
+ this.timer = setTimeout(() => this.flush(), BATCH_TIMEOUT);
2983
+ }
2984
+ }
2985
+ async flush() {
2986
+ this.clearTimer();
2987
+ if (this.queue.length === 0) {
2988
+ return;
2989
+ }
2990
+ const events = this.queue.slice(0, MAX_BATCH_SIZE);
2991
+ this.queue = this.queue.slice(MAX_BATCH_SIZE);
2992
+ this.persistQueue();
2993
+ try {
2994
+ await this.httpClient.post(`/api/analytics/${this.projectId}/log`, { events });
2995
+ } catch (error) {
2996
+ this.queue = [...events, ...this.queue];
2997
+ this.persistQueue();
2998
+ console.error("Failed to send analytics events:", error);
2999
+ }
3000
+ if (this.queue.length > 0) {
3001
+ this.timer = setTimeout(() => this.flush(), BATCH_TIMEOUT);
3002
+ }
3003
+ }
3004
+ clearTimer() {
3005
+ if (this.timer) {
3006
+ clearTimeout(this.timer);
3007
+ this.timer = null;
3008
+ }
3009
+ }
3010
+ getOrCreateSessionId() {
3011
+ try {
3012
+ const stored = localStorage.getItem(STORAGE_KEY_SESSION);
3013
+ if (stored) {
3014
+ const session = JSON.parse(stored);
3015
+ const now = Date.now();
3016
+ if (now - session.lastActivityAt > SESSION_DURATION) {
3017
+ return this.createNewSession();
3018
+ }
3019
+ session.lastActivityAt = now;
3020
+ localStorage.setItem(STORAGE_KEY_SESSION, JSON.stringify(session));
3021
+ return session.id;
3022
+ }
3023
+ return this.createNewSession();
3024
+ } catch {
3025
+ return null;
3026
+ }
3027
+ }
3028
+ createNewSession() {
3029
+ const now = Date.now();
3030
+ const randomId = Math.random().toString(36).substring(2, 10);
3031
+ const session = {
3032
+ id: `sess_${now}_${randomId}`,
3033
+ startedAt: now,
3034
+ lastActivityAt: now
3035
+ };
3036
+ try {
3037
+ localStorage.setItem(STORAGE_KEY_SESSION, JSON.stringify(session));
3038
+ } catch {
3039
+ }
3040
+ return session.id;
3041
+ }
3042
+ loadQueue() {
3043
+ try {
3044
+ const stored = localStorage.getItem(STORAGE_KEY_QUEUE);
3045
+ if (stored) {
3046
+ this.queue = JSON.parse(stored);
3047
+ if (this.queue.length > 0) {
3048
+ this.timer = setTimeout(() => this.flush(), BATCH_TIMEOUT);
3049
+ }
3050
+ }
3051
+ } catch {
3052
+ this.queue = [];
3053
+ }
3054
+ }
3055
+ persistQueue() {
3056
+ try {
3057
+ if (this.queue.length === 0) {
3058
+ localStorage.removeItem(STORAGE_KEY_QUEUE);
3059
+ } else {
3060
+ localStorage.setItem(STORAGE_KEY_QUEUE, JSON.stringify(this.queue));
3061
+ }
3062
+ } catch {
3063
+ }
3064
+ }
3065
+ trackPageview() {
3066
+ if (!this.hasTrackedPageview) {
3067
+ this.log("pageview");
3068
+ this.hasTrackedPageview = true;
3069
+ }
3070
+ }
3071
+ setupRouteChangeListener() {
3072
+ const originalPushState = history.pushState;
3073
+ const originalReplaceState = history.replaceState;
3074
+ history.pushState = (...args) => {
3075
+ originalPushState.apply(history, args);
3076
+ this.log("pageview");
3077
+ };
3078
+ history.replaceState = (...args) => {
3079
+ originalReplaceState.apply(history, args);
3080
+ this.log("pageview");
3081
+ };
3082
+ window.addEventListener("popstate", () => {
3083
+ this.log("pageview");
3084
+ });
3085
+ }
3086
+ setupUnloadListener() {
3087
+ window.addEventListener("pagehide", () => {
3088
+ this.flush();
3089
+ });
3090
+ window.addEventListener("unload", () => {
3091
+ this.flush();
3092
+ });
3093
+ }
3094
+ };
3095
+
2883
3096
  // src/client.ts
2884
3097
  var BlinkClientImpl = class {
2885
3098
  auth;
@@ -2889,6 +3102,7 @@ var BlinkClientImpl = class {
2889
3102
  data;
2890
3103
  realtime;
2891
3104
  notifications;
3105
+ analytics;
2892
3106
  httpClient;
2893
3107
  constructor(config) {
2894
3108
  this.auth = new BlinkAuth(config);
@@ -2903,6 +3117,14 @@ var BlinkClientImpl = class {
2903
3117
  this.data = new BlinkDataImpl(this.httpClient, config.projectId);
2904
3118
  this.realtime = new BlinkRealtimeImpl(this.httpClient, config.projectId);
2905
3119
  this.notifications = new BlinkNotificationsImpl(this.httpClient);
3120
+ this.analytics = new BlinkAnalyticsImpl(this.httpClient, config.projectId);
3121
+ this.auth.onAuthStateChanged((state) => {
3122
+ if (state.isAuthenticated && state.user) {
3123
+ this.analytics.setUserId(state.user.id);
3124
+ } else {
3125
+ this.analytics.setUserId(null);
3126
+ }
3127
+ });
2906
3128
  }
2907
3129
  };
2908
3130
  function createClient(config) {
@@ -2917,6 +3139,7 @@ function createClient(config) {
2917
3139
  }
2918
3140
 
2919
3141
  exports.BlinkAIImpl = BlinkAIImpl;
3142
+ exports.BlinkAnalyticsImpl = BlinkAnalyticsImpl;
2920
3143
  exports.BlinkDataImpl = BlinkDataImpl;
2921
3144
  exports.BlinkDatabase = BlinkDatabase;
2922
3145
  exports.BlinkRealtimeChannel = BlinkRealtimeChannel;
package/dist/index.mjs CHANGED
@@ -2878,6 +2878,219 @@ var BlinkNotificationsImpl = class {
2878
2878
  }
2879
2879
  };
2880
2880
 
2881
+ // src/analytics.ts
2882
+ var SESSION_DURATION = 30 * 60 * 1e3;
2883
+ var MAX_BATCH_SIZE = 10;
2884
+ var BATCH_TIMEOUT = 3e3;
2885
+ var MAX_STRING_LENGTH = 256;
2886
+ var STORAGE_KEY_QUEUE = "blinkAnalyticsQueue";
2887
+ var STORAGE_KEY_SESSION = "blinkAnalyticsSession";
2888
+ var BlinkAnalyticsImpl = class {
2889
+ httpClient;
2890
+ projectId;
2891
+ queue = [];
2892
+ timer = null;
2893
+ enabled = true;
2894
+ userId = null;
2895
+ hasTrackedPageview = false;
2896
+ constructor(httpClient, projectId) {
2897
+ this.httpClient = httpClient;
2898
+ this.projectId = projectId;
2899
+ if (typeof window === "undefined") {
2900
+ return;
2901
+ }
2902
+ if (navigator.doNotTrack === "1") {
2903
+ this.enabled = false;
2904
+ return;
2905
+ }
2906
+ this.loadQueue();
2907
+ this.trackPageview();
2908
+ this.setupRouteChangeListener();
2909
+ this.setupUnloadListener();
2910
+ }
2911
+ /**
2912
+ * Log a custom analytics event
2913
+ */
2914
+ log(eventName, data = {}) {
2915
+ if (!this.enabled || typeof window === "undefined") {
2916
+ return;
2917
+ }
2918
+ const event = this.buildEvent(eventName, data);
2919
+ this.enqueue(event);
2920
+ }
2921
+ /**
2922
+ * Disable analytics tracking
2923
+ */
2924
+ disable() {
2925
+ this.enabled = false;
2926
+ this.clearTimer();
2927
+ }
2928
+ /**
2929
+ * Enable analytics tracking
2930
+ */
2931
+ enable() {
2932
+ this.enabled = true;
2933
+ }
2934
+ /**
2935
+ * Check if analytics is enabled
2936
+ */
2937
+ isEnabled() {
2938
+ return this.enabled;
2939
+ }
2940
+ /**
2941
+ * Set the user ID for analytics events
2942
+ */
2943
+ setUserId(userId) {
2944
+ this.userId = userId;
2945
+ }
2946
+ // Private methods
2947
+ buildEvent(type, data = {}) {
2948
+ const sessionId = this.getOrCreateSessionId();
2949
+ return {
2950
+ type,
2951
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2952
+ project_id: this.projectId,
2953
+ user_id: this.userId,
2954
+ session_id: sessionId,
2955
+ pathname: window.location.pathname,
2956
+ referrer: document.referrer || null,
2957
+ screen_width: window.innerWidth,
2958
+ ...this.sanitizeData(data)
2959
+ };
2960
+ }
2961
+ sanitizeData(data) {
2962
+ if (typeof data === "string") {
2963
+ return data.length > MAX_STRING_LENGTH ? data.slice(0, MAX_STRING_LENGTH - 3) + "..." : data;
2964
+ }
2965
+ if (typeof data === "object" && data !== null) {
2966
+ const result = {};
2967
+ for (const key in data) {
2968
+ result[key] = this.sanitizeData(data[key]);
2969
+ }
2970
+ return result;
2971
+ }
2972
+ return data;
2973
+ }
2974
+ enqueue(event) {
2975
+ this.queue.push(event);
2976
+ this.persistQueue();
2977
+ if (this.queue.length >= MAX_BATCH_SIZE) {
2978
+ this.flush();
2979
+ } else if (!this.timer) {
2980
+ this.timer = setTimeout(() => this.flush(), BATCH_TIMEOUT);
2981
+ }
2982
+ }
2983
+ async flush() {
2984
+ this.clearTimer();
2985
+ if (this.queue.length === 0) {
2986
+ return;
2987
+ }
2988
+ const events = this.queue.slice(0, MAX_BATCH_SIZE);
2989
+ this.queue = this.queue.slice(MAX_BATCH_SIZE);
2990
+ this.persistQueue();
2991
+ try {
2992
+ await this.httpClient.post(`/api/analytics/${this.projectId}/log`, { events });
2993
+ } catch (error) {
2994
+ this.queue = [...events, ...this.queue];
2995
+ this.persistQueue();
2996
+ console.error("Failed to send analytics events:", error);
2997
+ }
2998
+ if (this.queue.length > 0) {
2999
+ this.timer = setTimeout(() => this.flush(), BATCH_TIMEOUT);
3000
+ }
3001
+ }
3002
+ clearTimer() {
3003
+ if (this.timer) {
3004
+ clearTimeout(this.timer);
3005
+ this.timer = null;
3006
+ }
3007
+ }
3008
+ getOrCreateSessionId() {
3009
+ try {
3010
+ const stored = localStorage.getItem(STORAGE_KEY_SESSION);
3011
+ if (stored) {
3012
+ const session = JSON.parse(stored);
3013
+ const now = Date.now();
3014
+ if (now - session.lastActivityAt > SESSION_DURATION) {
3015
+ return this.createNewSession();
3016
+ }
3017
+ session.lastActivityAt = now;
3018
+ localStorage.setItem(STORAGE_KEY_SESSION, JSON.stringify(session));
3019
+ return session.id;
3020
+ }
3021
+ return this.createNewSession();
3022
+ } catch {
3023
+ return null;
3024
+ }
3025
+ }
3026
+ createNewSession() {
3027
+ const now = Date.now();
3028
+ const randomId = Math.random().toString(36).substring(2, 10);
3029
+ const session = {
3030
+ id: `sess_${now}_${randomId}`,
3031
+ startedAt: now,
3032
+ lastActivityAt: now
3033
+ };
3034
+ try {
3035
+ localStorage.setItem(STORAGE_KEY_SESSION, JSON.stringify(session));
3036
+ } catch {
3037
+ }
3038
+ return session.id;
3039
+ }
3040
+ loadQueue() {
3041
+ try {
3042
+ const stored = localStorage.getItem(STORAGE_KEY_QUEUE);
3043
+ if (stored) {
3044
+ this.queue = JSON.parse(stored);
3045
+ if (this.queue.length > 0) {
3046
+ this.timer = setTimeout(() => this.flush(), BATCH_TIMEOUT);
3047
+ }
3048
+ }
3049
+ } catch {
3050
+ this.queue = [];
3051
+ }
3052
+ }
3053
+ persistQueue() {
3054
+ try {
3055
+ if (this.queue.length === 0) {
3056
+ localStorage.removeItem(STORAGE_KEY_QUEUE);
3057
+ } else {
3058
+ localStorage.setItem(STORAGE_KEY_QUEUE, JSON.stringify(this.queue));
3059
+ }
3060
+ } catch {
3061
+ }
3062
+ }
3063
+ trackPageview() {
3064
+ if (!this.hasTrackedPageview) {
3065
+ this.log("pageview");
3066
+ this.hasTrackedPageview = true;
3067
+ }
3068
+ }
3069
+ setupRouteChangeListener() {
3070
+ const originalPushState = history.pushState;
3071
+ const originalReplaceState = history.replaceState;
3072
+ history.pushState = (...args) => {
3073
+ originalPushState.apply(history, args);
3074
+ this.log("pageview");
3075
+ };
3076
+ history.replaceState = (...args) => {
3077
+ originalReplaceState.apply(history, args);
3078
+ this.log("pageview");
3079
+ };
3080
+ window.addEventListener("popstate", () => {
3081
+ this.log("pageview");
3082
+ });
3083
+ }
3084
+ setupUnloadListener() {
3085
+ window.addEventListener("pagehide", () => {
3086
+ this.flush();
3087
+ });
3088
+ window.addEventListener("unload", () => {
3089
+ this.flush();
3090
+ });
3091
+ }
3092
+ };
3093
+
2881
3094
  // src/client.ts
2882
3095
  var BlinkClientImpl = class {
2883
3096
  auth;
@@ -2887,6 +3100,7 @@ var BlinkClientImpl = class {
2887
3100
  data;
2888
3101
  realtime;
2889
3102
  notifications;
3103
+ analytics;
2890
3104
  httpClient;
2891
3105
  constructor(config) {
2892
3106
  this.auth = new BlinkAuth(config);
@@ -2901,6 +3115,14 @@ var BlinkClientImpl = class {
2901
3115
  this.data = new BlinkDataImpl(this.httpClient, config.projectId);
2902
3116
  this.realtime = new BlinkRealtimeImpl(this.httpClient, config.projectId);
2903
3117
  this.notifications = new BlinkNotificationsImpl(this.httpClient);
3118
+ this.analytics = new BlinkAnalyticsImpl(this.httpClient, config.projectId);
3119
+ this.auth.onAuthStateChanged((state) => {
3120
+ if (state.isAuthenticated && state.user) {
3121
+ this.analytics.setUserId(state.user.id);
3122
+ } else {
3123
+ this.analytics.setUserId(null);
3124
+ }
3125
+ });
2904
3126
  }
2905
3127
  };
2906
3128
  function createClient(config) {
@@ -2914,6 +3136,6 @@ function createClient(config) {
2914
3136
  return new BlinkClientImpl(clientConfig);
2915
3137
  }
2916
3138
 
2917
- export { BlinkAIImpl, BlinkDataImpl, BlinkDatabase, BlinkRealtimeChannel, BlinkRealtimeImpl, BlinkStorageImpl, BlinkTable, createClient };
3139
+ export { BlinkAIImpl, BlinkAnalyticsImpl, BlinkDataImpl, BlinkDatabase, BlinkRealtimeChannel, BlinkRealtimeImpl, BlinkStorageImpl, BlinkTable, createClient };
2918
3140
  //# sourceMappingURL=index.mjs.map
2919
3141
  //# sourceMappingURL=index.mjs.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blinkdotnew/sdk",
3
- "version": "0.7.1",
4
- "description": "Blink TypeScript SDK for client-side applications - Zero-boilerplate CRUD + auth + AI + notifications for modern SaaS/AI apps",
3
+ "version": "0.8.0",
4
+ "description": "Blink TypeScript SDK for client-side applications - Zero-boilerplate CRUD + auth + AI + analytics + notifications for modern SaaS/AI apps",
5
5
  "keywords": [
6
6
  "blink",
7
7
  "sdk",