@grainql/analytics-web 1.6.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.d.ts +32 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.d.ts +32 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.ts +32 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.global.dev.js +350 -146
- package/dist/index.global.dev.js.map +2 -2
- package/dist/index.global.js +2 -2
- package/dist/index.global.js.map +3 -3
- package/dist/index.js +393 -160
- package/dist/index.mjs +393 -160
- package/package.json +1 -1
package/dist/index.global.dev.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Grain Analytics Web SDK v1.
|
|
1
|
+
/* Grain Analytics Web SDK v1.7.0 | MIT License | Development Build */
|
|
2
2
|
"use strict";
|
|
3
3
|
var Grain = (() => {
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -133,6 +133,113 @@ var Grain = (() => {
|
|
|
133
133
|
console.log("[Grain Analytics]", ...args);
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Create error digest from events
|
|
138
|
+
*/
|
|
139
|
+
createErrorDigest(events) {
|
|
140
|
+
const eventNames = [...new Set(events.map((e) => e.eventName))];
|
|
141
|
+
const userIds = [...new Set(events.map((e) => e.userId))];
|
|
142
|
+
let totalProperties = 0;
|
|
143
|
+
let totalSize = 0;
|
|
144
|
+
events.forEach((event) => {
|
|
145
|
+
const properties = event.properties || {};
|
|
146
|
+
totalProperties += Object.keys(properties).length;
|
|
147
|
+
totalSize += JSON.stringify(event).length;
|
|
148
|
+
});
|
|
149
|
+
return {
|
|
150
|
+
eventCount: events.length,
|
|
151
|
+
totalProperties,
|
|
152
|
+
totalSize,
|
|
153
|
+
eventNames,
|
|
154
|
+
userIds
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Format error with beautiful structure
|
|
159
|
+
*/
|
|
160
|
+
formatError(error, context, events) {
|
|
161
|
+
const digest = events ? this.createErrorDigest(events) : {
|
|
162
|
+
eventCount: 0,
|
|
163
|
+
totalProperties: 0,
|
|
164
|
+
totalSize: 0,
|
|
165
|
+
eventNames: [],
|
|
166
|
+
userIds: []
|
|
167
|
+
};
|
|
168
|
+
let code = "UNKNOWN_ERROR";
|
|
169
|
+
let message = "An unknown error occurred";
|
|
170
|
+
if (error instanceof Error) {
|
|
171
|
+
message = error.message;
|
|
172
|
+
if (message.includes("fetch failed") || message.includes("network error")) {
|
|
173
|
+
code = "NETWORK_ERROR";
|
|
174
|
+
} else if (message.includes("timeout")) {
|
|
175
|
+
code = "TIMEOUT_ERROR";
|
|
176
|
+
} else if (message.includes("HTTP 4")) {
|
|
177
|
+
code = "CLIENT_ERROR";
|
|
178
|
+
} else if (message.includes("HTTP 5")) {
|
|
179
|
+
code = "SERVER_ERROR";
|
|
180
|
+
} else if (message.includes("JSON")) {
|
|
181
|
+
code = "PARSE_ERROR";
|
|
182
|
+
} else if (message.includes("auth") || message.includes("unauthorized")) {
|
|
183
|
+
code = "AUTH_ERROR";
|
|
184
|
+
} else if (message.includes("rate limit") || message.includes("429")) {
|
|
185
|
+
code = "RATE_LIMIT_ERROR";
|
|
186
|
+
} else {
|
|
187
|
+
code = "GENERAL_ERROR";
|
|
188
|
+
}
|
|
189
|
+
} else if (typeof error === "string") {
|
|
190
|
+
message = error;
|
|
191
|
+
code = "STRING_ERROR";
|
|
192
|
+
} else if (error && typeof error === "object" && "status" in error) {
|
|
193
|
+
const status = error.status;
|
|
194
|
+
code = `HTTP_${status}`;
|
|
195
|
+
message = `HTTP ${status} error`;
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
code,
|
|
199
|
+
message,
|
|
200
|
+
digest,
|
|
201
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
202
|
+
context,
|
|
203
|
+
originalError: error
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Log formatted error gracefully
|
|
208
|
+
*/
|
|
209
|
+
logError(formattedError) {
|
|
210
|
+
const { code, message, digest, timestamp, context } = formattedError;
|
|
211
|
+
const errorOutput = {
|
|
212
|
+
"\u{1F6A8} Grain Analytics Error": {
|
|
213
|
+
"Error Code": code,
|
|
214
|
+
"Message": message,
|
|
215
|
+
"Context": context,
|
|
216
|
+
"Timestamp": timestamp,
|
|
217
|
+
"Event Digest": {
|
|
218
|
+
"Events": digest.eventCount,
|
|
219
|
+
"Properties": digest.totalProperties,
|
|
220
|
+
"Size (bytes)": digest.totalSize,
|
|
221
|
+
"Event Names": digest.eventNames.length > 0 ? digest.eventNames.join(", ") : "None",
|
|
222
|
+
"User IDs": digest.userIds.length > 0 ? digest.userIds.slice(0, 3).join(", ") + (digest.userIds.length > 3 ? "..." : "") : "None"
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
console.error("\u{1F6A8} Grain Analytics Error:", errorOutput);
|
|
227
|
+
if (this.config.debug) {
|
|
228
|
+
console.error(`[Grain Analytics] ${code}: ${message} (${context}) - Events: ${digest.eventCount}, Props: ${digest.totalProperties}, Size: ${digest.totalSize}B`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Safely execute a function with error handling
|
|
233
|
+
*/
|
|
234
|
+
async safeExecute(fn, context, events) {
|
|
235
|
+
try {
|
|
236
|
+
return await fn();
|
|
237
|
+
} catch (error) {
|
|
238
|
+
const formattedError = this.formatError(error, context, events);
|
|
239
|
+
this.logError(formattedError);
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
136
243
|
formatEvent(event) {
|
|
137
244
|
return {
|
|
138
245
|
eventName: event.eventName,
|
|
@@ -216,18 +323,20 @@ var Grain = (() => {
|
|
|
216
323
|
} catch (error) {
|
|
217
324
|
lastError = error;
|
|
218
325
|
if (attempt === this.config.retryAttempts) {
|
|
219
|
-
|
|
326
|
+
const formattedError = this.formatError(error, `sendEvents (attempt ${attempt + 1}/${this.config.retryAttempts + 1})`, events);
|
|
327
|
+
this.logError(formattedError);
|
|
328
|
+
return;
|
|
220
329
|
}
|
|
221
330
|
if (!this.isRetriableError(error)) {
|
|
222
|
-
|
|
331
|
+
const formattedError = this.formatError(error, `sendEvents (non-retriable error)`, events);
|
|
332
|
+
this.logError(formattedError);
|
|
333
|
+
return;
|
|
223
334
|
}
|
|
224
335
|
const delayMs = this.config.retryDelay * Math.pow(2, attempt);
|
|
225
336
|
this.log(`Retrying in ${delayMs}ms after error:`, error);
|
|
226
337
|
await this.delay(delayMs);
|
|
227
338
|
}
|
|
228
339
|
}
|
|
229
|
-
console.error("[Grain Analytics] Failed to send events after all retries:", lastError);
|
|
230
|
-
throw lastError;
|
|
231
340
|
}
|
|
232
341
|
async sendEventsWithBeacon(events) {
|
|
233
342
|
if (events.length === 0)
|
|
@@ -252,7 +361,8 @@ var Grain = (() => {
|
|
|
252
361
|
});
|
|
253
362
|
this.log(`Successfully sent ${events.length} events via fetch (keepalive)`);
|
|
254
363
|
} catch (error) {
|
|
255
|
-
|
|
364
|
+
const formattedError = this.formatError(error, "sendEventsWithBeacon", events);
|
|
365
|
+
this.logError(formattedError);
|
|
256
366
|
}
|
|
257
367
|
}
|
|
258
368
|
startFlushTimer() {
|
|
@@ -262,7 +372,8 @@ var Grain = (() => {
|
|
|
262
372
|
this.flushTimer = window.setInterval(() => {
|
|
263
373
|
if (this.eventQueue.length > 0) {
|
|
264
374
|
this.flush().catch((error) => {
|
|
265
|
-
|
|
375
|
+
const formattedError = this.formatError(error, "auto-flush");
|
|
376
|
+
this.logError(formattedError);
|
|
266
377
|
});
|
|
267
378
|
}
|
|
268
379
|
}, this.config.flushInterval);
|
|
@@ -296,26 +407,34 @@ var Grain = (() => {
|
|
|
296
407
|
});
|
|
297
408
|
}
|
|
298
409
|
async track(eventOrName, propertiesOrOptions, options) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
410
|
+
try {
|
|
411
|
+
if (this.isDestroyed) {
|
|
412
|
+
const error = new Error("Grain Analytics: Client has been destroyed");
|
|
413
|
+
const formattedError = this.formatError(error, "track (client destroyed)");
|
|
414
|
+
this.logError(formattedError);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
let event;
|
|
418
|
+
let opts = {};
|
|
419
|
+
if (typeof eventOrName === "string") {
|
|
420
|
+
event = {
|
|
421
|
+
eventName: eventOrName,
|
|
422
|
+
properties: propertiesOrOptions
|
|
423
|
+
};
|
|
424
|
+
opts = options || {};
|
|
425
|
+
} else {
|
|
426
|
+
event = eventOrName;
|
|
427
|
+
opts = propertiesOrOptions || {};
|
|
428
|
+
}
|
|
429
|
+
const formattedEvent = this.formatEvent(event);
|
|
430
|
+
this.eventQueue.push(formattedEvent);
|
|
431
|
+
this.log(`Queued event: ${event.eventName}`, event.properties);
|
|
432
|
+
if (opts.flush || this.eventQueue.length >= this.config.batchSize) {
|
|
433
|
+
await this.flush();
|
|
434
|
+
}
|
|
435
|
+
} catch (error) {
|
|
436
|
+
const formattedError = this.formatError(error, "track");
|
|
437
|
+
this.logError(formattedError);
|
|
319
438
|
}
|
|
320
439
|
}
|
|
321
440
|
/**
|
|
@@ -352,32 +471,46 @@ var Grain = (() => {
|
|
|
352
471
|
* Set user properties
|
|
353
472
|
*/
|
|
354
473
|
async setProperty(properties, options) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
474
|
+
try {
|
|
475
|
+
if (this.isDestroyed) {
|
|
476
|
+
const error = new Error("Grain Analytics: Client has been destroyed");
|
|
477
|
+
const formattedError = this.formatError(error, "setProperty (client destroyed)");
|
|
478
|
+
this.logError(formattedError);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
const userId = options?.userId || this.getEffectiveUserId();
|
|
482
|
+
const propertyKeys = Object.keys(properties);
|
|
483
|
+
if (propertyKeys.length > 4) {
|
|
484
|
+
const error = new Error("Grain Analytics: Maximum 4 properties allowed per request");
|
|
485
|
+
const formattedError = this.formatError(error, "setProperty (validation)");
|
|
486
|
+
this.logError(formattedError);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (propertyKeys.length === 0) {
|
|
490
|
+
const error = new Error("Grain Analytics: At least one property is required");
|
|
491
|
+
const formattedError = this.formatError(error, "setProperty (validation)");
|
|
492
|
+
this.logError(formattedError);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
const serializedProperties = {};
|
|
496
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
497
|
+
if (value === null || value === void 0) {
|
|
498
|
+
serializedProperties[key] = "";
|
|
499
|
+
} else if (typeof value === "string") {
|
|
500
|
+
serializedProperties[key] = value;
|
|
501
|
+
} else {
|
|
502
|
+
serializedProperties[key] = JSON.stringify(value);
|
|
503
|
+
}
|
|
374
504
|
}
|
|
505
|
+
const payload = {
|
|
506
|
+
userId,
|
|
507
|
+
...serializedProperties
|
|
508
|
+
};
|
|
509
|
+
await this.sendProperties(payload);
|
|
510
|
+
} catch (error) {
|
|
511
|
+
const formattedError = this.formatError(error, "setProperty");
|
|
512
|
+
this.logError(formattedError);
|
|
375
513
|
}
|
|
376
|
-
const payload = {
|
|
377
|
-
userId,
|
|
378
|
-
...serializedProperties
|
|
379
|
-
};
|
|
380
|
-
await this.sendProperties(payload);
|
|
381
514
|
}
|
|
382
515
|
/**
|
|
383
516
|
* Send properties to the API
|
|
@@ -416,79 +549,126 @@ var Grain = (() => {
|
|
|
416
549
|
} catch (error) {
|
|
417
550
|
lastError = error;
|
|
418
551
|
if (attempt === this.config.retryAttempts) {
|
|
419
|
-
|
|
552
|
+
const formattedError = this.formatError(error, `sendProperties (attempt ${attempt + 1}/${this.config.retryAttempts + 1})`);
|
|
553
|
+
this.logError(formattedError);
|
|
554
|
+
return;
|
|
420
555
|
}
|
|
421
556
|
if (!this.isRetriableError(error)) {
|
|
422
|
-
|
|
557
|
+
const formattedError = this.formatError(error, "sendProperties (non-retriable error)");
|
|
558
|
+
this.logError(formattedError);
|
|
559
|
+
return;
|
|
423
560
|
}
|
|
424
561
|
const delayMs = this.config.retryDelay * Math.pow(2, attempt);
|
|
425
562
|
this.log(`Retrying in ${delayMs}ms after error:`, error);
|
|
426
563
|
await this.delay(delayMs);
|
|
427
564
|
}
|
|
428
565
|
}
|
|
429
|
-
console.error("[Grain Analytics] Failed to set properties after all retries:", lastError);
|
|
430
|
-
throw lastError;
|
|
431
566
|
}
|
|
432
567
|
// Template event methods
|
|
433
568
|
/**
|
|
434
569
|
* Track user login event
|
|
435
570
|
*/
|
|
436
571
|
async trackLogin(properties, options) {
|
|
437
|
-
|
|
572
|
+
try {
|
|
573
|
+
return await this.track("login", properties, options);
|
|
574
|
+
} catch (error) {
|
|
575
|
+
const formattedError = this.formatError(error, "trackLogin");
|
|
576
|
+
this.logError(formattedError);
|
|
577
|
+
}
|
|
438
578
|
}
|
|
439
579
|
/**
|
|
440
580
|
* Track user signup event
|
|
441
581
|
*/
|
|
442
582
|
async trackSignup(properties, options) {
|
|
443
|
-
|
|
583
|
+
try {
|
|
584
|
+
return await this.track("signup", properties, options);
|
|
585
|
+
} catch (error) {
|
|
586
|
+
const formattedError = this.formatError(error, "trackSignup");
|
|
587
|
+
this.logError(formattedError);
|
|
588
|
+
}
|
|
444
589
|
}
|
|
445
590
|
/**
|
|
446
591
|
* Track checkout event
|
|
447
592
|
*/
|
|
448
593
|
async trackCheckout(properties, options) {
|
|
449
|
-
|
|
594
|
+
try {
|
|
595
|
+
return await this.track("checkout", properties, options);
|
|
596
|
+
} catch (error) {
|
|
597
|
+
const formattedError = this.formatError(error, "trackCheckout");
|
|
598
|
+
this.logError(formattedError);
|
|
599
|
+
}
|
|
450
600
|
}
|
|
451
601
|
/**
|
|
452
602
|
* Track page view event
|
|
453
603
|
*/
|
|
454
604
|
async trackPageView(properties, options) {
|
|
455
|
-
|
|
605
|
+
try {
|
|
606
|
+
return await this.track("page_view", properties, options);
|
|
607
|
+
} catch (error) {
|
|
608
|
+
const formattedError = this.formatError(error, "trackPageView");
|
|
609
|
+
this.logError(formattedError);
|
|
610
|
+
}
|
|
456
611
|
}
|
|
457
612
|
/**
|
|
458
613
|
* Track purchase event
|
|
459
614
|
*/
|
|
460
615
|
async trackPurchase(properties, options) {
|
|
461
|
-
|
|
616
|
+
try {
|
|
617
|
+
return await this.track("purchase", properties, options);
|
|
618
|
+
} catch (error) {
|
|
619
|
+
const formattedError = this.formatError(error, "trackPurchase");
|
|
620
|
+
this.logError(formattedError);
|
|
621
|
+
}
|
|
462
622
|
}
|
|
463
623
|
/**
|
|
464
624
|
* Track search event
|
|
465
625
|
*/
|
|
466
626
|
async trackSearch(properties, options) {
|
|
467
|
-
|
|
627
|
+
try {
|
|
628
|
+
return await this.track("search", properties, options);
|
|
629
|
+
} catch (error) {
|
|
630
|
+
const formattedError = this.formatError(error, "trackSearch");
|
|
631
|
+
this.logError(formattedError);
|
|
632
|
+
}
|
|
468
633
|
}
|
|
469
634
|
/**
|
|
470
635
|
* Track add to cart event
|
|
471
636
|
*/
|
|
472
637
|
async trackAddToCart(properties, options) {
|
|
473
|
-
|
|
638
|
+
try {
|
|
639
|
+
return await this.track("add_to_cart", properties, options);
|
|
640
|
+
} catch (error) {
|
|
641
|
+
const formattedError = this.formatError(error, "trackAddToCart");
|
|
642
|
+
this.logError(formattedError);
|
|
643
|
+
}
|
|
474
644
|
}
|
|
475
645
|
/**
|
|
476
646
|
* Track remove from cart event
|
|
477
647
|
*/
|
|
478
648
|
async trackRemoveFromCart(properties, options) {
|
|
479
|
-
|
|
649
|
+
try {
|
|
650
|
+
return await this.track("remove_from_cart", properties, options);
|
|
651
|
+
} catch (error) {
|
|
652
|
+
const formattedError = this.formatError(error, "trackRemoveFromCart");
|
|
653
|
+
this.logError(formattedError);
|
|
654
|
+
}
|
|
480
655
|
}
|
|
481
656
|
/**
|
|
482
657
|
* Manually flush all queued events
|
|
483
658
|
*/
|
|
484
659
|
async flush() {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
660
|
+
try {
|
|
661
|
+
if (this.eventQueue.length === 0)
|
|
662
|
+
return;
|
|
663
|
+
const eventsToSend = [...this.eventQueue];
|
|
664
|
+
this.eventQueue = [];
|
|
665
|
+
const chunks = this.chunkEvents(eventsToSend, this.config.maxEventsPerRequest);
|
|
666
|
+
for (const chunk of chunks) {
|
|
667
|
+
await this.sendEvents(chunk);
|
|
668
|
+
}
|
|
669
|
+
} catch (error) {
|
|
670
|
+
const formattedError = this.formatError(error, "flush");
|
|
671
|
+
this.logError(formattedError);
|
|
492
672
|
}
|
|
493
673
|
}
|
|
494
674
|
// Remote Config Methods
|
|
@@ -547,82 +727,98 @@ var Grain = (() => {
|
|
|
547
727
|
* Fetch configurations from API
|
|
548
728
|
*/
|
|
549
729
|
async fetchConfig(options = {}) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
userId
|
|
558
|
-
immediateKeys
|
|
559
|
-
properties
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
730
|
+
try {
|
|
731
|
+
if (this.isDestroyed) {
|
|
732
|
+
const error = new Error("Grain Analytics: Client has been destroyed");
|
|
733
|
+
const formattedError = this.formatError(error, "fetchConfig (client destroyed)");
|
|
734
|
+
this.logError(formattedError);
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
const userId = options.userId || this.getEffectiveUserId();
|
|
738
|
+
const immediateKeys = options.immediateKeys || [];
|
|
739
|
+
const properties = options.properties || {};
|
|
740
|
+
const request = {
|
|
741
|
+
userId,
|
|
742
|
+
immediateKeys,
|
|
743
|
+
properties
|
|
744
|
+
};
|
|
745
|
+
let lastError;
|
|
746
|
+
for (let attempt = 0; attempt <= this.config.retryAttempts; attempt++) {
|
|
747
|
+
try {
|
|
748
|
+
const headers = await this.getAuthHeaders();
|
|
749
|
+
const url = `${this.config.apiUrl}/v1/client/${encodeURIComponent(this.config.tenantId)}/config/configurations`;
|
|
750
|
+
this.log(`Fetching configurations for user ${userId} (attempt ${attempt + 1})`);
|
|
751
|
+
const response = await fetch(url, {
|
|
752
|
+
method: "POST",
|
|
753
|
+
headers,
|
|
754
|
+
body: JSON.stringify(request)
|
|
755
|
+
});
|
|
756
|
+
if (!response.ok) {
|
|
757
|
+
let errorMessage = `HTTP ${response.status}`;
|
|
758
|
+
try {
|
|
759
|
+
const errorBody = await response.json();
|
|
760
|
+
if (errorBody?.message) {
|
|
761
|
+
errorMessage = errorBody.message;
|
|
762
|
+
}
|
|
763
|
+
} catch {
|
|
764
|
+
const errorText = await response.text();
|
|
765
|
+
if (errorText) {
|
|
766
|
+
errorMessage = errorText;
|
|
767
|
+
}
|
|
583
768
|
}
|
|
769
|
+
const error = new Error(`Failed to fetch configurations: ${errorMessage}`);
|
|
770
|
+
error.status = response.status;
|
|
771
|
+
throw error;
|
|
584
772
|
}
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
773
|
+
const configResponse = await response.json();
|
|
774
|
+
if (configResponse.configurations) {
|
|
775
|
+
this.updateConfigCache(configResponse, userId);
|
|
776
|
+
}
|
|
777
|
+
this.log(`Successfully fetched configurations for user ${userId}:`, configResponse);
|
|
778
|
+
return configResponse;
|
|
779
|
+
} catch (error) {
|
|
780
|
+
lastError = error;
|
|
781
|
+
if (attempt === this.config.retryAttempts) {
|
|
782
|
+
const formattedError = this.formatError(error, `fetchConfig (attempt ${attempt + 1}/${this.config.retryAttempts + 1})`);
|
|
783
|
+
this.logError(formattedError);
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
if (!this.isRetriableError(error)) {
|
|
787
|
+
const formattedError = this.formatError(error, "fetchConfig (non-retriable error)");
|
|
788
|
+
this.logError(formattedError);
|
|
789
|
+
return null;
|
|
790
|
+
}
|
|
791
|
+
const delayMs = this.config.retryDelay * Math.pow(2, attempt);
|
|
792
|
+
this.log(`Retrying config fetch in ${delayMs}ms after error:`, error);
|
|
793
|
+
await this.delay(delayMs);
|
|
602
794
|
}
|
|
603
|
-
const delayMs = this.config.retryDelay * Math.pow(2, attempt);
|
|
604
|
-
this.log(`Retrying config fetch in ${delayMs}ms after error:`, error);
|
|
605
|
-
await this.delay(delayMs);
|
|
606
795
|
}
|
|
796
|
+
return null;
|
|
797
|
+
} catch (error) {
|
|
798
|
+
const formattedError = this.formatError(error, "fetchConfig");
|
|
799
|
+
this.logError(formattedError);
|
|
800
|
+
return null;
|
|
607
801
|
}
|
|
608
|
-
console.error("[Grain Analytics] Failed to fetch configurations after all retries:", lastError);
|
|
609
|
-
throw lastError;
|
|
610
802
|
}
|
|
611
803
|
/**
|
|
612
804
|
* Get configuration asynchronously (cache-first with fallback to API)
|
|
613
805
|
*/
|
|
614
806
|
async getConfigAsync(key, options = {}) {
|
|
615
|
-
if (!options.forceRefresh && this.configCache?.configurations?.[key]) {
|
|
616
|
-
return this.configCache.configurations[key];
|
|
617
|
-
}
|
|
618
|
-
if (!options.forceRefresh && this.config.defaultConfigurations?.[key]) {
|
|
619
|
-
return this.config.defaultConfigurations[key];
|
|
620
|
-
}
|
|
621
807
|
try {
|
|
808
|
+
if (!options.forceRefresh && this.configCache?.configurations?.[key]) {
|
|
809
|
+
return this.configCache.configurations[key];
|
|
810
|
+
}
|
|
811
|
+
if (!options.forceRefresh && this.config.defaultConfigurations?.[key]) {
|
|
812
|
+
return this.config.defaultConfigurations[key];
|
|
813
|
+
}
|
|
622
814
|
const response = await this.fetchConfig(options);
|
|
623
|
-
|
|
815
|
+
if (response) {
|
|
816
|
+
return response.configurations[key];
|
|
817
|
+
}
|
|
818
|
+
return this.config.defaultConfigurations?.[key];
|
|
624
819
|
} catch (error) {
|
|
625
|
-
this.
|
|
820
|
+
const formattedError = this.formatError(error, "getConfigAsync");
|
|
821
|
+
this.logError(formattedError);
|
|
626
822
|
return this.config.defaultConfigurations?.[key];
|
|
627
823
|
}
|
|
628
824
|
}
|
|
@@ -630,14 +826,18 @@ var Grain = (() => {
|
|
|
630
826
|
* Get all configurations asynchronously (cache-first with fallback to API)
|
|
631
827
|
*/
|
|
632
828
|
async getAllConfigsAsync(options = {}) {
|
|
633
|
-
if (!options.forceRefresh && this.configCache?.configurations) {
|
|
634
|
-
return { ...this.config.defaultConfigurations, ...this.configCache.configurations };
|
|
635
|
-
}
|
|
636
829
|
try {
|
|
830
|
+
if (!options.forceRefresh && this.configCache?.configurations) {
|
|
831
|
+
return { ...this.config.defaultConfigurations, ...this.configCache.configurations };
|
|
832
|
+
}
|
|
637
833
|
const response = await this.fetchConfig(options);
|
|
638
|
-
|
|
834
|
+
if (response) {
|
|
835
|
+
return { ...this.config.defaultConfigurations, ...response.configurations };
|
|
836
|
+
}
|
|
837
|
+
return { ...this.config.defaultConfigurations };
|
|
639
838
|
} catch (error) {
|
|
640
|
-
this.
|
|
839
|
+
const formattedError = this.formatError(error, "getAllConfigsAsync");
|
|
840
|
+
this.logError(formattedError);
|
|
641
841
|
return { ...this.config.defaultConfigurations };
|
|
642
842
|
}
|
|
643
843
|
}
|
|
@@ -695,7 +895,8 @@ var Grain = (() => {
|
|
|
695
895
|
this.configRefreshTimer = window.setInterval(() => {
|
|
696
896
|
if (!this.isDestroyed && this.globalUserId) {
|
|
697
897
|
this.fetchConfig().catch((error) => {
|
|
698
|
-
|
|
898
|
+
const formattedError = this.formatError(error, "auto-config refresh");
|
|
899
|
+
this.logError(formattedError);
|
|
699
900
|
});
|
|
700
901
|
}
|
|
701
902
|
}, this.config.configRefreshInterval);
|
|
@@ -713,15 +914,18 @@ var Grain = (() => {
|
|
|
713
914
|
* Preload configurations for immediate access
|
|
714
915
|
*/
|
|
715
916
|
async preloadConfig(immediateKeys = [], properties) {
|
|
716
|
-
if (!this.globalUserId) {
|
|
717
|
-
this.log("Cannot preload config: no user ID set");
|
|
718
|
-
return;
|
|
719
|
-
}
|
|
720
917
|
try {
|
|
721
|
-
|
|
722
|
-
|
|
918
|
+
if (!this.globalUserId) {
|
|
919
|
+
this.log("Cannot preload config: no user ID set");
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
const response = await this.fetchConfig({ immediateKeys, properties });
|
|
923
|
+
if (response) {
|
|
924
|
+
this.startConfigRefreshTimer();
|
|
925
|
+
}
|
|
723
926
|
} catch (error) {
|
|
724
|
-
this.
|
|
927
|
+
const formattedError = this.formatError(error, "preloadConfig");
|
|
928
|
+
this.logError(formattedError);
|
|
725
929
|
}
|
|
726
930
|
}
|
|
727
931
|
/**
|