@clianta/sdk 1.7.1 → 1.7.3

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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Clianta SDK v1.7.1
2
+ * Clianta SDK v1.7.3
3
3
  * (c) 2026 Clianta
4
4
  * Released under the MIT License.
5
5
  */
@@ -8,7 +8,7 @@
8
8
  * @see SDK_VERSION in core/config.ts
9
9
  */
10
10
  /** SDK Version */
11
- const SDK_VERSION = '1.7.0';
11
+ const SDK_VERSION = '1.7.2';
12
12
  /** Default API endpoint — reads from env or falls back to localhost */
13
13
  const getDefaultApiEndpoint = () => {
14
14
  // Next.js (process.env)
@@ -219,8 +219,9 @@ class Transport {
219
219
  /**
220
220
  * Send identify request.
221
221
  * Returns contactId from the server response so the Tracker can store it.
222
+ * Retries on 5xx with exponential backoff (same policy as sendEvents).
222
223
  */
223
- async sendIdentify(data) {
224
+ async sendIdentify(data, attempt = 1) {
224
225
  const url = `${this.config.apiEndpoint}/api/public/track/identify`;
225
226
  try {
226
227
  const response = await this.fetchWithTimeout(url, {
@@ -238,16 +239,26 @@ class Transport {
238
239
  contactId: body.contactId ?? undefined,
239
240
  };
240
241
  }
241
- if (response.status >= 500) {
242
- logger.warn(`Identify server error (${response.status})`);
243
- }
244
- else {
245
- logger.error(`Identify failed with status ${response.status}:`, body.message);
242
+ // Server error retry with exponential backoff
243
+ if (response.status >= 500 && attempt < this.config.maxRetries) {
244
+ const backoff = this.config.retryDelay * Math.pow(2, attempt - 1);
245
+ logger.warn(`Identify server error (${response.status}), retrying in ${backoff}ms...`);
246
+ await this.delay(backoff);
247
+ return this.sendIdentify(data, attempt + 1);
246
248
  }
249
+ logger.error(`Identify failed with status ${response.status}:`, body.message);
247
250
  return { success: false, status: response.status };
248
251
  }
249
252
  catch (error) {
250
- logger.error('Identify request failed:', error);
253
+ // Network error retry if still online
254
+ const isOnline = typeof navigator === 'undefined' || navigator.onLine;
255
+ if (isOnline && attempt < this.config.maxRetries) {
256
+ const backoff = this.config.retryDelay * Math.pow(2, attempt - 1);
257
+ logger.warn(`Identify network error, retrying in ${backoff}ms (${attempt}/${this.config.maxRetries})...`);
258
+ await this.delay(backoff);
259
+ return this.sendIdentify(data, attempt + 1);
260
+ }
261
+ logger.error('Identify request failed after retries:', error);
251
262
  return { success: false, error: error };
252
263
  }
253
264
  }
@@ -565,9 +576,6 @@ function resetIds(useCookies = false) {
565
576
  // Ignore
566
577
  }
567
578
  }
568
- // ============================================
569
- // URL UTILITIES
570
- // ============================================
571
579
  /**
572
580
  * Extract UTM parameters from URL
573
581
  */
@@ -724,8 +732,9 @@ class EventQueue {
724
732
  flushInterval: config.flushInterval ?? 5000,
725
733
  maxQueueSize: config.maxQueueSize ?? MAX_QUEUE_SIZE,
726
734
  storageKey: config.storageKey ?? STORAGE_KEYS.EVENT_QUEUE,
735
+ persistMode: config.persistMode ?? 'session',
727
736
  };
728
- this.persistMode = config.persistMode || 'session';
737
+ this.persistMode = this.config.persistMode;
729
738
  this.isOnline = typeof navigator === 'undefined' || navigator.onLine;
730
739
  // Restore persisted queue
731
740
  this.restoreQueue();
@@ -789,9 +798,11 @@ class EventQueue {
789
798
  // Send to backend
790
799
  const result = await this.transport.sendEvents(events);
791
800
  if (!result.success) {
792
- // Re-queue events on failure (at the front)
801
+ // Re-queue events on failure (at the front), capped at maxQueueSize
793
802
  logger.warn('Flush failed, re-queuing events');
794
- this.queue.unshift(...events);
803
+ const availableSpace = this.config.maxQueueSize - this.queue.length;
804
+ const eventsToRequeue = events.slice(0, Math.max(0, availableSpace));
805
+ this.queue.unshift(...eventsToRequeue);
795
806
  this.persistQueue(this.queue);
796
807
  }
797
808
  else {
@@ -1238,7 +1249,7 @@ class FormsPlugin extends BasePlugin {
1238
1249
  if (this.trackedForms.has(form))
1239
1250
  return;
1240
1251
  this.trackedForms.add(form);
1241
- const formId = form.id || form.name || `form-${Math.random().toString(36).substr(2, 9)}`;
1252
+ const formId = form.id || form.name || `form-${Math.random().toString(36).substring(2, 11)}`;
1242
1253
  // Track form view
1243
1254
  this.track('form_view', 'Form Viewed', {
1244
1255
  formId,
@@ -1882,27 +1893,22 @@ class PopupFormsPlugin extends BasePlugin {
1882
1893
  super.destroy();
1883
1894
  }
1884
1895
  loadShownForms() {
1885
- try {
1886
- const stored = localStorage.getItem('clianta_shown_forms');
1887
- if (stored) {
1896
+ const stored = getLocalStorage('clianta_shown_forms');
1897
+ if (stored) {
1898
+ try {
1888
1899
  const data = JSON.parse(stored);
1889
1900
  this.shownForms = new Set(data.forms || []);
1890
1901
  }
1891
- }
1892
- catch (e) {
1893
- // Ignore storage errors
1902
+ catch {
1903
+ // Ignore parse errors
1904
+ }
1894
1905
  }
1895
1906
  }
1896
1907
  saveShownForms() {
1897
- try {
1898
- localStorage.setItem('clianta_shown_forms', JSON.stringify({
1899
- forms: Array.from(this.shownForms),
1900
- timestamp: Date.now(),
1901
- }));
1902
- }
1903
- catch (e) {
1904
- // Ignore storage errors
1905
- }
1908
+ setLocalStorage('clianta_shown_forms', JSON.stringify({
1909
+ forms: Array.from(this.shownForms),
1910
+ timestamp: Date.now(),
1911
+ }));
1906
1912
  }
1907
1913
  async fetchForms() {
1908
1914
  if (!this.tracker)
@@ -1921,7 +1927,7 @@ class PopupFormsPlugin extends BasePlugin {
1921
1927
  }
1922
1928
  }
1923
1929
  catch (error) {
1924
- console.error('[Clianta] Failed to fetch forms:', error);
1930
+ logger.error('Failed to fetch popup forms:', error);
1925
1931
  }
1926
1932
  }
1927
1933
  shouldShowForm(form) {
@@ -1932,7 +1938,7 @@ class PopupFormsPlugin extends BasePlugin {
1932
1938
  }
1933
1939
  else if (form.showFrequency === 'once_per_session') {
1934
1940
  const sessionKey = `clianta_form_${form._id}_shown`;
1935
- if (sessionStorage.getItem(sessionKey))
1941
+ if (getSessionStorage(sessionKey))
1936
1942
  return false;
1937
1943
  }
1938
1944
  return true;
@@ -2004,7 +2010,7 @@ class PopupFormsPlugin extends BasePlugin {
2004
2010
  // Mark as shown
2005
2011
  this.shownForms.add(form._id);
2006
2012
  this.saveShownForms();
2007
- sessionStorage.setItem(`clianta_form_${form._id}_shown`, 'true');
2013
+ setSessionStorage(`clianta_form_${form._id}_shown`, 'true');
2008
2014
  // Track view
2009
2015
  await this.trackFormView(form._id);
2010
2016
  // Render form
@@ -2320,17 +2326,17 @@ class PopupFormsPlugin extends BasePlugin {
2320
2326
  const redirect = new URL(form.redirectUrl, window.location.origin);
2321
2327
  const isSameOrigin = redirect.origin === window.location.origin;
2322
2328
  const isSafeProtocol = redirect.protocol === 'https:' || redirect.protocol === 'http:';
2323
- if (isSameOrigin || isSafeProtocol) {
2329
+ if (isSameOrigin && isSafeProtocol) {
2324
2330
  setTimeout(() => {
2325
2331
  window.location.href = redirect.href;
2326
2332
  }, 1500);
2327
2333
  }
2328
2334
  else {
2329
- console.warn('[Clianta] Blocked unsafe redirect URL:', form.redirectUrl);
2335
+ logger.warn('Blocked unsafe redirect URL:', form.redirectUrl);
2330
2336
  }
2331
2337
  }
2332
2338
  catch {
2333
- console.warn('[Clianta] Invalid redirect URL:', form.redirectUrl);
2339
+ logger.warn('Invalid redirect URL:', form.redirectUrl);
2334
2340
  }
2335
2341
  }
2336
2342
  // Close after delay
@@ -2343,7 +2349,7 @@ class PopupFormsPlugin extends BasePlugin {
2343
2349
  }
2344
2350
  }
2345
2351
  catch (error) {
2346
- console.error('[Clianta] Form submit error:', error);
2352
+ logger.error('Form submit error:', error);
2347
2353
  if (submitBtn) {
2348
2354
  submitBtn.disabled = false;
2349
2355
  submitBtn.textContent = form.submitButtonText || 'Subscribe';
@@ -2414,7 +2420,7 @@ const STORAGE_KEY_PATTERNS = [
2414
2420
  'token', 'jwt', 'auth', 'user', 'session', 'credential', 'account',
2415
2421
  ];
2416
2422
  /** JWT/user object fields containing email */
2417
- const EMAIL_CLAIMS = ['email', 'sub', 'preferred_username', 'user_email', 'mail', 'emailAddress', 'e_mail'];
2423
+ const EMAIL_CLAIMS = ['email', 'preferred_username', 'user_email', 'mail', 'emailAddress', 'e_mail'];
2418
2424
  /** Full name fields */
2419
2425
  const NAME_CLAIMS = ['name', 'full_name', 'display_name', 'displayName'];
2420
2426
  /** First name fields */
@@ -3358,6 +3364,7 @@ class Tracker {
3358
3364
  this.queue = new EventQueue(this.transport, {
3359
3365
  batchSize: this.config.batchSize,
3360
3366
  flushInterval: this.config.flushInterval,
3367
+ persistMode: this.config.persistMode,
3361
3368
  });
3362
3369
  // Get or create visitor and session IDs based on mode
3363
3370
  this.visitorId = this.createVisitorId();
@@ -3474,10 +3481,13 @@ class Tracker {
3474
3481
  logger.warn('SDK not initialized, event dropped');
3475
3482
  return;
3476
3483
  }
3484
+ const utmParams = getUTMParams();
3477
3485
  const event = {
3478
3486
  workspaceId: this.workspaceId,
3479
3487
  visitorId: this.visitorId,
3480
3488
  sessionId: this.sessionId,
3489
+ contactId: this.contactId ?? undefined,
3490
+ groupId: this.groupId ?? undefined,
3481
3491
  eventType: eventType,
3482
3492
  eventName,
3483
3493
  url: typeof window !== 'undefined' ? window.location.href : '',
@@ -3488,18 +3498,14 @@ class Tracker {
3488
3498
  websiteDomain: typeof window !== 'undefined' ? window.location.hostname : undefined,
3489
3499
  },
3490
3500
  device: getDeviceInfo(),
3491
- ...getUTMParams(),
3501
+ utmSource: utmParams.utmSource,
3502
+ utmMedium: utmParams.utmMedium,
3503
+ utmCampaign: utmParams.utmCampaign,
3504
+ utmTerm: utmParams.utmTerm,
3505
+ utmContent: utmParams.utmContent,
3492
3506
  timestamp: new Date().toISOString(),
3493
3507
  sdkVersion: SDK_VERSION,
3494
3508
  };
3495
- // Attach contactId if known (from a prior identify() call)
3496
- if (this.contactId) {
3497
- event.contactId = this.contactId;
3498
- }
3499
- // Attach groupId if known (from a prior group() call)
3500
- if (this.groupId) {
3501
- event.groupId = this.groupId;
3502
- }
3503
3509
  // Validate event against registered schema (debug mode only)
3504
3510
  this.validateEventSchema(eventType, properties);
3505
3511
  // Check consent before tracking