@v-tilt/browser 1.4.3 → 1.4.4

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/storage.d.ts CHANGED
@@ -1,23 +1,41 @@
1
1
  /**
2
- * Unified Storage Manager - Following PostHog's PostHogPersistence pattern
2
+ * Unified Storage Manager
3
3
  *
4
- * Single class handles all storage operations for both sessions and users.
5
- * Reduces code duplication and ensures consistent cookie handling.
4
+ * Handles all storage operations for sessions and users with consistent
5
+ * cookie handling across different persistence methods.
6
6
  *
7
7
  * Storage methods:
8
- * - cookie: Browser cookies (cross-subdomain support)
9
- * - localStorage: Persistent local storage
10
- * - sessionStorage: Tab-specific storage (cleared on tab close)
11
- * - localStorage+cookie: Both for redundancy
12
- * - memory: In-memory only (no persistence)
8
+ * - `cookie`: Browser cookies with cross-subdomain support
9
+ * - `localStorage`: Persistent local storage
10
+ * - `sessionStorage`: Tab-specific storage (cleared on tab close)
11
+ * - `localStorage+cookie`: Hybrid mode (default) - full data in localStorage,
12
+ * critical identity properties also in cookies for SSR support
13
+ * - `memory`: In-memory only (no persistence)
14
+ *
15
+ * The `localStorage+cookie` mode is designed for traditional server-side
16
+ * rendered websites where each page navigation reloads JavaScript:
17
+ * - Critical properties (anonymous_id, device_id, etc.) are stored in cookies
18
+ * - Cookies ensure identity persists across full page reloads
19
+ * - localStorage provides fast access for SPA-style navigation
20
+ * - Falls back to cookie-only if localStorage is unavailable
13
21
  */
14
22
  import { PersistenceMethod } from "./types";
23
+ /**
24
+ * Critical properties persisted to cookies in `localStorage+cookie` mode.
25
+ * These ensure identity survives full page reloads in traditional SSR websites.
26
+ */
27
+ export declare const COOKIE_PERSISTED_PROPERTIES: readonly ["__vt_anonymous_id", "__vt_device_id", "__vt_distinct_id", "__vt_user_state"];
28
+ /** Session cookie TTL: 30 minutes */
15
29
  export declare const SESSION_COOKIE_MAX_AGE = 1800;
30
+ /** User cookie TTL: 1 year */
16
31
  export declare const USER_COOKIE_MAX_AGE = 31536000;
17
32
  export interface StorageOptions {
18
33
  method: PersistenceMethod;
34
+ /** Enable cross-subdomain cookies (auto-detected if not specified) */
19
35
  cross_subdomain?: boolean;
36
+ /** Use secure cookies (auto-detected from protocol if not specified) */
20
37
  secure?: boolean;
38
+ /** Cookie SameSite attribute */
21
39
  sameSite?: "Strict" | "Lax" | "None";
22
40
  }
