@v-tilt/browser 1.3.0 → 1.4.0
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/all-external-dependencies.js.map +1 -1
- package/dist/array.full.js +1 -1
- package/dist/array.full.js.map +1 -1
- package/dist/array.js +1 -1
- package/dist/array.js.map +1 -1
- package/dist/array.no-external.js +1 -1
- package/dist/array.no-external.js.map +1 -1
- package/dist/chat.js +2 -0
- package/dist/chat.js.map +1 -0
- package/dist/entrypoints/chat.d.ts +22 -0
- package/dist/extensions/chat/chat-wrapper.d.ts +172 -0
- package/dist/extensions/chat/chat.d.ts +87 -0
- package/dist/extensions/chat/index.d.ts +10 -0
- package/dist/extensions/chat/types.d.ts +156 -0
- package/dist/external-scripts-loader.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/module.d.ts +279 -2
- package/dist/module.js +1 -1
- package/dist/module.js.map +1 -1
- package/dist/module.no-external.d.ts +279 -2
- package/dist/module.no-external.js +1 -1
- package/dist/module.no-external.js.map +1 -1
- package/dist/recorder.js.map +1 -1
- package/dist/types.d.ts +27 -0
- package/dist/utils/globals.d.ts +111 -1
- package/dist/vtilt.d.ts +11 -1
- package/dist/web-vitals.js.map +1 -1
- package/lib/entrypoints/chat.d.ts +22 -0
- package/lib/entrypoints/chat.js +32 -0
- package/lib/extensions/chat/chat-wrapper.d.ts +172 -0
- package/lib/extensions/chat/chat-wrapper.js +497 -0
- package/lib/extensions/chat/chat.d.ts +87 -0
- package/lib/extensions/chat/chat.js +998 -0
- package/lib/extensions/chat/index.d.ts +10 -0
- package/lib/extensions/chat/index.js +27 -0
- package/lib/extensions/chat/types.d.ts +156 -0
- package/lib/extensions/chat/types.js +22 -0
- package/lib/types.d.ts +27 -0
- package/lib/utils/globals.d.ts +111 -1
- package/lib/vtilt.d.ts +11 -1
- package/lib/vtilt.js +42 -1
- package/package.json +66 -65
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Chat Wrapper
|
|
4
|
+
*
|
|
5
|
+
* Lightweight wrapper that handles the decision of WHEN to load the chat widget.
|
|
6
|
+
* The actual chat logic is in LazyLoadedChat which is loaded on demand.
|
|
7
|
+
*
|
|
8
|
+
* This follows the same pattern as SessionRecordingWrapper.
|
|
9
|
+
*
|
|
10
|
+
* ## Initialization Modes (Intercom-like flexibility)
|
|
11
|
+
*
|
|
12
|
+
* 1. **Snippet-only (dashboard configured)**:
|
|
13
|
+
* Just add the vTilt snippet - widget auto-configures from project settings.
|
|
14
|
+
* ```js
|
|
15
|
+
* vt.init('token', { api_host: '...' })
|
|
16
|
+
* // Chat widget appears based on dashboard settings
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* 2. **Code-configured (optional overrides)**:
|
|
20
|
+
* Override dashboard settings with code for advanced customization.
|
|
21
|
+
* ```js
|
|
22
|
+
* vt.init('token', {
|
|
23
|
+
* api_host: '...',
|
|
24
|
+
* chat: {
|
|
25
|
+
* enabled: true,
|
|
26
|
+
* position: 'bottom-left',
|
|
27
|
+
* greeting: 'Custom greeting!'
|
|
28
|
+
* }
|
|
29
|
+
* })
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* Code config always takes precedence over dashboard settings.
|
|
33
|
+
*/
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
exports.ChatWrapper = exports.CHAT_LOADING = void 0;
|
|
36
|
+
const globals_1 = require("../../utils/globals");
|
|
37
|
+
const LOGGER_PREFIX = "[Chat]";
|
|
38
|
+
/** Status when lazy loading is in progress */
|
|
39
|
+
exports.CHAT_LOADING = "loading";
|
|
40
|
+
/**
|
|
41
|
+
* Chat Wrapper
|
|
42
|
+
*
|
|
43
|
+
* This is the lightweight class that lives in the main bundle.
|
|
44
|
+
* It handles:
|
|
45
|
+
* - Auto-fetching settings from dashboard (Intercom-like)
|
|
46
|
+
* - Merging server settings with code config
|
|
47
|
+
* - Deciding if chat should be enabled
|
|
48
|
+
* - Lazy loading the actual chat widget code
|
|
49
|
+
* - Delegating to LazyLoadedChat
|
|
50
|
+
* - Queuing messages/callbacks before widget loads
|
|
51
|
+
*/
|
|
52
|
+
class ChatWrapper {
|
|
53
|
+
constructor(_instance, config = {}) {
|
|
54
|
+
this._instance = _instance;
|
|
55
|
+
this._serverConfig = null;
|
|
56
|
+
this._configFetched = false;
|
|
57
|
+
this._isLoading = false;
|
|
58
|
+
this._loadError = null;
|
|
59
|
+
// Queues for operations before widget loads
|
|
60
|
+
this._pendingMessages = [];
|
|
61
|
+
this._pendingCallbacks = [];
|
|
62
|
+
this._messageCallbacks = [];
|
|
63
|
+
this._typingCallbacks = [];
|
|
64
|
+
this._connectionCallbacks = [];
|
|
65
|
+
this._config = config;
|
|
66
|
+
}
|
|
67
|
+
// ============================================================================
|
|
68
|
+
// Public API - State
|
|
69
|
+
// ============================================================================
|
|
70
|
+
/**
|
|
71
|
+
* Whether the chat widget is open
|
|
72
|
+
*/
|
|
73
|
+
get isOpen() {
|
|
74
|
+
var _a, _b;
|
|
75
|
+
return (_b = (_a = this._lazyLoadedChat) === null || _a === void 0 ? void 0 : _a.isOpen) !== null && _b !== void 0 ? _b : false;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Whether connected to realtime service
|
|
79
|
+
*/
|
|
80
|
+
get isConnected() {
|
|
81
|
+
var _a, _b;
|
|
82
|
+
return (_b = (_a = this._lazyLoadedChat) === null || _a === void 0 ? void 0 : _a.isConnected) !== null && _b !== void 0 ? _b : false;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Whether the widget is loading
|
|
86
|
+
*/
|
|
87
|
+
get isLoading() {
|
|
88
|
+
var _a, _b;
|
|
89
|
+
return this._isLoading || ((_b = (_a = this._lazyLoadedChat) === null || _a === void 0 ? void 0 : _a.isLoading) !== null && _b !== void 0 ? _b : false);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Number of unread messages
|
|
93
|
+
*/
|
|
94
|
+
get unreadCount() {
|
|
95
|
+
var _a, _b;
|
|
96
|
+
return (_b = (_a = this._lazyLoadedChat) === null || _a === void 0 ? void 0 : _a.unreadCount) !== null && _b !== void 0 ? _b : 0;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Current channel (if any)
|
|
100
|
+
*/
|
|
101
|
+
get channel() {
|
|
102
|
+
var _a, _b;
|
|
103
|
+
return (_b = (_a = this._lazyLoadedChat) === null || _a === void 0 ? void 0 : _a.channel) !== null && _b !== void 0 ? _b : null;
|
|
104
|
+
}
|
|
105
|
+
// ============================================================================
|
|
106
|
+
// Public API - Widget Control
|
|
107
|
+
// ============================================================================
|
|
108
|
+
/**
|
|
109
|
+
* Open the chat widget
|
|
110
|
+
*/
|
|
111
|
+
open() {
|
|
112
|
+
this._lazyLoadAndThen(() => { var _a; return (_a = this._lazyLoadedChat) === null || _a === void 0 ? void 0 : _a.open(); });
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Close the chat widget
|
|
116
|
+
*/
|
|
117
|
+
close() {
|
|
118
|
+
var _a;
|
|
119
|
+
(_a = this._lazyLoadedChat) === null || _a === void 0 ? void 0 : _a.close();
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Toggle the chat widget open/closed
|
|
123
|
+
*/
|
|
124
|
+
toggle() {
|
|
125
|
+
if (this._lazyLoadedChat) {
|
|
126
|
+
this._lazyLoadedChat.toggle();
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
this.open();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Show the chat widget (make visible but not necessarily open)
|
|
134
|
+
*/
|
|
135
|
+
show() {
|
|
136
|
+
this._lazyLoadAndThen(() => { var _a; return (_a = this._lazyLoadedChat) === null || _a === void 0 ? void 0 : _a.show(); });
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Hide the chat widget
|
|
140
|
+
*/
|
|
141
|
+
hide() {
|
|
142
|
+
var _a;
|
|
143
|
+
(_a = this._lazyLoadedChat) === null || _a === void 0 ? void 0 : _a.hide();
|
|
144
|
+
}
|
|
145
|
+
// ============================================================================
|
|
146
|
+
// Public API - Messaging
|
|
147
|
+
// ============================================================================
|
|
148
|
+
/**
|
|
149
|
+
* Send a message
|
|
150
|
+
*/
|
|
151
|
+
sendMessage(content) {
|
|
152
|
+
if (this._lazyLoadedChat) {
|
|
153
|
+
this._lazyLoadedChat.sendMessage(content);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
// Queue message until widget loads
|
|
157
|
+
this._pendingMessages.push(content);
|
|
158
|
+
this._lazyLoadAndThen(() => {
|
|
159
|
+
// Send all queued messages
|
|
160
|
+
this._pendingMessages.forEach((msg) => { var _a; return (_a = this._lazyLoadedChat) === null || _a === void 0 ? void 0 : _a.sendMessage(msg); });
|
|
161
|
+
this._pendingMessages = [];
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Mark messages as read
|
|
167
|
+
*/
|
|
168
|
+
markAsRead() {
|
|
169
|
+
var _a;
|
|
170
|
+
(_a = this._lazyLoadedChat) === null || _a === void 0 ? void 0 : _a.markAsRead();
|
|
171
|
+
}
|
|
172
|
+
// ============================================================================
|
|
173
|
+
// Public API - Events
|
|
174
|
+
// ============================================================================
|
|
175
|
+
/**
|
|
176
|
+
* Subscribe to new messages
|
|
177
|
+
*/
|
|
178
|
+
onMessage(callback) {
|
|
179
|
+
this._messageCallbacks.push(callback);
|
|
180
|
+
if (this._lazyLoadedChat) {
|
|
181
|
+
return this._lazyLoadedChat.onMessage(callback);
|
|
182
|
+
}
|
|
183
|
+
// Return unsubscribe that removes from queue
|
|
184
|
+
return () => {
|
|
185
|
+
const index = this._messageCallbacks.indexOf(callback);
|
|
186
|
+
if (index > -1) {
|
|
187
|
+
this._messageCallbacks.splice(index, 1);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Subscribe to typing indicators
|
|
193
|
+
*/
|
|
194
|
+
onTyping(callback) {
|
|
195
|
+
this._typingCallbacks.push(callback);
|
|
196
|
+
if (this._lazyLoadedChat) {
|
|
197
|
+
return this._lazyLoadedChat.onTyping(callback);
|
|
198
|
+
}
|
|
199
|
+
return () => {
|
|
200
|
+
const index = this._typingCallbacks.indexOf(callback);
|
|
201
|
+
if (index > -1) {
|
|
202
|
+
this._typingCallbacks.splice(index, 1);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Subscribe to connection changes
|
|
208
|
+
*/
|
|
209
|
+
onConnectionChange(callback) {
|
|
210
|
+
this._connectionCallbacks.push(callback);
|
|
211
|
+
if (this._lazyLoadedChat) {
|
|
212
|
+
return this._lazyLoadedChat.onConnectionChange(callback);
|
|
213
|
+
}
|
|
214
|
+
return () => {
|
|
215
|
+
const index = this._connectionCallbacks.indexOf(callback);
|
|
216
|
+
if (index > -1) {
|
|
217
|
+
this._connectionCallbacks.splice(index, 1);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
// ============================================================================
|
|
222
|
+
// Initialization
|
|
223
|
+
// ============================================================================
|
|
224
|
+
/**
|
|
225
|
+
* Start chat if enabled, called by VTilt after init
|
|
226
|
+
*
|
|
227
|
+
* This method supports two modes (Intercom-like):
|
|
228
|
+
* 1. Auto-config: Fetch settings from /api/chat/settings (default)
|
|
229
|
+
* 2. Code-only: Use only code config (autoConfig: false)
|
|
230
|
+
*/
|
|
231
|
+
async startIfEnabled() {
|
|
232
|
+
var _a, _b, _c, _d;
|
|
233
|
+
// If explicitly disabled in code, don't even fetch settings
|
|
234
|
+
if (this._config.enabled === false) {
|
|
235
|
+
console.info(`${LOGGER_PREFIX} disabled by code config`);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
// If disable_chat is set in main config, don't enable
|
|
239
|
+
const mainConfig = this._instance.getConfig();
|
|
240
|
+
if (mainConfig.disable_chat) {
|
|
241
|
+
console.info(`${LOGGER_PREFIX} disabled by disable_chat config`);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// Fetch server settings unless autoConfig is explicitly false
|
|
245
|
+
if (this._config.autoConfig !== false) {
|
|
246
|
+
await this._fetchServerSettings();
|
|
247
|
+
}
|
|
248
|
+
// After fetching, check if chat should be enabled
|
|
249
|
+
if (!this._isChatEnabled) {
|
|
250
|
+
console.info(`${LOGGER_PREFIX} not enabled (check dashboard settings)`);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
// Preload on idle if configured (code or server)
|
|
254
|
+
const shouldPreload = (_c = (_a = this._config.preload) !== null && _a !== void 0 ? _a : (_b = this._serverConfig) === null || _b === void 0 ? void 0 : _b.enabled) !== null && _c !== void 0 ? _c : false;
|
|
255
|
+
if (shouldPreload) {
|
|
256
|
+
this._schedulePreload();
|
|
257
|
+
}
|
|
258
|
+
// If enabled by server, show the chat bubble immediately
|
|
259
|
+
// (Note: we already returned above if this._config.enabled === false)
|
|
260
|
+
if ((_d = this._serverConfig) === null || _d === void 0 ? void 0 : _d.enabled) {
|
|
261
|
+
this._showBubble();
|
|
262
|
+
}
|
|
263
|
+
console.info(`${LOGGER_PREFIX} ready (lazy-load on demand)`);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Update configuration
|
|
267
|
+
*/
|
|
268
|
+
updateConfig(config) {
|
|
269
|
+
this._config = { ...this._config, ...config };
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Get the merged configuration (server + code, code takes precedence)
|
|
273
|
+
*/
|
|
274
|
+
getMergedConfig() {
|
|
275
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
276
|
+
const server = this._serverConfig;
|
|
277
|
+
const code = this._config;
|
|
278
|
+
return {
|
|
279
|
+
enabled: (_b = (_a = code.enabled) !== null && _a !== void 0 ? _a : server === null || server === void 0 ? void 0 : server.enabled) !== null && _b !== void 0 ? _b : false,
|
|
280
|
+
autoConfig: (_c = code.autoConfig) !== null && _c !== void 0 ? _c : true,
|
|
281
|
+
position: (_e = (_d = code.position) !== null && _d !== void 0 ? _d : server === null || server === void 0 ? void 0 : server.position) !== null && _e !== void 0 ? _e : "bottom-right",
|
|
282
|
+
greeting: (_f = code.greeting) !== null && _f !== void 0 ? _f : server === null || server === void 0 ? void 0 : server.greeting,
|
|
283
|
+
color: (_h = (_g = code.color) !== null && _g !== void 0 ? _g : server === null || server === void 0 ? void 0 : server.color) !== null && _h !== void 0 ? _h : "#6366f1",
|
|
284
|
+
aiMode: (_k = (_j = code.aiMode) !== null && _j !== void 0 ? _j : server === null || server === void 0 ? void 0 : server.ai_enabled) !== null && _k !== void 0 ? _k : true,
|
|
285
|
+
aiGreeting: (_l = code.aiGreeting) !== null && _l !== void 0 ? _l : server === null || server === void 0 ? void 0 : server.ai_greeting,
|
|
286
|
+
preload: (_m = code.preload) !== null && _m !== void 0 ? _m : false,
|
|
287
|
+
theme: (_o = code.theme) !== null && _o !== void 0 ? _o : {
|
|
288
|
+
primaryColor: (_q = (_p = code.color) !== null && _p !== void 0 ? _p : server === null || server === void 0 ? void 0 : server.color) !== null && _q !== void 0 ? _q : "#6366f1",
|
|
289
|
+
},
|
|
290
|
+
offlineMessage: (_r = code.offlineMessage) !== null && _r !== void 0 ? _r : server === null || server === void 0 ? void 0 : server.offline_message,
|
|
291
|
+
collectEmailOffline: (_t = (_s = code.collectEmailOffline) !== null && _s !== void 0 ? _s : server === null || server === void 0 ? void 0 : server.collect_email_offline) !== null && _t !== void 0 ? _t : true,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Destroy the chat widget
|
|
296
|
+
*/
|
|
297
|
+
destroy() {
|
|
298
|
+
var _a;
|
|
299
|
+
(_a = this._lazyLoadedChat) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
300
|
+
this._lazyLoadedChat = undefined;
|
|
301
|
+
this._pendingMessages = [];
|
|
302
|
+
this._pendingCallbacks = [];
|
|
303
|
+
this._messageCallbacks = [];
|
|
304
|
+
this._typingCallbacks = [];
|
|
305
|
+
this._connectionCallbacks = [];
|
|
306
|
+
}
|
|
307
|
+
// ============================================================================
|
|
308
|
+
// Private - Configuration
|
|
309
|
+
// ============================================================================
|
|
310
|
+
get _isChatEnabled() {
|
|
311
|
+
var _a;
|
|
312
|
+
// Code config takes precedence
|
|
313
|
+
if (this._config.enabled === false)
|
|
314
|
+
return false;
|
|
315
|
+
if (this._config.enabled === true)
|
|
316
|
+
return true;
|
|
317
|
+
// Fall back to server config
|
|
318
|
+
if (((_a = this._serverConfig) === null || _a === void 0 ? void 0 : _a.enabled) === true)
|
|
319
|
+
return true;
|
|
320
|
+
// Default: disabled unless explicitly enabled
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Fetch chat settings from the server
|
|
325
|
+
* This enables "snippet-only" installation where widget configures from dashboard
|
|
326
|
+
*/
|
|
327
|
+
async _fetchServerSettings() {
|
|
328
|
+
if (this._configFetched)
|
|
329
|
+
return;
|
|
330
|
+
const config = this._instance.getConfig();
|
|
331
|
+
const token = config.token;
|
|
332
|
+
const apiHost = config.api_host;
|
|
333
|
+
if (!token || !apiHost) {
|
|
334
|
+
console.warn(`${LOGGER_PREFIX} Cannot fetch settings: missing token or api_host`);
|
|
335
|
+
this._configFetched = true;
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
const url = `${apiHost}/api/chat/settings?token=${encodeURIComponent(token)}`;
|
|
340
|
+
const response = await fetch(url);
|
|
341
|
+
if (!response.ok) {
|
|
342
|
+
console.warn(`${LOGGER_PREFIX} Failed to fetch settings: ${response.status}`);
|
|
343
|
+
this._configFetched = true;
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
this._serverConfig = await response.json();
|
|
347
|
+
this._configFetched = true;
|
|
348
|
+
console.info(`${LOGGER_PREFIX} Loaded settings from dashboard`);
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
console.warn(`${LOGGER_PREFIX} Error fetching settings:`, error);
|
|
352
|
+
this._configFetched = true;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Show the chat bubble (launcher button) without fully loading the widget
|
|
357
|
+
* This creates a lightweight bubble that loads the full widget on click
|
|
358
|
+
*/
|
|
359
|
+
_showBubble() {
|
|
360
|
+
if (!(globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.document))
|
|
361
|
+
return;
|
|
362
|
+
// Don't create if already exists
|
|
363
|
+
if (document.getElementById("vtilt-chat-bubble"))
|
|
364
|
+
return;
|
|
365
|
+
const mergedConfig = this.getMergedConfig();
|
|
366
|
+
const position = mergedConfig.position || "bottom-right";
|
|
367
|
+
const color = mergedConfig.color || "#6366f1";
|
|
368
|
+
const bubble = document.createElement("div");
|
|
369
|
+
bubble.id = "vtilt-chat-bubble";
|
|
370
|
+
bubble.setAttribute("style", `
|
|
371
|
+
position: fixed;
|
|
372
|
+
bottom: 20px;
|
|
373
|
+
${position === "bottom-right" ? "right: 20px;" : "left: 20px;"}
|
|
374
|
+
width: 60px;
|
|
375
|
+
height: 60px;
|
|
376
|
+
border-radius: 50%;
|
|
377
|
+
background: ${color};
|
|
378
|
+
cursor: pointer;
|
|
379
|
+
display: flex;
|
|
380
|
+
align-items: center;
|
|
381
|
+
justify-content: center;
|
|
382
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
383
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
384
|
+
z-index: 999999;
|
|
385
|
+
`.trim());
|
|
386
|
+
bubble.innerHTML = `
|
|
387
|
+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
|
|
388
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
|
389
|
+
</svg>
|
|
390
|
+
`;
|
|
391
|
+
// Hover effect
|
|
392
|
+
bubble.addEventListener("mouseenter", () => {
|
|
393
|
+
bubble.style.transform = "scale(1.05)";
|
|
394
|
+
bubble.style.boxShadow = "0 6px 16px rgba(0, 0, 0, 0.2)";
|
|
395
|
+
});
|
|
396
|
+
bubble.addEventListener("mouseleave", () => {
|
|
397
|
+
bubble.style.transform = "scale(1)";
|
|
398
|
+
bubble.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.15)";
|
|
399
|
+
});
|
|
400
|
+
// Click to open (this will lazy load the full widget)
|
|
401
|
+
bubble.addEventListener("click", () => {
|
|
402
|
+
this.open();
|
|
403
|
+
});
|
|
404
|
+
document.body.appendChild(bubble);
|
|
405
|
+
}
|
|
406
|
+
get _scriptName() {
|
|
407
|
+
return "chat";
|
|
408
|
+
}
|
|
409
|
+
// ============================================================================
|
|
410
|
+
// Private - Lazy Loading
|
|
411
|
+
// ============================================================================
|
|
412
|
+
/**
|
|
413
|
+
* Schedule preload on idle
|
|
414
|
+
*/
|
|
415
|
+
_schedulePreload() {
|
|
416
|
+
if (typeof requestIdleCallback !== "undefined") {
|
|
417
|
+
requestIdleCallback(() => this._lazyLoad(), { timeout: 5000 });
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
// Fallback for browsers without requestIdleCallback
|
|
421
|
+
setTimeout(() => this._lazyLoad(), 3000);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Lazy load and then execute callback
|
|
426
|
+
*/
|
|
427
|
+
_lazyLoadAndThen(callback) {
|
|
428
|
+
if (this._lazyLoadedChat) {
|
|
429
|
+
callback();
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
this._pendingCallbacks.push(callback);
|
|
433
|
+
this._lazyLoad();
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Lazy load the chat script
|
|
437
|
+
*/
|
|
438
|
+
_lazyLoad() {
|
|
439
|
+
var _a, _b;
|
|
440
|
+
if (this._isLoading || this._lazyLoadedChat || this._loadError) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
// Check if already loaded
|
|
444
|
+
if ((_a = globals_1.assignableWindow.__VTiltExtensions__) === null || _a === void 0 ? void 0 : _a.initChat) {
|
|
445
|
+
this._onScriptLoaded();
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
this._isLoading = true;
|
|
449
|
+
const loadExternalDependency = (_b = globals_1.assignableWindow.__VTiltExtensions__) === null || _b === void 0 ? void 0 : _b.loadExternalDependency;
|
|
450
|
+
if (!loadExternalDependency) {
|
|
451
|
+
console.error(`${LOGGER_PREFIX} loadExternalDependency not available. Chat cannot start.`);
|
|
452
|
+
this._isLoading = false;
|
|
453
|
+
this._loadError = "loadExternalDependency not available";
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
loadExternalDependency(this._instance, this._scriptName, (err) => {
|
|
457
|
+
this._isLoading = false;
|
|
458
|
+
if (err) {
|
|
459
|
+
console.error(`${LOGGER_PREFIX} Failed to load:`, err);
|
|
460
|
+
this._loadError = String(err);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
this._onScriptLoaded();
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Called after the chat script is loaded
|
|
468
|
+
*/
|
|
469
|
+
_onScriptLoaded() {
|
|
470
|
+
var _a;
|
|
471
|
+
const initChat = (_a = globals_1.assignableWindow.__VTiltExtensions__) === null || _a === void 0 ? void 0 : _a.initChat;
|
|
472
|
+
if (!initChat) {
|
|
473
|
+
console.error(`${LOGGER_PREFIX} initChat not available after script load`);
|
|
474
|
+
this._loadError = "initChat not available";
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (!this._lazyLoadedChat) {
|
|
478
|
+
// Use merged config (server + code)
|
|
479
|
+
const mergedConfig = this.getMergedConfig();
|
|
480
|
+
this._lazyLoadedChat = initChat(this._instance, mergedConfig);
|
|
481
|
+
// Re-register queued callbacks
|
|
482
|
+
this._messageCallbacks.forEach((cb) => { var _a; return (_a = this._lazyLoadedChat) === null || _a === void 0 ? void 0 : _a.onMessage(cb); });
|
|
483
|
+
this._typingCallbacks.forEach((cb) => { var _a; return (_a = this._lazyLoadedChat) === null || _a === void 0 ? void 0 : _a.onTyping(cb); });
|
|
484
|
+
this._connectionCallbacks.forEach((cb) => { var _a; return (_a = this._lazyLoadedChat) === null || _a === void 0 ? void 0 : _a.onConnectionChange(cb); });
|
|
485
|
+
// Remove the lightweight bubble since full widget is now loaded
|
|
486
|
+
const existingBubble = document === null || document === void 0 ? void 0 : document.getElementById("vtilt-chat-bubble");
|
|
487
|
+
if (existingBubble) {
|
|
488
|
+
existingBubble.remove();
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// Execute pending callbacks
|
|
492
|
+
this._pendingCallbacks.forEach((cb) => cb());
|
|
493
|
+
this._pendingCallbacks = [];
|
|
494
|
+
console.info(`${LOGGER_PREFIX} loaded and ready`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
exports.ChatWrapper = ChatWrapper;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy Loaded Chat Implementation
|
|
3
|
+
*
|
|
4
|
+
* The actual chat widget implementation that is loaded on demand.
|
|
5
|
+
* This file is bundled into chat.js and loaded when chat is enabled.
|
|
6
|
+
*
|
|
7
|
+
* Uses Ably for real-time messaging.
|
|
8
|
+
*/
|
|
9
|
+
import type { VTilt } from "../../vtilt";
|
|
10
|
+
import type { ChatConfig, ChatChannel, LazyLoadedChatInterface } from "../../utils/globals";
|
|
11
|
+
import type { MessageCallback, TypingCallback, ConnectionCallback, Unsubscribe } from "./types";
|
|
12
|
+
export declare class LazyLoadedChat implements LazyLoadedChatInterface {
|
|
13
|
+
private _instance;
|
|
14
|
+
private _config;
|
|
15
|
+
private _state;
|
|
16
|
+
private _container;
|
|
17
|
+
private _widget;
|
|
18
|
+
private _bubble;
|
|
19
|
+
private _ably;
|
|
20
|
+
private _ablyChannel;
|
|
21
|
+
private _typingChannel;
|
|
22
|
+
private _connectionState;
|
|
23
|
+
private _messageCallbacks;
|
|
24
|
+
private _typingCallbacks;
|
|
25
|
+
private _connectionCallbacks;
|
|
26
|
+
private _typingTimeout;
|
|
27
|
+
private _typingDebounce;
|
|
28
|
+
private _isUserTyping;
|
|
29
|
+
private _initialUserReadAt;
|
|
30
|
+
private _isMarkingRead;
|
|
31
|
+
constructor(instance: VTilt, config?: ChatConfig);
|
|
32
|
+
get isOpen(): boolean;
|
|
33
|
+
get isConnected(): boolean;
|
|
34
|
+
get isLoading(): boolean;
|
|
35
|
+
get unreadCount(): number;
|
|
36
|
+
get channel(): ChatChannel | null;
|
|
37
|
+
open(): void;
|
|
38
|
+
close(): void;
|
|
39
|
+
toggle(): void;
|
|
40
|
+
show(): void;
|
|
41
|
+
hide(): void;
|
|
42
|
+
sendMessage(content: string): Promise<void>;
|
|
43
|
+
markAsRead(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Automatically mark unread agent/AI messages as read
|
|
46
|
+
* Called when widget opens or new messages arrive while open
|
|
47
|
+
*/
|
|
48
|
+
private _autoMarkAsRead;
|
|
49
|
+
/**
|
|
50
|
+
* Check if a message has been read by the user (using initial cursor)
|
|
51
|
+
*/
|
|
52
|
+
private _isMessageReadByUser;
|
|
53
|
+
onMessage(callback: MessageCallback): Unsubscribe;
|
|
54
|
+
onTyping(callback: TypingCallback): Unsubscribe;
|
|
55
|
+
onConnectionChange(callback: ConnectionCallback): Unsubscribe;
|
|
56
|
+
destroy(): void;
|
|
57
|
+
private _initializeChannel;
|
|
58
|
+
private _connectRealtime;
|
|
59
|
+
private _disconnectRealtime;
|
|
60
|
+
private _extractProjectId;
|
|
61
|
+
private _handleNewMessage;
|
|
62
|
+
private _handleTypingEvent;
|
|
63
|
+
private _handleReadCursorEvent;
|
|
64
|
+
private _notifyConnectionChange;
|
|
65
|
+
private _createUI;
|
|
66
|
+
private _attachEventListeners;
|
|
67
|
+
private _handleUserTyping;
|
|
68
|
+
private _sendTypingIndicator;
|
|
69
|
+
private _handleSend;
|
|
70
|
+
private _updateUI;
|
|
71
|
+
private _renderMessages;
|
|
72
|
+
private _getContainerStyles;
|
|
73
|
+
private _getBubbleStyles;
|
|
74
|
+
private _getBubbleHTML;
|
|
75
|
+
private _getWidgetStyles;
|
|
76
|
+
private _getWidgetHTML;
|
|
77
|
+
private _getMessageHTML;
|
|
78
|
+
/**
|
|
79
|
+
* Check if a message has been read by the agent using cursor comparison
|
|
80
|
+
*/
|
|
81
|
+
private _isMessageReadByAgent;
|
|
82
|
+
private _apiRequest;
|
|
83
|
+
private _trackEvent;
|
|
84
|
+
private _getTimeOpen;
|
|
85
|
+
private _escapeHTML;
|
|
86
|
+
private _formatTime;
|
|
87
|
+
}
|