@grainql/analytics-web 1.7.2 → 2.0.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.
Files changed (116) hide show
  1. package/README.md +71 -777
  2. package/dist/cjs/index.d.ts +35 -2
  3. package/dist/cjs/index.d.ts.map +1 -1
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/react/GrainProvider.d.ts +11 -0
  6. package/dist/cjs/react/GrainProvider.d.ts.map +1 -0
  7. package/dist/cjs/react/GrainProvider.js +79 -0
  8. package/dist/cjs/react/GrainProvider.js.map +1 -0
  9. package/dist/cjs/react/context.d.ts +11 -0
  10. package/dist/cjs/react/context.d.ts.map +1 -0
  11. package/dist/cjs/react/context.js +43 -0
  12. package/dist/cjs/react/context.js.map +1 -0
  13. package/dist/cjs/react/hooks/useAllConfigs.d.ts +8 -0
  14. package/dist/cjs/react/hooks/useAllConfigs.d.ts.map +1 -0
  15. package/dist/cjs/react/hooks/useAllConfigs.js +112 -0
  16. package/dist/cjs/react/hooks/useAllConfigs.js.map +1 -0
  17. package/dist/cjs/react/hooks/useConfig.d.ts +9 -0
  18. package/dist/cjs/react/hooks/useConfig.d.ts.map +1 -0
  19. package/dist/cjs/react/hooks/useConfig.js +116 -0
  20. package/dist/cjs/react/hooks/useConfig.js.map +1 -0
  21. package/dist/cjs/react/hooks/useGrainAnalytics.d.ts +6 -0
  22. package/dist/cjs/react/hooks/useGrainAnalytics.d.ts.map +1 -0
  23. package/dist/cjs/react/hooks/useGrainAnalytics.js +50 -0
  24. package/dist/cjs/react/hooks/useGrainAnalytics.js.map +1 -0
  25. package/dist/cjs/react/hooks/useTrack.d.ts +9 -0
  26. package/dist/cjs/react/hooks/useTrack.d.ts.map +1 -0
  27. package/dist/cjs/react/hooks/useTrack.js +53 -0
  28. package/dist/cjs/react/hooks/useTrack.js.map +1 -0
  29. package/dist/cjs/react/index.d.ts +36 -0
  30. package/dist/cjs/react/index.d.ts.map +1 -0
  31. package/dist/cjs/react/index.js +45 -0
  32. package/dist/cjs/react/index.js.map +1 -0
  33. package/dist/cjs/react/types.d.ts +33 -0
  34. package/dist/cjs/react/types.d.ts.map +1 -0
  35. package/dist/cjs/react/types.js +6 -0
  36. package/dist/cjs/react/types.js.map +1 -0
  37. package/dist/esm/index.d.ts +35 -2
  38. package/dist/esm/index.d.ts.map +1 -1
  39. package/dist/esm/index.js.map +1 -1
  40. package/dist/esm/react/GrainProvider.d.ts +11 -0
  41. package/dist/esm/react/GrainProvider.d.ts.map +1 -0
  42. package/dist/esm/react/GrainProvider.js +43 -0
  43. package/dist/esm/react/GrainProvider.js.map +1 -0
  44. package/dist/esm/react/context.d.ts +11 -0
  45. package/dist/esm/react/context.d.ts.map +1 -0
  46. package/dist/esm/react/context.js +7 -0
  47. package/dist/esm/react/context.js.map +1 -0
  48. package/dist/esm/react/hooks/useAllConfigs.d.ts +8 -0
  49. package/dist/esm/react/hooks/useAllConfigs.d.ts.map +1 -0
  50. package/dist/esm/react/hooks/useAllConfigs.js +76 -0
  51. package/dist/esm/react/hooks/useAllConfigs.js.map +1 -0
  52. package/dist/esm/react/hooks/useConfig.d.ts +9 -0
  53. package/dist/esm/react/hooks/useConfig.d.ts.map +1 -0
  54. package/dist/esm/react/hooks/useConfig.js +80 -0
  55. package/dist/esm/react/hooks/useConfig.js.map +1 -0
  56. package/dist/esm/react/hooks/useGrainAnalytics.d.ts +6 -0
  57. package/dist/esm/react/hooks/useGrainAnalytics.d.ts.map +1 -0
  58. package/dist/esm/react/hooks/useGrainAnalytics.js +14 -0
  59. package/dist/esm/react/hooks/useGrainAnalytics.js.map +1 -0
  60. package/dist/esm/react/hooks/useTrack.d.ts +9 -0
  61. package/dist/esm/react/hooks/useTrack.d.ts.map +1 -0
  62. package/dist/esm/react/hooks/useTrack.js +17 -0
  63. package/dist/esm/react/hooks/useTrack.js.map +1 -0
  64. package/dist/esm/react/index.d.ts +36 -0
  65. package/dist/esm/react/index.d.ts.map +1 -0
  66. package/dist/esm/react/index.js +37 -0
  67. package/dist/esm/react/index.js.map +1 -0
  68. package/dist/esm/react/types.d.ts +33 -0
  69. package/dist/esm/react/types.d.ts.map +1 -0
  70. package/dist/esm/react/types.js +5 -0
  71. package/dist/esm/react/types.js.map +1 -0
  72. package/dist/index.d.ts +35 -2
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.global.dev.js +124 -14
  75. package/dist/index.global.dev.js.map +2 -2
  76. package/dist/index.global.js +2 -2
  77. package/dist/index.global.js.map +3 -3
  78. package/dist/index.js +147 -15
  79. package/dist/index.mjs +147 -15
  80. package/dist/react/index.d.ts +405 -0
  81. package/dist/react/index.d.ts.map +1 -0
  82. package/dist/react/index.js +1181 -0
  83. package/dist/react/index.mjs +1176 -0
  84. package/dist/react/react/GrainProvider.d.ts +11 -0
  85. package/dist/react/react/GrainProvider.d.ts.map +1 -0
  86. package/dist/react/react/GrainProvider.js +45 -0
  87. package/dist/react/react/GrainProvider.mjs +42 -0
  88. package/dist/react/react/context.d.ts +11 -0
  89. package/dist/react/react/context.d.ts.map +1 -0
  90. package/dist/react/react/context.js +9 -0
  91. package/dist/react/react/context.mjs +6 -0
  92. package/dist/react/react/hooks/useAllConfigs.d.ts +8 -0
  93. package/dist/react/react/hooks/useAllConfigs.d.ts.map +1 -0
  94. package/dist/react/react/hooks/useAllConfigs.js +78 -0
  95. package/dist/react/react/hooks/useAllConfigs.mjs +75 -0
  96. package/dist/react/react/hooks/useConfig.d.ts +9 -0
  97. package/dist/react/react/hooks/useConfig.d.ts.map +1 -0
  98. package/dist/react/react/hooks/useConfig.js +82 -0
  99. package/dist/react/react/hooks/useConfig.mjs +79 -0
  100. package/dist/react/react/hooks/useGrainAnalytics.d.ts +6 -0
  101. package/dist/react/react/hooks/useGrainAnalytics.d.ts.map +1 -0
  102. package/dist/react/react/hooks/useGrainAnalytics.js +16 -0
  103. package/dist/react/react/hooks/useGrainAnalytics.mjs +13 -0
  104. package/dist/react/react/hooks/useTrack.d.ts +9 -0
  105. package/dist/react/react/hooks/useTrack.d.ts.map +1 -0
  106. package/dist/react/react/hooks/useTrack.js +19 -0
  107. package/dist/react/react/hooks/useTrack.mjs +16 -0
  108. package/dist/react/react/index.d.ts +36 -0
  109. package/dist/react/react/index.d.ts.map +1 -0
  110. package/dist/react/react/index.js +44 -0
  111. package/dist/react/react/index.mjs +36 -0
  112. package/dist/react/react/types.d.ts +33 -0
  113. package/dist/react/react/types.d.ts.map +1 -0
  114. package/dist/react/react/types.js +5 -0
  115. package/dist/react/react/types.mjs +4 -0
  116. package/package.json +20 -2