23
41
  export interface StorageItem<T = string> {
@@ -25,13 +43,15 @@ export interface StorageItem<T = string> {
25
43
  expiry?: number;
26
44
  }
27
45
  /**
28
- * Auto-detect if cross-subdomain cookies should be enabled
29
- * Returns false for platforms like herokuapp.com, vercel.app, netlify.app
46
+ * Check if cross-subdomain cookies should be enabled.
47
+ * Returns false for shared hosting platforms to prevent cookie conflicts.
30
48
  */
31
49
  export declare function shouldUseCrossSubdomainCookie(): boolean;
32
50
  /**
33
51
  * Unified Storage Manager
52
+ *
34
53
  * Provides consistent storage operations across all persistence methods
54
+ * with automatic fallbacks and SSR support.
35
55
  */
36
56
  export declare class StorageManager {
37
57
  private method;
@@ -39,62 +59,59 @@ export declare class StorageManager {
39
59
  private secure;
40
60
  private sameSite;
41
61
  private memoryStorage;
62
+ private _localStorageSupported;
42
63
  constructor(options: StorageOptions);
64
+ /** Check if localStorage is available (result is cached) */
65
+ private isLocalStorageSupported;
66
+ /** Check if a key should be persisted to cookies */
67
+ private isCriticalProperty;
43
68
  /**
44
- * Get a value from storage
69
+ * Get a value from storage.
70
+ *
71
+ * For `localStorage+cookie` mode:
72
+ * - Critical properties: read cookie first (for SSR), fall back to localStorage
73
+ * - Non-critical properties: read from localStorage only
45
74
  */
46
75
  get(key: string): string | null;
47
76
  /**
48
- * Set a value in storage
49
- * @param maxAge - Cookie max age in seconds (ignored for localStorage/sessionStorage)
77
+ * Set a value in storage.
78
+ * @param maxAge Cookie max age in seconds (only for cookie-based storage)
79
+ *
80
+ * For `localStorage+cookie` mode:
81
+ * - All values stored in localStorage (if available)
82
+ * - Critical properties ALWAYS stored in cookies for SSR support
50
83
  */
51
84
  set(key: string, value: string, maxAge?: number): void;
52
- /**
53
- * Remove a value from storage
54
- */
85
+ /** Remove a value from storage */
55
86
  remove(key: string): void;
56
- /**
57
- * Get JSON value with expiry check
58
- * Returns null if expired or not found
59
- */
87
+ private getLocalStoragePlusCookie;
88
+ private setLocalStoragePlusCookie;
89
+ private removeLocalStoragePlusCookie;
90
+ private getLocalStorage;
91
+ private setLocalStorage;
92
+ private removeLocalStorage;
93
+ private readFromSessionStorage;
94
+ private writeToSessionStorage;
95
+ private removeFromSessionStorage;
96
+ private getCookie;
97
+ private setCookie;
98
+ private removeCookie;
99
+ /** Get JSON value with expiry check */
60
100
  getWithExpiry<T>(key: string): T | null;
61
- /**
62
- * Set JSON value with optional expiry
63
- * @param ttlMs - Time to live in milliseconds
64
- */
101
+ /** Set JSON value with optional TTL */
65
102
  setWithExpiry<T>(key: string, value: T, ttlMs?: number): void;
66
- /**
67
- * Get JSON value (no expiry check)
68
- */
103
+ /** Get parsed JSON value */
69
104
  getJSON<T>(key: string): T | null;
70
- /**
71
- * Set JSON value
72
- */
105
+ /** Set JSON value */
73
106
  setJSON<T>(key: string, value: T, maxAge?: number): void;
74
- private getCookie;
75
- private setCookie;
76
- private removeCookie;
77
- private usesLocalStorage;
78
- private usesSessionStorage;
79
- /**
80
- * Check if sessionStorage is available
81
- */
107
+ /** Check if sessionStorage is available */
82
108
  canUseSessionStorage(): boolean;
83
- /**
84
- * Get direct access to sessionStorage (for window_id which always uses sessionStorage)
85
- */
109
+ /** Get sessionStorage instance (for window_id which always uses sessionStorage) */
86
110
  getSessionStorage(): Storage | null;
87
- /**
88
- * Update storage method at runtime
89
- */
111
+ /** Update storage method at runtime */
90
112
  setMethod(method: PersistenceMethod): void;
91
- /**
92
- * Get current storage method
93
- */
113
+ /** Get current storage method */
94
114
  getMethod(): PersistenceMethod;
95
115
  }
96
- /**
97
- * Create a shared storage instance
98
- * Use this for creating storage managers with consistent settings
99
- */
116
+ /** Factory function to create a StorageManager with default settings */
100
117
  export declare function createStorageManager(method: PersistenceMethod, cross_subdomain?: boolean): StorageManager;
@@ -94,7 +94,21 @@ export declare class UserManager {
94
94
  */
95
95
  update_referrer_info(): void;
96
96
  /**
97
- * Load user identity from storage
97
+ * Load user identity from storage.
98
+ *
99
+ * For traditional SSR websites where each page navigation reloads JavaScript,
100
+ * identity MUST be persisted immediately when generated to ensure the same
101
+ * anonymous_id is used across all page loads.
102
+ *
103
+ * Flow:
104
+ * 1. Load from storage (reads cookies first for critical properties in SSR mode)
105
+ * 2. Generate new IDs if not found
106
+ * 3. Immediately persist to storage (saved to both localStorage and cookies)
107
+ *
108
+ * With `localStorage+cookie` persistence (default):
109
+ * - Critical properties are stored in cookies for SSR compatibility
110
+ * - Full data is stored in localStorage for fast SPA-style access
111
+ * - Cookies ensure identity persists across full page reloads
98
112
  */
99
113
  private loadUserIdentity;
100
114
  /**
@@ -72,7 +72,7 @@ exports.loadScript = loadScript;
72
72
  /**
73
73
  * SDK version - used for loading correct script version from CDN
74
74
  */
75
- const SDK_VERSION = "1.4.3";
75
+ const SDK_VERSION = "1.4.4";
76
76
  /**
77
77
  * Get the URL for an extension script
78
78
  *
@@ -31,7 +31,7 @@
31
31
  * Code config always takes precedence over dashboard settings.
32
32
  */
33
33
  import type { VTilt } from "../../vtilt";
34
- import type { ChatConfig, ChatChannelSummary, ChatWidgetView } from "../../utils/globals";
34
+ import { type ChatConfig, type ChatChannelSummary, type ChatWidgetView } from "../../utils/globals";
35
35
  import type { MessageCallback, TypingCallback, ConnectionCallback, Unsubscribe } from "./types";
36
36
  /** Status when lazy loading is in progress */
37
37
  export declare const CHAT_LOADING: "loading";
@@ -352,13 +352,16 @@ class ChatWrapper {
352
352
  get _isChatEnabled() {
353
353
  var _a;
354
354
  // Code config takes precedence
355
- if (this._config.enabled === false)
355
+ if (this._config.enabled === false) {
356
356
  return false;
357
- if (this._config.enabled === true)
357
+ }
358
+ if (this._config.enabled === true) {
358
359
  return true;
360
+ }
359
361
  // Fall back to server config
360
- if (((_a = this._serverConfig) === null || _a === void 0 ? void 0 : _a.enabled) === true)
362
+ if (((_a = this._serverConfig) === null || _a === void 0 ? void 0 : _a.enabled) === true) {
361
363
  return true;
364
+ }
362
365
  // Default: disabled unless explicitly enabled
363
366
  return false;
364
367
  }
@@ -367,8 +370,9 @@ class ChatWrapper {
367
370
  * This enables "snippet-only" installation where widget configures from dashboard
368
371
  */
369
372
  async _fetchServerSettings() {
370
- if (this._configFetched)
373
+ if (this._configFetched) {
371
374
  return;
375
+ }
372
376
  const config = this._instance.getConfig();
373
377
  const token = config.token;
374
378
  const apiHost = config.api_host;
@@ -399,11 +403,13 @@ class ChatWrapper {
399
403
  * This creates a lightweight bubble that loads the full widget on click
400
404
  */
401
405
  _showBubble() {
402
- if (!(globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.document))
406
+ if (!(globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.document)) {
403
407
  return;
408
+ }
404
409
  // Don't create if already exists
405
- if (document.getElementById("vtilt-chat-bubble"))
410
+ if (document.getElementById("vtilt-chat-bubble")) {
406
411
  return;
412
+ }
407
413
  const mergedConfig = this.getMergedConfig();
408
414
  const position = mergedConfig.position || "bottom-right";
409
415
  const color = mergedConfig.color || "#6366f1";
@@ -2,8 +2,8 @@
2
2
  * Chat Widget - Lazy loaded chat implementation using Ably for real-time messaging.
3
3
  */
4
4
  import type { VTilt } from "../../vtilt";
5
- import type { ChatConfig, ChatChannel, ChatChannelSummary, ChatWidgetView, LazyLoadedChatInterface } from "../../utils/globals";
6
- import type { MessageCallback, TypingCallback, ConnectionCallback, Unsubscribe } from "./types";
5
+ import { type ChatConfig, type ChatChannel, type ChatChannelSummary, type ChatWidgetView, type LazyLoadedChatInterface } from "../../utils/globals";
6
+ import { type MessageCallback, type TypingCallback, type ConnectionCallback, type Unsubscribe } from "./types";
7
7
  export declare class LazyLoadedChat implements LazyLoadedChatInterface {
8
8
  private _instance;
9
9
  private _config;
@@ -117,8 +117,9 @@ class LazyLoadedChat {
117
117
  // ============================================================================
118
118
  open() {
119
119
  var _a;
120
- if (this._state.isOpen)
120
+ if (this._state.isOpen) {
121
121
  return;
122
+ }
122
123
  this._state.isOpen = true;
123
124
  this._updateUI();
124
125
  // Add opening animation
@@ -136,8 +137,9 @@ class LazyLoadedChat {
136
137
  }
137
138
  }
138
139
  close() {
139
- if (!this._state.isOpen)
140
+ if (!this._state.isOpen) {
140
141
  return;
142
+ }
141
143
  const timeOpen = this._getTimeOpen();
142
144
  // Add closing animation
143
145
  if (this._widget) {
@@ -210,11 +212,13 @@ class LazyLoadedChat {
210
212
  this._state.channel = response.channel;
211
213
  this._state.messages = response.messages || [];
212
214
  this._state.currentView = "conversation";
213
- this._state.agentLastReadAt = response.channel.agent_last_read_at || null;
215
+ this._state.agentLastReadAt =
216
+ response.channel.agent_last_read_at || null;
214
217
  this._initialUserReadAt = response.channel.user_last_read_at || null;
215
218
  this._connectRealtime();
216
- if (this._state.isOpen)
219
+ if (this._state.isOpen) {
217
220
  this._autoMarkAsRead();
221
+ }
218
222
  }
219
223
  }
220
224
  catch (error) {
@@ -242,7 +246,8 @@ class LazyLoadedChat {
242
246
  this._state.channel = response.channel;
243
247
  this._state.messages = response.messages || [];
244
248
  this._state.currentView = "conversation";
245
- this._state.agentLastReadAt = response.channel.agent_last_read_at || null;
249
+ this._state.agentLastReadAt =
250
+ response.channel.agent_last_read_at || null;
246
251
  this._initialUserReadAt = response.channel.user_last_read_at || null;
247
252
  const newChannelSummary = {
248
253
  id: response.channel.id,
@@ -286,7 +291,8 @@ class LazyLoadedChat {
286
291
  ? this._state.messages[this._state.messages.length - 1].content.substring(0, 100)
287
292
  : this._state.channels[channelIndex].last_message_preview,
288
293
  last_message_sender: this._state.messages.length > 0
289
- ? this._state.messages[this._state.messages.length - 1].sender_type
294
+ ? this._state.messages[this._state.messages.length - 1]
295
+ .sender_type
290
296
  : this._state.channels[channelIndex].last_message_sender,
291
297
  unread_count: 0, // We just viewed it
292
298
  };
@@ -304,8 +310,9 @@ class LazyLoadedChat {
304
310
  // ============================================================================
305
311
  async sendMessage(content) {
306
312
  var _a, _b, _c, _d;
307
- if (!content.trim())
313
+ if (!content.trim()) {
308
314
  return;
315
+ }
309
316
  // Ensure we're in conversation view with a channel
310
317
  if (!this._state.channel || this._state.currentView !== "conversation") {
311
318
  console.error(`${LOGGER_PREFIX} Cannot send message: not in conversation view`);
@@ -367,14 +374,18 @@ class LazyLoadedChat {
367
374
  this._autoMarkAsRead();
368
375
  }
369
376
  _autoMarkAsRead() {
370
- if (!this._state.channel || this._isMarkingRead)
377
+ if (!this._state.channel || this._isMarkingRead) {
371
378
  return;
379
+ }
372
380
  const latestMessage = this._state.messages[this._state.messages.length - 1];
373
- if (!latestMessage)
381
+ if (!latestMessage) {
374
382
  return;
375
- const hasUnreadAgentMessages = this._state.messages.some((m) => (m.sender_type === "agent" || m.sender_type === "ai") && !this._isMessageReadByUser(m.created_at));
376
- if (!hasUnreadAgentMessages)
383
+ }
384
+ const hasUnreadAgentMessages = this._state.messages.some((m) => (m.sender_type === "agent" || m.sender_type === "ai") &&
385
+ !this._isMessageReadByUser(m.created_at));
386
+ if (!hasUnreadAgentMessages) {
377
387
  return;
388
+ }
378
389
  this._state.unreadCount = 0;
379
390
  this._isMarkingRead = true;
380
391
  this._updateUI();
@@ -397,8 +408,9 @@ class LazyLoadedChat {
397
408
  });
398
409
  }
399
410
  _isMessageReadByUser(messageCreatedAt) {
400
- if (!this._initialUserReadAt)
411
+ if (!this._initialUserReadAt) {
401
412
  return false;
413
+ }
402
414
  return new Date(messageCreatedAt) <= new Date(this._initialUserReadAt);
403
415
  }
404
416
  // ============================================================================
@@ -408,24 +420,27 @@ class LazyLoadedChat {
408
420
  this._messageCallbacks.push(callback);
409
421
  return () => {
410
422
  const index = this._messageCallbacks.indexOf(callback);
411
- if (index > -1)
423
+ if (index > -1) {
412
424
  this._messageCallbacks.splice(index, 1);
425
+ }
413
426
  };
414
427
  }
415
428
  onTyping(callback) {
416
429
  this._typingCallbacks.push(callback);
417
430
  return () => {
418
431
  const index = this._typingCallbacks.indexOf(callback);
419
- if (index > -1)
432
+ if (index > -1) {
420
433
  this._typingCallbacks.splice(index, 1);
434
+ }
421
435
  };
422
436
  }
423
437
  onConnectionChange(callback) {
424
438
  this._connectionCallbacks.push(callback);
425
439
  return () => {
426
440
  const index = this._connectionCallbacks.indexOf(callback);
427
- if (index > -1)
441
+ if (index > -1) {
428
442
  this._connectionCallbacks.splice(index, 1);
443
+ }
429
444
  };
430
445
  }
431
446
  // ============================================================================
@@ -434,8 +449,9 @@ class LazyLoadedChat {
434
449
  destroy() {
435
450
  var _a;
436
451
  this._disconnectRealtime();
437
- if (this._typingDebounce)
452
+ if (this._typingDebounce) {
438
453
  clearTimeout(this._typingDebounce);
454
+ }
439
455
  if ((_a = this._container) === null || _a === void 0 ? void 0 : _a.parentNode) {
440
456
  this._container.parentNode.removeChild(this._container);
441
457
  }
@@ -549,11 +565,15 @@ class LazyLoadedChat {
549
565
  return parts[0] || "";
550
566
  }
551
567
  _handleNewMessage(message) {
552
- if (this._state.messages.some((m) => m.id === message.id))
568
+ if (this._state.messages.some((m) => m.id === message.id)) {
553
569
  return;
570
+ }
554
571
  // Skip own messages but replace temp message if present
555
- if (message.sender_type === "user" && message.sender_id === this._distinctId) {
556
- const tempIndex = this._state.messages.findIndex((m) => m.id.startsWith("temp-") && m.content === message.content && m.sender_type === "user");
572
+ if (message.sender_type === "user" &&
573
+ message.sender_id === this._distinctId) {
574
+ const tempIndex = this._state.messages.findIndex((m) => m.id.startsWith("temp-") &&
575
+ m.content === message.content &&
576
+ m.sender_type === "user");
557
577
  if (tempIndex !== -1) {
558
578
  this._state.messages[tempIndex] = message;
559
579
  this._updateUI();
@@ -581,17 +601,20 @@ class LazyLoadedChat {
581
601
  this._updateUI();
582
602
  }
583
603
  _handleTypingEvent(event) {
584
- if (event.sender_type === "user")
604
+ if (event.sender_type === "user") {
585
605
  return;
586
- const senderName = event.sender_name || (event.sender_type === "ai" ? "AI Assistant" : "Agent");
606
+ }
607
+ const senderName = event.sender_name ||
608
+ (event.sender_type === "ai" ? "AI Assistant" : "Agent");
587
609
  this._state.isTyping = event.is_typing;
588
610
  this._state.typingSender = event.is_typing ? senderName : null;
589
611
  this._typingCallbacks.forEach((cb) => cb(event.is_typing, senderName));
590
612
  this._updateUI();
591
613
  }
592
614
  _handleReadCursorEvent(event) {
593
- if (event.reader_type !== "agent")
615
+ if (event.reader_type !== "agent") {
594
616
  return;
617
+ }
595
618
  this._state.agentLastReadAt = event.read_at;
596
619
  this._updateUI();
597
620
  }
@@ -602,8 +625,9 @@ class LazyLoadedChat {
602
625
  // Private - UI
603
626
  // ============================================================================
604
627
  _createUI() {
605
- if (!globals_1.document)
628
+ if (!globals_1.document) {
606
629
  return;
630
+ }
607
631
  this._container = globals_1.document.createElement("div");
608
632
  this._container.id = "vtilt-chat-container";
609
633
  this._container.setAttribute("style", this._getContainerStyles());
@@ -625,22 +649,25 @@ class LazyLoadedChat {
625
649
  (_c = (_b = this._widget) === null || _b === void 0 ? void 0 : _b.querySelector(".vtilt-chat-close")) === null || _c === void 0 ? void 0 : _c.addEventListener("click", () => this.close());
626
650
  }
627
651
  _handleUserTyping() {
628
- if (!this._typingChannel)
652
+ if (!this._typingChannel) {
629
653
  return;
654
+ }
630
655
  if (!this._isUserTyping) {
631
656
  this._isUserTyping = true;
632
657
  this._sendTypingIndicator(true);
633
658
  }
634
- if (this._typingDebounce)
659
+ if (this._typingDebounce) {
635
660
  clearTimeout(this._typingDebounce);
661
+ }
636
662
  this._typingDebounce = setTimeout(() => {
637
663
  this._isUserTyping = false;
638
664
  this._sendTypingIndicator(false);
639
665
  }, 2000);
640
666
  }
641
667
  _sendTypingIndicator(isTyping) {
642
- if (!this._typingChannel)
668
+ if (!this._typingChannel) {
643
669
  return;
670
+ }
644
671
  try {
645
672
  this._typingChannel.publish("typing", {
646
673
  sender_type: "user",
@@ -656,8 +683,9 @@ class LazyLoadedChat {
656
683
  _handleSend() {
657
684
  var _a;
658
685
  const input = (_a = this._widget) === null || _a === void 0 ? void 0 : _a.querySelector(".vtilt-chat-input");
659
- if (!input)
686
+ if (!input) {
660
687
  return;
688
+ }
661
689
  const content = input.value.trim();
662
690
  if (content) {
663
691
  if (this._isUserTyping) {
@@ -673,8 +701,9 @@ class LazyLoadedChat {
673
701
  }
674
702
  }
675
703
  _updateUI() {
676
- if (!this._container || !this._widget || !this._bubble)
704
+ if (!this._container || !this._widget || !this._bubble) {
677
705
  return;
706
+ }
678
707
  // Update visibility
679
708
  this._container.style.display = this._state.isVisible ? "block" : "none";
680
709
  // Update widget open state
@@ -724,8 +753,9 @@ class LazyLoadedChat {
724
753
  _updateHeader() {
725
754
  var _a, _b;
726
755
  const header = (_a = this._widget) === null || _a === void 0 ? void 0 : _a.querySelector(".vtilt-chat-header");
727
- if (!header)
756
+ if (!header) {
728
757
  return;
758
+ }
729
759
  const greeting = this._config.greeting || "Messages";
730
760
  const primary = this._theme.primaryColor;
731
761
  if (this._state.currentView === "list") {
@@ -981,15 +1011,17 @@ class LazyLoadedChat {
981
1011
  flex: 1;
982
1012
  min-width: 0;
983
1013
  line-height: 1.4;
984
- ">${senderPrefix}${this._escapeHTML(preview)}${channel.status === "closed" ? ' · Closed' : ""}</div>
985
- ${hasUnread ? `<div style="
1014
+ ">${senderPrefix}${this._escapeHTML(preview)}${channel.status === "closed" ? " · Closed" : ""}</div>
1015
+ ${hasUnread
1016
+ ? `<div style="
986
1017
  min-width: 10px;
987
1018
  width: 10px;
988
1019
  height: 10px;
989
1020
  background: ${primary};
990
1021
  border-radius: 50%;
991
1022
  flex-shrink: 0;
992
- "></div>` : ""}
1023
+ "></div>`
1024
+ : ""}
993
1025
  </div>
994
1026
  </div>
995
1027
  </div>
@@ -1097,8 +1129,9 @@ class LazyLoadedChat {
1097
1129
  (_d = (_c = this._widget) === null || _c === void 0 ? void 0 : _c.querySelectorAll(".vtilt-channel-item")) === null || _d === void 0 ? void 0 : _d.forEach((item) => {
1098
1130
  item.addEventListener("click", () => {
1099
1131
  const channelId = item.getAttribute("data-channel-id");
1100
- if (channelId)
1132
+ if (channelId) {
1101
1133
  this.selectChannel(channelId);
1134
+ }
1102
1135
  });
1103
1136
  });
1104
1137
  }
@@ -1121,29 +1154,37 @@ class LazyLoadedChat {
1121
1154
  const diffMins = Math.floor(diffMs / 60000);
1122
1155
  const diffHours = Math.floor(diffMs / 3600000);
1123
1156
  const diffDays = Math.floor(diffMs / 86400000);
1124
- if (diffMins < 1)
1157
+ if (diffMins < 1) {
1125
1158
  return "Just now";
1126
- if (diffMins < 60)
1159
+ }
1160
+ if (diffMins < 60) {
1127
1161
  return `${diffMins}m ago`;
1128
- if (diffHours < 24)
1162
+ }
1163
+ if (diffHours < 24) {
1129
1164
  return `${diffHours}h ago`;
1130
- if (diffDays < 7)
1165
+ }
1166
+ if (diffDays < 7) {
1131
1167
  return `${diffDays}d ago`;
1168
+ }
1132
1169
  return date.toLocaleDateString();
1133
1170
  }
1134
1171
  _renderMessages() {
1135
1172
  var _a;
1136
1173
  const container = (_a = this._widget) === null || _a === void 0 ? void 0 : _a.querySelector(".vtilt-chat-messages");
1137
- if (!container)
1174
+ if (!container) {
1138
1175
  return;
1176
+ }
1139
1177
  const primary = this._theme.primaryColor;
1140
- const firstUnreadIndex = this._state.messages.findIndex((m) => (m.sender_type === "agent" || m.sender_type === "ai") && !this._isMessageReadByUser(m.created_at));
1141
- const html = this._state.messages.map((msg, i) => {
1178
+ const firstUnreadIndex = this._state.messages.findIndex((m) => (m.sender_type === "agent" || m.sender_type === "ai") &&
1179
+ !this._isMessageReadByUser(m.created_at));
1180
+ const html = this._state.messages
1181
+ .map((msg, i) => {
1142
1182
  const divider = i === firstUnreadIndex && firstUnreadIndex > 0
1143
1183
  ? `<div style="display:flex;align-items:center;gap:12px;margin:12px 0"><div style="flex:1;height:1px;background:#DDD"></div><span style="font-size:12px;font-weight:600;color:${primary}">New</span><div style="flex:1;height:1px;background:#DDD"></div></div>`
1144
1184
  : "";
1145
1185
  return divider + this._getMessageHTML(msg);
1146
- }).join("");
1186
+ })
1187
+ .join("");
1147
1188
  container.innerHTML = html;
1148
1189
  container.scrollTop = container.scrollHeight;
1149
1190
  }
@@ -1375,7 +1416,9 @@ class LazyLoadedChat {
1375
1416
  </div>
1376
1417
  `;
1377
1418
  }
1378
- const senderLabel = isAi ? "AI Assistant" : (message.sender_name || "Support");
1419
+ const senderLabel = isAi
1420
+ ? "AI Assistant"
1421
+ : message.sender_name || "Support";
1379
1422
  return `
1380
1423
  <div class="vtilt-msg" style="
1381
1424
  display: flex;
@@ -1416,8 +1459,9 @@ class LazyLoadedChat {
1416
1459
  `;
1417
1460
  }
1418
1461
  _isMessageReadByAgent(messageCreatedAt) {
1419
- if (!this._state.agentLastReadAt)
1462
+ if (!this._state.agentLastReadAt) {
1420
1463
  return false;
1464
+ }
1421
1465
  return new Date(messageCreatedAt) <= new Date(this._state.agentLastReadAt);
1422
1466
  }
1423
1467
  // ============================================================================
@@ -1456,8 +1500,9 @@ class LazyLoadedChat {
1456
1500
  return 0;
1457
1501
  }
1458
1502
  _escapeHTML(text) {
1459
- if (!globals_1.document)
1503
+ if (!globals_1.document) {
1460
1504
  return text;
1505
+ }
1461
1506
  const div = globals_1.document.createElement("div");
1462
1507
  div.textContent = text;
1463
1508
  return div.innerHTML;