@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/README.md
CHANGED
|
@@ -127,6 +127,34 @@ Wabbit.init({
|
|
|
127
127
|
});
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
+
### Session Persistence
|
|
131
|
+
|
|
132
|
+
Control whether chat sessions persist across browser sessions:
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
Wabbit.init({
|
|
136
|
+
apiKey: 'pk_live_xxx',
|
|
137
|
+
persistSession: true, // Default: sessions persist (uses localStorage)
|
|
138
|
+
chat: {
|
|
139
|
+
enabled: true,
|
|
140
|
+
collectionId: 'abc123'
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Options:**
|
|
146
|
+
- `persistSession: true` (default) - Sessions persist across browser close/reopen using **localStorage**. Users can continue conversations after reopening the browser.
|
|
147
|
+
- `persistSession: false` - Sessions are cleared when browser closes using **sessionStorage**. Users start fresh each browser session, but conversations persist during page refreshes.
|
|
148
|
+
|
|
149
|
+
**Use Cases:**
|
|
150
|
+
- **Persistent (true)**: Customer support, ongoing consultations, learning platforms
|
|
151
|
+
- **Ephemeral (false)**: Sensitive data, temporary assistance, demo sites
|
|
152
|
+
|
|
153
|
+
**Technical Details:**
|
|
154
|
+
- `true`: Session ID stored in localStorage, survives browser restart
|
|
155
|
+
- `false`: Session ID stored in sessionStorage, cleared on browser close but survives page refresh
|
|
156
|
+
- Message history fetched from server on reconnect (not stored locally)
|
|
157
|
+
|
|
130
158
|
## Testing
|
|
131
159
|
|
|
132
160
|
### Option 1: Copy Embed Code from Dashboard (Recommended)
|
|
@@ -266,6 +294,7 @@ Initialize the Wabbit SDK.
|
|
|
266
294
|
**Parameters:**
|
|
267
295
|
- `config.apiKey` (string, required): Your Wabbit API key
|
|
268
296
|
- `config.apiUrl` (string, optional): API base URL (default: auto-detected)
|
|
297
|
+
- `config.persistSession` (boolean, optional): Whether to persist sessions across browser sessions (default: `true`)
|
|
269
298
|
- `config.chat` (ChatConfig, optional): Chat widget configuration
|
|
270
299
|
- `config.forms` (FormConfig, optional): Form widget configuration
|
|
271
300
|
- `config.emailCapture` (EmailCaptureConfig, optional): Email capture configuration
|
|
@@ -274,6 +303,7 @@ Initialize the Wabbit SDK.
|
|
|
274
303
|
```javascript
|
|
275
304
|
Wabbit.init({
|
|
276
305
|
apiKey: 'pk_live_xxx',
|
|
306
|
+
persistSession: true, // Optional: default is true
|
|
277
307
|
chat: { enabled: true, collectionId: 'abc123' }
|
|
278
308
|
});
|
|
279
309
|
```
|
package/dist/wabbit-embed.cjs.js
CHANGED
|
@@ -120,6 +120,143 @@ function mergeConfig(config) {
|
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* localStorage wrapper with type safety
|
|
125
|
+
*/
|
|
126
|
+
/**
|
|
127
|
+
* Memory storage implementation (clears on page reload)
|
|
128
|
+
*/
|
|
129
|
+
class MemoryStorage {
|
|
130
|
+
constructor() {
|
|
131
|
+
this.store = new Map();
|
|
132
|
+
}
|
|
133
|
+
getItem(key) {
|
|
134
|
+
return this.store.get(key) ?? null;
|
|
135
|
+
}
|
|
136
|
+
setItem(key, value) {
|
|
137
|
+
this.store.set(key, value);
|
|
138
|
+
}
|
|
139
|
+
removeItem(key) {
|
|
140
|
+
this.store.delete(key);
|
|
141
|
+
}
|
|
142
|
+
clear() {
|
|
143
|
+
this.store.clear();
|
|
144
|
+
}
|
|
145
|
+
get length() {
|
|
146
|
+
return this.store.size;
|
|
147
|
+
}
|
|
148
|
+
key(index) {
|
|
149
|
+
const keys = Array.from(this.store.keys());
|
|
150
|
+
return keys[index] ?? null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Safe storage wrapper
|
|
155
|
+
*/
|
|
156
|
+
class SafeStorage {
|
|
157
|
+
constructor(storage = localStorage, prefix = 'wabbit_') {
|
|
158
|
+
this.storage = storage;
|
|
159
|
+
this.prefix = prefix;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get item from storage
|
|
163
|
+
*/
|
|
164
|
+
get(key) {
|
|
165
|
+
try {
|
|
166
|
+
const item = this.storage.getItem(this.prefix + key);
|
|
167
|
+
if (item === null) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
return JSON.parse(item);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
console.error(`[Wabbit] Failed to get storage item "${key}":`, error);
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Set item in storage
|
|
179
|
+
*/
|
|
180
|
+
set(key, value) {
|
|
181
|
+
try {
|
|
182
|
+
this.storage.setItem(this.prefix + key, JSON.stringify(value));
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.error(`[Wabbit] Failed to set storage item "${key}":`, error);
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Remove item from storage
|
|
192
|
+
*/
|
|
193
|
+
remove(key) {
|
|
194
|
+
this.storage.removeItem(this.prefix + key);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Clear all items with prefix
|
|
198
|
+
*/
|
|
199
|
+
clear() {
|
|
200
|
+
if (this.storage.length !== undefined && this.storage.key) {
|
|
201
|
+
const keys = [];
|
|
202
|
+
for (let i = 0; i < this.storage.length; i++) {
|
|
203
|
+
const key = this.storage.key(i);
|
|
204
|
+
if (key && key.startsWith(this.prefix)) {
|
|
205
|
+
keys.push(key);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
keys.forEach((key) => this.storage.removeItem(key));
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
// Fallback: try to clear common keys
|
|
212
|
+
const commonKeys = ['session_id', 'email_capture_dismissed'];
|
|
213
|
+
commonKeys.forEach((key) => this.remove(key));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Export singleton instance (for backward compatibility)
|
|
218
|
+
const storage = new SafeStorage();
|
|
219
|
+
/**
|
|
220
|
+
* Create a storage instance with the specified backend
|
|
221
|
+
*
|
|
222
|
+
* @param persistSession - Whether to persist sessions across browser sessions
|
|
223
|
+
* @returns SafeStorage instance with appropriate backend
|
|
224
|
+
*/
|
|
225
|
+
function createStorage(persistSession = true) {
|
|
226
|
+
let storageBackend;
|
|
227
|
+
if (persistSession) {
|
|
228
|
+
// Use localStorage (persists across browser sessions)
|
|
229
|
+
storageBackend = typeof window !== 'undefined' ? window.localStorage : new MemoryStorage();
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
// Use sessionStorage (clears when browser closes)
|
|
233
|
+
storageBackend = typeof window !== 'undefined' ? window.sessionStorage : new MemoryStorage();
|
|
234
|
+
}
|
|
235
|
+
return new SafeStorage(storageBackend);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get item from storage (simple string getter)
|
|
239
|
+
*/
|
|
240
|
+
function getStorageItem(key) {
|
|
241
|
+
try {
|
|
242
|
+
return localStorage.getItem('wabbit_' + key);
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Set item in storage (simple string setter)
|
|
250
|
+
*/
|
|
251
|
+
function setStorageItem(key, value) {
|
|
252
|
+
try {
|
|
253
|
+
localStorage.setItem('wabbit_' + key, value);
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
console.error(`[Wabbit] Failed to set storage item "${key}":`, error);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
123
260
|
/**
|
|
124
261
|
* Main Wabbit SDK class
|
|
125
262
|
*
|
|
@@ -136,6 +273,8 @@ class Wabbit {
|
|
|
136
273
|
this.formsWidget = null; // FormWidget | null
|
|
137
274
|
this.emailCaptureWidget = null; // EmailCapture | null
|
|
138
275
|
this.config = config;
|
|
276
|
+
// Create storage instance with appropriate backend based on persistSession
|
|
277
|
+
this.storage = createStorage(config.persistSession ?? true);
|
|
139
278
|
}
|
|
140
279
|
/**
|
|
141
280
|
* Initialize the Wabbit SDK
|
|
@@ -215,7 +354,7 @@ class Wabbit {
|
|
|
215
354
|
}
|
|
216
355
|
}
|
|
217
356
|
};
|
|
218
|
-
const chatWidget = new ChatWidget(chatConfig);
|
|
357
|
+
const chatWidget = new ChatWidget(chatConfig, Wabbit.instance.storage);
|
|
219
358
|
chatWidget.init();
|
|
220
359
|
Wabbit.instance.chatWidget = chatWidget;
|
|
221
360
|
});
|
|
@@ -538,105 +677,13 @@ class Wabbit {
|
|
|
538
677
|
}
|
|
539
678
|
Wabbit.instance = null;
|
|
540
679
|
|
|
541
|
-
/**
|
|
542
|
-
* localStorage wrapper with type safety
|
|
543
|
-
*/
|
|
544
|
-
/**
|
|
545
|
-
* Safe storage wrapper
|
|
546
|
-
*/
|
|
547
|
-
class SafeStorage {
|
|
548
|
-
constructor(storage = localStorage, prefix = 'wabbit_') {
|
|
549
|
-
this.storage = storage;
|
|
550
|
-
this.prefix = prefix;
|
|
551
|
-
}
|
|
552
|
-
/**
|
|
553
|
-
* Get item from storage
|
|
554
|
-
*/
|
|
555
|
-
get(key) {
|
|
556
|
-
try {
|
|
557
|
-
const item = this.storage.getItem(this.prefix + key);
|
|
558
|
-
if (item === null) {
|
|
559
|
-
return null;
|
|
560
|
-
}
|
|
561
|
-
return JSON.parse(item);
|
|
562
|
-
}
|
|
563
|
-
catch (error) {
|
|
564
|
-
console.error(`[Wabbit] Failed to get storage item "${key}":`, error);
|
|
565
|
-
return null;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
/**
|
|
569
|
-
* Set item in storage
|
|
570
|
-
*/
|
|
571
|
-
set(key, value) {
|
|
572
|
-
try {
|
|
573
|
-
this.storage.setItem(this.prefix + key, JSON.stringify(value));
|
|
574
|
-
return true;
|
|
575
|
-
}
|
|
576
|
-
catch (error) {
|
|
577
|
-
console.error(`[Wabbit] Failed to set storage item "${key}":`, error);
|
|
578
|
-
return false;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
/**
|
|
582
|
-
* Remove item from storage
|
|
583
|
-
*/
|
|
584
|
-
remove(key) {
|
|
585
|
-
this.storage.removeItem(this.prefix + key);
|
|
586
|
-
}
|
|
587
|
-
/**
|
|
588
|
-
* Clear all items with prefix
|
|
589
|
-
*/
|
|
590
|
-
clear() {
|
|
591
|
-
if (this.storage.length !== undefined && this.storage.key) {
|
|
592
|
-
const keys = [];
|
|
593
|
-
for (let i = 0; i < this.storage.length; i++) {
|
|
594
|
-
const key = this.storage.key(i);
|
|
595
|
-
if (key && key.startsWith(this.prefix)) {
|
|
596
|
-
keys.push(key);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
keys.forEach((key) => this.storage.removeItem(key));
|
|
600
|
-
}
|
|
601
|
-
else {
|
|
602
|
-
// Fallback: try to clear common keys
|
|
603
|
-
const commonKeys = ['session_id', 'email_capture_dismissed'];
|
|
604
|
-
commonKeys.forEach((key) => this.remove(key));
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
// Export singleton instance
|
|
609
|
-
const storage = new SafeStorage();
|
|
610
|
-
/**
|
|
611
|
-
* Get item from storage (simple string getter)
|
|
612
|
-
*/
|
|
613
|
-
function getStorageItem(key) {
|
|
614
|
-
try {
|
|
615
|
-
return localStorage.getItem('wabbit_' + key);
|
|
616
|
-
}
|
|
617
|
-
catch {
|
|
618
|
-
return null;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* Set item in storage (simple string setter)
|
|
623
|
-
*/
|
|
624
|
-
function setStorageItem(key, value) {
|
|
625
|
-
try {
|
|
626
|
-
localStorage.setItem('wabbit_' + key, value);
|
|
627
|
-
}
|
|
628
|
-
catch (error) {
|
|
629
|
-
console.error(`[Wabbit] Failed to set storage item "${key}":`, error);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
680
|
/**
|
|
634
681
|
* WebSocket client for chat functionality
|
|
635
682
|
*
|
|
636
683
|
* Based on demo-website/src/lib/websocket.ts but without React dependencies
|
|
637
684
|
*/
|
|
638
685
|
class ChatWebSocketClient {
|
|
639
|
-
constructor(apiKey, wsUrl, sessionId = null) {
|
|
686
|
+
constructor(apiKey, wsUrl, sessionId = null, storage) {
|
|
640
687
|
this.ws = null;
|
|
641
688
|
this.status = 'disconnected';
|
|
642
689
|
this.reconnectAttempts = 0;
|
|
@@ -659,6 +706,7 @@ class ChatWebSocketClient {
|
|
|
659
706
|
this.onMessageEnd = null;
|
|
660
707
|
this.apiKey = apiKey;
|
|
661
708
|
this.wsUrl = wsUrl;
|
|
709
|
+
this.storage = storage;
|
|
662
710
|
this.sessionId = sessionId || this.getStoredSessionId();
|
|
663
711
|
}
|
|
664
712
|
setStatus(status) {
|
|
@@ -727,9 +775,9 @@ class ChatWebSocketClient {
|
|
|
727
775
|
switch (data.type) {
|
|
728
776
|
case 'welcome':
|
|
729
777
|
this.sessionId = data.session_id;
|
|
730
|
-
// Store session ID in localStorage
|
|
778
|
+
// Store session ID in storage (localStorage or sessionStorage)
|
|
731
779
|
if (this.sessionId) {
|
|
732
|
-
storage.set('session_id', this.sessionId);
|
|
780
|
+
this.storage.set('session_id', this.sessionId);
|
|
733
781
|
}
|
|
734
782
|
if (this.onWelcome) {
|
|
735
783
|
this.onWelcome(data.session_id, data.collection_id, data.message || 'Connected');
|
|
@@ -855,7 +903,7 @@ class ChatWebSocketClient {
|
|
|
855
903
|
}
|
|
856
904
|
clearSession() {
|
|
857
905
|
this.sessionId = null;
|
|
858
|
-
storage.remove('session_id');
|
|
906
|
+
this.storage.remove('session_id');
|
|
859
907
|
// Clear message queue only when user explicitly starts new session
|
|
860
908
|
// Queue should persist across reconnection attempts
|
|
861
909
|
this.messageQueue = [];
|
|
@@ -868,7 +916,7 @@ class ChatWebSocketClient {
|
|
|
868
916
|
this.messageQueue = [];
|
|
869
917
|
}
|
|
870
918
|
getStoredSessionId() {
|
|
871
|
-
return storage.get('session_id') || null;
|
|
919
|
+
return this.storage.get('session_id') || null;
|
|
872
920
|
}
|
|
873
921
|
/**
|
|
874
922
|
* Get current message queue size
|
|
@@ -1238,10 +1286,21 @@ class ChatPanel {
|
|
|
1238
1286
|
formatMessage(content) {
|
|
1239
1287
|
// Escape HTML first
|
|
1240
1288
|
let formatted = escapeHtml(content);
|
|
1289
|
+
// Markdown links [text](url) - must come before URL auto-linking
|
|
1290
|
+
formatted = formatted.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
1241
1291
|
// Simple markdown-like formatting
|
|
1242
1292
|
formatted = formatted.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
1243
1293
|
formatted = formatted.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
1244
1294
|
formatted = formatted.replace(/`(.+?)`/g, '<code style="background: rgba(0,0,0,0.1); padding: 2px 4px; border-radius: 3px;">$1</code>');
|
|
1295
|
+
// Auto-link URLs (not already inside an href attribute)
|
|
1296
|
+
formatted = formatted.replace(/(?<!href=")(https?:\/\/[^\s<]+)/g, '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
1297
|
+
// Auto-link email addresses
|
|
1298
|
+
formatted = formatted.replace(/(?<!["\/])([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g, '<a href="mailto:$1">$1</a>');
|
|
1299
|
+
// Auto-link phone numbers (international format with +)
|
|
1300
|
+
formatted = formatted.replace(/(?<!["\/])(\+\d[\d\s-]{7,})/g, (_match, phone) => {
|
|
1301
|
+
const cleanPhone = phone.replace(/[\s-]/g, '');
|
|
1302
|
+
return `<a href="tel:${cleanPhone}">${phone}</a>`;
|
|
1303
|
+
});
|
|
1245
1304
|
formatted = formatted.replace(/\n/g, '<br>');
|
|
1246
1305
|
return formatted;
|
|
1247
1306
|
}
|
|
@@ -1794,6 +1853,20 @@ function injectChatStyles(primaryColor = '#6366f1', theme) {
|
|
|
1794
1853
|
line-height: 1.5;
|
|
1795
1854
|
}
|
|
1796
1855
|
|
|
1856
|
+
.wabbit-chat-message-content a {
|
|
1857
|
+
color: var(--wabbit-primary);
|
|
1858
|
+
text-decoration: underline;
|
|
1859
|
+
text-underline-offset: 2px;
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
.wabbit-chat-message-content a:hover {
|
|
1863
|
+
opacity: 0.8;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
.wabbit-chat-message-user .wabbit-chat-message-content a {
|
|
1867
|
+
color: inherit;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1797
1870
|
/* Typing Indicator */
|
|
1798
1871
|
.wabbit-chat-typing {
|
|
1799
1872
|
display: flex;
|
|
@@ -2029,7 +2102,7 @@ function adjustColor(color, amount) {
|
|
|
2029
2102
|
* Chat Widget - Main class that integrates all chat components
|
|
2030
2103
|
*/
|
|
2031
2104
|
class ChatWidget {
|
|
2032
|
-
constructor(config) {
|
|
2105
|
+
constructor(config, storage) {
|
|
2033
2106
|
this.wsClient = null;
|
|
2034
2107
|
this.bubble = null;
|
|
2035
2108
|
this.panel = null;
|
|
@@ -2039,6 +2112,7 @@ class ChatWidget {
|
|
|
2039
2112
|
this.onChatReadyCallback = null;
|
|
2040
2113
|
this.chatReadyFired = false;
|
|
2041
2114
|
this.config = config;
|
|
2115
|
+
this.storage = storage;
|
|
2042
2116
|
this.onChatReadyCallback = config.onChatReady || null;
|
|
2043
2117
|
}
|
|
2044
2118
|
/**
|
|
@@ -2067,8 +2141,8 @@ class ChatWidget {
|
|
|
2067
2141
|
this.setupThemeWatcher();
|
|
2068
2142
|
// Create WebSocket client
|
|
2069
2143
|
const wsUrl = this.getWebSocketUrl();
|
|
2070
|
-
this.wsClient = new ChatWebSocketClient(this.config.apiKey || '', wsUrl, null // Will use stored session if available
|
|
2071
|
-
);
|
|
2144
|
+
this.wsClient = new ChatWebSocketClient(this.config.apiKey || '', wsUrl, null, // Will use stored session if available
|
|
2145
|
+
this.storage);
|
|
2072
2146
|
// Set up event handlers
|
|
2073
2147
|
this.setupWebSocketHandlers();
|
|
2074
2148
|
// Only create bubble for widget mode (not inline)
|