@chat21/chat21-web-widget 5.0.70 → 5.0.71-rc.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # chat21-web-widget ver 5.0
2
2
 
3
+ ### 5.0.71-rc.2
4
+ - added: hiddenMessage tiledesk property to start a conversation with an hidden info message
5
+ - added: ability to listen from parent message and start a new Conversation with an hidden intentName info message
6
+ - changed: minor UI fix carousel component
7
+
8
+ ### 5.0.71-rc.1
9
+ - added: ability to start conversation from an intent hiddenMessage name
10
+ - added: postMessage to notice parent the current message
11
+ - changed: carousel UI if image is not available
12
+
3
13
  ### 5.0.70 in PROD
4
14
 
5
15
  ### 5.0.70-rc.1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@chat21/chat21-web-widget",
3
3
  "author": "Tiledesk SRL",
4
- "version": "5.0.70",
4
+ "version": "5.0.71-rc.2",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.tiledesk.com",
7
7
  "repository": {
@@ -690,42 +690,6 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
690
690
  }
691
691
  // ========= end:: START UI ============//
692
692
 
693
- // private openNewConversation() {
694
- // this.logger.debug('[APP-COMP] openNewConversation in APP COMPONENT');
695
- // this.g.newConversationStart = true;
696
- // // controllo i dipartimenti se sono 1 o 2 seleziono dipartimento e nascondo modale dipartimento
697
- // // altrimenti mostro modale dipartimenti
698
- // const preChatForm = this.g.preChatForm;
699
- // const attributes = this.g.attributes;
700
- // const departments = this.g.departments;
701
- // // that.g.wdLog(['departments: ', departments, departments.length);
702
- // if (preChatForm && (!attributes || !attributes.userFullname || !attributes.userEmail)) {
703
- // // if (preChatForm && (!attributes.userFullname || !attributes.userEmail)) {
704
- // this.isOpenConversation = false;
705
- // this.g.setParameter('isOpenPrechatForm', true);
706
- // // this.settingsSaverService.setVariable('isOpenPrechatForm', true);
707
- // this.isOpenSelectionDepartment = false;
708
- // if (departments && departments.length > 1 && this.g.departmentID == null) {
709
- // this.isOpenSelectionDepartment = true;
710
- // }
711
- // } else {
712
- // // this.g.isOpenPrechatForm = false;
713
- // this.g.setParameter('isOpenPrechatForm', false);
714
- // // this.settingsSaverService.setVariable('isOpenPrechatForm', false);
715
- // this.isOpenConversation = false;
716
- // this.isOpenSelectionDepartment = false;
717
- // if (departments && departments.length > 1 && this.g.departmentID == null) {
718
- // this.isOpenSelectionDepartment = true;
719
- // } else {
720
- // this.isOpenConversation = true;
721
- // }
722
- // }
723
-
724
- // this.logger.debug('[APP-COMP] isOpenPrechatForm', this.g.isOpenPrechatForm, ' isOpenSelectionDepartment:', this.isOpenSelectionDepartment);
725
- // if (this.g.isOpenPrechatForm === false && this.isOpenSelectionDepartment === false) {
726
- // this.startNewConversation();
727
- // }
728
- // }
729
693
 
730
694
  /**
731
695
  * genero un nuovo conversationWith
@@ -748,6 +712,13 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
748
712
  this.appStorageService.setItem('recipientId', newConvId)
749
713
  this.logger.debug('[APP-COMP] recipientId: ', this.g.recipientId);
750
714
  this.isConversationArchived = false;
715
+
716
+ /** allow to start conversation with an hidden message (without publishing 'new_conversation' event) */
717
+ if(this.g.hiddenMessage){
718
+ this.onNewConversationWithMessage(this.g.hiddenMessage)
719
+ return;
720
+ }
721
+
751
722
  this.triggerNewConversationEvent(newConvId);
752
723
  }
753
724
 
@@ -781,9 +752,12 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
781
752
  if (this.g.windowContext.window.location) {
782
753
  attributes['sourcePage'] = this.g.windowContext.window.location.href;
783
754
  }
