@blinkdotnew/sdk 0.7.0 → 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 +94 -33
- package/dist/index.d.mts +67 -1
- package/dist/index.d.ts +67 -1
- package/dist/index.js +228 -1
- package/dist/index.mjs +228 -2
- package/package.json +4 -2
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,
|
|
@@ -107,7 +123,9 @@ This SDK powers every Blink-generated app with:
|
|
|
107
123
|
- **🤖 AI**: Text generation with web search, object generation, image creation, speech synthesis, and transcription
|
|
108
124
|
- **📄 Data**: Extract text content from documents, secure API proxy with secret substitution, web scraping, screenshots, and web search
|
|
109
125
|
- **📁 Storage**: File upload, download, and management
|
|
126
|
+
- **📧 Notifications**: Email sending with attachments, custom branding, and delivery tracking
|
|
110
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
|
|
111
129
|
- **🌐 Universal**: Works on client-side and server-side
|
|
112
130
|
- **📱 Framework Agnostic**: React, Vue, Svelte, vanilla JS, Node.js, Deno
|
|
113
131
|
- **🔄 Real-time**: Built-in auth state management and token refresh
|
|
@@ -499,6 +517,81 @@ const { publicUrl } = await blink.storage.upload(
|
|
|
499
517
|
await blink.storage.remove('file1.jpg', 'file2.jpg')
|
|
500
518
|
```
|
|
501
519
|
|
|
520
|
+
### Notifications Operations
|
|
521
|
+
|
|
522
|
+
```typescript
|
|
523
|
+
// Send a simple email
|
|
524
|
+
const { success, messageId } = await blink.notifications.email({
|
|
525
|
+
to: 'customer@example.com',
|
|
526
|
+
subject: 'Your order has shipped!',
|
|
527
|
+
html: '<h1>Order Confirmation</h1><p>Your order #12345 is on its way.</p>'
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
// Send an email with attachments and custom sender
|
|
531
|
+
const { success } = await blink.notifications.email({
|
|
532
|
+
to: ['team@example.com', 'manager@example.com'],
|
|
533
|
+
from: 'Blink Invoicing', // Custom sender name
|
|
534
|
+
replyTo: 'support@example.com',
|
|
535
|
+
subject: 'New Invoice',
|
|
536
|
+
html: '<p>Please find the invoice attached.</p>',
|
|
537
|
+
text: 'Please find the invoice attached.', // Plain text fallback
|
|
538
|
+
cc: 'manager@example.com',
|
|
539
|
+
bcc: 'archive@example.com',
|
|
540
|
+
attachments: [
|
|
541
|
+
{
|
|
542
|
+
url: 'https://example.com/invoice.pdf',
|
|
543
|
+
filename: 'invoice.pdf',
|
|
544
|
+
type: 'application/pdf'
|
|
545
|
+
}
|
|
546
|
+
]
|
|
547
|
+
})
|
|
548
|
+
|
|
549
|
+
// Send to multiple recipients
|
|
550
|
+
const { success } = await blink.notifications.email({
|
|
551
|
+
to: ['user1@example.com', 'user2@example.com'],
|
|
552
|
+
subject: 'Team Update',
|
|
553
|
+
html: '<h2>Weekly Update</h2><p>Here are this week\'s highlights...</p>'
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
// Error handling
|
|
557
|
+
try {
|
|
558
|
+
await blink.notifications.email({
|
|
559
|
+
to: 'customer@example.com',
|
|
560
|
+
subject: 'Welcome!',
|
|
561
|
+
html: '<p>Welcome to our service!</p>'
|
|
562
|
+
})
|
|
563
|
+
} catch (error) {
|
|
564
|
+
if (error instanceof BlinkNotificationsError) {
|
|
565
|
+
console.error('Failed to send email:', error.message)
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
```
|
|
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
|
+
|
|
502
595
|
### Realtime Operations
|
|
503
596
|
|
|
504
597
|
```typescript
|
|
@@ -1045,36 +1138,4 @@ MIT © Blink Team
|
|
|
1045
1138
|
|
|
1046
1139
|
**Made with ❤️ by the Blink team**
|
|
1047
1140
|
|
|
1048
|
-
🤖 **Ready to build your next app?** Visit [blink.new](https://blink.new) and let our AI create it for you in seconds!
|
|
1049
|
-
|
|
1050
|
-
### Notifications (NEW!)
|
|
1051
|
-
|
|
1052
|
-
```typescript
|
|
1053
|
-
// Send a simple email
|
|
1054
|
-
const { success, messageId } = await blink.notifications.email({
|
|
1055
|
-
to: 'customer@example.com',
|
|
1056
|
-
subject: 'Your order has shipped!',
|
|
1057
|
-
html: '<h1>Order Confirmation</h1><p>Your order #12345 is on its way.</p>'
|
|
1058
|
-
});
|
|
1059
|
-
|
|
1060
|
-
// Send an email with attachments, a custom 'from' name, and a 'replyTo' address
|
|
1061
|
-
const { success } = await blink.notifications.email({
|
|
1062
|
-
to: ['team@example.com', 'manager@example.com'],
|
|
1063
|
-
from: 'Blink Invoicing', // Displays as: "Blink Invoicing" <noreply@project.blink-email.com>
|
|
1064
|
-
replyTo: 'support@example.com',
|
|
1065
|
-
subject: 'New Invoice',
|
|
1066
|
-
html: '<p>Please find the invoice attached.</p>',
|
|
1067
|
-
attachments: [
|
|
1068
|
-
{ url: 'https://example.com/invoice.pdf', filename: 'invoice.pdf' }
|
|
1069
|
-
]
|
|
1070
|
-
});
|
|
1071
|
-
|
|
1072
|
-
// Handle errors
|
|
1073
|
-
try {
|
|
1074
|
-
await blink.notifications.email({ to: 'invalid-email' });
|
|
1075
|
-
} catch (error) {
|
|
1076
|
-
if (error instanceof BlinkNotificationsError) {
|
|
1077
|
-
console.error('Failed to send email:', error.message);
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
```
|
|
1141
|
+
🤖 **Ready to build your next app?** Visit [blink.new](https://blink.new) and let our AI create it for you in seconds!
|
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
|
@@ -60,6 +60,10 @@ var BlinkRealtimeError = class extends BlinkError {
|
|
|
60
60
|
}
|
|
61
61
|
};
|
|
62
62
|
var BlinkNotificationsError = class extends BlinkError {
|
|
63
|
+
constructor(message, status, details) {
|
|
64
|
+
super(message, "NOTIFICATIONS_ERROR", status, details);
|
|
65
|
+
this.name = "BlinkNotificationsError";
|
|
66
|
+
}
|
|
63
67
|
};
|
|
64
68
|
|
|
65
69
|
// ../core/src/query-builder.ts
|
|
@@ -2871,11 +2875,224 @@ var BlinkNotificationsImpl = class {
|
|
|
2871
2875
|
throw error;
|
|
2872
2876
|
}
|
|
2873
2877
|
const errorMessage = error.response?.data?.error?.message || error.message || "An unknown error occurred";
|
|
2874
|
-
throw new BlinkNotificationsError(`Failed to send email: ${errorMessage}`, error.response?.data?.error);
|
|
2878
|
+
throw new BlinkNotificationsError(`Failed to send email: ${errorMessage}`, error.response?.status, error.response?.data?.error);
|
|
2875
2879
|
}
|
|
2876
2880
|
}
|
|
2877
2881
|
};
|
|
2878
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
|
+
|
|
2879
3096
|
// src/client.ts
|
|
2880
3097
|
var BlinkClientImpl = class {
|
|
2881
3098
|
auth;
|
|
@@ -2885,6 +3102,7 @@ var BlinkClientImpl = class {
|
|
|
2885
3102
|
data;
|
|
2886
3103
|
realtime;
|
|
2887
3104
|
notifications;
|
|
3105
|
+
analytics;
|
|
2888
3106
|
httpClient;
|
|
2889
3107
|
constructor(config) {
|
|
2890
3108
|
this.auth = new BlinkAuth(config);
|
|
@@ -2899,6 +3117,14 @@ var BlinkClientImpl = class {
|
|
|
2899
3117
|
this.data = new BlinkDataImpl(this.httpClient, config.projectId);
|
|
2900
3118
|
this.realtime = new BlinkRealtimeImpl(this.httpClient, config.projectId);
|
|
2901
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
|
+
});
|
|
2902
3128
|
}
|
|
2903
3129
|
};
|
|
2904
3130
|
function createClient(config) {
|
|
@@ -2913,6 +3139,7 @@ function createClient(config) {
|
|
|
2913
3139
|
}
|
|
2914
3140
|
|
|
2915
3141
|
exports.BlinkAIImpl = BlinkAIImpl;
|
|
3142
|
+
exports.BlinkAnalyticsImpl = BlinkAnalyticsImpl;
|
|
2916
3143
|
exports.BlinkDataImpl = BlinkDataImpl;
|
|
2917
3144
|
exports.BlinkDatabase = BlinkDatabase;
|
|
2918
3145
|
exports.BlinkRealtimeChannel = BlinkRealtimeChannel;
|
package/dist/index.mjs
CHANGED
|
@@ -58,6 +58,10 @@ var BlinkRealtimeError = class extends BlinkError {
|
|
|
58
58
|
}
|
|
59
59
|
};
|
|
60
60
|
var BlinkNotificationsError = class extends BlinkError {
|
|
61
|
+
constructor(message, status, details) {
|
|
62
|
+
super(message, "NOTIFICATIONS_ERROR", status, details);
|
|
63
|
+
this.name = "BlinkNotificationsError";
|
|
64
|
+
}
|
|
61
65
|
};
|
|
62
66
|
|
|
63
67
|
// ../core/src/query-builder.ts
|
|
@@ -2869,11 +2873,224 @@ var BlinkNotificationsImpl = class {
|
|
|
2869
2873
|
throw error;
|
|
2870
2874
|
}
|
|
2871
2875
|
const errorMessage = error.response?.data?.error?.message || error.message || "An unknown error occurred";
|
|
2872
|
-
throw new BlinkNotificationsError(`Failed to send email: ${errorMessage}`, error.response?.data?.error);
|
|
2876
|
+
throw new BlinkNotificationsError(`Failed to send email: ${errorMessage}`, error.response?.status, error.response?.data?.error);
|
|
2873
2877
|
}
|
|
2874
2878
|
}
|
|
2875
2879
|
};
|
|
2876
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
|
+
|
|
2877
3094
|
// src/client.ts
|
|
2878
3095
|
var BlinkClientImpl = class {
|
|
2879
3096
|
auth;
|
|
@@ -2883,6 +3100,7 @@ var BlinkClientImpl = class {
|
|
|
2883
3100
|
data;
|
|
2884
3101
|
realtime;
|
|
2885
3102
|
notifications;
|
|
3103
|
+
analytics;
|
|
2886
3104
|
httpClient;
|
|
2887
3105
|
constructor(config) {
|
|
2888
3106
|
this.auth = new BlinkAuth(config);
|
|
@@ -2897,6 +3115,14 @@ var BlinkClientImpl = class {
|
|
|
2897
3115
|
this.data = new BlinkDataImpl(this.httpClient, config.projectId);
|
|
2898
3116
|
this.realtime = new BlinkRealtimeImpl(this.httpClient, config.projectId);
|
|
2899
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
|
+
});
|
|
2900
3126
|
}
|
|
2901
3127
|
};
|
|
2902
3128
|
function createClient(config) {
|
|
@@ -2910,6 +3136,6 @@ function createClient(config) {
|
|
|
2910
3136
|
return new BlinkClientImpl(clientConfig);
|
|
2911
3137
|
}
|
|
2912
3138
|
|
|
2913
|
-
export { BlinkAIImpl, BlinkDataImpl, BlinkDatabase, BlinkRealtimeChannel, BlinkRealtimeImpl, BlinkStorageImpl, BlinkTable, createClient };
|
|
3139
|
+
export { BlinkAIImpl, BlinkAnalyticsImpl, BlinkDataImpl, BlinkDatabase, BlinkRealtimeChannel, BlinkRealtimeImpl, BlinkStorageImpl, BlinkTable, createClient };
|
|
2914
3140
|
//# sourceMappingURL=index.mjs.map
|
|
2915
3141
|
//# sourceMappingURL=index.mjs.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blinkdotnew/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Blink TypeScript SDK for client-side applications - Zero-boilerplate CRUD + auth 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",
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
"database",
|
|
11
11
|
"ai",
|
|
12
12
|
"storage",
|
|
13
|
+
"notifications",
|
|
14
|
+
"email",
|
|
13
15
|
"crud",
|
|
14
16
|
"client"
|
|
15
17
|
],
|