urasoft-live-support 1.0.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/README.md +1413 -0
- package/README.tr.md +632 -0
- package/dist/core/ChatWidget.d.ts +41 -0
- package/dist/core/ChatWidget.d.ts.map +1 -0
- package/dist/index.d.ts +169 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +7088 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +7091 -0
- package/dist/index.js.map +1 -0
- package/dist/signalr-client.d.ts +21 -0
- package/dist/signalr-client.d.ts.map +1 -0
- package/dist/styles.css +829 -0
- package/dist/types.d.ts +107 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,1413 @@
|
|
|
1
|
+
# @urasoft/live-chat-widget
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@urasoft/live-chat-widget)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
|
|
9
|
+
**Universal live chat widget library for web applications**
|
|
10
|
+
|
|
11
|
+
A modern, TypeScript-based chat widget with SignalR support, styled with Tailwind CSS. Works seamlessly with Vue, React, Next.js, Angular, or vanilla JavaScript.
|
|
12
|
+
|
|
13
|
+
[Features](#-features) • [Installation](#-installation) • [Quick Start](#-quick-start) • [Documentation](#-documentation)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 🚀 Features
|
|
20
|
+
|
|
21
|
+
- 💬 **Framework Agnostic** - Works with Vue, React, Next.js, Angular, or vanilla JavaScript
|
|
22
|
+
- 🔌 **Built-in SignalR** - Real-time communication with configurable Hub connection
|
|
23
|
+
- 🌐 **HTTP REST API Support** - Send messages via HTTP endpoints with custom body mapping
|
|
24
|
+
- 🔄 **Hybrid Mode** - Send via HTTP, receive via SignalR (perfect for existing APIs)
|
|
25
|
+
- 🔄 **Smart Reconnection** - Multiple reconnection strategies (exponential, linear, fixed, custom)
|
|
26
|
+
- 📡 **10+ Event Callbacks** - Full control over connection lifecycle
|
|
27
|
+
- 🎨 **Fully Customizable** - Position, colors, messages, and styling
|
|
28
|
+
- 📱 **Responsive Design** - Mobile and desktop-friendly
|
|
29
|
+
- ⚡ **Lightweight** - Minimal dependencies, optimized bundle size
|
|
30
|
+
- 🎯 **TypeScript First** - Complete type definitions with IntelliSense support
|
|
31
|
+
- 🌐 **Flexible Configuration** - Headers (static/dynamic), query params, JWT token support
|
|
32
|
+
- 🎛️ **Transport Options** - WebSockets, Server-Sent Events, or Long Polling
|
|
33
|
+
|
|
34
|
+
## 📦 Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install @urasoft/live-chat-widget
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or with yarn:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
yarn add @urasoft/live-chat-widget
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Or with pnpm:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pnpm add @urasoft/live-chat-widget
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 🔧 Quick Start
|
|
53
|
+
|
|
54
|
+
### Basic Usage with SignalR
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { ChatWidget } from "@urasoft/live-chat-widget";
|
|
58
|
+
import "@urasoft/live-chat-widget/dist/styles.css";
|
|
59
|
+
|
|
60
|
+
const chat = new ChatWidget({
|
|
61
|
+
position: "bottom-right",
|
|
62
|
+
primaryColor: "#007bff",
|
|
63
|
+
title: "Live Support",
|
|
64
|
+
signalR: {
|
|
65
|
+
hubUrl: "https://your-server.com/chatHub",
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### With JWT Authentication
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const chat = new ChatWidget({
|
|
74
|
+
position: "bottom-right",
|
|
75
|
+
signalR: {
|
|
76
|
+
hubUrl: "/chatHub",
|
|
77
|
+
accessTokenFactory: async () => {
|
|
78
|
+
const response = await fetch("/api/auth/token");
|
|
79
|
+
const data = await response.json();
|
|
80
|
+
return data.token;
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### HTTP + SignalR Hybrid (Send via HTTP, Receive via SignalR)
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
const chat = new ChatWidget({
|
|
90
|
+
position: "bottom-right",
|
|
91
|
+
primaryColor: "#007bff",
|
|
92
|
+
title: "Live Support",
|
|
93
|
+
// HTTP: Send messages via REST API
|
|
94
|
+
http: {
|
|
95
|
+
sendEndpoint: "https://api.example.com/chat/messages",
|
|
96
|
+
method: "POST",
|
|
97
|
+
headers: {
|
|
98
|
+
"Authorization": "Bearer YOUR_TOKEN",
|
|
99
|
+
"Content-Type": "application/json"
|
|
100
|
+
},
|
|
101
|
+
bodyMapper: (message) => ({
|
|
102
|
+
content: message,
|
|
103
|
+
userId: "user-123",
|
|
104
|
+
timestamp: new Date().toISOString()
|
|
105
|
+
})
|
|
106
|
+
},
|
|
107
|
+
// SignalR: Receive messages in real-time
|
|
108
|
+
signalR: {
|
|
109
|
+
hubUrl: "https://api.example.com/chatHub",
|
|
110
|
+
receiveMethodName: "ReceiveMessage",
|
|
111
|
+
onMessageReceived: (message) => {
|
|
112
|
+
console.log("New message:", message);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### User Flow
|
|
119
|
+
|
|
120
|
+
**SignalR Only (Default):**
|
|
121
|
+
1. User clicks the chat bubble
|
|
122
|
+
2. Clicks "Connect to Live Support" button
|
|
123
|
+
3. SignalR connection is established automatically
|
|
124
|
+
4. Real-time messaging begins
|
|
125
|
+
5. User can disconnect by clicking the disconnect button (🔌)
|
|
126
|
+
|
|
127
|
+
**HTTP + SignalR Hybrid:**
|
|
128
|
+
1. User clicks the chat bubble (no connection button needed)
|
|
129
|
+
2. User can immediately start typing
|
|
130
|
+
3. Messages are sent via HTTP POST to your API
|
|
131
|
+
4. Responses are received via SignalR in real-time
|
|
132
|
+
|
|
133
|
+
## 📖 Documentation
|
|
134
|
+
|
|
135
|
+
### Table of Contents
|
|
136
|
+
|
|
137
|
+
- [Configuration Options](#configuration-options)
|
|
138
|
+
- [SignalR Integration](#signalr-integration)
|
|
139
|
+
- [Reconnection Strategies](#reconnection-strategies)
|
|
140
|
+
- [Event Callbacks](#event-callbacks)
|
|
141
|
+
- [Transport & Logging](#transport--logging)
|
|
142
|
+
- [HTTP Integration](#http-integration)
|
|
143
|
+
- [Basic HTTP Setup](#basic-http-setup)
|
|
144
|
+
- [Custom Body Mapping](#custom-body-mapping)
|
|
145
|
+
- [Dynamic Headers](#dynamic-headers)
|
|
146
|
+
- [HTTP + SignalR Hybrid](#http--signalr-hybrid-1)
|
|
147
|
+
- [Framework Integration](#framework-integration)
|
|
148
|
+
- [React / Next.js](#react--nextjs)
|
|
149
|
+
- [Vue 3](#vue-3)
|
|
150
|
+
- [Angular](#angular)
|
|
151
|
+
- [Vanilla JavaScript](#vanilla-javascript)
|
|
152
|
+
- [API Methods](#api-methods)
|
|
153
|
+
- [Backend Setup](#backend-setup)
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Configuration Options
|
|
158
|
+
|
|
159
|
+
### Complete Configuration Interface
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
interface ChatConfig {
|
|
163
|
+
// ============================================
|
|
164
|
+
// UI Configuration
|
|
165
|
+
// ============================================
|
|
166
|
+
|
|
167
|
+
/** Widget position on screen
|
|
168
|
+
* @default 'bottom-right'
|
|
169
|
+
*/
|
|
170
|
+
position?: "bottom-right" | "bottom-left" | "top-right" | "top-left";
|
|
171
|
+
|
|
172
|
+
/** Primary color for the chat widget
|
|
173
|
+
* @default '#007bff'
|
|
174
|
+
*/
|
|
175
|
+
primaryColor?: string;
|
|
176
|
+
|
|
177
|
+
/** Welcome message displayed when chat opens */
|
|
178
|
+
welcomeMessage?: string;
|
|
179
|
+
|
|
180
|
+
/** Chat header title
|
|
181
|
+
* @default 'Live Support'
|
|
182
|
+
*/
|
|
183
|
+
title?: string;
|
|
184
|
+
|
|
185
|
+
/** Input placeholder text
|
|
186
|
+
* @default 'Type your message...'
|
|
187
|
+
*/
|
|
188
|
+
placeholder?: string;
|
|
189
|
+
|
|
190
|
+
/** Agent/Support team name
|
|
191
|
+
* @default 'Support Team'
|
|
192
|
+
*/
|
|
193
|
+
agentName?: string;
|
|
194
|
+
|
|
195
|
+
/** Agent avatar URL */
|
|
196
|
+
agentAvatar?: string;
|
|
197
|
+
|
|
198
|
+
/** Auto-open chat on page load
|
|
199
|
+
* @default false
|
|
200
|
+
*/
|
|
201
|
+
autoOpen?: boolean;
|
|
202
|
+
|
|
203
|
+
/** Chat button icon
|
|
204
|
+
* @default '💬'
|
|
205
|
+
*/
|
|
206
|
+
buttonIcon?: string;
|
|
207
|
+
|
|
208
|
+
// ============================================
|
|
209
|
+
// SignalR Configuration
|
|
210
|
+
// ============================================
|
|
211
|
+
|
|
212
|
+
signalR?: {
|
|
213
|
+
/** SignalR Hub URL (required) */
|
|
214
|
+
hubUrl: string;
|
|
215
|
+
|
|
216
|
+
/** Query parameters appended to Hub URL */
|
|
217
|
+
queryParams?: Record<string, string>;
|
|
218
|
+
|
|
219
|
+
/** HTTP Headers sent with the connection */
|
|
220
|
+
headers?: Record<string, string>;
|
|
221
|
+
|
|
222
|
+
/** Function to retrieve JWT token for authentication */
|
|
223
|
+
accessTokenFactory?: () => string | Promise<string>;
|
|
224
|
+
|
|
225
|
+
/** Hub method name for sending messages
|
|
226
|
+
* @default 'SendMessage'
|
|
227
|
+
*/
|
|
228
|
+
sendMethodName?: string;
|
|
229
|
+
|
|
230
|
+
/** Hub method name for receiving messages
|
|
231
|
+
* @default 'ReceiveMessage'
|
|
232
|
+
*/
|
|
233
|
+
receiveMethodName?: string;
|
|
234
|
+
|
|
235
|
+
// ============================================
|
|
236
|
+
// Reconnection Settings
|
|
237
|
+
// ============================================
|
|
238
|
+
|
|
239
|
+
reconnect?: {
|
|
240
|
+
/** Maximum number of reconnection attempts
|
|
241
|
+
* @default 5
|
|
242
|
+
*/
|
|
243
|
+
maxRetries?: number;
|
|
244
|
+
|
|
245
|
+
/** Reconnection delay strategy
|
|
246
|
+
* - exponential: 1s, 2s, 4s, 8s, 16s... (default)
|
|
247
|
+
* - linear: 1s, 2s, 3s, 4s, 5s...
|
|
248
|
+
* - fixed: same delay each time
|
|
249
|
+
* - custom function: (retryCount) => delayInMs
|
|
250
|
+
* @default 'exponential'
|
|
251
|
+
*/
|
|
252
|
+
delayStrategy?:
|
|
253
|
+
| "exponential"
|
|
254
|
+
| "linear"
|
|
255
|
+
| "fixed"
|
|
256
|
+
| ((retryCount: number) => number);
|
|
257
|
+
|
|
258
|
+
/** Initial delay in milliseconds
|
|
259
|
+
* @default 1000
|
|
260
|
+
*/
|
|
261
|
+
initialDelay?: number;
|
|
262
|
+
|
|
263
|
+
/** Maximum delay in milliseconds
|
|
264
|
+
* @default 30000
|
|
265
|
+
*/
|
|
266
|
+
maxDelay?: number;
|
|
267
|
+
|
|
268
|
+
/** Fixed delay in milliseconds (when using 'fixed' strategy)
|
|
269
|
+
* @default 5000
|
|
270
|
+
*/
|
|
271
|
+
fixedDelay?: number;
|
|
272
|
+
|
|
273
|
+
/** Disable automatic reconnection
|
|
274
|
+
* @default false
|
|
275
|
+
*/
|
|
276
|
+
disableAutoReconnect?: boolean;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// ============================================
|
|
280
|
+
// Event Callbacks
|
|
281
|
+
// ============================================
|
|
282
|
+
|
|
283
|
+
/** Called when connection is established */
|
|
284
|
+
onConnected?: (connectionId?: string) => void;
|
|
285
|
+
|
|
286
|
+
/** Called when connection is lost */
|
|
287
|
+
onDisconnected?: (error?: Error) => void;
|
|
288
|
+
|
|
289
|
+
/** Called when reconnection attempt starts */
|
|
290
|
+
onReconnecting?: (error?: Error) => void;
|
|
291
|
+
|
|
292
|
+
/** Called when reconnection succeeds */
|
|
293
|
+
onReconnected?: (connectionId?: string) => void;
|
|
294
|
+
|
|
295
|
+
/** Called before each retry attempt */
|
|
296
|
+
onRetry?: (
|
|
297
|
+
retryCount: number,
|
|
298
|
+
delayMs: number,
|
|
299
|
+
elapsedMs?: number
|
|
300
|
+
) => void;
|
|
301
|
+
|
|
302
|
+
/** Called when all reconnection attempts fail */
|
|
303
|
+
onReconnectFailed?: () => void;
|
|
304
|
+
|
|
305
|
+
/** Called when a message is received */
|
|
306
|
+
onMessageReceived?: (message: any) => void;
|
|
307
|
+
|
|
308
|
+
/** Called before sending a message (can modify message) */
|
|
309
|
+
onBeforeSend?: (message: string) => string | Promise<string>;
|
|
310
|
+
|
|
311
|
+
/** Called after message is sent */
|
|
312
|
+
onAfterSend?: (message: string) => void;
|
|
313
|
+
|
|
314
|
+
/** Called when any error occurs */
|
|
315
|
+
onError?: (error: Error) => void;
|
|
316
|
+
|
|
317
|
+
// ============================================
|
|
318
|
+
// Transport & Logging
|
|
319
|
+
// ============================================
|
|
320
|
+
|
|
321
|
+
/** Transport protocol
|
|
322
|
+
* @default 'All'
|
|
323
|
+
*/
|
|
324
|
+
transport?: "WebSockets" | "ServerSentEvents" | "LongPolling" | "All";
|
|
325
|
+
|
|
326
|
+
/** Logging level
|
|
327
|
+
* @default 'Information'
|
|
328
|
+
*/
|
|
329
|
+
logLevel?:
|
|
330
|
+
| "Trace"
|
|
331
|
+
| "Debug"
|
|
332
|
+
| "Information"
|
|
333
|
+
| "Warning"
|
|
334
|
+
| "Error"
|
|
335
|
+
| "Critical"
|
|
336
|
+
| "None";
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// ============================================
|
|
340
|
+
// HTTP Configuration (Optional - for REST API message sending)
|
|
341
|
+
// ============================================
|
|
342
|
+
|
|
343
|
+
http?: {
|
|
344
|
+
/** HTTP Endpoint URL for sending messages (required) */
|
|
345
|
+
sendEndpoint: string;
|
|
346
|
+
|
|
347
|
+
/** HTTP Method
|
|
348
|
+
* @default 'POST'
|
|
349
|
+
*/
|
|
350
|
+
method?: "POST" | "PUT" | "PATCH";
|
|
351
|
+
|
|
352
|
+
/** HTTP Headers (static or dynamic function) */
|
|
353
|
+
headers?:
|
|
354
|
+
| Record<string, string>
|
|
355
|
+
| (() => Record<string, string> | Promise<Record<string, string>>);
|
|
356
|
+
|
|
357
|
+
/** Custom body mapper - transform message before sending
|
|
358
|
+
* @example (message) => ({ content: message, userId: '123' })
|
|
359
|
+
*/
|
|
360
|
+
bodyMapper?: (message: string, attachments?: FileAttachment[]) => any;
|
|
361
|
+
|
|
362
|
+
/** Response handler (optional) */
|
|
363
|
+
onResponse?: (response: Response) => void | Promise<void>;
|
|
364
|
+
|
|
365
|
+
/** Error handler */
|
|
366
|
+
onError?: (error: Error) => void;
|
|
367
|
+
|
|
368
|
+
/** Request timeout in milliseconds
|
|
369
|
+
* @default 30000
|
|
370
|
+
*/
|
|
371
|
+
timeout?: number;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
/** Callback when message is sent (when NOT using SignalR or HTTP) */
|
|
375
|
+
onSendMessage?: (message: string) => void;
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## SignalR Integration
|
|
382
|
+
|
|
383
|
+
### Basic SignalR Setup
|
|
384
|
+
|
|
385
|
+
The widget automatically manages SignalR connections:
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
import { ChatWidget } from "@urasoft/live-chat-widget";
|
|
389
|
+
import "@urasoft/live-chat-widget/dist/styles.css";
|
|
390
|
+
|
|
391
|
+
const chat = new ChatWidget({
|
|
392
|
+
signalR: {
|
|
393
|
+
hubUrl: "https://api.example.com/chatHub",
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Reconnection Strategies
|
|
399
|
+
|
|
400
|
+
The widget supports multiple reconnection strategies when connection is lost.
|
|
401
|
+
|
|
402
|
+
#### 1. Exponential Backoff (Default)
|
|
403
|
+
|
|
404
|
+
Delay doubles with each attempt: 1s → 2s → 4s → 8s → 16s...
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
const chat = new ChatWidget({
|
|
408
|
+
signalR: {
|
|
409
|
+
hubUrl: "/chatHub",
|
|
410
|
+
reconnect: {
|
|
411
|
+
maxRetries: 10,
|
|
412
|
+
delayStrategy: "exponential",
|
|
413
|
+
initialDelay: 1000, // Start with 1 second
|
|
414
|
+
maxDelay: 60000, // Cap at 60 seconds
|
|
415
|
+
},
|
|
416
|
+
onRetry: (retryCount, delayMs) => {
|
|
417
|
+
console.log(`🔄 Retry attempt ${retryCount}, waiting ${delayMs}ms`);
|
|
418
|
+
},
|
|
419
|
+
onReconnected: (connectionId) => {
|
|
420
|
+
console.log("✅ Reconnected:", connectionId);
|
|
421
|
+
},
|
|
422
|
+
onReconnectFailed: () => {
|
|
423
|
+
console.error("❌ All reconnection attempts failed");
|
|
424
|
+
alert("Unable to connect. Please refresh the page.");
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
#### 2. Linear Backoff
|
|
431
|
+
|
|
432
|
+
Delay increases linearly: 1s → 2s → 3s → 4s → 5s...
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
const chat = new ChatWidget({
|
|
436
|
+
signalR: {
|
|
437
|
+
hubUrl: "/chatHub",
|
|
438
|
+
reconnect: {
|
|
439
|
+
maxRetries: 5,
|
|
440
|
+
delayStrategy: "linear",
|
|
441
|
+
initialDelay: 2000, // Start with 2 seconds
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
#### 3. Fixed Delay
|
|
448
|
+
|
|
449
|
+
Same delay for each attempt: 5s → 5s → 5s → 5s...
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
const chat = new ChatWidget({
|
|
453
|
+
signalR: {
|
|
454
|
+
hubUrl: "/chatHub",
|
|
455
|
+
reconnect: {
|
|
456
|
+
maxRetries: 8,
|
|
457
|
+
delayStrategy: "fixed",
|
|
458
|
+
fixedDelay: 5000, // Wait 5 seconds between each attempt
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
#### 4. Custom Delay Function
|
|
465
|
+
|
|
466
|
+
Define your own reconnection strategy:
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
const chat = new ChatWidget({
|
|
470
|
+
signalR: {
|
|
471
|
+
hubUrl: "/chatHub",
|
|
472
|
+
reconnect: {
|
|
473
|
+
maxRetries: 10,
|
|
474
|
+
delayStrategy: (retryCount) => {
|
|
475
|
+
// Custom strategy: Fibonacci-like delays
|
|
476
|
+
if (retryCount === 0) return 1000;
|
|
477
|
+
if (retryCount === 1) return 1000;
|
|
478
|
+
return Math.min(
|
|
479
|
+
fibonacci(retryCount) * 1000,
|
|
480
|
+
60000 // Cap at 60 seconds
|
|
481
|
+
);
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
function fibonacci(n: number): number {
|
|
488
|
+
if (n <= 1) return 1;
|
|
489
|
+
return fibonacci(n - 1) + fibonacci(n - 2);
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Event Callbacks
|
|
494
|
+
|
|
495
|
+
Monitor every stage of the SignalR connection lifecycle:
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
const chat = new ChatWidget({
|
|
499
|
+
signalR: {
|
|
500
|
+
hubUrl: "/chatHub",
|
|
501
|
+
|
|
502
|
+
// Connection established
|
|
503
|
+
onConnected: (connectionId) => {
|
|
504
|
+
console.log("✅ Connected, Connection ID:", connectionId);
|
|
505
|
+
analytics.track("chat_connected");
|
|
506
|
+
},
|
|
507
|
+
|
|
508
|
+
// Connection lost
|
|
509
|
+
onDisconnected: (error) => {
|
|
510
|
+
console.log("❌ Disconnected:", error?.message);
|
|
511
|
+
analytics.track("chat_disconnected");
|
|
512
|
+
},
|
|
513
|
+
|
|
514
|
+
// Reconnection started
|
|
515
|
+
onReconnecting: (error) => {
|
|
516
|
+
console.log("🔄 Reconnecting...", error?.message);
|
|
517
|
+
// Show "Connecting..." in UI
|
|
518
|
+
},
|
|
519
|
+
|
|
520
|
+
// Reconnection succeeded
|
|
521
|
+
onReconnected: (connectionId) => {
|
|
522
|
+
console.log("✅ Reconnected:", connectionId);
|
|
523
|
+
// Show "Connected" in UI
|
|
524
|
+
},
|
|
525
|
+
|
|
526
|
+
// Before each retry attempt
|
|
527
|
+
onRetry: (retryCount, delayMs, elapsedMs) => {
|
|
528
|
+
console.log(`🔄 Attempt ${retryCount}, will wait ${delayMs}ms`);
|
|
529
|
+
console.log(`⏱️ Total elapsed time: ${elapsedMs}ms`);
|
|
530
|
+
// Update progress bar
|
|
531
|
+
},
|
|
532
|
+
|
|
533
|
+
// All retry attempts exhausted
|
|
534
|
+
onReconnectFailed: () => {
|
|
535
|
+
console.error("❌ Reconnection failed");
|
|
536
|
+
alert("Cannot connect to server. Please refresh the page.");
|
|
537
|
+
},
|
|
538
|
+
|
|
539
|
+
// Message received
|
|
540
|
+
onMessageReceived: (message) => {
|
|
541
|
+
console.log("📩 Message received:", message);
|
|
542
|
+
playNotificationSound();
|
|
543
|
+
},
|
|
544
|
+
|
|
545
|
+
// Before sending message (can modify it)
|
|
546
|
+
onBeforeSend: async (message) => {
|
|
547
|
+
console.log("📤 Sending:", message);
|
|
548
|
+
// Encrypt, sanitize, or format the message
|
|
549
|
+
const sanitized = sanitizeMessage(message);
|
|
550
|
+
return sanitized;
|
|
551
|
+
},
|
|
552
|
+
|
|
553
|
+
// After message sent
|
|
554
|
+
onAfterSend: (message) => {
|
|
555
|
+
console.log("✅ Sent:", message);
|
|
556
|
+
analytics.track("message_sent", { length: message.length });
|
|
557
|
+
},
|
|
558
|
+
|
|
559
|
+
// Any error occurs
|
|
560
|
+
onError: (error) => {
|
|
561
|
+
console.error("⚠️ SignalR Error:", error.message);
|
|
562
|
+
// Send to error tracking service (Sentry, LogRocket, etc.)
|
|
563
|
+
errorReporter.captureException(error);
|
|
564
|
+
},
|
|
565
|
+
|
|
566
|
+
reconnect: {
|
|
567
|
+
maxRetries: 5,
|
|
568
|
+
delayStrategy: "exponential",
|
|
569
|
+
},
|
|
570
|
+
},
|
|
571
|
+
});
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### Transport & Logging
|
|
575
|
+
|
|
576
|
+
Configure transport protocol and logging level:
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
const chat = new ChatWidget({
|
|
580
|
+
signalR: {
|
|
581
|
+
hubUrl: "/chatHub",
|
|
582
|
+
|
|
583
|
+
// Transport type
|
|
584
|
+
// Options: 'WebSockets' | 'ServerSentEvents' | 'LongPolling' | 'All'
|
|
585
|
+
transport: "WebSockets",
|
|
586
|
+
|
|
587
|
+
// Log level
|
|
588
|
+
// Use 'Debug' for development, 'Warning' or 'Error' for production
|
|
589
|
+
// Options: 'Trace' | 'Debug' | 'Information' | 'Warning' | 'Error' | 'Critical' | 'None'
|
|
590
|
+
logLevel: "Debug",
|
|
591
|
+
},
|
|
592
|
+
});
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
### JWT Authentication
|
|
596
|
+
|
|
597
|
+
Secure your connection with JWT tokens:
|
|
598
|
+
|
|
599
|
+
```typescript
|
|
600
|
+
const chat = new ChatWidget({
|
|
601
|
+
signalR: {
|
|
602
|
+
hubUrl: "/chatHub",
|
|
603
|
+
accessTokenFactory: async () => {
|
|
604
|
+
const response = await fetch("/api/auth/token");
|
|
605
|
+
const { token } = await response.json();
|
|
606
|
+
return token;
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
});
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
### Query Parameters
|
|
613
|
+
|
|
614
|
+
Pass user information via query parameters:
|
|
615
|
+
|
|
616
|
+
```typescript
|
|
617
|
+
const chat = new ChatWidget({
|
|
618
|
+
signalR: {
|
|
619
|
+
hubUrl: "/chatHub",
|
|
620
|
+
queryParams: {
|
|
621
|
+
userId: currentUser.id,
|
|
622
|
+
userName: currentUser.name,
|
|
623
|
+
department: "support",
|
|
624
|
+
language: "en",
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
});
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### Custom Headers
|
|
631
|
+
|
|
632
|
+
Add custom HTTP headers to the connection:
|
|
633
|
+
|
|
634
|
+
```typescript
|
|
635
|
+
const chat = new ChatWidget({
|
|
636
|
+
signalR: {
|
|
637
|
+
hubUrl: "/chatHub",
|
|
638
|
+
headers: {
|
|
639
|
+
"X-Api-Key": "your-api-key",
|
|
640
|
+
"X-Client-Version": "1.0.0",
|
|
641
|
+
"X-Device-Id": deviceId,
|
|
642
|
+
},
|
|
643
|
+
},
|
|
644
|
+
});
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
---
|
|
648
|
+
|
|
649
|
+
## HTTP Integration
|
|
650
|
+
|
|
651
|
+
The widget supports sending messages via HTTP REST API while optionally receiving messages through SignalR. This hybrid approach is perfect for scenarios where you want to use your existing REST API for message submission.
|
|
652
|
+
|
|
653
|
+
### Basic HTTP Setup
|
|
654
|
+
|
|
655
|
+
Send messages via HTTP POST to your API endpoint:
|
|
656
|
+
|
|
657
|
+
```typescript
|
|
658
|
+
import { ChatWidget } from "@urasoft/live-chat-widget";
|
|
659
|
+
import "@urasoft/live-chat-widget/dist/styles.css";
|
|
660
|
+
|
|
661
|
+
const chat = new ChatWidget({
|
|
662
|
+
position: "bottom-right",
|
|
663
|
+
primaryColor: "#007bff",
|
|
664
|
+
title: "Support Chat",
|
|
665
|
+
http: {
|
|
666
|
+
sendEndpoint: "https://api.example.com/chat/messages",
|
|
667
|
+
method: "POST",
|
|
668
|
+
headers: {
|
|
669
|
+
"Content-Type": "application/json",
|
|
670
|
+
"Authorization": "Bearer YOUR_API_TOKEN"
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
**Default Request Body Format:**
|
|
677
|
+
```json
|
|
678
|
+
{
|
|
679
|
+
"message": "User's message text",
|
|
680
|
+
"attachments": [],
|
|
681
|
+
"timestamp": "2025-01-15T10:30:00.000Z"
|
|
682
|
+
}
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### Custom Body Mapping
|
|
686
|
+
|
|
687
|
+
Transform the message into your API's expected format:
|
|
688
|
+
|
|
689
|
+
```typescript
|
|
690
|
+
const chat = new ChatWidget({
|
|
691
|
+
http: {
|
|
692
|
+
sendEndpoint: "https://api.example.com/messages",
|
|
693
|
+
bodyMapper: (message, attachments) => ({
|
|
694
|
+
// Custom format for your API
|
|
695
|
+
content: message,
|
|
696
|
+
userId: getCurrentUserId(),
|
|
697
|
+
sessionId: getSessionId(),
|
|
698
|
+
files: attachments?.map(f => ({
|
|
699
|
+
filename: f.name,
|
|
700
|
+
base64: f.data,
|
|
701
|
+
mimeType: f.type
|
|
702
|
+
})),
|
|
703
|
+
metadata: {
|
|
704
|
+
timestamp: new Date().toISOString(),
|
|
705
|
+
platform: "web",
|
|
706
|
+
language: navigator.language
|
|
707
|
+
}
|
|
708
|
+
})
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
### Dynamic Headers
|
|
714
|
+
|
|
715
|
+
Use a function to provide dynamic headers (e.g., fresh JWT tokens):
|
|
716
|
+
|
|
717
|
+
```typescript
|
|
718
|
+
const chat = new ChatWidget({
|
|
719
|
+
http: {
|
|
720
|
+
sendEndpoint: "/api/chat/send",
|
|
721
|
+
headers: async () => {
|
|
722
|
+
// Fetch fresh token on every request
|
|
723
|
+
const token = await getAccessToken();
|
|
724
|
+
return {
|
|
725
|
+
"Content-Type": "application/json",
|
|
726
|
+
"Authorization": `Bearer ${token}`,
|
|
727
|
+
"X-Request-ID": generateRequestId(),
|
|
728
|
+
"X-User-ID": getCurrentUserId()
|
|
729
|
+
};
|
|
730
|
+
},
|
|
731
|
+
bodyMapper: (message) => ({
|
|
732
|
+
text: message,
|
|
733
|
+
userId: getCurrentUserId()
|
|
734
|
+
})
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### HTTP + SignalR Hybrid
|
|
740
|
+
|
|
741
|
+
**Perfect use case:** Send messages via your REST API, receive responses via SignalR
|
|
742
|
+
|
|
743
|
+
```typescript
|
|
744
|
+
const chat = new ChatWidget({
|
|
745
|
+
position: "bottom-right",
|
|
746
|
+
|
|
747
|
+
// HTTP: Send messages to REST API
|
|
748
|
+
http: {
|
|
749
|
+
sendEndpoint: "https://api.example.com/chat/messages",
|
|
750
|
+
method: "POST",
|
|
751
|
+
headers: async () => ({
|
|
752
|
+
"Authorization": `Bearer ${await getToken()}`,
|
|
753
|
+
"Content-Type": "application/json"
|
|
754
|
+
}),
|
|
755
|
+
bodyMapper: (message) => ({
|
|
756
|
+
content: message,
|
|
757
|
+
userId: currentUser.id,
|
|
758
|
+
conversationId: conversationId
|
|
759
|
+
}),
|
|
760
|
+
onResponse: async (response) => {
|
|
761
|
+
const data = await response.json();
|
|
762
|
+
console.log("Message sent successfully:", data.messageId);
|
|
763
|
+
},
|
|
764
|
+
onError: (error) => {
|
|
765
|
+
console.error("Failed to send message:", error);
|
|
766
|
+
alert("Could not send message. Please try again.");
|
|
767
|
+
},
|
|
768
|
+
timeout: 10000 // 10 seconds
|
|
769
|
+
},
|
|
770
|
+
|
|
771
|
+
// SignalR: Receive messages in real-time
|
|
772
|
+
signalR: {
|
|
773
|
+
hubUrl: "https://api.example.com/chatHub",
|
|
774
|
+
queryParams: {
|
|
775
|
+
userId: currentUser.id,
|
|
776
|
+
conversationId: conversationId
|
|
777
|
+
},
|
|
778
|
+
receiveMethodName: "ReceiveMessage",
|
|
779
|
+
onConnected: (connectionId) => {
|
|
780
|
+
console.log("Connected to SignalR:", connectionId);
|
|
781
|
+
},
|
|
782
|
+
onMessageReceived: (message) => {
|
|
783
|
+
console.log("New message received:", message);
|
|
784
|
+
},
|
|
785
|
+
reconnect: {
|
|
786
|
+
maxRetries: 5,
|
|
787
|
+
delayStrategy: "exponential"
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
### Error Handling & Response Processing
|
|
794
|
+
|
|
795
|
+
```typescript
|
|
796
|
+
const chat = new ChatWidget({
|
|
797
|
+
http: {
|
|
798
|
+
sendEndpoint: "/api/chat/messages",
|
|
799
|
+
|
|
800
|
+
// Handle successful responses
|
|
801
|
+
onResponse: async (response) => {
|
|
802
|
+
const data = await response.json();
|
|
803
|
+
|
|
804
|
+
// Track analytics
|
|
805
|
+
analytics.track("message_sent", {
|
|
806
|
+
messageId: data.id,
|
|
807
|
+
timestamp: data.timestamp
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
// Show confirmation toast
|
|
811
|
+
if (data.queued) {
|
|
812
|
+
showToast("Your message is being processed...");
|
|
813
|
+
}
|
|
814
|
+
},
|
|
815
|
+
|
|
816
|
+
// Handle errors
|
|
817
|
+
onError: (error) => {
|
|
818
|
+
// Log to error tracking service
|
|
819
|
+
errorTracker.captureException(error);
|
|
820
|
+
|
|
821
|
+
// Show user-friendly message
|
|
822
|
+
if (error.name === "AbortError") {
|
|
823
|
+
showToast("Request timed out. Please try again.");
|
|
824
|
+
} else if (error.message.includes("401")) {
|
|
825
|
+
showToast("Session expired. Please log in again.");
|
|
826
|
+
redirectToLogin();
|
|
827
|
+
} else {
|
|
828
|
+
showToast("Failed to send message. Please check your connection.");
|
|
829
|
+
}
|
|
830
|
+
},
|
|
831
|
+
|
|
832
|
+
timeout: 15000 // 15 seconds
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
### HTTP Only (No SignalR)
|
|
838
|
+
|
|
839
|
+
If you don't need real-time message receiving:
|
|
840
|
+
|
|
841
|
+
```typescript
|
|
842
|
+
const chat = new ChatWidget({
|
|
843
|
+
position: "bottom-right",
|
|
844
|
+
title: "Contact Us",
|
|
845
|
+
welcomeMessage: "Send us a message and we'll get back to you soon!",
|
|
846
|
+
|
|
847
|
+
http: {
|
|
848
|
+
sendEndpoint: "https://api.example.com/contact/submit",
|
|
849
|
+
bodyMapper: (message) => ({
|
|
850
|
+
message: message,
|
|
851
|
+
email: userEmail,
|
|
852
|
+
subject: "Website Inquiry",
|
|
853
|
+
timestamp: new Date().toISOString()
|
|
854
|
+
}),
|
|
855
|
+
onResponse: async (response) => {
|
|
856
|
+
const data = await response.json();
|
|
857
|
+
// Show success message in chat
|
|
858
|
+
chat.addAgentMessage(
|
|
859
|
+
`Thank you! Your message has been received. Reference: ${data.ticketId}`
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
### Backend API Example (Node.js/Express)
|
|
867
|
+
|
|
868
|
+
```javascript
|
|
869
|
+
app.post("/api/chat/messages", async (req, res) => {
|
|
870
|
+
const { content, userId, conversationId } = req.body;
|
|
871
|
+
|
|
872
|
+
// Save message to database
|
|
873
|
+
const message = await db.messages.create({
|
|
874
|
+
content,
|
|
875
|
+
userId,
|
|
876
|
+
conversationId,
|
|
877
|
+
timestamp: new Date()
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
// Process message (AI, routing, etc.)
|
|
881
|
+
const response = await processMessage(message);
|
|
882
|
+
|
|
883
|
+
// Send response via SignalR to user
|
|
884
|
+
await signalRHub.clients.user(userId).send("ReceiveMessage", {
|
|
885
|
+
text: response.content,
|
|
886
|
+
avatar: response.agentAvatar,
|
|
887
|
+
timestamp: new Date()
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
// Return confirmation
|
|
891
|
+
res.json({
|
|
892
|
+
success: true,
|
|
893
|
+
messageId: message.id,
|
|
894
|
+
queued: true
|
|
895
|
+
});
|
|
896
|
+
});
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
---
|
|
900
|
+
|
|
901
|
+
## Framework Integration
|
|
902
|
+
|
|
903
|
+
### React / Next.js
|
|
904
|
+
|
|
905
|
+
```tsx
|
|
906
|
+
"use client"; // For Next.js 13+ App Router
|
|
907
|
+
|
|
908
|
+
import { useEffect, useRef } from "react";
|
|
909
|
+
import { ChatWidget } from "@urasoft/live-chat-widget";
|
|
910
|
+
import "@urasoft/live-chat-widget/dist/styles.css";
|
|
911
|
+
|
|
912
|
+
export default function ChatComponent() {
|
|
913
|
+
const chatRef = useRef<ChatWidget | null>(null);
|
|
914
|
+
|
|
915
|
+
useEffect(() => {
|
|
916
|
+
chatRef.current = new ChatWidget({
|
|
917
|
+
position: "bottom-right",
|
|
918
|
+
primaryColor: "#007bff",
|
|
919
|
+
title: "Live Support",
|
|
920
|
+
signalR: {
|
|
921
|
+
hubUrl: process.env.NEXT_PUBLIC_CHAT_HUB_URL || "/chatHub",
|
|
922
|
+
queryParams: {
|
|
923
|
+
userId: "user-123",
|
|
924
|
+
},
|
|
925
|
+
accessTokenFactory: async () => {
|
|
926
|
+
const response = await fetch("/api/auth/token");
|
|
927
|
+
const data = await response.json();
|
|
928
|
+
return data.token;
|
|
929
|
+
},
|
|
930
|
+
onConnected: (connectionId) => {
|
|
931
|
+
console.log("Chat connected:", connectionId);
|
|
932
|
+
},
|
|
933
|
+
onMessageReceived: (message) => {
|
|
934
|
+
console.log("New message:", message);
|
|
935
|
+
},
|
|
936
|
+
},
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
return () => {
|
|
940
|
+
chatRef.current?.destroy();
|
|
941
|
+
};
|
|
942
|
+
}, []);
|
|
943
|
+
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
### Vue 3
|
|
949
|
+
|
|
950
|
+
```vue
|
|
951
|
+
<template>
|
|
952
|
+
<div></div>
|
|
953
|
+
</template>
|
|
954
|
+
|
|
955
|
+
<script setup lang="ts">
|
|
956
|
+
import { onMounted, onUnmounted } from "vue";
|
|
957
|
+
import { ChatWidget } from "@urasoft/live-chat-widget";
|
|
958
|
+
import "@urasoft/live-chat-widget/dist/styles.css";
|
|
959
|
+
|
|
960
|
+
let chat: ChatWidget | null = null;
|
|
961
|
+
|
|
962
|
+
onMounted(() => {
|
|
963
|
+
chat = new ChatWidget({
|
|
964
|
+
position: "bottom-right",
|
|
965
|
+
primaryColor: "#42b883",
|
|
966
|
+
title: "Live Support",
|
|
967
|
+
signalR: {
|
|
968
|
+
hubUrl: import.meta.env.VITE_CHAT_HUB_URL || "/chatHub",
|
|
969
|
+
queryParams: {
|
|
970
|
+
sessionId: "vue-session-123",
|
|
971
|
+
},
|
|
972
|
+
onConnected: (connectionId) => {
|
|
973
|
+
console.log("Chat connected:", connectionId);
|
|
974
|
+
},
|
|
975
|
+
onMessageReceived: (message) => {
|
|
976
|
+
console.log("New message:", message);
|
|
977
|
+
},
|
|
978
|
+
},
|
|
979
|
+
});
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
onUnmounted(() => {
|
|
983
|
+
chat?.destroy();
|
|
984
|
+
});
|
|
985
|
+
</script>
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
### Angular
|
|
989
|
+
|
|
990
|
+
```typescript
|
|
991
|
+
import { Component, OnInit, OnDestroy } from "@angular/core";
|
|
992
|
+
import { ChatWidget } from "@urasoft/live-chat-widget";
|
|
993
|
+
import "@urasoft/live-chat-widget/dist/styles.css";
|
|
994
|
+
|
|
995
|
+
@Component({
|
|
996
|
+
selector: "app-chat",
|
|
997
|
+
template: "",
|
|
998
|
+
})
|
|
999
|
+
export class ChatComponent implements OnInit, OnDestroy {
|
|
1000
|
+
private chat: ChatWidget | null = null;
|
|
1001
|
+
|
|
1002
|
+
ngOnInit() {
|
|
1003
|
+
this.chat = new ChatWidget({
|
|
1004
|
+
position: "bottom-right",
|
|
1005
|
+
primaryColor: "#dd0031",
|
|
1006
|
+
title: "Live Support",
|
|
1007
|
+
signalR: {
|
|
1008
|
+
hubUrl: "/chatHub",
|
|
1009
|
+
queryParams: {
|
|
1010
|
+
userId: "angular-user-123",
|
|
1011
|
+
},
|
|
1012
|
+
onConnected: (connectionId) => {
|
|
1013
|
+
console.log("Chat connected:", connectionId);
|
|
1014
|
+
},
|
|
1015
|
+
onMessageReceived: (message) => {
|
|
1016
|
+
console.log("New message:", message);
|
|
1017
|
+
},
|
|
1018
|
+
},
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
ngOnDestroy() {
|
|
1023
|
+
this.chat?.destroy();
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
### Vanilla JavaScript
|
|
1029
|
+
|
|
1030
|
+
```html
|
|
1031
|
+
<!DOCTYPE html>
|
|
1032
|
+
<html lang="en">
|
|
1033
|
+
<head>
|
|
1034
|
+
<meta charset="UTF-8" />
|
|
1035
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
1036
|
+
<title>Chat Widget Demo</title>
|
|
1037
|
+
<link
|
|
1038
|
+
rel="stylesheet"
|
|
1039
|
+
href="node_modules/@urasoft/live-chat-widget/dist/styles.css"
|
|
1040
|
+
/>
|
|
1041
|
+
</head>
|
|
1042
|
+
<body>
|
|
1043
|
+
<h1>My Website</h1>
|
|
1044
|
+
|
|
1045
|
+
<script type="module">
|
|
1046
|
+
import { ChatWidget } from "./node_modules/@urasoft/live-chat-widget/dist/index.esm.js";
|
|
1047
|
+
|
|
1048
|
+
const chat = new ChatWidget({
|
|
1049
|
+
position: "bottom-right",
|
|
1050
|
+
primaryColor: "#007bff",
|
|
1051
|
+
title: "Live Support",
|
|
1052
|
+
welcomeMessage: "Hello! How can we help you today?",
|
|
1053
|
+
signalR: {
|
|
1054
|
+
hubUrl: "https://your-server.com/chatHub",
|
|
1055
|
+
queryParams: {
|
|
1056
|
+
userId: "guest-" + Date.now(),
|
|
1057
|
+
},
|
|
1058
|
+
onConnected: () => {
|
|
1059
|
+
console.log("Connected to live chat");
|
|
1060
|
+
},
|
|
1061
|
+
onMessageReceived: (message) => {
|
|
1062
|
+
console.log("Received:", message);
|
|
1063
|
+
},
|
|
1064
|
+
},
|
|
1065
|
+
});
|
|
1066
|
+
</script>
|
|
1067
|
+
</body>
|
|
1068
|
+
</html>
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
---
|
|
1072
|
+
|
|
1073
|
+
## API Methods
|
|
1074
|
+
|
|
1075
|
+
### `open()`
|
|
1076
|
+
|
|
1077
|
+
Open the chat window programmatically.
|
|
1078
|
+
|
|
1079
|
+
```typescript
|
|
1080
|
+
chat.open();
|
|
1081
|
+
```
|
|
1082
|
+
|
|
1083
|
+
### `close()`
|
|
1084
|
+
|
|
1085
|
+
Close the chat window programmatically.
|
|
1086
|
+
|
|
1087
|
+
```typescript
|
|
1088
|
+
chat.close();
|
|
1089
|
+
```
|
|
1090
|
+
|
|
1091
|
+
### `destroy()`
|
|
1092
|
+
|
|
1093
|
+
Completely remove the widget from the page and disconnect SignalR.
|
|
1094
|
+
|
|
1095
|
+
```typescript
|
|
1096
|
+
chat.destroy();
|
|
1097
|
+
```
|
|
1098
|
+
|
|
1099
|
+
### `addAgentMessage(message, avatar?)`
|
|
1100
|
+
|
|
1101
|
+
Add an agent message programmatically (useful for SignalR message handling).
|
|
1102
|
+
|
|
1103
|
+
```typescript
|
|
1104
|
+
chat.addAgentMessage("Hello! How can I help you?", "/avatar.png");
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1107
|
+
### `isConnected()`
|
|
1108
|
+
|
|
1109
|
+
Check if SignalR is currently connected.
|
|
1110
|
+
|
|
1111
|
+
```typescript
|
|
1112
|
+
if (chat.isConnected()) {
|
|
1113
|
+
console.log("Chat is connected");
|
|
1114
|
+
}
|
|
1115
|
+
```
|
|
1116
|
+
|
|
1117
|
+
---
|
|
1118
|
+
|
|
1119
|
+
## Backend Setup
|
|
1120
|
+
|
|
1121
|
+
### ASP.NET Core SignalR Hub (C#)
|
|
1122
|
+
|
|
1123
|
+
```csharp
|
|
1124
|
+
using Microsoft.AspNetCore.SignalR;
|
|
1125
|
+
|
|
1126
|
+
public class ChatHub : Hub
|
|
1127
|
+
{
|
|
1128
|
+
public async Task SendMessage(string message)
|
|
1129
|
+
{
|
|
1130
|
+
// Get user identifier
|
|
1131
|
+
var userId = Context.UserIdentifier;
|
|
1132
|
+
var connectionId = Context.ConnectionId;
|
|
1133
|
+
|
|
1134
|
+
Console.WriteLine($"Message from {userId}: {message}");
|
|
1135
|
+
|
|
1136
|
+
// Process message and generate response
|
|
1137
|
+
var response = await ProcessMessageAsync(message, userId);
|
|
1138
|
+
|
|
1139
|
+
// Send response back to caller
|
|
1140
|
+
await Clients.Caller.SendAsync("ReceiveMessage", response);
|
|
1141
|
+
|
|
1142
|
+
// Or broadcast to all clients
|
|
1143
|
+
// await Clients.All.SendAsync("ReceiveMessage", response);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
public override async Task OnConnectedAsync()
|
|
1147
|
+
{
|
|
1148
|
+
// Get query parameters
|
|
1149
|
+
var httpContext = Context.GetHttpContext();
|
|
1150
|
+
var userId = httpContext?.Request.Query["userId"];
|
|
1151
|
+
var userName = httpContext?.Request.Query["userName"];
|
|
1152
|
+
|
|
1153
|
+
Console.WriteLine($"User connected: {userId} ({userName})");
|
|
1154
|
+
|
|
1155
|
+
// Send welcome message
|
|
1156
|
+
await Clients.Caller.SendAsync("ReceiveMessage",
|
|
1157
|
+
"Welcome to live support! How can we help you today?");
|
|
1158
|
+
|
|
1159
|
+
await base.OnConnectedAsync();
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
public override async Task OnDisconnectedAsync(Exception? exception)
|
|
1163
|
+
{
|
|
1164
|
+
Console.WriteLine($"User disconnected: {Context.ConnectionId}");
|
|
1165
|
+
|
|
1166
|
+
if (exception != null)
|
|
1167
|
+
{
|
|
1168
|
+
Console.WriteLine($"Disconnection error: {exception.Message}");
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
await base.OnDisconnectedAsync(exception);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
private async Task<string> ProcessMessageAsync(string message, string userId)
|
|
1175
|
+
{
|
|
1176
|
+
// Add your message processing logic here
|
|
1177
|
+
// e.g., AI chatbot, database queries, etc.
|
|
1178
|
+
|
|
1179
|
+
await Task.Delay(500); // Simulate processing
|
|
1180
|
+
|
|
1181
|
+
return $"You said: {message}. How else can I assist you?";
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
```
|
|
1185
|
+
|
|
1186
|
+
### Configure SignalR in Startup/Program.cs
|
|
1187
|
+
|
|
1188
|
+
```csharp
|
|
1189
|
+
var builder = WebApplication.CreateBuilder(args);
|
|
1190
|
+
|
|
1191
|
+
// Add SignalR
|
|
1192
|
+
builder.Services.AddSignalR();
|
|
1193
|
+
|
|
1194
|
+
// Add CORS if needed
|
|
1195
|
+
builder.Services.AddCors(options =>
|
|
1196
|
+
{
|
|
1197
|
+
options.AddPolicy("ChatPolicy", policy =>
|
|
1198
|
+
{
|
|
1199
|
+
policy.WithOrigins("http://localhost:3000", "https://yourdomain.com")
|
|
1200
|
+
.AllowAnyHeader()
|
|
1201
|
+
.AllowAnyMethod()
|
|
1202
|
+
.AllowCredentials();
|
|
1203
|
+
});
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
var app = builder.Build();
|
|
1207
|
+
|
|
1208
|
+
app.UseCors("ChatPolicy");
|
|
1209
|
+
|
|
1210
|
+
// Map SignalR hub
|
|
1211
|
+
app.MapHub<ChatHub>("/chatHub");
|
|
1212
|
+
|
|
1213
|
+
app.Run();
|
|
1214
|
+
```
|
|
1215
|
+
|
|
1216
|
+
### Node.js / Express Example
|
|
1217
|
+
|
|
1218
|
+
```javascript
|
|
1219
|
+
const express = require("express");
|
|
1220
|
+
const http = require("http");
|
|
1221
|
+
const { Server } = require("socket.io");
|
|
1222
|
+
|
|
1223
|
+
const app = express();
|
|
1224
|
+
const server = http.createServer(app);
|
|
1225
|
+
const io = new Server(server, {
|
|
1226
|
+
cors: {
|
|
1227
|
+
origin: "http://localhost:3000",
|
|
1228
|
+
methods: ["GET", "POST"],
|
|
1229
|
+
},
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
io.on("connection", (socket) => {
|
|
1233
|
+
console.log("User connected:", socket.id);
|
|
1234
|
+
|
|
1235
|
+
// Get query parameters
|
|
1236
|
+
const { userId, userName } = socket.handshake.query;
|
|
1237
|
+
console.log(`User: ${userName} (${userId})`);
|
|
1238
|
+
|
|
1239
|
+
// Send welcome message
|
|
1240
|
+
socket.emit("ReceiveMessage", "Welcome to live support!");
|
|
1241
|
+
|
|
1242
|
+
// Handle incoming messages
|
|
1243
|
+
socket.on("SendMessage", (message) => {
|
|
1244
|
+
console.log("Message received:", message);
|
|
1245
|
+
|
|
1246
|
+
// Process and respond
|
|
1247
|
+
const response = `You said: ${message}`;
|
|
1248
|
+
socket.emit("ReceiveMessage", response);
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
socket.on("disconnect", () => {
|
|
1252
|
+
console.log("User disconnected:", socket.id);
|
|
1253
|
+
});
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
server.listen(3001, () => {
|
|
1257
|
+
console.log("SignalR server running on port 3001");
|
|
1258
|
+
});
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
---
|
|
1262
|
+
|
|
1263
|
+
## Custom Styling
|
|
1264
|
+
|
|
1265
|
+
### Using CSS Variables
|
|
1266
|
+
|
|
1267
|
+
```css
|
|
1268
|
+
:root {
|
|
1269
|
+
--chat-primary-color: #007bff;
|
|
1270
|
+
--chat-secondary-color: #6c757d;
|
|
1271
|
+
--chat-background: #ffffff;
|
|
1272
|
+
--chat-text-color: #212529;
|
|
1273
|
+
}
|
|
1274
|
+
```
|
|
1275
|
+
|
|
1276
|
+
### Overriding Tailwind Classes
|
|
1277
|
+
|
|
1278
|
+
```css
|
|
1279
|
+
/* Override specific chat widget styles */
|
|
1280
|
+
.chat-widget-container {
|
|
1281
|
+
/* Your custom styles */
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
.chat-header {
|
|
1285
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
.chat-message {
|
|
1289
|
+
border-radius: 12px;
|
|
1290
|
+
}
|
|
1291
|
+
```
|
|
1292
|
+
|
|
1293
|
+
---
|
|
1294
|
+
|
|
1295
|
+
## Troubleshooting
|
|
1296
|
+
|
|
1297
|
+
### Connection Issues
|
|
1298
|
+
|
|
1299
|
+
**Problem:** Chat won't connect to SignalR Hub
|
|
1300
|
+
|
|
1301
|
+
**Solutions:**
|
|
1302
|
+
|
|
1303
|
+
- Verify `hubUrl` is correct and accessible
|
|
1304
|
+
- Check CORS settings on your server
|
|
1305
|
+
- Ensure SignalR Hub is properly configured
|
|
1306
|
+
- Check browser console for error messages
|
|
1307
|
+
- Verify `accessTokenFactory` returns valid token (if using authentication)
|
|
1308
|
+
|
|
1309
|
+
### Reconnection Not Working
|
|
1310
|
+
|
|
1311
|
+
**Problem:** Chat doesn't reconnect after connection loss
|
|
1312
|
+
|
|
1313
|
+
**Solutions:**
|
|
1314
|
+
|
|
1315
|
+
- Check `disableAutoReconnect` is not set to `true`
|
|
1316
|
+
- Verify `maxRetries` is greater than 0
|
|
1317
|
+
- Check `onError` callback for error details
|
|
1318
|
+
- Ensure network connectivity is restored
|
|
1319
|
+
|
|
1320
|
+
### Messages Not Displaying
|
|
1321
|
+
|
|
1322
|
+
**Problem:** Messages sent but not appearing in chat
|
|
1323
|
+
|
|
1324
|
+
**Solutions:**
|
|
1325
|
+
|
|
1326
|
+
- Verify `receiveMethodName` matches server-side method
|
|
1327
|
+
- Check `onMessageReceived` callback is firing
|
|
1328
|
+
- Ensure message format matches expected structure
|
|
1329
|
+
- Check browser console for errors
|
|
1330
|
+
|
|
1331
|
+
---
|
|
1332
|
+
|
|
1333
|
+
## Performance Tips
|
|
1334
|
+
|
|
1335
|
+
1. **Use WebSockets transport** for best performance:
|
|
1336
|
+
|
|
1337
|
+
```typescript
|
|
1338
|
+
signalR: {
|
|
1339
|
+
transport: "WebSockets";
|
|
1340
|
+
}
|
|
1341
|
+
```
|
|
1342
|
+
|
|
1343
|
+
2. **Adjust log level in production**:
|
|
1344
|
+
|
|
1345
|
+
```typescript
|
|
1346
|
+
signalR: {
|
|
1347
|
+
logLevel: "Warning"; // or "Error"
|
|
1348
|
+
}
|
|
1349
|
+
```
|
|
1350
|
+
|
|
1351
|
+
3. **Implement message throttling** in `onBeforeSend`:
|
|
1352
|
+
|
|
1353
|
+
```typescript
|
|
1354
|
+
onBeforeSend: async (message) => {
|
|
1355
|
+
await throttle(1000); // Wait 1 second between messages
|
|
1356
|
+
return message;
|
|
1357
|
+
};
|
|
1358
|
+
```
|
|
1359
|
+
|
|
1360
|
+
4. **Clean up properly** when component unmounts:
|
|
1361
|
+
```typescript
|
|
1362
|
+
useEffect(() => {
|
|
1363
|
+
const chat = new ChatWidget({...});
|
|
1364
|
+
return () => chat.destroy(); // Important!
|
|
1365
|
+
}, []);
|
|
1366
|
+
```
|
|
1367
|
+
|
|
1368
|
+
---
|
|
1369
|
+
|
|
1370
|
+
## Browser Support
|
|
1371
|
+
|
|
1372
|
+
- ✅ Chrome/Edge (latest)
|
|
1373
|
+
- ✅ Firefox (latest)
|
|
1374
|
+
- ✅ Safari (latest)
|
|
1375
|
+
- ✅ Opera (latest)
|
|
1376
|
+
- ✅ Mobile browsers (iOS Safari, Chrome Mobile)
|
|
1377
|
+
|
|
1378
|
+
---
|
|
1379
|
+
|
|
1380
|
+
## Contributing
|
|
1381
|
+
|
|
1382
|
+
Contributions are welcome! Please feel free to submit issues or pull requests.
|
|
1383
|
+
|
|
1384
|
+
1. Fork the repository
|
|
1385
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
1386
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
1387
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
1388
|
+
5. Open a Pull Request
|
|
1389
|
+
|
|
1390
|
+
---
|
|
1391
|
+
|
|
1392
|
+
## License
|
|
1393
|
+
|
|
1394
|
+
MIT © [Urasoft](https://urasoft.com)
|
|
1395
|
+
|
|
1396
|
+
---
|
|
1397
|
+
|
|
1398
|
+
## Links
|
|
1399
|
+
|
|
1400
|
+
- **Documentation:** [GitHub Repository](https://github.com/UrasoftYazilim/live-chat-widget)
|
|
1401
|
+
- **npm Package:** [npmjs.com](https://www.npmjs.com/package/@urasoft/live-chat-widget)
|
|
1402
|
+
- **Issues:** [GitHub Issues](https://github.com/UrasoftYazilim/live-chat-widget/issues)
|
|
1403
|
+
- **Contact:** [Urasoft](https://urasoft.com)
|
|
1404
|
+
|
|
1405
|
+
---
|
|
1406
|
+
|
|
1407
|
+
<div align="center">
|
|
1408
|
+
|
|
1409
|
+
Made with ❤️ by [Urasoft](https://urasoft.com)
|
|
1410
|
+
|
|
1411
|
+
**[⬆ back to top](#urasoft-live-chat-widget)**
|
|
1412
|
+
|
|
1413
|
+
</div>
|