releasebird-javascript-sdk 1.0.72 → 1.0.73

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.
@@ -40,6 +40,10 @@ export default class RbirdWebsiteWidget {
40
40
  countNotificationsTimer = null;
41
41
  countNotificationsDelay = 1000; // 1 second
42
42
 
43
+ // Message bubbles
44
+ messageBubblesContainer = null;
45
+ dismissedBubbles = new Set(); // Track dismissed bubbles by chatId
46
+
43
47
  static getInstance() {
44
48
  if (!this.instance) {
45
49
  this.instance = new RbirdWebsiteWidget();
@@ -110,6 +114,9 @@ export default class RbirdWebsiteWidget {
110
114
  // Disable scrolling on the main page
111
115
  document.body.style.overflow = 'hidden';
112
116
 
117
+ // Hide message bubbles when widget is open
118
+ this.hideMessageBubbles();
119
+
113
120
  this.registerListeners();
114
121
  if (this.hideWidgetButton) {
115
122
  this.hideWidgetButton.style.display = "none";
@@ -264,6 +271,10 @@ export default class RbirdWebsiteWidget {
264
271
 
265
272
  this.websiteWidget.onclick = () => this.openWebsiteWidget();
266
273
  this.unregisterListeners();
274
+
275
+ // Reload message bubbles when widget is closed
276
+ this.dismissedBubbles.clear(); // Reset dismissed bubbles
277
+ this.fetchUnreadMessages();
267
278
  }
268
279
 
269
280
  initButton() {
@@ -301,7 +312,9 @@ export default class RbirdWebsiteWidget {
301
312
 
302
313
  if (e.data === 'newMessageArrived') {
303
314
  this.countNotifications();
315
+ // Also refresh message bubbles when new message arrives
304
316
  if (!RbirdUtils.hasClass(this.widgetContent, 'cta__modal--visible')) {
317
+ this.fetchUnreadMessages();
305
318
  if (this.iframe) {
306
319
  this.iframe.contentWindow?.postMessage({
307
320
  type: 'showMessageTab',
@@ -347,6 +360,7 @@ export default class RbirdWebsiteWidget {
347
360
  window.onmessage = (e) => this.handleEvent(e, this);
348
361
 
349
362
  this.countNotifications();
363
+ this.fetchUnreadMessages();
350
364
  }
351
365
 
352
366
  updateIframe(iframeSrc) {
@@ -378,5 +392,230 @@ export default class RbirdWebsiteWidget {
378
392
  document.addEventListener("keydown", this.escListener);
379
393
  }
380
394
 
395
+ /**
396
+ * Fetch unread messages and display as bubbles above the widget button
397
+ */
398
+ fetchUnreadMessages() {
399
+ if (typeof window === 'undefined') return;
400
+ if (!RbirdSessionManager.getInstance().identify?.people && !RbirdSessionManager.getInstance().anonymousIdentifier) {
401
+ return;
402
+ }
403
+
404
+ const http = new XMLHttpRequest();
405
+ http.open("GET", `${API}/ewidget/unread/messages`);
406
+ http.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
407
+ http.setRequestHeader("apiKey", RbirdSessionManager.getInstance().apiKey);
408
+ if (RbirdSessionManager.getInstance().identify?.people) {
409
+ http.setRequestHeader("peopleId", RbirdSessionManager.getInstance().identify.people);
410
+ }
411
+ if (RbirdSessionManager.getInstance().anonymousIdentifier) {
412
+ http.setRequestHeader("ai", RbirdSessionManager.getInstance().anonymousIdentifier);
413
+ }
414
+
415
+ const that = this;
416
+ http.onerror = function () {
417
+ console.error('[RbirdWidget] Failed to fetch unread messages');
418
+ };
419
+ http.onreadystatechange = function () {
420
+ if (http.readyState === XMLHttpRequest.DONE) {
421
+ console.log('[RbirdWidget] Unread messages response:', http.status, http.responseText);
422
+ if (http.status === 200 || http.status === 201) {
423
+ try {
424
+ const messages = JSON.parse(http.responseText);
425
+ console.log('[RbirdWidget] Parsed messages:', messages);
426
+ that.renderMessageBubbles(messages);
427
+ } catch (e) {
428
+ console.error('[RbirdWidget] Error parsing unread messages:', e);
429
+ }
430
+ }
431
+ }
432
+ };
433
+ http.send();
434
+ }
435
+
436
+ /**
437
+ * Render message bubbles above the widget button
438
+ * @param {Array} messages - Array of unread messages (one per chat, max 5)
439
+ */
440
+ renderMessageBubbles(messages) {
441
+ console.log('[RbirdWidget] renderMessageBubbles called with:', messages);
442
+ if (typeof window === 'undefined' || typeof document === 'undefined') return;
443
+
444
+ // Don't show bubbles if widget is open
445
+ if (this.isOpen()) {
446
+ console.log('[RbirdWidget] Widget is open, hiding bubbles');
447
+ this.hideMessageBubbles();
448
+ return;
449
+ }
450
+
451
+ // Filter out dismissed bubbles and limit to 5
452
+ const filteredMessages = messages
453
+ .filter(msg => !this.dismissedBubbles.has(msg.chatId))
454
+ .slice(0, 5);
455
+
456
+ console.log('[RbirdWidget] Filtered messages:', filteredMessages);
457
+
458
+ if (filteredMessages.length === 0) {
459
+ console.log('[RbirdWidget] No messages to display');
460
+ this.hideMessageBubbles();
461
+ return;
462
+ }
463
+
464
+ // Create or get container
465
+ if (!this.messageBubblesContainer) {
466
+ this.messageBubblesContainer = document.createElement('div');
467
+ this.messageBubblesContainer.className = 'rbird-message-bubbles-container';
468
+ document.body.appendChild(this.messageBubblesContainer);
469
+ }
470
+
471
+ // Clear existing bubbles
472
+ this.messageBubblesContainer.innerHTML = '';
473
+
474
+ // Render each bubble
475
+ filteredMessages.forEach(msg => {
476
+ const bubble = this.createMessageBubble(msg);
477
+ this.messageBubblesContainer.appendChild(bubble);
478
+ });
479
+
480
+ this.messageBubblesContainer.style.display = 'flex';
481
+ }
482
+
483
+ /**
484
+ * Create a single message bubble element
485
+ * @param {Object} msg - Message object with chatId, text, senderName, senderAvatar, timestamp
486
+ */
487
+ createMessageBubble(msg) {
488
+ const bubble = document.createElement('div');
489
+ bubble.className = 'rbird-message-bubble';
490
+ bubble.setAttribute('data-chat-id', msg.chatId);
491
+
492
+ // Avatar
493
+ const avatarDiv = document.createElement('div');
494
+ avatarDiv.className = 'rbird-message-bubble-avatar';
495
+ if (msg.senderAvatar) {
496
+ const avatarImg = document.createElement('img');
497
+ avatarImg.src = msg.senderAvatar;
498
+ avatarImg.alt = msg.senderName || 'Avatar';
499
+ avatarDiv.appendChild(avatarImg);
500
+ } else {
501
+ // Show initials
502
+ const initials = (msg.senderName || 'U').charAt(0).toUpperCase();
503
+ avatarDiv.textContent = initials;
504
+ }
505
+
506
+ // Content
507
+ const contentDiv = document.createElement('div');
508
+ contentDiv.className = 'rbird-message-bubble-content';
509
+
510
+ const textP = document.createElement('p');
511
+ textP.className = 'rbird-message-bubble-text';
512
+ textP.textContent = msg.text || '';
513
+
514
+ const metaDiv = document.createElement('div');
515
+ metaDiv.className = 'rbird-message-bubble-meta';
516
+
517
+ const senderSpan = document.createElement('span');
518
+ senderSpan.className = 'rbird-message-bubble-sender';
519
+ senderSpan.textContent = msg.senderName || 'Support';
520
+
521
+ const dotSpan = document.createElement('span');
522
+ dotSpan.textContent = '·';
523
+
524
+ const timeSpan = document.createElement('span');
525
+ timeSpan.className = 'rbird-message-bubble-time';
526
+ timeSpan.textContent = this.formatMessageTime(msg.timestamp);
527
+
528
+ metaDiv.appendChild(senderSpan);
529
+ metaDiv.appendChild(dotSpan);
530
+ metaDiv.appendChild(timeSpan);
531
+
532
+ contentDiv.appendChild(textP);
533
+ contentDiv.appendChild(metaDiv);
534
+
535
+ // Close button
536
+ const closeBtn = document.createElement('button');
537
+ closeBtn.className = 'rbird-message-bubble-close';
538
+ closeBtn.innerHTML = '×';
539
+ closeBtn.onclick = (e) => {
540
+ e.stopPropagation();
541
+ this.dismissMessageBubble(msg.chatId, bubble);
542
+ };
543
+
544
+ bubble.appendChild(avatarDiv);
545
+ bubble.appendChild(contentDiv);
546
+ bubble.appendChild(closeBtn);
547
+
548
+ // Click on bubble opens widget
549
+ bubble.onclick = () => {
550
+ this.hideMessageBubbles();
551
+ this.openWebsiteWidget();
552
+ // Send message to iframe to open the specific chat
553
+ if (this.iframe) {
554
+ this.iframe.contentWindow?.postMessage({
555
+ type: 'openChat',
556
+ chatId: msg.chatId
557
+ }, '*');
558
+ }
559
+ };
560
+
561
+ return bubble;
562
+ }
563
+
564
+ /**
565
+ * Format timestamp to relative time (e.g., "Just now", "5 min ago")
566
+ */
567
+ formatMessageTime(timestamp) {
568
+ if (!timestamp) return '';
569
+
570
+ const now = new Date();
571
+ const msgDate = new Date(timestamp);
572
+ const diffMs = now - msgDate;
573
+ const diffSec = Math.floor(diffMs / 1000);
574
+ const diffMin = Math.floor(diffSec / 60);
575
+ const diffHour = Math.floor(diffMin / 60);
576
+ const diffDay = Math.floor(diffHour / 24);
577
+
578
+ if (diffSec < 60) {
579
+ return 'Just now';
580
+ } else if (diffMin < 60) {
581
+ return `${diffMin} min ago`;
582
+ } else if (diffHour < 24) {
583
+ return `${diffHour}h ago`;
584
+ } else if (diffDay === 1) {
585
+ return 'Yesterday';
586
+ } else if (diffDay < 7) {
587
+ return `${diffDay}d ago`;
588
+ } else {
589
+ return msgDate.toLocaleDateString();
590
+ }
591
+ }
592
+
593
+ /**
594
+ * Dismiss a single message bubble
595
+ */
596
+ dismissMessageBubble(chatId, bubbleElement) {
597
+ this.dismissedBubbles.add(chatId);
598
+
599
+ if (bubbleElement) {
600
+ bubbleElement.style.animation = 'rbird-bubble-slide-out 0.2s ease-in forwards';
601
+ setTimeout(() => {
602
+ bubbleElement.remove();
603
+ // Hide container if no more bubbles
604
+ if (this.messageBubblesContainer && this.messageBubblesContainer.children.length === 0) {
605
+ this.messageBubblesContainer.style.display = 'none';
606
+ }
607
+ }, 200);
608
+ }
609
+ }
610
+
611
+ /**
612
+ * Hide all message bubbles
613
+ */
614
+ hideMessageBubbles() {
615
+ if (this.messageBubblesContainer) {
616
+ this.messageBubblesContainer.style.display = 'none';
617
+ this.messageBubblesContainer.innerHTML = '';
618
+ }
619
+ }
381
620
 
382
621
  }
package/src/Styles.js CHANGED
@@ -381,6 +381,148 @@ background: transparent
381
381
  0% { transform: rotate(0deg); }
382
382
  100% { transform: rotate(360deg); }
383
383
  }
384
+
385
+ /* Message Bubbles - Unread messages preview */
386
+ .rbird-message-bubbles-container {
387
+ position: fixed;
388
+ bottom: ${spaceBottom + 70}px;
389
+ ${launcherPosition === 'right' ? `right: ${spaceLeftRight}px;` : `left: ${spaceLeftRight}px;`}
390
+ z-index: 9999999;
391
+ display: flex;
392
+ flex-direction: column;
393
+ gap: 10px;
394
+ max-width: 320px;
395
+ pointer-events: none;
396
+ }
397
+
398
+ .rbird-message-bubble {
399
+ background: #ffffff;
400
+ border-radius: 12px;
401
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
402
+ padding: 12px 14px;
403
+ display: flex;
404
+ align-items: flex-start;
405
+ gap: 10px;
406
+ cursor: pointer;
407
+ pointer-events: auto;
408
+ animation: rbird-bubble-slide-in 0.3s ease-out;
409
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
410
+ position: relative;
411
+ }
412
+
413
+ .rbird-message-bubble:hover {
414
+ transform: translateY(-2px);
415
+ box-shadow: 0 6px 25px rgba(0, 0, 0, 0.2);
416
+ }
417
+
418
+ .rbird-message-bubble-avatar {
419
+ width: 36px;
420
+ height: 36px;
421
+ border-radius: 50%;
422
+ background-color: #e0e0e0;
423
+ flex-shrink: 0;
424
+ object-fit: cover;
425
+ display: flex;
426
+ align-items: center;
427
+ justify-content: center;
428
+ font-size: 14px;
429
+ font-weight: 600;
430
+ color: #666;
431
+ }
432
+
433
+ .rbird-message-bubble-avatar img {
434
+ width: 100%;
435
+ height: 100%;
436
+ border-radius: 50%;
437
+ object-fit: cover;
438
+ }
439
+
440
+ .rbird-message-bubble-content {
441
+ flex: 1;
442
+ min-width: 0;
443
+ overflow: hidden;
444
+ }
445
+
446
+ .rbird-message-bubble-text {
447
+ font-size: 14px;
448
+ line-height: 1.4;
449
+ color: #1a1a1a;
450
+ margin: 0 0 4px 0;
451
+ overflow: hidden;
452
+ text-overflow: ellipsis;
453
+ display: -webkit-box;
454
+ -webkit-line-clamp: 2;
455
+ -webkit-box-orient: vertical;
456
+ word-break: break-word;
457
+ }
458
+
459
+ .rbird-message-bubble-meta {
460
+ font-size: 12px;
461
+ color: #888;
462
+ display: flex;
463
+ align-items: center;
464
+ gap: 6px;
465
+ }
466
+
467
+ .rbird-message-bubble-sender {
468
+ font-weight: 500;
469
+ color: #666;
470
+ }
471
+
472
+ .rbird-message-bubble-time {
473
+ color: #999;
474
+ }
475
+
476
+ .rbird-message-bubble-close {
477
+ position: absolute;
478
+ top: 6px;
479
+ right: 6px;
480
+ width: 18px;
481
+ height: 18px;
482
+ border: none;
483
+ background: rgba(0, 0, 0, 0.1);
484
+ border-radius: 50%;
485
+ cursor: pointer;
486
+ display: flex;
487
+ align-items: center;
488
+ justify-content: center;
489
+ font-size: 12px;
490
+ color: #666;
491
+ opacity: 0;
492
+ transition: opacity 0.2s ease;
493
+ padding: 0;
494
+ line-height: 1;
495
+ }
496
+
497
+ .rbird-message-bubble:hover .rbird-message-bubble-close {
498
+ opacity: 1;
499
+ }
500
+
501
+ .rbird-message-bubble-close:hover {
502
+ background: rgba(0, 0, 0, 0.2);
503
+ }
504
+
505
+ @keyframes rbird-bubble-slide-in {
506
+ from {
507
+ opacity: 0;
508
+ transform: translateY(20px) scale(0.95);
509
+ }
510
+ to {
511
+ opacity: 1;
512
+ transform: translateY(0) scale(1);
513
+ }
514
+ }
515
+
516
+ @keyframes rbird-bubble-slide-out {
517
+ from {
518
+ opacity: 1;
519
+ transform: translateY(0) scale(1);
520
+ }
521
+ to {
522
+ opacity: 0;
523
+ transform: translateY(20px) scale(0.95);
524
+ }
525
+ }
384
526
  }`
385
527
 
386
528
  const oldNode = document.querySelector(".rbird-styles");