@data-netmonk/mona-chat-widget 2.5.1 → 2.6.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 CHANGED
@@ -1,760 +1,903 @@
1
- # Mona Chat Widget
2
-
3
- Chat widget package developed by Netmonk data & solution team to be imported in Netmonk products
4
-
5
- ---
6
-
7
- ### Recent Updates & Breaking Changes
8
-
9
- ---
10
-
11
- **Latest Version Changes:**
12
-
13
- ⚠️ **Breaking Changes:**
14
- 1. **Removed `type` and `agentType` props** - These parameters are no longer used and have been removed from all components
15
- 2. **Renamed `botServerUrl` to `webhookUrl`** - For better clarity and consistency
16
- 3. **`webhookUrl` is now required** - Must be provided as a prop
17
- 4. **`authUrl` and `username` are now direct props** - No longer part of `data` prop for better clarity and type safety
18
- 5. **`userId` is now optional** - Widget automatically generates visitor ID for guest users via browser fingerprinting when `userId` is not provided
19
-
20
- **New Features:**
21
- 1. **Guest user support** - Users can chat without logging in
22
- - Automatic visitor ID generation using browser fingerprinting (FingerprintJS + SHA256)
23
- - `auth: false` flag automatically added to API requests for guest users
24
- - Persistent sessions across page reloads for anonymous visitors
25
- 2. **Authentication support** - Automatic token management with refresh on 401 errors
26
- - Pass `authUrl` as a direct prop
27
- - Widget handles token lifecycle automatically
28
- 3. **Enhanced user authentication handling** - Smart detection of authenticated vs guest users
29
- - Compares `userId` with visitor ID to determine authentication status
30
- - Automatic `auth` flag management in all API requests
31
- 4. **Enhanced data prop** - Support for custom variables passed to backend via `data` prop
32
- 5. **Improved error handling** - Better fallback mechanisms and error messages
33
- 6. **Direct `username` prop** - Pass username as a top-level prop instead of in data string
34
-
35
- 📝 **Migration Guide:**
36
- ```jsx
37
- // Old usage (deprecated)
38
- <ChatWidget
39
- userId="user123"
40
- sourceId="source456"
41
- type="prime"
42
- botServerUrl="https://api.example.com"
43
- />
44
-
45
- // Previous version (with data prop)
46
- <ChatWidget
47
- userId="user123"
48
- sourceId="source456"
49
- webhookUrl="https://api.example.com/webhook"
50
- data="authUrl=https://api.example.com/login/chatwidget~username=John"
51
- />
52
-
53
- // New usage - Authenticated user (current - recommended)
54
- <ChatWidget
55
- userId="user123"
56
- sourceId="source456"
57
- webhookUrl="https://api.example.com/webhook"
58
- authUrl="https://api.example.com/login/chatwidget"
59
- username="John"
60
- data="email=john@example.com~phone=+1234567890"
61
- />
62
-
63
- // New usage - Guest user (without login)
64
- <ChatWidget
65
- sourceId="source456"
66
- webhookUrl="https://api.example.com/webhook"
67
- username="Guest"
68
- />
69
- // Widget automatically generates visitor ID and adds auth: false flag
70
-
71
- // New usage - Conditional (handles both logged-in and guest users)
72
- <ChatWidget
73
- userId={currentUser?.id} // undefined for guests
74
- sourceId="source456"
75
- webhookUrl="https://api.example.com/webhook"
76
- username={currentUser?.name || "Guest"}
77
- />
78
- ```
79
-
80
- ---
81
-
82
- ## 🚅 Quick start
83
-
84
- ### Prerequisites
85
-
86
- ---
87
-
88
- 1. Install dependencies
89
- ```
90
- npm install --legacy-peer-deps
91
- ```
92
- 2. Copy .env.example
93
- ```
94
- cp .env.example .env
95
- ```
96
- 3. Populate .env
97
- 4. Enable mock mode (optional)
98
-
99
- To test the chat widget without a backend server, set `VITE_USE_MOCK_RESPONSES=true` in your `.env` file. The widget will respond to messages like:
100
- - "start", "hello", "hi", "halo" - Greeting messages
101
- - "help", "bantuan" - Help information
102
- - "terima kasih", "thank you" - Acknowledgments
103
- - "bye", "goodbye" - Farewell messages
104
- - And more! Check `src/components/ChatWidget/utils/helpers.js` for full list
105
-
106
- ---
107
-
108
- ### Mock Mode (Demo without Backend)
109
-
110
- ---
111
-
112
- The chat widget includes a built-in mock mode for testing and demonstrations without requiring a backend server.
113
-
114
- **To enable mock mode:**
115
- 1. Set `VITE_USE_MOCK_RESPONSES=true` in your `.env` file
116
- 2. Run the app normally with `npm run dev`
117
-
118
- **Mock responses include:**
119
- - Greetings (hello, hi, halo, start) - with interactive buttons
120
- - Help commands
121
- - Device list with clickable buttons (type "devices" or "show devices")
122
- - Thank you acknowledgments
123
- - Farewells
124
- - Time-based greetings (good morning, etc.)
125
- - And automatically falls back to mock mode if backend fails
126
-
127
- **Interactive Buttons Demo:**
128
- Type these messages to see button responses:
129
- - "start" - Welcome message with action buttons
130
- - "show devices" or "devices" - List of devices as clickable buttons
131
- - Click any button to trigger the next action
132
-
133
- **To add custom mock responses:**
134
- Edit the `mockBotResponses` object in `src/components/ChatWidget/utils/mockBotResponses.js`
135
-
136
- For text-only responses:
137
- ```javascript
138
- "trigger": "Response text"
139
- ```
140
-
141
- For responses with buttons:
142
- ```javascript
143
- "trigger": {
144
- text: "Your message here",
145
- buttons: [
146
- { title: "Button 1", payload: "action_1" },
147
- { title: "Button 2", payload: "action_2" },
148
- { title: "Link Button", url: "https://example.com" }
149
- ]
150
- }
151
- ```
152
-
153
- ---
154
-
155
- ### Storybook
156
-
157
- ---
158
-
159
- 1. **How to run Storybook locally** (access at http://localhost:5177)
160
-
161
- ```
162
- npm run storybook
163
- ```
164
-
165
- 2. **How to build Storybook**
166
-
167
- ```
168
- npm run build-storybook
169
- ```
170
-
171
- 3. **How to serve Storybook**
172
-
173
- ```
174
- npm run serve-storybook
175
- ```
176
-
177
- ---
178
-
179
- ### Library (how to update and publish)
180
-
181
- ---
182
-
183
- 1. **Commit changes**
184
- ```
185
- git add .
186
- git commit -m "Your commit message"
187
- ```
188
- 2. **Update version**
189
-
190
- ```bash
191
- npm version patch # for bug fixes (1.0.0 -> 1.0.1)
192
- ```
193
-
194
- ```bash
195
- npm version minor # for new features (1.0.0 -> 1.1.0)
196
- ```
197
-
198
- ```bash
199
- npm version major # for breaking changes (1.0.0 -> 2.0.0)
200
- ```
201
-
202
- 3. **Build as a library** (build file at `/dist` directory)
203
-
204
- ```
205
- npm run build
206
- ```
207
-
208
- 4. **Copy declaration file to `/dist`**
209
-
210
- ```
211
- cp ./src/declarations/index.d.ts ./dist/index.d.ts
212
- ```
213
-
214
- 5. **Publish**
215
-
216
- ```
217
- npm publish
218
- ```
219
-
220
- ---
221
-
222
- ### Library (how to import on your project)
223
-
224
- ---
225
-
226
- 1. **Install package**
227
- ```bash
228
- npm install @data-netmonk/mona-chat-widget
229
- ```
230
-
231
- 2. **Import styles on your `App.jsx` or `index.jsx`**
232
-
233
- ```jsx
234
- import "@data-netmonk/mona-chat-widget/dist/style.css";
235
- ```
236
-
237
- 3. **Import & use component**
238
-
239
- ```jsx
240
- import { ChatWidget } from "@data-netmonk/mona-chat-widget";
241
-
242
- function App() {
243
- return (
244
- <ChatWidget
245
- userId="user123"
246
- sourceId="691e1b5952068ff7aaeccffc9"
247
- webhookUrl="https://your-backend-url.com"
248
- />
249
- );
250
- }
251
- ```
252
-
253
- ---
254
-
255
- ### Component Props
256
-
257
- ---
258
-
259
- #### Required Props
260
-
261
- | Prop | Type | Description |
262
- |------|------|-------------|
263
- | `sourceId` | `string` | **Required.** Source/channel identifier for the chat |
264
- | `webhookUrl` | `string` | **Required.** Backend webhook URL |
265
-
266
- #### Optional Props
267
-
268
- | Prop | Type | Default | Description |
269
- |------|------|---------|-------------|
270
- | `userId` | `string` | Generated visitor ID | Unique identifier for the user. **Optional** - if not provided, a unique visitor ID is automatically generated using browser fingerprinting for guest users |
271
- | `authUrl` | `string` | - | Authentication endpoint URL for token-based authentication |
272
- | `username` | `string` | - | Username for the session (sent to backend in variables) |
273
- | `data` | `string` | - | Additional custom variables in format `key1=value1~key2=value2` |
274
- | `width` | `string` | `"25vw"` | Widget width (CSS value) |
275
- | `height` | `string` | `"90vh"` | Widget height (CSS value) |
276
- | `right` | `string` | `"1.25rem"` | Distance from right edge |
277
- | `bottom` | `string` | `"1.25rem"` | Distance from bottom edge |
278
- | `zIndex` | `number` | `2000` | CSS z-index for widget positioning |
279
- | `position` | `string` | `"fixed"` | CSS position (`"fixed"` or `"relative"`) |
280
- | `onToggle` | `function` | - | Callback when widget opens/closes: `(isOpen: boolean) => void` |
281
-
282
- ---
283
-
284
- ### Guest User Support
285
-
286
- ---
287
-
288
- The widget now supports **guest users** (users who haven't logged in or before logging in). When `userId` is not provided, the widget automatically generates a unique visitor ID using browser fingerprinting.
289
-
290
- **How it works:**
291
-
292
- 1. **Browser Fingerprinting**: Uses FingerprintJS to generate a unique identifier based on:
293
- - Browser characteristics (user agent, screen resolution, timezone, etc.)
294
- - Incognito/private browsing detection
295
- - Combined into a SHA256 hash for consistency
296
-
297
- 2. **Automatic Guest Detection**: The widget automatically detects guest users and:
298
- - Generates a visitor ID if `userId` is not provided
299
- - Adds `auth: false` flag to all API requests for guest users
300
- - Uses the visitor ID as the effective user ID throughout the session
301
-
302
- 3. **Persistent Sessions**: The visitor ID remains consistent across page reloads (unless browser fingerprint changes or user clears data)
303
-
304
- **When to use guest mode:**
305
- - Public-facing websites where users can chat without logging in
306
- - Support widgets for anonymous visitors
307
- - Pre-login customer service interactions
308
- - Any scenario where user authentication is optional
309
-
310
- **When to provide userId:**
311
- - Users who are logged into your application
312
- - When you need to track chat history across devices
313
- - When authentication is required for personalized responses
314
- - Enterprise or internal applications
315
-
316
- ---
317
-
318
- ### Usage Examples
319
-
320
- ---
321
-
322
- #### Basic Usage (Authenticated User)
323
-
324
- ```jsx
325
- import { ChatWidget } from "@data-netmonk/mona-chat-widget";
326
- import "@data-netmonk/mona-chat-widget/dist/style.css";
327
-
328
- function App() {
329
- return (
330
- <ChatWidget
331
- userId="user123"
332
- sourceId="691e1b5952068ff7aaeccffc9"
333
- webhookUrl="https://api.example.com/webhook"
334
- />
335
- );
336
- }
337
- ```
338
-
339
- #### Guest User (Without Login)
340
-
341
- For anonymous visitors or users who haven't logged in:
342
-
343
- ```jsx
344
- import { ChatWidget } from "@data-netmonk/mona-chat-widget";
345
- import "@data-netmonk/mona-chat-widget/dist/style.css";
346
-
347
- function App() {
348
- return (
349
- <ChatWidget
350
- sourceId="691e1b5952068ff7aaeccffc9"
351
- webhookUrl="https://api.example.com/webhook"
352
- />
353
- );
354
- }
355
- ```
356
-
357
- **What happens:**
358
- - Widget automatically generates a visitor ID using browser fingerprinting
359
- - All API requests include `auth: false` in variables to indicate guest user
360
- - No login required - users can start chatting immediately
361
-
362
- #### Conditional userId (Logged in or Guest)
363
-
364
- Handle both logged-in users and guests dynamically:
365
-
366
- ```jsx
367
- import { ChatWidget } from "@data-netmonk/mona-chat-widget";
368
- import "@data-netmonk/mona-chat-widget/dist/style.css";
369
-
370
- function App() {
371
- const currentUser = getCurrentUser(); // Your auth function
372
-
373
- return (
374
- <ChatWidget
375
- userId={currentUser?.id} // Provide userId if logged in, undefined if not
376
- sourceId="691e1b5952068ff7aaeccffc9"
377
- webhookUrl="https://api.example.com/webhook"
378
- username={currentUser?.name}
379
- />
380
- );
381
- }
382
- ```
383
-
384
- **Behavior:**
385
- - If `currentUser.id` exists Uses provided userId (authenticated user)
386
- - If `currentUser.id` is `null`/`undefined` → Generates visitor ID (guest user)
387
- - Backend receives `auth: false` flag only for guest users
388
-
389
- #### With Custom Variables
390
-
391
- Pass custom user data like email, phone number, etc. to the backend:
392
-
393
- ```jsx
394
- <ChatWidget
395
- userId="user123"
396
- sourceId="691e1b5952068ff7aaeccffc9"
397
- webhookUrl="https://api.example.com/webhook"
398
- username="John Doe"
399
- data="telephone_number=+628123456789~email=john@example.com"
400
- />
401
- ```
402
-
403
- **Variables sent to backend:**
404
- ```json
405
- {
406
- "chat_id": "...",
407
- "session_id": "...",
408
- "user_id": "user123",
409
- "message": "Hello",
410
- "type": "text",
411
- "variables": {
412
- "username": "John Doe",
413
- "telephone_number": "+628123456789",
414
- "email": "john@example.com"
415
- }
416
- }
417
- ```
418
- }
419
- ```
420
- #### With Authentication (NEW!)
421
-
422
- The widget supports automatic authentication with token refresh on expiry:
423
-
424
- ```jsx
425
- <ChatWidget
426
- userId="user123"
427
- sourceId="691e1b5952068ff7aaeccffc9"
428
- webhookUrl="https://api.example.com/webhook"
429
- authUrl="https://api.example.com/login/chatwidget"
430
- username="John Doe"
431
- />
432
- ```
433
-
434
- **How authentication works:**
435
-
436
- 1. **Initial Authentication**: When the widget loads (on Launcher mount), if `authUrl` is provided as a prop, it automatically calls the auth API:
437
- ```
438
- POST {authUrl}
439
- Body: { "user_id": "user123" }
440
- Response: { "token": "eyJ..." }
441
- ```
442
-
443
- 2. **Session Initialization**: After getting the token, the widget calls the init endpoint to establish the session:
444
- ```
445
- POST {webhookUrl}/{sourceId}/init
446
- Body: {
447
- "session_id": "...",
448
- "user_id": "user123",
449
- "token": "eyJ...",
450
- "username": "John Doe"
451
- }
452
- ```
453
-
454
- 3. **Automatic Token Refresh**: If the webhook returns **401 Unauthorized** (token expired/revoked), the widget automatically:
455
- - Calls the `authUrl` again to get a fresh token
456
- - Re-initializes the session with the new token
457
- - Updates the `authToken` in variables
458
- - Retries the failed request with the new token
459
- - This happens transparently without user interaction
460
-
461
- **Combined example with auth and custom variables:**
462
- ```jsx
463
- <ChatWidget
464
- userId="user123"
465
- sourceId="691e1b5952068ff7aaeccffc9"
466
- webhookUrl="https://api.example.com/webhook"
467
- authUrl="https://api.example.com/login/chatwidget"
468
- username="John"
469
- data="email=john@example.com~phone=+628123456789"
470
- />
471
- ```
472
-
473
- **Auth Flag (`auth` variable):**
474
- The widget automatically adds an `auth: false` flag to the `variables` object when the user is a guest (not authenticated):
475
- - When `userId === visitorId` (browser fingerprint), the widget adds `auth: false` to variables
476
- - When `userId !== visitorId` (authenticated user), no `auth` flag is added to variables
477
-
478
- **Guest user example** (userId equals browser fingerprint):
479
- ```json
480
- {
481
- "chat_id": "...",
482
- "session_id": "...",
483
- "user_id": "visitor_abc123",
484
- "message": "Hello",
485
- "type": "text",
486
- "variables": {
487
- "username": "Guest",
488
- "auth": false
489
- }
490
- }
491
- ```
492
-
493
- **Authenticated user example** (userId is different from browser fingerprint):
494
- ```json
495
- {
496
- "chat_id": "...",
497
- "session_id": "...",
498
- "user_id": "user123",
499
- "message": "Hello",
500
- "type": "text",
501
- "variables": {
502
- "username": "John Doe",
503
- "email": "john@example.com"
504
- }
505
- }
506
- ```
507
-
508
- This `auth: false` flag is automatically added in:
509
- - Session initialization payload (when guest)
510
- - All message requests (when guest)
511
- - Button postback requests (when guest)
512
-
513
- **Note:** The `data` prop is for additional custom variables only. Required parameters (`userId`, `sourceId`, `webhookUrl`) and common optional parameters (`authUrl`, `username`) should be passed as direct props for better type safety and clarity.
514
-
515
- #### Custom Styling
516
-
517
- ```jsx
518
- <ChatWidget
519
- userId="user123" // Optional
520
- sourceId="691e1b5952068ff7aaeccffc9"
521
- webhookUrl="https://api.example.com/webhook"
522
- width="400px"
523
- height="600px"
524
- right="20px"
525
- bottom="20px"
526
- zIndex={9999}
527
- />
528
- ```
529
-
530
- #### Embedded in Layout (Relative Positioning)
531
-
532
- ```jsx
533
- <div style={{ display: 'grid', gridTemplateColumns: '1fr 400px' }}>
534
- <div>Main content</div>
535
- <ChatWidget
536
- userId="user123" // Optional
537
- sourceId="691e1b5952068ff7aaeccffc9"
538
- webhookUrl="https://api.example.com/webhook"
539
- position="relative"
540
- width="100%"
541
- height="100vh"
542
- />
543
- </div>
544
- ```
545
-
546
- #### With Toggle Callback
547
-
548
- ```jsx
549
- function App() {
550
- const handleToggle = (isOpen) => {
551
- console.log('Chat widget is', isOpen ? 'open' : 'closed');
552
- // Track analytics, update UI, etc.
553
- };
554
-
555
- return (
556
- <ChatWidget
557
- userId="user123" // Optional
558
- sourceId="691e1b5952068ff7aaeccffc9"
559
- webhookUrl="https://api.example.com/webhook"
560
- onToggle={handleToggle}
561
- />
562
- );
563
- }
564
- ```
565
-
566
- #### Full-Screen Widget
567
-
568
- ```jsx
569
- <ChatWidget
570
- userId="user123" // Optional - supports guest users
571
- sourceId="691e1b5952068ff7aaeccffc9"
572
- webhookUrl="https://api.example.com/webhook"
573
- width="100vw"
574
- height="100vh"
575
- right="0"
576
- bottom="0"
577
- />
578
- ```
579
-
580
- ---
581
-
582
- ### Data Prop Format & Helper Function
583
-
584
- ---
585
-
586
- The `data` prop allows you to pass additional custom variables via a single string. This is especially useful when you need to dynamically pass user-specific data or pass it through URL parameters.
587
-
588
- **Format:** `key=value` pairs separated by `~`
589
-
590
- **Example data string:**
591
- ```
592
- email=john@example.com~phone=+1234567890~department=Engineering
593
- ```
594
-
595
- **Note:** `authUrl` and `username` are now **direct props** and should not be included in the `data` string.
596
-
597
- #### Building Data String Programmatically
598
-
599
- Instead of manually constructing the data string, you can create a helper function to build it dynamically. This approach is recommended for production applications as it:
600
- - Prevents syntax errors in the data string format
601
- - Makes the code more maintainable and readable
602
- - Allows for conditional inclusion of parameters
603
- - Handles URL encoding automatically if needed
604
-
605
- **Create a helper function** (`src/helpers/chatWidget.js` or similar):
606
-
607
- ```jsx
608
- /**
609
- * Builds the data string for ChatWidget component
610
- * @param {Object} params - Object containing all parameters
611
- * @param {string} params.email - Optional: User's email address
612
- * @param {string} params.phone - Optional: User's phone number
613
- * @param {Object} params.customFields - Optional: Any additional custom fields as key-value pairs
614
- * @returns {string} Formatted data string for ChatWidget (always returns a string)
615
- *
616
- * @example
617
- * // Returns: "email=john@example.com~phone=+1234567890~department=Engineering"
618
- * buildChatWidgetData({
619
- * email: "john@example.com",
620
- * phone: "+1234567890",
621
- * customFields: { department: "Engineering" }
622
- * });
623
- */
624
- export const buildChatWidgetData = ({
625
- email,
626
- phone,
627
- customFields = {}
628
- }) => {
629
- const parts = [];
630
-
631
- // Add standard fields
632
- if (email) {
633
- parts.push(`email=${encodeURIComponent(email)}`);
634
- }
635
-
636
- if (phone) {
637
- parts.push(`phone=${encodeURIComponent(phone)}`);
638
- }
639
-
640
- // Add any custom fields dynamically
641
- // Note: customFields is just a convenience parameter for the helper function
642
- // Each field will be flattened into the string format: key=value~key2=value2
643
- Object.entries(customFields).forEach(([key, value]) => {
644
- if (value) {
645
- parts.push(`${key}=${encodeURIComponent(String(value))}`);
646
- }
647
- });
648
-
649
- // Returns a string like: "email=john@example.com~phone=+1234567890~department=Engineering"
650
- return parts.join("~");
651
- };
652
- ```
653
-
654
- **Important:** The `customFields` parameter is **NOT** passed as an object to the widget. It's just a convenient way to pass multiple additional fields to the helper function. The function flattens everything into a single string format.
655
-
656
- **Usage in your application:**
657
-
658
- ```jsx
659
- import { ChatWidget } from "@data-netmonk/mona-chat-widget";
660
- import "@data-netmonk/mona-chat-widget/dist/style.css";
661
- import { buildChatWidgetData } from "./helpers/chatWidget";
662
-
663
- function App() {
664
- // Get user data from your auth system, state, or props
665
- const user = {
666
- id: "user123",
667
- name: "John Doe",
668
- email: "john@example.com",
669
- phone: "+628123456789"
670
- };
671
-
672
- // Build the data string with custom variables only
673
- const customData = buildChatWidgetData({
674
- email: user.email,
675
- phone: user.phone,
676
- customFields: {
677
- department: "Engineering",
678
- role: "Developer",
679
- company: "Acme Corp"
680
- }
681
- });
682
-
683
- // customData is now a STRING like:
684
- // "email=john@example.com~phone=%2B628123456789~department=Engineering~role=Developer~company=Acme%20Corp"
685
-
686
- return (
687
- <ChatWidget
688
- userId={user.id}
689
- sourceId="691e1b5952068ff7aaeccffc9"
690
- webhookUrl="https://api.example.com/webhook"
691
- authUrl="https://api.example.com/login/chatwidget" // Direct prop
692
- username={user.name} // Direct prop
693
- data={customData} // Only additional variables
694
- />
695
- );
696
- }
697
- ```
698
-
699
- **With conditional authentication:**
700
-
701
- ```jsx
702
- function App() {
703
- const authUrl = import.meta.env.VITE_CHAT_AUTH_ENABLED === "true"
704
- ? import.meta.env.VITE_CHAT_AUTH_URL
705
- : null;
706
-
707
- const customData = buildChatWidgetData({
708
- email: getCurrentUser().email,
709
- customFields: {
710
- department: "Sales"
711
- }
712
- });
713
-
714
- return (
715
- <ChatWidget
716
- userId={getCurrentUser().id}
717
- sourceId="691e1b5952068ff7aaeccffc9"
718
- webhookUrl={import.meta.env.VITE_WEBHOOK_URL}
719
- authUrl={authUrl} // Direct prop (conditionally set)
720
- username={getCurrentUser().name} // Direct prop
721
- data={customData} // Only additional variables
722
- />
723
- );
724
- }
725
- ```
726
-
727
- **The helper function handles:**
728
- - ✅ Proper formatting with `~` separators
729
- - URL encoding for special characters
730
- - ✅ Conditional parameter inclusion (only adds if value exists)
731
- - Support for dynamic custom fields
732
- - ✅ Type safety and documentation via JSDoc comments
733
-
734
- ### Standalone app (for demonstration)
735
-
736
- 1. **How to run locally** (access at `http://localhost:${PORT}/${APP_PREFIX}`)
737
-
738
- ```
739
- npm run dev
740
- ```
741
-
742
- 2. **How to build as a standalone app** (build file at `/dist-app` directory)
743
-
744
- ```
745
- npm run build-app
746
- ```
747
-
748
- 3. **How to serve standalone app** (access at `http://localhost:${PORT}/${APP_PREFIX}`)
749
-
750
- ```
751
- npm run serve
752
- ```
753
-
754
- 4. **How to run on docker** (access at `http://localhost:${PORT}/${APP_PREFIX}`)
755
-
756
- ```
757
- docker-compose up --build
758
- ```
759
-
760
- ---
1
+ # Mona Chat Widget
2
+
3
+ Chat widget package developed by Netmonk data & solution team to be imported in Netmonk products
4
+
5
+ ---
6
+
7
+ ### Recent Updates & Breaking Changes
8
+
9
+ ---
10
+
11
+ **Latest Version Changes (`v2.6.1`):**
12
+
13
+ **Non-breaking Changes:**
14
+ 1. **Built-in voice mode button** - Chat widget now includes a microphone button to toggle voice mode directly from the input area
15
+ 2. **Speech-to-text flow for voice mode** - Recorded user audio is sent to the configured `VITE_STT_ENDPOINT`, then the transcription is forwarded as a normal chat message
16
+ 3. **Phoneme-driven avatar animation** - The widget can animate Mona's avatar during TTS playback based on phoneme/viseme IDs returned by the backend
17
+ 4. **Supported phoneme IDs** - `A`, `BP`, `ChJ`, `E`, `FV`, `I`, `KG`, `L`, `M`, `O`, `SZ`, `U`
18
+
19
+ ⚠️ **Previous Breaking Changes:**
20
+ 1. **Removed `type` and `agentType` props** - These parameters are no longer used and have been removed from all components
21
+ 2. **Renamed `botServerUrl` to `webhookUrl`** - For better clarity and consistency
22
+ 3. **`webhookUrl` is now required** - Must be provided as a prop
23
+ 4. **`authUrl` and `username` are now direct props** - No longer part of `data` prop for better clarity and type safety
24
+ 5. **`userId` is now optional** - Widget automatically generates visitor ID for guest users via browser fingerprinting when `userId` is not provided
25
+
26
+ **New Features:**
27
+ 1. **Guest user support** - Users can chat without logging in
28
+ - Automatic visitor ID generation using browser fingerprinting (FingerprintJS + SHA256)
29
+ - `auth: false` flag automatically added to API requests for guest users
30
+ - Persistent sessions across page reloads for anonymous visitors
31
+ 2. **Authentication support** - Automatic token management with refresh on 401 errors
32
+ - Pass `authUrl` as a direct prop
33
+ - Widget handles token lifecycle automatically
34
+ 3. **Enhanced user authentication handling** - Smart detection of authenticated vs guest users
35
+ - Compares `userId` with visitor ID to determine authentication status
36
+ - Automatic `auth` flag management in all API requests
37
+ 4. **Enhanced data prop** - Support for custom variables passed to backend via `data` prop
38
+ 5. **Improved error handling** - Better fallback mechanisms and error messages
39
+ 6. **Direct `username` prop** - Pass username as a top-level prop instead of in data string
40
+
41
+ 📝 **Migration Guide:**
42
+ ```jsx
43
+ // Old usage (deprecated)
44
+ <ChatWidget
45
+ userId="user123"
46
+ sourceId="source456"
47
+ type="prime"
48
+ botServerUrl="https://api.example.com"
49
+ />
50
+
51
+ // Previous version (with data prop)
52
+ <ChatWidget
53
+ userId="user123"
54
+ sourceId="source456"
55
+ webhookUrl="https://api.example.com/webhook"
56
+ data="authUrl=https://api.example.com/login/chatwidget~username=John"
57
+ />
58
+
59
+ // New usage - Authenticated user (current - recommended)
60
+ <ChatWidget
61
+ userId="user123"
62
+ sourceId="source456"
63
+ webhookUrl="https://api.example.com/webhook"
64
+ authUrl="https://api.example.com/login/chatwidget"
65
+ username="John"
66
+ data="email=john@example.com~phone=+1234567890"
67
+ />
68
+
69
+ // New usage - Guest user (without login)
70
+ <ChatWidget
71
+ sourceId="source456"
72
+ webhookUrl="https://api.example.com/webhook"
73
+ username="Guest"
74
+ />
75
+ // Widget automatically generates visitor ID and adds auth: false flag
76
+
77
+ // New usage - Conditional (handles both logged-in and guest users)
78
+ <ChatWidget
79
+ userId={currentUser?.id} // undefined for guests
80
+ sourceId="source456"
81
+ webhookUrl="https://api.example.com/webhook"
82
+ username={currentUser?.name || "Guest"}
83
+ />
84
+ ```
85
+
86
+ ---
87
+
88
+ ## 🚅 Quick start
89
+
90
+ ### Prerequisites
91
+
92
+ ---
93
+
94
+ 1. Install dependencies
95
+ ```
96
+ npm install --legacy-peer-deps
97
+ ```
98
+ 2. Copy .env.example
99
+ ```
100
+ cp .env.example .env
101
+ ```
102
+ 3. Populate .env
103
+ 4. Optional speech endpoints
104
+
105
+ To enable voice mode and phoneme-based avatar animation, set these environment variables in your `.env` file:
106
+ ```
107
+ VITE_STT_ENDPOINT=https://voice.netmonk-ai.tech/stt
108
+ VITE_STT_API_KEY=
109
+ VITE_STT_API_KEY_HEADER=x-api-key
110
+ VITE_STT_API_KEY_PREFIX=
111
+ VITE_TTS_ENDPOINT=https://your-tts-service.example.com/synthesize
112
+ VITE_TTS_API_KEY=
113
+ VITE_TTS_API_KEY_HEADER=Authorization
114
+ VITE_TTS_API_KEY_PREFIX=Bearer
115
+ VITE_PREFER_WEBHOOK_TTS=true
116
+ VITE_TTS_MINIO_OBJECT_ENDPOINT=http://localhost:8000/minio/object
117
+ VITE_TTS_MINIO_API_KEY=
118
+ VITE_TTS_MINIO_API_KEY_HEADER=X-API-Key
119
+ VITE_TTS_MINIO_API_KEY_PREFIX=
120
+ VITE_TTS_MINIO_BUCKET=chatbot-tts
121
+ VITE_TTS_MINIO_DOWNLOAD=false
122
+ ```
123
+
124
+ `VITE_STT_ENDPOINT` is used by the built-in mic button to transcribe recorded audio.
125
+ For the Netmonk STT service, set `VITE_STT_API_KEY_HEADER=x-api-key` and leave `VITE_STT_API_KEY_PREFIX` empty so the widget sends the raw key value without `Bearer`.
126
+ `VITE_PREFER_WEBHOOK_TTS=true` makes the widget prioritize TTS assets from webhook response (for example `tts_assets.items[].audio_object_key`) and use `VITE_TTS_ENDPOINT` only as fallback.
127
+ Widget tidak lagi konek langsung ke MinIO. Object diambil lewat endpoint voice-engine:
128
+ `GET /minio/object?object_key=<key>&bucket=<bucket>&download=<true|false>`.
129
+ `VITE_TTS_MINIO_API_KEY` opsional. Jika diisi, widget mengirim header `X-API-Key` (atau header custom via `VITE_TTS_MINIO_API_KEY_HEADER`).
130
+ `VITE_TTS_MINIO_BUCKET` dipakai sebagai default bucket bila payload webhook tidak mengirim bucket.
131
+ Set `VITE_TTS_MINIO_DOWNLOAD=true` jika ingin force download mode saat fetch object.
132
+ `VITE_TTS_ENDPOINT` is used for fallback TTS playback.
133
+ The widget expects the TTS response body to contain raw audio binary such as `audio/wav`, and reads lip-sync metadata from `x-tts-visemes-b64`, `x-tts-phonemes-b64`, and `x-tts-phoneme-timeline-b64` response headers.
134
+ If your TTS endpoint is called cross-origin, the server must expose those headers with `Access-Control-Expose-Headers`.
135
+ If your TTS service uses a different header such as `x-api-key`, set `VITE_TTS_API_KEY_HEADER=x-api-key` and leave the prefix empty.
136
+ 5. Optional TTS debug logging
137
+
138
+ To inspect TTS queue and playback lifecycle in browser console, set `VITE_DEBUG_TTS=true` in your `.env` file.
139
+ Keep this disabled in production to avoid noisy logs.
140
+ 6. Enable mock mode (optional)
141
+
142
+ To test the chat widget without a backend server, set `VITE_USE_MOCK_RESPONSES=true` in your `.env` file. The widget will respond to messages like:
143
+ - "start", "hello", "hi", "halo" - Greeting messages
144
+ - "help", "bantuan" - Help information
145
+ - "terima kasih", "thank you" - Acknowledgments
146
+ - "bye", "goodbye" - Farewell messages
147
+ - And more! Check `src/components/ChatWidget/utils/helpers.js` for full list
148
+
149
+ ---
150
+
151
+ ### Mock Mode (Demo without Backend)
152
+
153
+ ---
154
+
155
+ The chat widget includes a built-in mock mode for testing and demonstrations without requiring a backend server.
156
+
157
+ **To enable mock mode:**
158
+ 1. Set `VITE_USE_MOCK_RESPONSES=true` in your `.env` file
159
+ 2. Run the app normally with `npm run dev`
160
+
161
+ **Mock responses include:**
162
+ - Greetings (hello, hi, halo, start) - with interactive buttons
163
+ - Help commands
164
+ - Device list with clickable buttons (type "devices" or "show devices")
165
+ - Thank you acknowledgments
166
+ - Farewells
167
+ - Time-based greetings (good morning, etc.)
168
+ - And automatically falls back to mock mode if backend fails
169
+
170
+ **Interactive Buttons Demo:**
171
+ Type these messages to see button responses:
172
+ - "start" - Welcome message with action buttons
173
+ - "show devices" or "devices" - List of devices as clickable buttons
174
+ - Click any button to trigger the next action
175
+
176
+ **To add custom mock responses:**
177
+ Edit the `mockBotResponses` object in `src/components/ChatWidget/utils/mockBotResponses.js`
178
+
179
+ For text-only responses:
180
+ ```javascript
181
+ "trigger": "Response text"
182
+ ```
183
+
184
+ For responses with buttons:
185
+ ```javascript
186
+ "trigger": {
187
+ text: "Your message here",
188
+ buttons: [
189
+ { title: "Button 1", payload: "action_1" },
190
+ { title: "Button 2", payload: "action_2" },
191
+ { title: "Link Button", url: "https://example.com" }
192
+ ]
193
+ }
194
+ ```
195
+
196
+ ---
197
+
198
+ ### Storybook
199
+
200
+ ---
201
+
202
+ 1. **How to run Storybook locally** (access at http://localhost:5177)
203
+
204
+ ```
205
+ npm run storybook
206
+ ```
207
+
208
+ 2. **How to build Storybook**
209
+
210
+ ```
211
+ npm run build-storybook
212
+ ```
213
+
214
+ 3. **How to serve Storybook**
215
+
216
+ ```
217
+ npm run serve-storybook
218
+ ```
219
+
220
+ ---
221
+
222
+ ### Library (how to update and publish)
223
+
224
+ ---
225
+
226
+ 1. **Commit changes**
227
+ ```
228
+ git add .
229
+ git commit -m "Your commit message"
230
+ ```
231
+ 2. **Update version**
232
+
233
+ ```bash
234
+ npm version patch # for bug fixes (1.0.0 -> 1.0.1)
235
+ ```
236
+
237
+ ```bash
238
+ npm version minor # for new features (1.0.0 -> 1.1.0)
239
+ ```
240
+
241
+ ```bash
242
+ npm version major # for breaking changes (1.0.0 -> 2.0.0)
243
+ ```
244
+
245
+ 3. **Build as a library** (build file at `/dist` directory)
246
+
247
+ ```
248
+ npm run build
249
+ ```
250
+
251
+ 4. **Copy declaration file to `/dist`**
252
+
253
+ ```
254
+ cp ./src/declarations/index.d.ts ./dist/index.d.ts
255
+ ```
256
+
257
+ 5. **Publish**
258
+
259
+ ```
260
+ npm publish
261
+ ```
262
+
263
+ ---
264
+
265
+ ### Library (how to import on your project)
266
+
267
+ ---
268
+
269
+ 1. **Install package**
270
+ ```bash
271
+ npm install @data-netmonk/mona-chat-widget
272
+ ```
273
+
274
+ 2. **Import styles on your `App.jsx` or `index.jsx`**
275
+
276
+ ```jsx
277
+ import "@data-netmonk/mona-chat-widget/dist/style.css";
278
+ ```
279
+
280
+ 3. **Import & use component**
281
+
282
+ ```jsx
283
+ import { ChatWidget } from "@data-netmonk/mona-chat-widget";
284
+
285
+ function App() {
286
+ return (
287
+ <ChatWidget
288
+ userId="user123"
289
+ sourceId="691e1b5952068ff7aaeccffc9"
290
+ webhookUrl="https://your-backend-url.com"
291
+ />
292
+ );
293
+ }
294
+ ```
295
+
296
+ ---
297
+
298
+ ### Component Props
299
+
300
+ ---
301
+
302
+ #### Required Props
303
+
304
+ | Prop | Type | Description |
305
+ |------|------|-------------|
306
+ | `sourceId` | `string` | **Required.** Source/channel identifier for the chat |
307
+ | `webhookUrl` | `string` | **Required.** Backend webhook URL |
308
+
309
+ #### Optional Props
310
+
311
+ | Prop | Type | Default | Description |
312
+ |------|------|---------|-------------|
313
+ | `userId` | `string` | Generated visitor ID | Unique identifier for the user. **Optional** - if not provided, a unique visitor ID is automatically generated using browser fingerprinting for guest users |
314
+ | `authUrl` | `string` | - | Authentication endpoint URL for token-based authentication |
315
+ | `username` | `string` | - | Username for the session (sent to backend in variables) |
316
+ | `data` | `string` | - | Additional custom variables in format `key1=value1~key2=value2` |
317
+ | `width` | `string` | `"25vw"` | Widget width (CSS value) |
318
+ | `height` | `string` | `"90vh"` | Widget height (CSS value) |
319
+ | `right` | `string` | `"1.25rem"` | Distance from right edge |
320
+ | `bottom` | `string` | `"1.25rem"` | Distance from bottom edge |
321
+ | `zIndex` | `number` | `2000` | CSS z-index for widget positioning |
322
+ | `position` | `string` | `"fixed"` | CSS position (`"fixed"` or `"relative"`) |
323
+ | `onToggle` | `function` | - | Callback when widget opens/closes: `(isOpen: boolean) => void` |
324
+
325
+ ---
326
+
327
+ ### Voice Mode & Phoneme Support
328
+
329
+ ---
330
+
331
+ The widget now includes a built-in microphone button in the input area. Clicking the button toggles voice mode, requests microphone access, records speech, sends the recorded audio to `VITE_STT_ENDPOINT`, and forwards the returned transcription as a regular user message.
332
+
333
+ If `VITE_STT_API_KEY` is set, the STT request also includes the configured auth header. For `https://voice.netmonk-ai.tech/stt`, use `x-api-key` without any prefix. Other providers can still use `Authorization: Bearer <key>` or any custom header via `.env`.
334
+
335
+ **Expected STT request/response shape:**
336
+
337
+ - Request body: `multipart/form-data`
338
+ - Audio field name: `audio`
339
+ - Request headers: `Accept: application/json`, plus `x-api-key: <key>` when configured for the Netmonk STT service
340
+ - Expected JSON response:
341
+
342
+ ```json
343
+ {
344
+ "text": "Halo, saya mau tanya status tiket saya"
345
+ }
346
+ ```
347
+
348
+ During TTS playback, the header avatar can switch between phoneme images using the metadata returned by the TTS service. The widget currently supports these phoneme IDs:
349
+
350
+ - `A`
351
+ - `BP`
352
+ - `ChJ`
353
+ - `E`
354
+ - `FV`
355
+ - `I`
356
+ - `KG`
357
+ - `L`
358
+ - `M`
359
+ - `O`
360
+ - `SZ`
361
+ - `U`
362
+
363
+ Phoneme IDs are normalized case-insensitively in the widget, so values such as `ChJ` and `CHJ` resolve to the same avatar image.
364
+
365
+ **Preferred webhook TTS asset shape (MinIO-first):**
366
+
367
+ ```json
368
+ {
369
+ "messages": [{ "type": "text", "text": "Halo, ada yang bisa saya bantu?" }],
370
+ "tts_assets": {
371
+ "items": [
372
+ {
373
+ "bucket": "chatbot-tts",
374
+ "response_index": 1,
375
+ "audio_object_key": "tts/...wav",
376
+ "phoneme_object_key": "tts/...phonemes.txt",
377
+ "phoneme_timeline_object_key": "tts/...phoneme-timeline.json",
378
+ "viseme_timeline_object_key": "tts/...viseme-timeline.json"
379
+ }
380
+ ]
381
+ }
382
+ }
383
+ ```
384
+
385
+ When this payload is available, the widget resolves MinIO object keys first for audio and timeline metadata. If no usable asset is found, it falls back to `VITE_TTS_ENDPOINT`.
386
+
387
+ **Expected TTS response shape:**
388
+
389
+ ```text
390
+ body: <raw WAV or other audio binary>
391
+ content-type: audio/wav
392
+ x-tts-visemes-b64: W3siaWQiOiJNIiwic3RhcnRNcyI6MCwiZW5kTXMiOjEyMH1d
393
+ x-tts-phonemes-b64: TSBBCg==
394
+ x-tts-phoneme-timeline-b64: W3sicGhvbmVtZSI6Ik0iLCJzdGFydE1zIjowLCJlbmRNcyI6MTIwfV0=
395
+ ```
396
+
397
+ `x-tts-phonemes-b64` is the legacy phoneme string, while `x-tts-phoneme-timeline-b64` is the timed phoneme timeline. The base64 headers should decode as:
398
+
399
+ Decoded `x-tts-phonemes-b64`:
400
+
401
+ ```text
402
+ M A
403
+ ```
404
+
405
+ Decoded `x-tts-phoneme-timeline-b64`:
406
+
407
+ ```json
408
+ [
409
+ { "phoneme": "M", "startMs": 0, "endMs": 120 }
410
+ ]
411
+ ```
412
+
413
+ Decoded `x-tts-visemes-b64`:
414
+
415
+ ```json
416
+ [
417
+ { "id": "M", "startMs": 0, "endMs": 120 },
418
+ { "id": "A", "startMs": 121, "endMs": 260 },
419
+ { "id": "SZ", "startMs": 261, "endMs": 420 }
420
+ ]
421
+ ```
422
+
423
+ The widget still supports the older JSON body format as a fallback, but header-based metadata is now the preferred format for binary audio responses.
424
+
425
+ ---
426
+
427
+ ### Guest User Support
428
+
429
+ ---
430
+
431
+ The widget now supports **guest users** (users who haven't logged in or before logging in). When `userId` is not provided, the widget automatically generates a unique visitor ID using browser fingerprinting.
432
+
433
+ **How it works:**
434
+
435
+ 1. **Browser Fingerprinting**: Uses FingerprintJS to generate a unique identifier based on:
436
+ - Browser characteristics (user agent, screen resolution, timezone, etc.)
437
+ - Incognito/private browsing detection
438
+ - Combined into a SHA256 hash for consistency
439
+
440
+ 2. **Automatic Guest Detection**: The widget automatically detects guest users and:
441
+ - Generates a visitor ID if `userId` is not provided
442
+ - Adds `auth: false` flag to all API requests for guest users
443
+ - Uses the visitor ID as the effective user ID throughout the session
444
+
445
+ 3. **Persistent Sessions**: The visitor ID remains consistent across page reloads (unless browser fingerprint changes or user clears data)
446
+
447
+ **When to use guest mode:**
448
+ - Public-facing websites where users can chat without logging in
449
+ - Support widgets for anonymous visitors
450
+ - Pre-login customer service interactions
451
+ - Any scenario where user authentication is optional
452
+
453
+ **When to provide userId:**
454
+ - Users who are logged into your application
455
+ - When you need to track chat history across devices
456
+ - When authentication is required for personalized responses
457
+ - Enterprise or internal applications
458
+
459
+ ---
460
+
461
+ ### Usage Examples
462
+
463
+ ---
464
+
465
+ #### Basic Usage (Authenticated User)
466
+
467
+ ```jsx
468
+ import { ChatWidget } from "@data-netmonk/mona-chat-widget";
469
+ import "@data-netmonk/mona-chat-widget/dist/style.css";
470
+
471
+ function App() {
472
+ return (
473
+ <ChatWidget
474
+ userId="user123"
475
+ sourceId="691e1b5952068ff7aaeccffc9"
476
+ webhookUrl="https://api.example.com/webhook"
477
+ />
478
+ );
479
+ }
480
+ ```
481
+
482
+ #### Guest User (Without Login)
483
+
484
+ For anonymous visitors or users who haven't logged in:
485
+
486
+ ```jsx
487
+ import { ChatWidget } from "@data-netmonk/mona-chat-widget";
488
+ import "@data-netmonk/mona-chat-widget/dist/style.css";
489
+
490
+ function App() {
491
+ return (
492
+ <ChatWidget
493
+ sourceId="691e1b5952068ff7aaeccffc9"
494
+ webhookUrl="https://api.example.com/webhook"
495
+ />
496
+ );
497
+ }
498
+ ```
499
+
500
+ **What happens:**
501
+ - Widget automatically generates a visitor ID using browser fingerprinting
502
+ - All API requests include `auth: false` in variables to indicate guest user
503
+ - No login required - users can start chatting immediately
504
+
505
+ #### Conditional userId (Logged in or Guest)
506
+
507
+ Handle both logged-in users and guests dynamically:
508
+
509
+ ```jsx
510
+ import { ChatWidget } from "@data-netmonk/mona-chat-widget";
511
+ import "@data-netmonk/mona-chat-widget/dist/style.css";
512
+
513
+ function App() {
514
+ const currentUser = getCurrentUser(); // Your auth function
515
+
516
+ return (
517
+ <ChatWidget
518
+ userId={currentUser?.id} // Provide userId if logged in, undefined if not
519
+ sourceId="691e1b5952068ff7aaeccffc9"
520
+ webhookUrl="https://api.example.com/webhook"
521
+ username={currentUser?.name}
522
+ />
523
+ );
524
+ }
525
+ ```
526
+
527
+ **Behavior:**
528
+ - If `currentUser.id` exists → Uses provided userId (authenticated user)
529
+ - If `currentUser.id` is `null`/`undefined` → Generates visitor ID (guest user)
530
+ - Backend receives `auth: false` flag only for guest users
531
+
532
+ #### With Custom Variables
533
+
534
+ Pass custom user data like email, phone number, etc. to the backend:
535
+
536
+ ```jsx
537
+ <ChatWidget
538
+ userId="user123"
539
+ sourceId="691e1b5952068ff7aaeccffc9"
540
+ webhookUrl="https://api.example.com/webhook"
541
+ username="John Doe"
542
+ data="telephone_number=+628123456789~email=john@example.com"
543
+ />
544
+ ```
545
+
546
+ **Variables sent to backend:**
547
+ ```json
548
+ {
549
+ "chat_id": "...",
550
+ "session_id": "...",
551
+ "user_id": "user123",
552
+ "message": "Hello",
553
+ "type": "text",
554
+ "variables": {
555
+ "username": "John Doe",
556
+ "telephone_number": "+628123456789",
557
+ "email": "john@example.com"
558
+ }
559
+ }
560
+ ```
561
+ }
562
+ ```
563
+ #### With Authentication (NEW!)
564
+
565
+ The widget supports automatic authentication with token refresh on expiry:
566
+
567
+ ```jsx
568
+ <ChatWidget
569
+ userId="user123"
570
+ sourceId="691e1b5952068ff7aaeccffc9"
571
+ webhookUrl="https://api.example.com/webhook"
572
+ authUrl="https://api.example.com/login/chatwidget"
573
+ username="John Doe"
574
+ />
575
+ ```
576
+
577
+ **How authentication works:**
578
+
579
+ 1. **Initial Authentication**: When the widget loads (on Launcher mount), if `authUrl` is provided as a prop, it automatically calls the auth API:
580
+ ```
581
+ POST {authUrl}
582
+ Body: { "user_id": "user123" }
583
+ Response: { "token": "eyJ..." }
584
+ ```
585
+
586
+ 2. **Session Initialization**: After getting the token, the widget calls the init endpoint to establish the session:
587
+ ```
588
+ POST {webhookUrl}/{sourceId}/init
589
+ Body: {
590
+ "session_id": "...",
591
+ "user_id": "user123",
592
+ "token": "eyJ...",
593
+ "username": "John Doe"
594
+ }
595
+ ```
596
+
597
+ 3. **Automatic Token Refresh**: If the webhook returns **401 Unauthorized** (token expired/revoked), the widget automatically:
598
+ - Calls the `authUrl` again to get a fresh token
599
+ - Re-initializes the session with the new token
600
+ - Updates the `authToken` in variables
601
+ - Retries the failed request with the new token
602
+ - This happens transparently without user interaction
603
+
604
+ **Combined example with auth and custom variables:**
605
+ ```jsx
606
+ <ChatWidget
607
+ userId="user123"
608
+ sourceId="691e1b5952068ff7aaeccffc9"
609
+ webhookUrl="https://api.example.com/webhook"
610
+ authUrl="https://api.example.com/login/chatwidget"
611
+ username="John"
612
+ data="email=john@example.com~phone=+628123456789"
613
+ />
614
+ ```
615
+
616
+ **Auth Flag (`auth` variable):**
617
+ The widget automatically adds an `auth: false` flag to the `variables` object when the user is a guest (not authenticated):
618
+ - When `userId === visitorId` (browser fingerprint), the widget adds `auth: false` to variables
619
+ - When `userId !== visitorId` (authenticated user), no `auth` flag is added to variables
620
+
621
+ **Guest user example** (userId equals browser fingerprint):
622
+ ```json
623
+ {
624
+ "chat_id": "...",
625
+ "session_id": "...",
626
+ "user_id": "visitor_abc123",
627
+ "message": "Hello",
628
+ "type": "text",
629
+ "variables": {
630
+ "username": "Guest",
631
+ "auth": false
632
+ }
633
+ }
634
+ ```
635
+
636
+ **Authenticated user example** (userId is different from browser fingerprint):
637
+ ```json
638
+ {
639
+ "chat_id": "...",
640
+ "session_id": "...",
641
+ "user_id": "user123",
642
+ "message": "Hello",
643
+ "type": "text",
644
+ "variables": {
645
+ "username": "John Doe",
646
+ "email": "john@example.com"
647
+ }
648
+ }
649
+ ```
650
+
651
+ This `auth: false` flag is automatically added in:
652
+ - Session initialization payload (when guest)
653
+ - All message requests (when guest)
654
+ - Button postback requests (when guest)
655
+
656
+ **Note:** The `data` prop is for additional custom variables only. Required parameters (`userId`, `sourceId`, `webhookUrl`) and common optional parameters (`authUrl`, `username`) should be passed as direct props for better type safety and clarity.
657
+
658
+ #### Custom Styling
659
+
660
+ ```jsx
661
+ <ChatWidget
662
+ userId="user123" // Optional
663
+ sourceId="691e1b5952068ff7aaeccffc9"
664
+ webhookUrl="https://api.example.com/webhook"
665
+ width="400px"
666
+ height="600px"
667
+ right="20px"
668
+ bottom="20px"
669
+ zIndex={9999}
670
+ />
671
+ ```
672
+
673
+ #### Embedded in Layout (Relative Positioning)
674
+
675
+ ```jsx
676
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 400px' }}>
677
+ <div>Main content</div>
678
+ <ChatWidget
679
+ userId="user123" // Optional
680
+ sourceId="691e1b5952068ff7aaeccffc9"
681
+ webhookUrl="https://api.example.com/webhook"
682
+ position="relative"
683
+ width="100%"
684
+ height="100vh"
685
+ />
686
+ </div>
687
+ ```
688
+
689
+ #### With Toggle Callback
690
+
691
+ ```jsx
692
+ function App() {
693
+ const handleToggle = (isOpen) => {
694
+ console.log('Chat widget is', isOpen ? 'open' : 'closed');
695
+ // Track analytics, update UI, etc.
696
+ };
697
+
698
+ return (
699
+ <ChatWidget
700
+ userId="user123" // Optional
701
+ sourceId="691e1b5952068ff7aaeccffc9"
702
+ webhookUrl="https://api.example.com/webhook"
703
+ onToggle={handleToggle}
704
+ />
705
+ );
706
+ }
707
+ ```
708
+
709
+ #### Full-Screen Widget
710
+
711
+ ```jsx
712
+ <ChatWidget
713
+ userId="user123" // Optional - supports guest users
714
+ sourceId="691e1b5952068ff7aaeccffc9"
715
+ webhookUrl="https://api.example.com/webhook"
716
+ width="100vw"
717
+ height="100vh"
718
+ right="0"
719
+ bottom="0"
720
+ />
721
+ ```
722
+
723
+ ---
724
+
725
+ ### Data Prop Format & Helper Function
726
+
727
+ ---
728
+
729
+ The `data` prop allows you to pass additional custom variables via a single string. This is especially useful when you need to dynamically pass user-specific data or pass it through URL parameters.
730
+
731
+ **Format:** `key=value` pairs separated by `~`
732
+
733
+ **Example data string:**
734
+ ```
735
+ email=john@example.com~phone=+1234567890~department=Engineering
736
+ ```
737
+
738
+ **Note:** `authUrl` and `username` are now **direct props** and should not be included in the `data` string.
739
+
740
+ #### Building Data String Programmatically
741
+
742
+ Instead of manually constructing the data string, you can create a helper function to build it dynamically. This approach is recommended for production applications as it:
743
+ - Prevents syntax errors in the data string format
744
+ - Makes the code more maintainable and readable
745
+ - Allows for conditional inclusion of parameters
746
+ - Handles URL encoding automatically if needed
747
+
748
+ **Create a helper function** (`src/helpers/chatWidget.js` or similar):
749
+
750
+ ```jsx
751
+ /**
752
+ * Builds the data string for ChatWidget component
753
+ * @param {Object} params - Object containing all parameters
754
+ * @param {string} params.email - Optional: User's email address
755
+ * @param {string} params.phone - Optional: User's phone number
756
+ * @param {Object} params.customFields - Optional: Any additional custom fields as key-value pairs
757
+ * @returns {string} Formatted data string for ChatWidget (always returns a string)
758
+ *
759
+ * @example
760
+ * // Returns: "email=john@example.com~phone=+1234567890~department=Engineering"
761
+ * buildChatWidgetData({
762
+ * email: "john@example.com",
763
+ * phone: "+1234567890",
764
+ * customFields: { department: "Engineering" }
765
+ * });
766
+ */
767
+ export const buildChatWidgetData = ({
768
+ email,
769
+ phone,
770
+ customFields = {}
771
+ }) => {
772
+ const parts = [];
773
+
774
+ // Add standard fields
775
+ if (email) {
776
+ parts.push(`email=${encodeURIComponent(email)}`);
777
+ }
778
+
779
+ if (phone) {
780
+ parts.push(`phone=${encodeURIComponent(phone)}`);
781
+ }
782
+
783
+ // Add any custom fields dynamically
784
+ // Note: customFields is just a convenience parameter for the helper function
785
+ // Each field will be flattened into the string format: key=value~key2=value2
786
+ Object.entries(customFields).forEach(([key, value]) => {
787
+ if (value) {
788
+ parts.push(`${key}=${encodeURIComponent(String(value))}`);
789
+ }
790
+ });
791
+
792
+ // Returns a string like: "email=john@example.com~phone=+1234567890~department=Engineering"
793
+ return parts.join("~");
794
+ };
795
+ ```
796
+
797
+ **Important:** The `customFields` parameter is **NOT** passed as an object to the widget. It's just a convenient way to pass multiple additional fields to the helper function. The function flattens everything into a single string format.
798
+
799
+ **Usage in your application:**
800
+
801
+ ```jsx
802
+ import { ChatWidget } from "@data-netmonk/mona-chat-widget";
803
+ import "@data-netmonk/mona-chat-widget/dist/style.css";
804
+ import { buildChatWidgetData } from "./helpers/chatWidget";
805
+
806
+ function App() {
807
+ // Get user data from your auth system, state, or props
808
+ const user = {
809
+ id: "user123",
810
+ name: "John Doe",
811
+ email: "john@example.com",
812
+ phone: "+628123456789"
813
+ };
814
+
815
+ // Build the data string with custom variables only
816
+ const customData = buildChatWidgetData({
817
+ email: user.email,
818
+ phone: user.phone,
819
+ customFields: {
820
+ department: "Engineering",
821
+ role: "Developer",
822
+ company: "Acme Corp"
823
+ }
824
+ });
825
+
826
+ // customData is now a STRING like:
827
+ // "email=john@example.com~phone=%2B628123456789~department=Engineering~role=Developer~company=Acme%20Corp"
828
+
829
+ return (
830
+ <ChatWidget
831
+ userId={user.id}
832
+ sourceId="691e1b5952068ff7aaeccffc9"
833
+ webhookUrl="https://api.example.com/webhook"
834
+ authUrl="https://api.example.com/login/chatwidget" // Direct prop
835
+ username={user.name} // Direct prop
836
+ data={customData} // Only additional variables
837
+ />
838
+ );
839
+ }
840
+ ```
841
+
842
+ **With conditional authentication:**
843
+
844
+ ```jsx
845
+ function App() {
846
+ const authUrl = import.meta.env.VITE_CHAT_AUTH_ENABLED === "true"
847
+ ? import.meta.env.VITE_CHAT_AUTH_URL
848
+ : null;
849
+
850
+ const customData = buildChatWidgetData({
851
+ email: getCurrentUser().email,
852
+ customFields: {
853
+ department: "Sales"
854
+ }
855
+ });
856
+
857
+ return (
858
+ <ChatWidget
859
+ userId={getCurrentUser().id}
860
+ sourceId="691e1b5952068ff7aaeccffc9"
861
+ webhookUrl={import.meta.env.VITE_WEBHOOK_URL}
862
+ authUrl={authUrl} // Direct prop (conditionally set)
863
+ username={getCurrentUser().name} // Direct prop
864
+ data={customData} // Only additional variables
865
+ />
866
+ );
867
+ }
868
+ ```
869
+
870
+ **The helper function handles:**
871
+ - ✅ Proper formatting with `~` separators
872
+ - ✅ URL encoding for special characters
873
+ - ✅ Conditional parameter inclusion (only adds if value exists)
874
+ - ✅ Support for dynamic custom fields
875
+ - ✅ Type safety and documentation via JSDoc comments
876
+
877
+ ### Standalone app (for demonstration)
878
+
879
+ 1. **How to run locally** (access at `http://localhost:${PORT}/${APP_PREFIX}`)
880
+
881
+ ```
882
+ npm run dev
883
+ ```
884
+
885
+ 2. **How to build as a standalone app** (build file at `/dist-app` directory)
886
+
887
+ ```
888
+ npm run build-app
889
+ ```
890
+
891
+ 3. **How to serve standalone app** (access at `http://localhost:${PORT}/${APP_PREFIX}`)
892
+
893
+ ```
894
+ npm run serve
895
+ ```
896
+
897
+ 4. **How to run on docker** (access at `http://localhost:${PORT}/${APP_PREFIX}`)
898
+
899
+ ```
900
+ docker-compose up --build
901
+ ```
902
+
903
+ ---