784
- if(this.g.windowContext.window.document){
755
+ if(this.g.windowContext.window.document) {
785
756
  attributes['sourceTitle'] = this.g.windowContext.window.document.title;
786
757
  }
758
+ // if(this.g.windowContext.window.document && this.g.windowContext.window.document.cookie) {
759
+ // attributes['cookie'] = this.g.windowContext.window.document.cookie;
760
+ // }
787
761
  if (projectid) {
788
762
  attributes['projectId'] = projectid;
789
763
  }
@@ -1409,9 +1383,9 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
1409
1383
  };
1410
1384
 
1411
1385
  /** set state reinit */
1412
- windowContext['tiledesk'].startConversation = function () {
1386
+ windowContext['tiledesk'].startConversation = function (text: string) {
1413
1387
  ngZone.run(() => {
1414
- windowContext['tiledesk']['angularcomponent'].component.onNewConversation();
1388
+ windowContext['tiledesk']['angularcomponent'].component.onNewConversation(text);
1415
1389
  });
1416
1390
  };
1417
1391
 
@@ -1741,8 +1715,9 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
1741
1715
  * carico conversazione - stack 1
1742
1716
  * home - stack 0
1743
1717
  */
1744
- onNewConversation() {
1718
+ onNewConversation(text?: string) {
1745
1719
  this.logger.debug('[APP-COMP] returnNewConversation in APP COMPONENT');
1720
+ if(text) this.g.setParameter('hiddenMessage', text);
1746
1721
  this.g.newConversationStart = true;
1747
1722
  // controllo i dipartimenti se sono 1 o 2 seleziono dipartimento e nascondo modale dipartimento
1748
1723
  // altrimenti mostro modale dipartimenti
@@ -1779,6 +1754,28 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
1779
1754
  }
1780
1755
  }
1781
1756
 
1757
+
1758
+ onNewConversationWithMessage(text: string, subType: string = 'info'){
1759
+ this.logger.log('[APP-COMP] onNewConversationWithMessage in APP COMPONENT', text);
1760
+
1761
+ const newConvId = this.generateNewUidConversation();
1762
+ this.g.setParameter('recipientId', newConvId);
1763
+ this.appStorageService.setItem('recipientId', newConvId)
1764
+
1765
+ let message: any = {}
1766
+ message.attributes = { subtype: subType, ...this.g.attributes}
1767
+ message.userAgent = this.g.attributes['client']
1768
+ message.request_id = newConvId
1769
+ message.sourcePage = this.g.attributes['sourcePage']
1770
+ message.language = this.g.lang
1771
+ message.text = '/'+ text
1772
+ message.participants = this.g.participants
1773
+ message.departmentid = this.g.attributes.departmentId
1774
+ // this.sendMessage(message)
1775
+ this.tiledeskRequestsService.sendMessageToRequest(newConvId, this.g.tiledeskToken, message)
1776
+ return;
1777
+ }
1778
+
1782
1779
  /**
1783
1780
  * MODAL HOME:
1784
1781
  * open all-conversation
@@ -504,7 +504,8 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
504
504
  '',
505
505
  '',
506
506
  false,
507
- 'text')
507
+ 'text',
508
+ false)
508
509
  }
509
510
  }
510
511
 
@@ -63,7 +63,7 @@
63
63
  <!-- [ngClass]="{'slide-in-left': !isFirstMessage(message?.sender, i)}" -->
64
64
  <chat-bubble-message class="messages msg_receive"
65
65
  [ngClass]="{'slide-in-left': false}"
66
- [class.no-background]="(isImage(message) || isFrame(message)) && ((message?.text && message?.text.trim() === '') || !message?.text)"
66
+ [class.no-background]="(isImage(message) || isFrame(message) || isCarousel(message)) && ((message?.text && message?.text.trim() === '') || !message?.text)"
67
67
  [class.emoticon]="isEmojii(message?.text)"
68
68
  [style.margin-left]="isSameSender(message?.sender, i)? '50px': null"
69
69
  [ngStyle]="{'background': stylesMap.get('bubbleReceivedBackground'), 'color': stylesMap.get('bubbleReceivedTextColor')}"
@@ -109,8 +109,7 @@
109
109
  </div>
110
110
 
111
111
  <!-- carousel -->
112
- <div *ngIf="message?.attributes && message?.attributes?.attachment
113
- && message?.attributes?.attachment?.gallery" [ngClass]="{'slide-in-left': false}" class="carousel_container">
112
+ <div *ngIf="isCarousel(message)" [ngClass]="{'slide-in-left': false}" class="carousel_container">
114
113
  <chat-carousel class="carousel_container"
115
114
  [message]="message"
116
115
  [isConversationArchived]="isConversationArchived"
@@ -6,7 +6,7 @@ import { UploadService } from 'src/chat21-core/providers/abstract/upload.service
6
6
  import { LoggerInstance } from 'src/chat21-core/providers/logger/loggerInstance';
7
7
  import { MESSAGE_TYPE_INFO, MESSAGE_TYPE_MINE, MESSAGE_TYPE_OTHERS } from 'src/chat21-core/utils/constants';
8
8
  import { isPopupUrl, popupUrl } from 'src/chat21-core/utils/utils';
9
- import { isEmojii, isFirstMessage, isFrame, isImage, isInfo, isLastMessage, isMine, isSameSender, messageType } from 'src/chat21-core/utils/utils-message';
9
+ import { isCarousel, isEmojii, isFirstMessage, isFrame, isImage, isInfo, isLastMessage, isMine, isSameSender, messageType } from 'src/chat21-core/utils/utils-message';
10
10
 
11
11
  @Component({
12
12
  selector: 'chat-conversation-content',
@@ -45,6 +45,7 @@ export class ConversationContentComponent implements OnInit {
45
45
  // ========= begin:: dichiarazione funzioni ======= //
46
46
  isPopupUrl = isPopupUrl;
47
47
  popupUrl = popupUrl;
48
+ isCarousel = isCarousel;
48
49
  // ========= end:: dichiarazione funzioni ======= //
49
50
 
50
51
  // ========== begin:: set icon status message ======= //
@@ -8,9 +8,13 @@
8
8
  <!-- <div class="card" style="width: 17px;"></div> -->
9
9
  <div class="card" *ngFor="let card of gallery; let i = index">
10
10
  <div [style.opacity]="i+1 === activeElement? 1: 0.5">
11
- <div class="card-image">
11
+ <div class="card-image" *ngIf="card?.preview?.src !== ''">
12
12
  <img [src]="card?.preview?.src" alt="img" draggable="false">
13
13
  </div>
14
+ <div class="card-image card-image-placeholder" *ngIf="card?.preview?.src == ''">
15
+ <img src="assets/images/icons/no-image.svg" alt="img" draggable="false">
16
+ <span>Image not available</span>
17
+ </div>
14
18
  <div class="card-content">
15
19
  <div class="card-title">{{card?.title}}</div>
16
20
  <div class="card-description">{{card?.description}}</div>
@@ -100,6 +100,26 @@
100
100
  .carousel .card .card-image {
101
101
  height: 150px;
102
102
  width: var(--cardWidth);
103
+ border-radius: 8px 8px 0px 0px;
104
+ }
105
+
106
+ .carousel .card .card-image-placeholder{
107
+ display: flex;
108
+ flex-direction: column;
109
+ justify-content: center;
110
+ gap: 10px;
111
+ background: #c1b9b952;
112
+ align-items: center;
113
+
114
+ img{
115
+ width: 30px;
116
+ height: 30px;
117
+ filter: brightness(0) saturate(100%) invert(44%) sepia(8%) saturate(449%) hue-rotate(190deg) brightness(94%) contrast(93%);
118
+ }
119
+
120
+ span{
121
+ font-size: 0.8rem;
122
+ }
103
123
  }
104
124
  .card .card-image img {
105
125
  height: 100%;
@@ -109,7 +129,9 @@
109
129
  background: transparent!important;
110
130
  display: block;
111
131
  max-width: 100% !important;
132
+ width: 100%;
112
133
  border-radius: 8px 8px 0px 0px;
134
+
113
135
  }
114
136
  .carousel .card .card-content {
115
137
  // font-weight: 500;
@@ -1,5 +1,6 @@
1
1
  import { Component, ElementRef, EventEmitter, Input, OnInit, Output, SimpleChange, SimpleChanges, ViewChildren } from '@angular/core';
2
2
  import { MessageModel } from 'src/chat21-core/models/message';
3
+ import { isCarousel } from 'src/chat21-core/utils/utils-message';
3
4
 
4
5
  @Component({
5
6
  selector: 'chat-carousel',
@@ -34,7 +35,7 @@ export class CarouselComponent implements OnInit{
34
35
  constructor(private elementRef: ElementRef) { }
35
36
 
36
37
  ngOnInit() {
37
- console.log('[CAROUSEL-MESSAGE] hello', this.message)
38
+ console.log('[CAROUSEL-MESSAGE] hello', this.message, isCarousel(this.message))
38
39
 
39
40
 
40
41
  this.wrapper = this.elementRef.nativeElement.querySelector('.wrapper')
@@ -76,7 +77,7 @@ export class CarouselComponent implements OnInit{
76
77
  ngOnChanges(changes: SimpleChanges){
77
78
  if(this.message && this.message.attributes && this.message.attributes?.attachment && this.message.attributes?.attachment?.gallery){
78
79
  this.gallery = this.message.attributes.attachment.gallery
79
- console.log('carrrrrrrrr', this.wrapper, this.elementRef.nativeElement.querySelector(".card"))
80
+ // console.log('carrrrrrrrr', this.wrapper, this.elementRef.nativeElement.querySelector(".card"))
80
81
  // this.firstCardWidth = (this.elementRef.nativeElement.querySelector(".card") as HTMLElement).offsetWidth
81
82
  }
82
83
 
@@ -95,7 +96,7 @@ export class CarouselComponent implements OnInit{
95
96
  let gap = 17
96
97
  let cardPerView = Math.round(this.carousel.offsetWidth / width);
97
98
 
98
- console.log('go to -->', direction, width, this.firstCardWidth, cardPerView, this.carousel.offsetWidth)
99
+ // console.log('go to -->', direction, width, this.firstCardWidth, cardPerView, this.carousel.offsetWidth)
99
100
 
100
101
  // this.carousel.scrollLeft += direction == "previous" ? -(width+gap) : width+gap;
101
102
  this.carousel.scrollLeft += direction == "previous" ? -width : width;
@@ -110,7 +111,6 @@ export class CarouselComponent implements OnInit{
110
111
  actionButtonClick(ev, button, index){
111
112
  this.button = button
112
113
  this.type = button.type
113
- console.log('buttonnnnnnn', ev, button)
114
114
  if ( button && ((button.action && button.action !== '') || (button.link && button.link !== '') || button.text !== '' )) {
115
115
 
116
116
  //set clicked button as the active one
@@ -1774,6 +1774,11 @@ export class GlobalSettingsService {
1774
1774
  if (TEMP) {
1775
1775
  globals.disconnetTime = stringToNumber(TEMP);
1776
1776
  }
1777
+
1778
+ TEMP = getParameterByName(windowContext, 'tiledesk_hiddenMessage');
1779
+ if (TEMP) {
1780
+ globals.hiddenMessage = TEMP;
1781
+ }
1777
1782
 
1778
1783
  }
1779
1784
 
@@ -218,6 +218,8 @@ export class Globals {
218
218
  onPageChangeVisibilityDesktop: 'open' | 'close' | 'last'; // ******* new ********
219
219
  displayOnMobile: boolean; // ******* new ********
220
220
  displayOnDesktop: boolean; // ******* new ********
221
+
222
+ hiddenMessage: string; // ******* new ********
221
223
  constructor(
222
224
  ) { }
223
225
 
@@ -414,6 +416,8 @@ export class Globals {
414
416
  this.displayOnMobile = true
415
417
  this.onPageChangeVisibilityMobile = 'close'
416
418
 
419
+ /**set an hidden message to show when conversation starts */
420
+ this.hiddenMessage = null
417
421
 
418
422
  // ============ END: SET EXTERNAL PARAMETERS ==============//
419
423
 
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
2
+ <g>
3
+ <rect fill="none" height="24" width="24"/>
4
+ <path d="M21.9,21.9l-8.49-8.49l0,0L3.59,3.59l0,0L2.1,2.1L0.69,3.51L3,5.83V19c0,1.1,0.9,2,2,2h13.17l2.31,2.31L21.9,21.9z M5,18 l3.5-4.5l2.5,3.01L12.17,15l3,3H5z M21,18.17L5.83,3H19c1.1,0,2,0.9,2,2V18.17z"/>
5
+ </g>
6
+ </svg>
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  Chat21Client
3
3
 
4
- v0.1.12.6
4
+ v0.1.12.7
5
5
 
6
6
  @Author Andrea Sponziello
7
7
  @Member Gabriele Panico
@@ -546,7 +546,7 @@ class Chat21Client {
546
546
  if (this.log) {
547
547
  console.log("this.on_message_handler already subscribed. Reconnected num", this.reconnections)
548
548
  }
549
- callbsubscribedCallbackack();
549
+ subscribedCallback();
550
550
  return
551
551
  }
552
552
  this.subscribeToMyConversations(() => {
@@ -1000,7 +1000,10 @@ class Chat21Client {
1000
1000
 
1001
1001
  this.client.on('connect', // TODO if token is wrong it must reply with an error!
1002
1002
  () => {
1003
- if (this.log) {console.log("Chat client connected. User:" + user_id)}
1003
+ if (this.log) {
1004
+ console.log("Chat client connected. User:" + user_id)
1005
+ console.log("Chat client connected. this.connected:" + this.connected)
1006
+ }
1004
1007
  if (!this.connected) {
1005
1008
  if (this.log) {console.log("Chat client first connection for:" + user_id)}
1006
1009
  this.connected = true
@@ -1027,6 +1030,7 @@ class Chat21Client {
1027
1030
  );
1028
1031
  this.client.on('close',
1029
1032
  () => {
1033
+ this.connected = false
1030
1034
  if (this.log) {console.log("Chat client close event");}
1031
1035
  }
1032
1036
  );
@@ -377,11 +377,12 @@
377
377
  }
378
378
  }, 1000);
379
379
 
380
+ //open widget after 3s if is closed
380
381
  setTimeout(() => {
381
382
  if(event_data && event_data.detail && event_data.detail.global && !event_data.detail.global.isOpen){
382
383
  window.Tiledesk('open')
383
384
  }
384
- }, 3000);
385
+ }, 1000);
385
386
 
386
387
  });
