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