@wabbit-dashboard/embed 1.1.0 → 1.1.1
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/README.md +30 -0
- package/dist/wabbit-embed.cjs.js +175 -101
- package/dist/wabbit-embed.cjs.js.map +1 -1
- package/dist/wabbit-embed.d.ts +44 -42
- package/dist/wabbit-embed.esm.js +175 -101
- package/dist/wabbit-embed.esm.js.map +1 -1
- package/dist/wabbit-embed.umd.js +175 -101
- package/dist/wabbit-embed.umd.js.map +1 -1
- package/dist/wabbit-embed.umd.min.js +1 -1
- package/dist/wabbit-embed.umd.min.js.map +1 -1
- package/package.json +1 -1
package/dist/wabbit-embed.umd.js
CHANGED
|
@@ -122,6 +122,143 @@
|
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* localStorage wrapper with type safety
|
|
127
|
+
*/
|
|
128
|
+
/**
|
|
129
|
+
* Memory storage implementation (clears on page reload)
|
|
130
|
+
*/
|
|
131
|
+
class MemoryStorage {
|
|
132
|
+
constructor() {
|
|
133
|
+
this.store = new Map();
|
|
134
|
+
}
|
|
135
|
+
getItem(key) {
|
|
136
|
+
return this.store.get(key) ?? null;
|
|
137
|
+
}
|
|
138
|
+
setItem(key, value) {
|
|
139
|
+
this.store.set(key, value);
|
|
140
|
+
}
|
|
141
|
+
removeItem(key) {
|
|
142
|
+
this.store.delete(key);
|
|
143
|
+
}
|
|
144
|
+
clear() {
|
|
145
|
+
this.store.clear();
|
|
146
|
+
}
|
|
147
|
+
get length() {
|
|
148
|
+
return this.store.size;
|
|
149
|
+
}
|
|
150
|
+
key(index) {
|
|
151
|
+
const keys = Array.from(this.store.keys());
|
|
152
|
+
return keys[index] ?? null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Safe storage wrapper
|
|
157
|
+
*/
|
|
158
|
+
class SafeStorage {
|
|
159
|
+
constructor(storage = localStorage, prefix = 'wabbit_') {
|
|
160
|
+
this.storage = storage;
|
|
161
|
+
this.prefix = prefix;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Get item from storage
|
|
165
|
+
*/
|
|
166
|
+
get(key) {
|
|
167
|
+
try {
|
|
168
|
+
const item = this.storage.getItem(this.prefix + key);
|
|
169
|
+
if (item === null) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
return JSON.parse(item);
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
console.error(`[Wabbit] Failed to get storage item "${key}":`, error);
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Set item in storage
|
|
181
|
+
*/
|
|
182
|
+
set(key, value) {
|
|
183
|
+
try {
|
|
184
|
+
this.storage.setItem(this.prefix + key, JSON.stringify(value));
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
console.error(`[Wabbit] Failed to set storage item "${key}":`, error);
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Remove item from storage
|
|
194
|
+
*/
|
|
195
|
+
remove(key) {
|
|
196
|
+
this.storage.removeItem(this.prefix + key);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Clear all items with prefix
|
|
200
|
+
*/
|
|
201
|
+
clear() {
|
|
202
|
+
if (this.storage.length !== undefined && this.storage.key) {
|
|
203
|
+
const keys = [];
|
|
204
|
+
for (let i = 0; i < this.storage.length; i++) {
|
|
205
|
+
const key = this.storage.key(i);
|
|
206
|
+
if (key && key.startsWith(this.prefix)) {
|
|
207
|
+
keys.push(key);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
keys.forEach((key) => this.storage.removeItem(key));
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
// Fallback: try to clear common keys
|
|
214
|
+
const commonKeys = ['session_id', 'email_capture_dismissed'];
|
|
215
|
+
commonKeys.forEach((key) => this.remove(key));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Export singleton instance (for backward compatibility)
|
|
220
|
+
const storage = new SafeStorage();
|
|
221
|
+
/**
|
|
222
|
+
* Create a storage instance with the specified backend
|
|
223
|
+
*
|
|
224
|
+
* @param persistSession - Whether to persist sessions across browser sessions
|
|
225
|
+
* @returns SafeStorage instance with appropriate backend
|
|
226
|
+
*/
|
|
227
|
+
function createStorage(persistSession = true) {
|
|
228
|
+
let storageBackend;
|
|
229
|
+
if (persistSession) {
|
|
230
|
+
// Use localStorage (persists across browser sessions)
|
|
231
|
+
storageBackend = typeof window !== 'undefined' ? window.localStorage : new MemoryStorage();
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
// Use sessionStorage (clears when browser closes)
|
|
235
|
+
storageBackend = typeof window !== 'undefined' ? window.sessionStorage : new MemoryStorage();
|
|
236
|
+
}
|
|
237
|
+
return new SafeStorage(storageBackend);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Get item from storage (simple string getter)
|
|
241
|
+
*/
|
|
242
|
+
function getStorageItem(key) {
|
|
243
|
+
try {
|
|
244
|
+
return localStorage.getItem('wabbit_' + key);
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Set item in storage (simple string setter)
|
|
252
|
+
*/
|
|
253
|
+
function setStorageItem(key, value) {
|
|
254
|
+
try {
|
|
255
|
+
localStorage.setItem('wabbit_' + key, value);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
console.error(`[Wabbit] Failed to set storage item "${key}":`, error);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
125
262
|
/**
|
|
126
263
|
* Main Wabbit SDK class
|
|
127
264
|
*
|
|
@@ -138,6 +275,8 @@
|
|
|
138
275
|
this.formsWidget = null; // FormWidget | null
|
|
139
276
|
this.emailCaptureWidget = null; // EmailCapture | null
|
|
140
277
|
this.config = config;
|
|
278
|
+
// Create storage instance with appropriate backend based on persistSession
|
|
279
|
+
this.storage = createStorage(config.persistSession ?? true);
|
|
141
280
|
}
|
|
142
281
|
/**
|
|
143
282
|
* Initialize the Wabbit SDK
|
|
@@ -217,7 +356,7 @@
|
|
|
217
356
|
}
|
|
218
357
|
}
|
|
219
358
|
};
|
|
220
|
-
const chatWidget = new ChatWidget(chatConfig);
|
|
359
|
+
const chatWidget = new ChatWidget(chatConfig, Wabbit.instance.storage);
|
|
221
360
|
chatWidget.init();
|
|
222
361
|
Wabbit.instance.chatWidget = chatWidget;
|
|
223
362
|
});
|
|
@@ -540,105 +679,13 @@
|
|
|
540
679
|
}
|
|
541
680
|
Wabbit.instance = null;
|
|
542
681
|
|
|
543
|
-
/**
|
|
544
|
-
* localStorage wrapper with type safety
|
|
545
|
-
*/
|
|
546
|
-
/**
|
|
547
|
-
* Safe storage wrapper
|
|
548
|
-
*/
|
|
549
|
-
class SafeStorage {
|
|
550
|
-
constructor(storage = localStorage, prefix = 'wabbit_') {
|
|
551
|
-
this.storage = storage;
|
|
552
|
-
this.prefix = prefix;
|
|
553
|
-
}
|
|
554
|
-
/**
|
|
555
|
-
* Get item from storage
|
|
556
|
-
*/
|
|
557
|
-
get(key) {
|
|
558
|
-
try {
|
|
559
|
-
const item = this.storage.getItem(this.prefix + key);
|
|
560
|
-
if (item === null) {
|
|
561
|
-
return null;
|
|
562
|
-
}
|
|
563
|
-
return JSON.parse(item);
|
|
564
|
-
}
|
|
565
|
-
catch (error) {
|
|
566
|
-
console.error(`[Wabbit] Failed to get storage item "${key}":`, error);
|
|
567
|
-
return null;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
/**
|
|
571
|
-
* Set item in storage
|
|
572
|
-
*/
|
|
573
|
-
set(key, value) {
|
|
574
|
-
try {
|
|
575
|
-
this.storage.setItem(this.prefix + key, JSON.stringify(value));
|
|
576
|
-
return true;
|
|
577
|
-
}
|
|
578
|
-
catch (error) {
|
|
579
|
-
console.error(`[Wabbit] Failed to set storage item "${key}":`, error);
|
|
580
|
-
return false;
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
/**
|
|
584
|
-
* Remove item from storage
|
|
585
|
-
*/
|
|
586
|
-
remove(key) {
|
|
587
|
-
this.storage.removeItem(this.prefix + key);
|
|
588
|
-
}
|
|
589
|
-
/**
|
|
590
|
-
* Clear all items with prefix
|
|
591
|
-
*/
|
|
592
|
-
clear() {
|
|
593
|
-
if (this.storage.length !== undefined && this.storage.key) {
|
|
594
|
-
const keys = [];
|
|
595
|
-
for (let i = 0; i < this.storage.length; i++) {
|
|
596
|
-
const key = this.storage.key(i);
|
|
597
|
-
if (key && key.startsWith(this.prefix)) {
|
|
598
|
-
keys.push(key);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
keys.forEach((key) => this.storage.removeItem(key));
|
|
602
|
-
}
|
|
603
|
-
else {
|
|
604
|
-
// Fallback: try to clear common keys
|
|
605
|
-
const commonKeys = ['session_id', 'email_capture_dismissed'];
|
|
606
|
-
commonKeys.forEach((key) => this.remove(key));
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
// Export singleton instance
|
|
611
|
-
const storage = new SafeStorage();
|
|
612
|
-
/**
|
|
613
|
-
* Get item from storage (simple string getter)
|
|
614
|
-
*/
|
|
615
|
-
function getStorageItem(key) {
|
|
616
|
-
try {
|
|
617
|
-
return localStorage.getItem('wabbit_' + key);
|
|
618
|
-
}
|
|
619
|
-
catch {
|
|
620
|
-
return null;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
/**
|
|
624
|
-
* Set item in storage (simple string setter)
|
|
625
|
-
*/
|
|
626
|
-
function setStorageItem(key, value) {
|
|
627
|
-
try {
|
|
628
|
-
localStorage.setItem('wabbit_' + key, value);
|
|
629
|
-
}
|
|
630
|
-
catch (error) {
|
|
631
|
-
console.error(`[Wabbit] Failed to set storage item "${key}":`, error);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
682
|
/**
|
|
636
683
|
* WebSocket client for chat functionality
|
|
637
684
|
*
|
|
638
685
|
* Based on demo-website/src/lib/websocket.ts but without React dependencies
|
|
639
686
|
*/
|
|
640
687
|
class ChatWebSocketClient {
|
|
641
|
-
constructor(apiKey, wsUrl, sessionId = null) {
|
|
688
|
+
constructor(apiKey, wsUrl, sessionId = null, storage) {
|
|
642
689
|
this.ws = null;
|
|
643
690
|
this.status = 'disconnected';
|
|
644
691
|
this.reconnectAttempts = 0;
|
|
@@ -661,6 +708,7 @@
|
|
|
661
708
|
this.onMessageEnd = null;
|
|
662
709
|
this.apiKey = apiKey;
|
|
663
710
|
this.wsUrl = wsUrl;
|
|
711
|
+
this.storage = storage;
|
|
664
712
|
this.sessionId = sessionId || this.getStoredSessionId();
|
|
665
713
|
}
|
|
666
714
|
setStatus(status) {
|
|
@@ -729,9 +777,9 @@
|
|
|
729
777
|
switch (data.type) {
|
|
730
778
|
case 'welcome':
|
|
731
779
|
this.sessionId = data.session_id;
|
|
732
|
-
// Store session ID in localStorage
|
|
780
|
+
// Store session ID in storage (localStorage or sessionStorage)
|
|
733
781
|
if (this.sessionId) {
|
|
734
|
-
storage.set('session_id', this.sessionId);
|
|
782
|
+
this.storage.set('session_id', this.sessionId);
|
|
735
783
|
}
|
|
736
784
|
if (this.onWelcome) {
|
|
737
785
|
this.onWelcome(data.session_id, data.collection_id, data.message || 'Connected');
|
|
@@ -857,7 +905,7 @@
|
|
|
857
905
|
}
|
|
858
906
|
clearSession() {
|
|
859
907
|
this.sessionId = null;
|
|
860
|
-
storage.remove('session_id');
|
|
908
|
+
this.storage.remove('session_id');
|
|
861
909
|
// Clear message queue only when user explicitly starts new session
|
|
862
910
|
// Queue should persist across reconnection attempts
|
|
863
911
|
this.messageQueue = [];
|
|
@@ -870,7 +918,7 @@
|
|
|
870
918
|
this.messageQueue = [];
|
|
871
919
|
}
|
|
872
920
|
getStoredSessionId() {
|
|
873
|
-
return storage.get('session_id') || null;
|
|
921
|
+
return this.storage.get('session_id') || null;
|
|
874
922
|
}
|
|
875
923
|
/**
|
|
876
924
|
* Get current message queue size
|
|
@@ -1240,10 +1288,21 @@
|
|
|
1240
1288
|
formatMessage(content) {
|
|
1241
1289
|
// Escape HTML first
|
|
1242
1290
|
let formatted = escapeHtml(content);
|
|
1291
|
+
// Markdown links [text](url) - must come before URL auto-linking
|
|
1292
|
+
formatted = formatted.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
1243
1293
|
// Simple markdown-like formatting
|
|
1244
1294
|
formatted = formatted.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
1245
1295
|
formatted = formatted.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
1246
1296
|
formatted = formatted.replace(/`(.+?)`/g, '<code style="background: rgba(0,0,0,0.1); padding: 2px 4px; border-radius: 3px;">$1</code>');
|
|
1297
|
+
// Auto-link URLs (not already inside an href attribute)
|
|
1298
|
+
formatted = formatted.replace(/(?<!href=")(https?:\/\/[^\s<]+)/g, '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
1299
|
+
// Auto-link email addresses
|
|
1300
|
+
formatted = formatted.replace(/(?<!["\/])([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g, '<a href="mailto:$1">$1</a>');
|
|
1301
|
+
// Auto-link phone numbers (international format with +)
|
|
1302
|
+
formatted = formatted.replace(/(?<!["\/])(\+\d[\d\s-]{7,})/g, (_match, phone) => {
|
|
1303
|
+
const cleanPhone = phone.replace(/[\s-]/g, '');
|
|
1304
|
+
return `<a href="tel:${cleanPhone}">${phone}</a>`;
|
|
1305
|
+
});
|
|
1247
1306
|
formatted = formatted.replace(/\n/g, '<br>');
|
|
1248
1307
|
return formatted;
|
|
1249
1308
|
}
|
|
@@ -1796,6 +1855,20 @@
|
|
|
1796
1855
|
line-height: 1.5;
|
|
1797
1856
|
}
|
|
1798
1857
|
|
|
1858
|
+
.wabbit-chat-message-content a {
|
|
1859
|
+
color: var(--wabbit-primary);
|
|
1860
|
+
text-decoration: underline;
|
|
1861
|
+
text-underline-offset: 2px;
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
.wabbit-chat-message-content a:hover {
|
|
1865
|
+
opacity: 0.8;
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
.wabbit-chat-message-user .wabbit-chat-message-content a {
|
|
1869
|
+
color: inherit;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1799
1872
|
/* Typing Indicator */
|
|
1800
1873
|
.wabbit-chat-typing {
|
|
1801
1874
|
display: flex;
|
|
@@ -2031,7 +2104,7 @@
|
|
|
2031
2104
|
* Chat Widget - Main class that integrates all chat components
|
|
2032
2105
|
*/
|
|
2033
2106
|
class ChatWidget {
|
|
2034
|
-
constructor(config) {
|
|
2107
|
+
constructor(config, storage) {
|
|
2035
2108
|
this.wsClient = null;
|
|
2036
2109
|
this.bubble = null;
|
|
2037
2110
|
this.panel = null;
|
|
@@ -2041,6 +2114,7 @@
|
|
|
2041
2114
|
this.onChatReadyCallback = null;
|
|
2042
2115
|
this.chatReadyFired = false;
|
|
2043
2116
|
this.config = config;
|
|
2117
|
+
this.storage = storage;
|
|
2044
2118
|
this.onChatReadyCallback = config.onChatReady || null;
|
|
2045
2119
|
}
|
|
2046
2120
|
/**
|
|
@@ -2069,8 +2143,8 @@
|
|
|
2069
2143
|
this.setupThemeWatcher();
|
|
2070
2144
|
// Create WebSocket client
|
|
2071
2145
|
const wsUrl = this.getWebSocketUrl();
|
|
2072
|
-
this.wsClient = new ChatWebSocketClient(this.config.apiKey || '', wsUrl, null // Will use stored session if available
|
|
2073
|
-
);
|
|
2146
|
+
this.wsClient = new ChatWebSocketClient(this.config.apiKey || '', wsUrl, null, // Will use stored session if available
|
|
2147
|
+
this.storage);
|
|
2074
2148
|
// Set up event handlers
|
|
2075
2149
|
this.setupWebSocketHandlers();
|
|
2076
2150
|
// Only create bubble for widget mode (not inline)
|