387
388
 
@@ -391,11 +392,11 @@
391
392
  // document.getElementById("preloader").style.display = "none";
392
393
  // }, 1000);
393
394
  //open widget after 3s if is closed
394
- setTimeout(() => {
395
- if(event_data && event_data.detail && event_data.detail.global && !event_data.detail.global.isOpen){
396
- window.Tiledesk('open')
397
- }
398
- }, 3000);
395
+ // setTimeout(() => {
396
+ // if(event_data && event_data.detail && event_data.detail.global && !event_data.detail.global.isOpen){
397
+ // window.Tiledesk('open')
398
+ // }
399
+ // }, 3000);
399
400
  });
400
401
 
401
402
  window.Tiledesk('onAuthStateChanged', function(event_data) {
@@ -405,6 +406,22 @@
405
406
  // window.Tiledesk('show')
406
407
  // }
407
408
  });
409
+
410
+ window.Tiledesk('onMessageCreated', function(event_data) {
411
+ console.log("onMessageCreated!", event_data);
412
+ window.parent.postMessage(event_data.detail, '*')
413
+ // if(event_data.detail.isLogged){
414
+ // console.log("isLogged!!!!", event_data);
415
+ // window.Tiledesk('show')
416
+ // }
417
+ });
418
+
419
+ window.addEventListener('message', (event_data)=> {
420
+ if(event_data && event_data.data && event_data.data.action === 'restart'){
421
+ window.Tiledesk('startConversation', event_data.data.intentName)
422
+ }
423
+ })
424
+
408
425
  </script>