package/README.md CHANGED
@@ -1,19 +1,19 @@
1
- # @grainql/analytics-web
1
+ # Grain Analytics Web SDK
2
2
 
3
- A lightweight, dependency-free TypeScript SDK for sending analytics events and managing remote configurations via Grain's REST API with automatic batching, retry logic, and reliable event delivery.
3
+ A lightweight, dependency-free TypeScript SDK for analytics and remote configuration management.
4
+
5
+ [![npm version](https://badge.fury.io/js/@grainql%2Fanalytics-web.svg)](https://www.npmjs.com/package/@grainql/analytics-web)
6
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/@grainql/analytics-web)](https://bundlephobia.com/package/@grainql/analytics-web)
4
7
 
5
8
  ## Features
6
9
 
7
- - 🚀 **Zero dependencies** - Tiny bundle size
8
- - 📦 **Automatic batching** - Efficient event queueing and bulk sending
9
- - 🔄 **Retry logic** - Exponential backoff for failed requests
10
- - 📡 **Beacon API** - Reliable event delivery on page exit
11
- - 🔐 **Multiple auth strategies** - NONE, server-side, and JWT support
12
- - 📱 **Cross-platform** - Works in browsers, Node.js, and React Native
13
- - 🎯 **TypeScript first** - Full type safety out of the box
14
- - 👤 **Global user tracking** - Set user ID once, used for all events
15
- - 📊 **Template events** - Pre-built event tracking for common scenarios
16
- - ⚙️ **Remote Config** - Dynamic configuration management with caching and real-time updates
10
+ - 🚀 **Zero dependencies** - ~6KB gzipped
11
+ - 📦 **Automatic batching** - Efficient event delivery
12
+ - 🔄 **Retry logic** - Reliable with exponential backoff
13
+ - 🎯 **TypeScript first** - Full type safety
14
+ - ⚙️ **Remote Config** - Dynamic app control without deployments
15
+ - ⚛️ **React Hooks** - Seamless React integration
16
+ - 📱 **Cross-platform** - Browser, Node.js, React Native
17
17
 
18
18
  ## Installation
19
19
 
@@ -21,819 +21,113 @@ A lightweight, dependency-free TypeScript SDK for sending analytics events and m
21
21
  npm install @grainql/analytics-web
22
22
  ```
23
23
 
24
- > **Latest Version**: v1.6.0 includes comprehensive remote configuration management capabilities alongside the existing analytics features.
25
-
26
24
  ## Quick Start
27
25
 
28
- ### Basic Usage (No Authentication)
26
+ ### Vanilla JavaScript/TypeScript
29
27
 
30
28
  ```typescript
31
29
  import { createGrainAnalytics } from '@grainql/analytics-web';
32
30
 
33
31
  const grain = createGrainAnalytics({
34
- tenantId: 'your-tenant-id',
35
- authStrategy: 'NONE'
36
- });
37
-
38
- // Track an event
39
- grain.track('page_view', {
40
- page: '/home',
41
- referrer: document.referrer
42
- });
43
-
44
- // Track with user ID
45
- grain.track('button_click', {
46
- button: 'signup',
47
- userId: 'user123' // Note: Subject to same security restrictions as setProperty
48
- });
49
- ```
50
-
51
- ### Global User ID
52
-
53
- Set a user ID once and it will be used for all subsequent events:
54
-
55
- ```typescript
56
- // Set global user ID in config
57
- const grain = createGrainAnalytics({
58
- tenantId: 'your-tenant-id',
59
- userId: 'user123' // Global user ID for all events
60
- });
61
-
62
- // Or set it after initialization
63
- grain.setUserId('user123');
64
-
65
- // All events will now use this user ID
66
- grain.track('page_view', { page: '/dashboard' });
67
- ```
68
-
69
- ### User Properties
70
-
71
- Set user properties that can be used for analytics and segmentation:
72
-
73
- ```typescript
74
- // Set properties for the current user
75
- await grain.setProperty({
76
- plan: 'premium',
77
- status: 'active',
78
- signupDate: '2024-01-15',
79
- source: 'web'
80
- });
81
-
82
- // Set properties for a specific user
83
- await grain.setProperty({
84
- plan: 'free',
85
- lastLogin: new Date().toISOString()
86
- }, { userId: 'user123' });
87
-
88
- // Properties are automatically serialized to strings
89
- await grain.setProperty({
90
- isActive: true, // Becomes "true"
91
- count: 42, // Becomes "42"
92
- metadata: { // Becomes JSON string
93
- source: 'api',
94
- version: '2.0'
95
- }
96
- });
97
- ```
98
-
99
- **Important Security Notes:**
100
- - You can set up to 4 properties per request, and all values are automatically converted to strings
101
- - **UserId Override Restrictions**: When using `{ userId: 'specific-user' }` in options, ensure you have proper permissions:
102
- - If your tenant requires JWT authentication, the userId must match the JWT subject
103
- - Grain may block requests if too many distinct userIds are used from the same instance, browser, device, or IP
104
- - Use userId overrides only when you have explicit permission to set properties for other users
105
-
106
- ### Server-Side Authentication
107
-
108
- ```typescript
109
- const grain = createGrainAnalytics({
110
- tenantId: 'your-tenant-id',
111
- authStrategy: 'SERVER_SIDE',
112
- secretKey: 'your-secret-key'
113
- });
114
- ```
115
-
116
- ### JWT Authentication
117
-
118
- ```typescript
119
- const grain = createGrainAnalytics({
120
- tenantId: 'your-tenant-id',
121
- authStrategy: 'JWT',
122
- authProvider: {
123
- async getToken() {
124
- // Return your JWT token from Auth0, next-auth, etc.
125
- return await getAccessToken();
126
- }
127
- }
32
+ tenantId: 'your-tenant-id'
128
33
  });
129
- ```
130
-
131
- ## Remote Config
132
34
 
133
- The SDK includes powerful remote configuration capabilities that allow you to dynamically control your application's behavior without code deployments.
35
+ // Track events
36
+ grain.track('page_viewed', { page: '/home' });
134
37
 
135
- ### Basic Usage
136
-
137
- ```typescript
138
- const grain = createGrainAnalytics({
139
- tenantId: 'your-tenant-id',
140
- userId: 'user123',
141
- // Set default values for immediate access
142
- defaultConfigurations: {
143
- hero_text: 'Welcome to our app!',
144
- button_color: 'blue',
145
- feature_enabled: 'false'
146
- }
147
- });
148
-
149
- // Get configuration value (synchronous, from cache or defaults)
150
- const heroText = grain.getConfig('hero_text'); // Returns cached value or default
151
-
152
- // Get configuration asynchronously (cache-first, with API fallback)
153
- const buttonColor = await grain.getConfigAsync('button_color');
154
-
155
- // Get all configurations
156
- const allConfigs = grain.getAllConfigs();
157
- ```
158
-
159
- ### Preloading Configurations
160
-
161
- Preload configurations at page load for immediate access:
162
-
163
- ```typescript
164
- // Set user ID first
165
- grain.setUserId('user123');
166
-
167
- // Preload specific keys for immediate access
168
- await grain.preloadConfig(['hero_text', 'button_color', 'feature_enabled']);
169
-
170
- // Now these values are available synchronously
38
+ // Get remote config
171
39
  const heroText = grain.getConfig('hero_text');
172
- const buttonColor = grain.getConfig('button_color');
173
- ```
174
-
175
- ### Advanced Configuration Options
176
-
177
- ```typescript
178
- const grain = createGrainAnalytics({
179
- tenantId: 'your-tenant-id',
180
- userId: 'user123',
181
- // Default values for immediate access
182
- defaultConfigurations: {
183
- hero_text: 'Default Hero Text',
184
- button_color: 'blue',
185
- feature_enabled: 'false'
186
- },
187
- // Custom cache key
188
- configCacheKey: 'my_app_config',
189
- // Auto-refresh every 2 minutes
190
- configRefreshInterval: 120000,
191
- // Enable/disable caching
192
- enableConfigCache: true
193
- });
194
- ```
195
-
196
- ### Fetching Configurations with Properties
197
-
198
- Send user properties to get personalized configurations:
199
-
200
- ```typescript
201
- // Fetch with user properties for personalization
202
- const configs = await grain.getAllConfigsAsync({
203
- properties: {
204
- plan: 'premium',
205
- location: 'US',
206
- signup_date: '2024-01-15'
207
- }
208
- });
209
-
210
- // Or fetch specific keys with properties
211
- const heroText = await grain.getConfigAsync('hero_text', {
212
- properties: {
213
- plan: 'premium',
214
- location: 'US'
215
- }
216
- });
217
40
  ```
218
41
 
219
- ### Configuration Change Listeners
220
-
221
- Listen for configuration changes in real-time:
42
+ ### React
222
43
 
223
44
  ```typescript
224
- // Add a listener for configuration changes
225
- grain.addConfigChangeListener((configurations) => {
226
- console.log('Configurations updated:', configurations);
227
-
228
- // Update UI based on new configurations
229
- if (configurations.hero_text) {
230
- document.getElementById('hero').textContent = configurations.hero_text;
231
- }
232
-
233
- if (configurations.button_color) {
234
- document.getElementById('cta-button').style.backgroundColor = configurations.button_color;
235
- }
236
- });
237
-
238
- // Remove listener when no longer needed
239
- const listener = (configs) => { /* ... */ };
240
- grain.addConfigChangeListener(listener);
241
- grain.removeConfigChangeListener(listener);
242
- ```
243
-
244
- ### Force Refresh
245
-
246
- Force refresh configurations from the API:
247
-
248
- ```typescript
249
- // Force refresh a specific configuration
250
- const latestHeroText = await grain.getConfigAsync('hero_text', {
251
- forceRefresh: true
252
- });
253
-
254
- // Force refresh all configurations
255
- const allLatestConfigs = await grain.getAllConfigsAsync({
256
- forceRefresh: true
257
- });
258
- ```
259
-
260
- ### Error Handling
261
-
262
- The SDK gracefully handles configuration fetch failures:
263
-
264
- ```typescript
265
- try {
266
- const configs = await grain.getAllConfigsAsync();
267
- // Use configurations
268
- } catch (error) {
269
- console.error('Failed to fetch configurations:', error);
270
- // Fall back to default values
271
- const heroText = grain.getConfig('hero_text'); // Returns default if available
272
- }
273
- ```
274
-
275
- ### React Integration Example
276
-
277
- ```typescript
278
- import { useEffect, useState } from 'react';
279
- import { createGrainAnalytics } from '@grainql/analytics-web';
280
-
281
- const grain = createGrainAnalytics({
282
- tenantId: 'your-tenant-id',
283
- userId: 'user123',
284
- defaultConfigurations: {
285
- hero_text: 'Loading...',
286
- button_color: 'blue'
287
- }
288
- });
45
+ import { GrainProvider, useConfig, useTrack } from '@grainql/analytics-web/react';
289
46
 
290
47
  function App() {
291
- const [configs, setConfigs] = useState(grain.getAllConfigs());
292
-
293
- useEffect(() => {
294
- // Preload configurations
295
- grain.preloadConfig(['hero_text', 'button_color']).then(() => {
296
- setConfigs(grain.getAllConfigs());
297
- });
298
-
299
- // Listen for configuration changes
300
- const handleConfigChange = (newConfigs) => {
301
- setConfigs(newConfigs);
302
- };
303
-
304
- grain.addConfigChangeListener(handleConfigChange);
305
-
306
- return () => {
307
- grain.removeConfigChangeListener(handleConfigChange);
308
- };
309
- }, []);
48
+ return (
49
+ <GrainProvider config={{ tenantId: 'your-tenant-id' }}>
50
+ <HomePage />
51
+ </GrainProvider>
52
+ );
53
+ }
310
54
 
55
+ function HomePage() {
56
+ const { value: heroText } = useConfig('hero_text');
57
+ const track = useTrack();
58
+
311
59
  return (
312
60
  <div>
313
- <h1 style={{ color: configs.button_color }}>
314
- {configs.hero_text}
315
- </h1>
61
+ <h1>{heroText || 'Welcome!'}</h1>
62
+ <button onClick={() => track('cta_clicked')}>
63
+ Get Started
64
+ </button>
316
65
  </div>
317
66
  );
318
67
  }
319
68
  ```
320
69
 
321
- ## Template Events
70
+ ## Documentation
322
71
 
323
- The SDK provides pre-built event tracking methods for common scenarios:
72
+ For comprehensive guides, API reference, and examples, visit our documentation:
324
73
 
325
- ### User Authentication
74
+ **📚 [Full Documentation](https://docs.grainql.com)** <!-- Update with actual docs URL -->
326
75
 
327
- ```typescript
328
- // Track login
329
- await grain.trackLogin({
330
- method: 'email',
331
- success: true,
332
- rememberMe: true
333
- });
76
+ ### Key Topics
334
77
 
335
- // Track signup
336
- await grain.trackSignup({
337
- method: 'google',
338
- source: 'landing_page',
339
- plan: 'pro',
340
- success: true
341
- });
342
- ```
78
+ - **[Quick Start Guide](https://docs.grainql.com/quickstart)** - Get started in 5 minutes
79
+ - **[Event Tracking](https://docs.grainql.com/core/event-tracking)** - Track user actions
80
+ - **[Remote Configuration](https://docs.grainql.com/core/remote-config)** - Dynamic app control
81
+ - **[React Hooks](https://docs.grainql.com/react/overview)** - React integration
82
+ - **[API Reference](https://docs.grainql.com/api-reference/overview)** - Complete API docs
83
+ - **[Examples](https://docs.grainql.com/examples/react)** - Real-world examples
343
84
 
344
- ### E-commerce
345
85
 
346
- ```typescript
347
- // Track checkout
348
- await grain.trackCheckout({
349
- orderId: 'order_123',
350
- total: 99.99,
351
- currency: 'USD',
352
- items: [
353
- { id: 'prod_1', name: 'Product 1', price: 49.99, quantity: 1 },
354
- { id: 'prod_2', name: 'Product 2', price: 50.00, quantity: 1 }
355
- ],
356
- paymentMethod: 'credit_card',
357
- success: true
358
- });
359
-
360
- // Track purchase
361
- await grain.trackPurchase({
362
- orderId: 'order_123',
363
- total: 99.99,
364
- currency: 'USD',
365
- paymentMethod: 'credit_card',
366
- shipping: 5.99,
367
- tax: 8.50
368
- });
369
-
370
- // Track cart interactions
371
- await grain.trackAddToCart({
372
- itemId: 'prod_1',
373
- itemName: 'Product 1',
374
- price: 49.99,
375
- quantity: 1,
376
- currency: 'USD'
377
- });
378
-
379
- await grain.trackRemoveFromCart({
380
- itemId: 'prod_2',
381
- itemName: 'Product 2',
382
- price: 50.00,
383
- quantity: 1
384
- });
385
- ```
386
-
387
- ### Page Views and Search
86
+ ## Key Concepts
388
87
 
88
+ ### Event Tracking
89
+ Track user actions with automatic batching and retry logic:
389
90
  ```typescript
390
- // Track page view
391
- await grain.trackPageView({
392
- page: '/products',
393
- title: 'Product Catalog',
394
- referrer: 'https://google.com',
395
- url: 'https://yoursite.com/products'
396
- });
397
-
398
- // Track search
399
- await grain.trackSearch({
400
- query: 'blue shoes',
401
- results: 24,
402
- filters: { category: 'footwear', color: 'blue' },
403
- sortBy: 'price_asc'
404
- });
91
+ grain.track('button_clicked', { button: 'signup' });
92
+ await grain.trackPurchase({ orderId: '123', total: 99.99 });
405
93
  ```
406
94
 
407
- ## API Reference
408
-
409
- ### Configuration Options
410
-
95
+ ### Remote Configuration
96
+ Control your app dynamically without code deployments:
411
97
  ```typescript
412
- interface GrainConfig {
413
- tenantId: string; // Required: Your Grain tenant ID
414
- apiUrl?: string; // API base URL (default: 'https://api.grainql.com')
415
- authStrategy?: AuthStrategy; // 'NONE' | 'SERVER_SIDE' | 'JWT' (default: 'NONE')
416
- secretKey?: string; // Required for SERVER_SIDE auth
417
- authProvider?: AuthProvider; // Required for JWT auth
418
- userId?: string; // Global user ID for all events
419
- batchSize?: number; // Events per batch (default: 50)
420
- flushInterval?: number; // Auto-flush interval in ms (default: 5000)
421
- retryAttempts?: number; // Retry attempts for failed requests (default: 3)
422
- retryDelay?: number; // Base retry delay in ms (default: 1000)
423
- debug?: boolean; // Enable debug logging (default: false)
424
- // Remote Config options
425
- defaultConfigurations?: Record<string, string>; // Default values for configurations
426
- configCacheKey?: string; // Custom cache key for configurations (default: 'grain_config')
427
- configRefreshInterval?: number; // Auto-refresh interval in ms (default: 300000)
428
- enableConfigCache?: boolean; // Enable/disable configuration caching (default: true)
98
+ const featureEnabled = grain.getConfig('new_feature');
99
+ if (featureEnabled === 'true') {
100
+ // Show feature
429
101
  }
430
102
  ```
431
103
 
432
- ### Core Methods
433
-
434
- #### `track(eventName, properties?, options?)`
435
-
436
- Track a single event:
437
-
438
- ```typescript
439
- grain.track('purchase', {
440
- product_id: 'abc123',
441
- price: 29.99,
442
- currency: 'USD'
443
- });
444
- ```
445
-
446
- #### `track(event, options?)`
447
-
448
- Track with a full event object:
449
-
104
+ ### User Identification
105
+ Track users across sessions:
450
106
  ```typescript
451
- grain.track({
452
- eventName: 'purchase',
453
- userId: 'user123',
454
- properties: {
455
- product_id: 'abc123',
456
- price: 29.99
457
- },
458
- timestamp: new Date()
459
- });
107
+ grain.setUserId('user_123');
108
+ await grain.setProperty({ plan: 'premium' });
460
109
  ```
461
110
 
462
- #### `setUserId(userId)`
111
+ ## More Examples
463
112
 
464
- Set global user ID for all subsequent events:
113
+ Check the [examples directory](./examples) for:
114
+ - Vanilla JavaScript usage
115
+ - React integration
116
+ - Next.js setup
117
+ - E-commerce tracking
118
+ - Authentication flows
465
119
 
466
- ```typescript
467
- grain.setUserId('user123');
468
- ```
469
-
470
- #### `getUserId()`
471
-
472
- Get current global user ID:
473
-
474
- ```typescript
475
- const userId = grain.getUserId(); // Returns 'user123' or null
476
- ```
477
-
478
- #### `identify(userId)`
479
-
480
- Alias for `setUserId()` - sets user ID for subsequent events:
481
-
482
- ```typescript
483
- grain.identify('user123');
484
- ```
485
-
486
- #### `setProperty(properties, options?)`
487
-
488
- Set user properties for analytics and segmentation:
489
-
490
- ```typescript
491
- // Set properties for current user
492
- await grain.setProperty({
493
- plan: 'premium',
494
- status: 'active',
495
- signupDate: '2024-01-15'
496
- });
497
-
498
- // Set properties for specific user
499
- await grain.setProperty({
500
- plan: 'free'
501
- }, { userId: 'user123' });
502
- ```
503
-
504
- **Parameters:**
505
- - `properties`: Object with up to 4 key-value pairs
506
- - `options.userId`: Optional user ID override
507
-
508
- **Security Considerations:**
509
- - When using `options.userId`, ensure you have proper permissions to set properties for other users
510
- - With JWT authentication, the userId must match the JWT subject
511
- - Grain may block requests if too many distinct userIds are used from the same source
512
-
513
- #### `flush()`
514
-
515
- Manually flush all queued events:
516
-
517
- ```typescript
518
- await grain.flush();
519
- ```
520
-
521
- #### `destroy()`
522
-
523
- Clean up resources and send remaining events:
524
-
525
- ```typescript
526
- grain.destroy();
527
- ```
528
-
529
- ### Remote Config Methods
530
-
531
- #### `getConfig(key)`
532
-
533
- Get configuration value synchronously (from cache or defaults):
534
-
535
- ```typescript
536
- const heroText = grain.getConfig('hero_text'); // Returns string | undefined
537
- ```
538
-
539
- #### `getAllConfigs()`
540
-
541
- Get all configurations synchronously (from cache or defaults):
542
-
543
- ```typescript
544
- const configs = grain.getAllConfigs(); // Returns Record<string, string>
545
- ```
546
-
547
- #### `getConfigAsync(key, options?)`
548
-
549
- Get configuration value asynchronously (cache-first with API fallback):
550
-
551
- ```typescript
552
- const heroText = await grain.getConfigAsync('hero_text');
553
- const buttonColor = await grain.getConfigAsync('button_color', {
554
- properties: { plan: 'premium' },
555
- forceRefresh: true
556
- });
557
- ```
558
-
559
- #### `getAllConfigsAsync(options?)`
560
-
561
- Get all configurations asynchronously (cache-first with API fallback):
562
-
563
- ```typescript
564
- const configs = await grain.getAllConfigsAsync();
565
- const allConfigs = await grain.getAllConfigsAsync({
566
- properties: { plan: 'premium', location: 'US' },
567
- forceRefresh: true
568
- });
569
- ```
570
-
571
- #### `fetchConfig(options?)`
572
-
573
- Fetch configurations directly from API:
574
-
575
- ```typescript
576
- const response = await grain.fetchConfig({
577
- immediateKeys: ['hero_text', 'button_color'],
578
- properties: { plan: 'premium' }
579
- });
580
- // Returns RemoteConfigResponse with full API response
581
- ```
582
-
583
- #### `preloadConfig(immediateKeys?, properties?)`
584
-
585
- Preload configurations for immediate access:
586
-
587
- ```typescript
588
- await grain.preloadConfig(['hero_text', 'button_color']);
589
- // Configurations are now available synchronously
590
- ```
591
-
592
- #### `addConfigChangeListener(listener)`
593
-
594
- Add listener for configuration changes:
595
-
596
- ```typescript
597
- const listener = (configurations) => {
598
- console.log('Configs updated:', configurations);
599
- };
600
- grain.addConfigChangeListener(listener);
601
- ```
602
-
603
- #### `removeConfigChangeListener(listener)`
604
-
605
- Remove configuration change listener:
606
-
607
- ```typescript
608
- grain.removeConfigChangeListener(listener);
609
- ```
610
-
611
- ### Template Event Methods
612
-
613
- #### Authentication Events
614
-
615
- - `trackLogin(properties?, options?)` - Track user login
616
- - `trackSignup(properties?, options?)` - Track user signup
617
-
618
- #### E-commerce Events
619
-
620
- - `trackCheckout(properties?, options?)` - Track checkout process
621
- - `trackPurchase(properties?, options?)` - Track completed purchase
622
- - `trackAddToCart(properties?, options?)` - Track item added to cart
623
- - `trackRemoveFromCart(properties?, options?)` - Track item removed from cart
624
-
625
- #### Navigation Events
626
-
627
- - `trackPageView(properties?, options?)` - Track page views
628
- - `trackSearch(properties?, options?)` - Track search queries
629
-
630
- ### Template Event Properties
631
-
632
- All template events support their own typed properties:
633
-
634
- ```typescript
635
- // Login event properties
636
- interface LoginEventProperties {
637
- method?: string; // 'email', 'google', 'facebook', etc.
638
- success?: boolean; // Whether login was successful
639
- errorMessage?: string; // Error message if login failed
640
- loginAttempt?: number; // Attempt number
641
- rememberMe?: boolean; // Whether "remember me" was checked
642
- twoFactorEnabled?: boolean; // Whether 2FA was used
643
- }
644
-
645
- // Checkout event properties
646
- interface CheckoutEventProperties {
647
- orderId?: string; // Unique order identifier
648
- total?: number; // Total amount
649
- currency?: string; // Currency code
650
- items?: Array<{ // Array of items
651
- id: string;
652
- name: string;
653
- price: number;
654
- quantity: number;
655
- }>;
656
- paymentMethod?: string; // 'credit_card', 'paypal', 'stripe', etc.
657
- success?: boolean; // Whether checkout was successful
658
- errorMessage?: string; // Error message if checkout failed
659
- couponCode?: string; // Applied coupon code
660
- discount?: number; // Discount amount
661
- }
662
- ```
663
-
664
- ## Security Considerations
665
-
666
- ### User ID Override Restrictions
667
-
668
- When using userId overrides in event tracking or property setting, be aware of these security restrictions:
669
-
670
- - **JWT Authentication**: If your tenant requires JWT authentication, any userId override must match the JWT subject
671
- - **Rate Limiting**: Grain may block requests if too many distinct userIds are used from the same instance, browser, device, or IP address
672
- - **Permissions**: Only use userId overrides when you have explicit permission to track events or set properties for other users
673
-
674
- **Best Practices:**
675
- - Use global `setUserId()` for the current user instead of per-event userId overrides
676
- - Avoid switching between many different userIds from the same client
677
- - Ensure your authentication strategy aligns with your userId usage patterns
678
-
679
- ## Authentication Strategies
680
-
681
- ### NONE
682
- No authentication required. Events are sent directly to the API.
683
-
684
- ### SERVER_SIDE
685
- Use a secret key for server-side authentication:
686
- - Obtain your secret key from the Grain dashboard
687
- - Include it in your configuration
688
- - Events are sent with `Authorization: Chase {SECRET}` header
689
-
690
- ### JWT
691
- Use JWT tokens for client-side authentication:
692
- - Configure JWT settings in your Grain tenant
693
- - Provide an auth provider that returns valid tokens
694
- - Supports Auth0, next-auth, and other JWT providers
695
-
696
- ## Build Outputs
697
-
698
- The package provides multiple build formats:
699
-
700
- - **ESM**: `dist/index.mjs` - Modern ES modules
701
- - **CommonJS**: `dist/index.js` - Node.js compatible
702
- - **IIFE**: `dist/index.global.js` - Browser global (`window.Grain`)
703
- - **Types**: `dist/index.d.ts` - TypeScript definitions
704
-
705
- ### Browser Usage (Script Tag)
706
-
707
- ```html
708
- <script src="https://unpkg.com/@grainql/analytics-web/dist/index.global.js"></script>
709
- <script>
710
- const grain = Grain.createGrainAnalytics({
711
- tenantId: 'your-tenant-id',
712
- userId: 'user123'
713
- });
714
-
715
- grain.trackPageView({ page: '/home' });
716
- grain.trackLogin({ method: 'email', success: true });
717
- </script>
718
- ```
719
-
720
- ## Advanced Usage
721
-
722
- ### Custom Auth Provider
723
-
724
- ```typescript
725
- class Auth0Provider {
726
- constructor(private auth0Client) {}
727
-
728
- async getToken() {
729
- return await this.auth0Client.getTokenSilently();
730
- }
731
- }
732
-
733
- const grain = createGrainAnalytics({
734
- tenantId: 'your-tenant-id',
735
- authStrategy: 'JWT',
736
- authProvider: new Auth0Provider(auth0Client)
737
- });
738
- ```
739
-
740
- ### Error Handling
741
-
742
- ```typescript
743
- try {
744
- await grain.track('event', { data: 'value' }, { flush: true });
745
- } catch (error) {
746
- console.error('Failed to send event:', error);
747
- }
748
- ```
749
-
750
- ### Debug Mode
751
-
752
- ```typescript
753
- const grain = createGrainAnalytics({
754
- tenantId: 'your-tenant-id',
755
- debug: true // Enables console logging
756
- });
757
- ```
758
-
759
- ### User Session Management
760
-
761
- ```typescript
762
- // Set user ID when user logs in
763
- grain.setUserId('user123');
764
-
765
- // Track user-specific events
766
- await grain.trackLogin({ method: 'email', success: true });
767
- await grain.trackPageView({ page: '/dashboard' });
768
-
769
- // Clear user ID when user logs out
770
- grain.setUserId(null);
771
- ```
120
+ ## Contributing
772
121
 
773
- ## Changelog
774
-
775
- ### [1.6.0] - 2025-09-08
776
-
777
- #### Added
778
- - **Remote Config API**: Complete remote configuration management system
779
- - `getConfig()` and `getAllConfigs()` for synchronous access
780
- - `getConfigAsync()` and `getAllConfigsAsync()` for async access with cache-first strategy
781
- - `fetchConfig()` for direct API calls
782
- - `preloadConfig()` for preloading configurations at page load
783
- - Configuration change listeners with `addConfigChangeListener()` and `removeConfigChangeListener()`
784
- - Automatic configuration caching with localStorage persistence
785
- - Auto-refresh timer for keeping configurations up-to-date
786
- - Default values support for immediate access without API calls
787
- - User properties support for personalized configurations
788
- - Force refresh option to bypass cache
789
- - Full authentication support (NONE, SERVER_SIDE, JWT)
790
- - **Enhanced Configuration Options**:
791
- - `defaultConfigurations` for setting default values
792
- - `configCacheKey` for custom cache keys
793
- - `configRefreshInterval` for auto-refresh timing
794
- - `enableConfigCache` for cache control
795
-
796
- #### Technical
797
- - Added comprehensive remote config interfaces and types
798
- - Implemented robust error handling and retry logic for config API
799
- - Added localStorage-based caching with graceful fallbacks
800
- - Integrated config refresh timer with proper cleanup
801
- - Enhanced TypeScript interfaces for better type safety
802
- - Added comprehensive test coverage for remote config functionality
803
- - Improved test stability and reliability
804
-
805
- ### [1.4.0] - 2024-12-19
806
-
807
- #### Added
808
- - **User Properties API**: New `setProperty()` method for setting user properties
809
- - Set up to 4 properties per request
810
- - Automatic string serialization for all values
811
- - Support for user-specific property overrides
812
- - Full authentication support (NONE, SERVER_SIDE, JWT)
813
- - **Updated API Endpoints**:
814
- - Events now use `/v1/events/{tenant}/multi` endpoint
815
- - Properties use `/v1/events/{tenant}/properties` endpoint
816
-
817
- #### Changed
818
- - Updated all event sending to use the new `/multi` endpoint
819
- - Enhanced error handling and retry logic for properties API
820
-
821
- #### Technical
822
- - Added comprehensive test coverage for properties functionality
823
- - Updated all existing tests to use new endpoint structure
824
- - Improved TypeScript interfaces for better type safety
825
-
826
- ### [1.3.0] - Previous Release
827
-
828
- - Template events for common analytics scenarios
829
- - Enhanced user ID management
830
- - Improved error handling and retry logic
831
- - Cross-platform compatibility improvements
122
+ We welcome contributions! Please see our contributing guidelines for more details.
832
123
 
833
124
  ## License
834
125
 
835
- MIT
126
+ MIT © Grain Analytics
836
127
 
837
128
  ## Support
838
129
 
839
- For questions and support, please visit [grainql.com](https://grainql.com) or contact our support team.
130
+ - **Documentation**: [docs.grainql.com](https://docs.grainql.com)
131
+ - **Dashboard**: [grainql.com/dashboard](https://grainql.com/dashboard)
132
+ - **Issues**: [GitHub Issues](https://github.com/GrainQL/analytics-web/issues)
133
+ - **Email**: support@grainql.com