409
426
 
410
427
  <script type="application/javascript">
@@ -453,14 +470,14 @@
453
470
  </div>
454
471
 
455
472
  <header id="header">
456
- <ul class="nav navbar-nav navbar-left">
473
+ <!-- <ul class="nav navbar-nav navbar-left">
457
474
  <li class="sign-up"><button class="share-btn" onclick="copyLink()">SHARE THIS PROTOTYPE</button></li>
458
- </ul>
475
+ </ul> -->
459
476
  </header>
460
477
  <!-- The actual snackbar -->
461
478
  <div id="snackbar">Copied to clipboard...</div>
462
479
 
463
- <div id="wrapper" class="mockup">
480
+ <!-- <div id="wrapper" class="mockup"> -->
464
481
  </div>
465
482
 
466
483
  <footer id="footer">
@@ -20,6 +20,7 @@ export class ConversationModel {
20
20
  public color: string,
21
21
  public avatar: string,
22
22
  public archived: boolean,
23
- public type: string
23
+ public type: string,
24
+ public sound: boolean
24
25
  ) { }
25
26
  }
@@ -150,6 +150,7 @@ export class MQTTConversationsHandler extends ConversationsHandlerService {
150
150
  this.logger.debug('[MQTTConversationsHandler] connecting MQTT conversations handler');
151
151
  this.chat21Service.chatClient.onConversationAdded( (conv) => {
152
152
  let conversation = this.completeConversation(conv); // needed to get the "conversation_with", and find the conv in the conv-history
153
+ conversation.sound = true
153
154
  this.logger.log("[MQTTConversationsHandler] onConversationAdded completed:",conversation);
154
155
  const index = this.searchIndexInArrayForConversationWith(this.conversations, conversation.conversation_with);
155
156
  if (index > -1) {
@@ -162,6 +163,7 @@ export class MQTTConversationsHandler extends ConversationsHandlerService {
162
163
  }
163
164
  });
164
165
  this.chat21Service.chatClient.onConversationUpdated( (conv, topic) => {
166
+ conv.sound = true;
165
167
  this.logger.debug('[MQTTConversationsHandler] conversation updated:', JSON.stringify(conv));
166
168
  this.changed(conv);
167
169
  });
@@ -184,6 +186,7 @@ export class MQTTConversationsHandler extends ConversationsHandlerService {
184
186
  this.logger.debug('[MQTTConversationsHandler] Last conversations', conversations, 'err', err);
185
187
  if (!err) {
186
188
  conversations.forEach(conv => {
189
+ conv.sound = false;
187
190
  this.added(conv);
188
191
  });
189
192
  loaded();
@@ -49,7 +49,7 @@ export class MQTTPresenceService extends PresenceService {
49
49
  const that = this;
50
50
  let local_BSIsOnline = new BehaviorSubject<any>(null);
51
51
  this.webSocketService.wsRequesterStatus$.subscribe((data: any) => {
52
- this.logger.log('[NATIVEPresenceSERVICE] $subs to wsService - data ', data, userid);
52
+ // this.logger.log('[NATIVEPresenceSERVICE] $subs to wsService - data ', data, userid);
53
53
  if (data && data.presence && data.presence.status === 'online' ) {
54
54
  that.BSIsOnline.next({ uid: data.uuid_user, isOnline: true });
55
55
  local_BSIsOnline.next({ uid: data.uuid_user, isOnline: true });
@@ -10,6 +10,14 @@ import {
10
10
  TYPE_SUPPORT_GROUP
11
11
  } from './constants';
12
12
 
13
+ /** */
14
+ export function isCarousel(message: any) {
15
+ if (message && message.type && message.type === 'gallery' && message?.attributes && message?.attributes?.attachment && message?.attributes?.attachment?.gallery ) {
16
+ return true;
17
+ }
18
+ return false;
19
+ }
20
+
13
21
  /** */
14
22
  export function isImage(message: any) {
15
23
  if (message && message.type && message.type === 'image' && message.metadata && message.metadata.src) {