@chat21/chat21-web-widget 5.1.2-rc6 → 5.1.4

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
@@ -7,6 +7,23 @@
7
7
  *Tiledesk SRL*
8
8
 
9
9
 
10
+ # 5.1.4
11
+
12
+ # 5.1.3-rc1
13
+ - **changed** Check if the uploaded file extension directly matches one of those allowed in the control panel (e.g. .xls)
14
+
15
+ # 5.1.3
16
+
17
+ # 5.1.2-rc9
18
+ - **added** .xls and .xlsx to file Upload Accept
19
+
20
+ # 5.1.2-rc8
21
+ - **bug-fixed**: css margin icon
22
+ - **added**: added text verification to delete dangerous patterns
23
+
24
+ # 5.1.2-rc7
25
+ - **bug-fixed**: ctest button color (#ffffff)
26
+
10
27
  # 5.1.2-rc6
11
28
  - **bug-fixed**: cannot upload xlsx files
12
29
 
@@ -40,6 +57,8 @@
40
57
  # 5.1.1-rc1
41
58
  - **bug-fixed**: marked pipe not renders urls
42
59
 
60
+ # 5.1.0
61
+
43
62
  # 5.1.0-rc34
44
63
  - **bug-fixed**: css fixed carousel
45
64
 
@@ -2,8 +2,18 @@
2
2
  version=`node -e 'console.log(require("./package.json").version)'`
3
3
  echo "version $version"
4
4
 
5
+ npm i
6
+
7
+ cp src/environments/real_data/environment.prod.ts src/environments/environment.prod.ts
8
+
5
9
  # --build-optimizer=false if localstorage is disabled (webview) appears https://github.com/firebase/angularfire/issues/970
6
10
  ng build --configuration="prod" --aot=true
11
+ ##--base-href='./v5/' --output-hashing none
12
+
13
+ ### SET HASHING : START ###
14
+ cp ./src/launch_template.js ./dist/browser/launch.js
15
+ node ./src/build_launch.js
16
+ ### SET HASHING : END ###
7
17
 
8
18
  #### FIREBASE #####
9
19
  # cd dist
@@ -13,15 +23,19 @@ ng build --configuration="prod" --aot=true
13
23
  # cd ..
14
24
 
15
25
  # #### MQTT #####
16
- cd dist
26
+ cd dist/browser
17
27
  # aws s3 sync . s3://tiledesk-widget/v5/latest/
18
- aws s3 sync . s3://tiledesk-widget/v6/$version/ --cache-control max-age=300
19
- aws s3 sync . s3://tiledesk-widget/v6/ --cache-control max-age=300
20
- cd ..
21
-
28
+ aws s3 sync . s3://tiledesk-widget/v6/$version/ --cache-control max-age=86400 --exclude='launch.js' #8days
29
+ aws s3 sync . s3://tiledesk-widget/v6/$version/ --cache-control "no-store,no-cache,private" --exclude='*' --include='launch.js'
30
+ aws s3 sync . s3://tiledesk-widget/v6/ --cache-control max-age=86400 --exclude='launch.js' #8days
31
+ aws s3 sync . s3://tiledesk-widget/v6/ --cache-control "no-store,no-cache,private" --exclude='*' --include='launch.js'
32
+ cd ../..
22
33
 
23
34
  aws cloudfront create-invalidation --distribution-id E3EJDWEHY08CZZ --paths "/*"
35
+
36
+ git restore src/environments/environment.pre.ts
37
+
24
38
  echo new version deployed $version on s3://tiledesk-widget/v5
25
39
  echo available on https://s3.eu-west-1.amazonaws.com/tiledesk-widget/v5/index.html
26
40
  echo https://widget.tiledesk.com/v5/index.html
27
- echo https://widget.tiledesk.com/v5/$version/index.html
41
+ echo https://widget.tiledesk.com/v5/$version/index.html
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.1.2-rc6",
4
+ "version": "5.1.4",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.tiledesk.com",
7
7
  "repository": {
@@ -1414,7 +1414,9 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
1414
1414
  this.logger.log('[CONV-COMP] ----> FILE - (dragleave) drag ev ', event)
1415
1415
  if (event.dataTransfer && event.dataTransfer.files) {
1416
1416
  const files = event.dataTransfer.files;
1417
- const canUploadFile = checkAcceptedFile(files[0].type, this.g.fileUploadAccept)
1417
+ const nameFile = files[0].name;
1418
+ const typeFile = files[0].type;
1419
+ const canUploadFile = checkAcceptedFile(nameFile, typeFile, this.g.fileUploadAccept)
1418
1420
  if(!canUploadFile){
1419
1421
  this.logger.error('[IMAGE-UPLOAD] detectFiles: can not upload current file type--> NOT ALLOWED', this.g.fileUploadAccept)
1420
1422
  return;
@@ -138,19 +138,19 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
138
138
 
139
139
  const that = this;
140
140
  if (event.target.files && event.target.files[0]) {
141
+ const nameFile = event.target.files[0].name;
142
+ const typeFile = event.target.files[0].type;
143
+ const size = event.target.files[0].size;
141
144
 
142
- const canUploadFile = checkAcceptedFile(event.target.files[0].type, this.fileUploadAccept)
145
+ const canUploadFile = checkAcceptedFile(nameFile, typeFile, this.fileUploadAccept)
143
146
  if(!canUploadFile){
144
147
  this.logger.error('[IMAGE-UPLOAD] detectFiles: can not upload current file type--> NOT ALLOWED', this.fileUploadAccept)
145
148
  this.isFilePendingToUpload = false;
146
149
  return;
147
150
  }
148
-
149
- const nameFile = event.target.files[0].name;
150
- const typeFile = event.target.files[0].type;
151
- const size = event.target.files[0].size
151
+
152
152
  const reader = new FileReader();
153
- that.logger.debug('[CONV-FOOTER] OK preload: ', nameFile, typeFile, reader);
153
+ // that.logger.debug('[CONV-FOOTER] OK preload: ', nameFile, typeFile, reader);
154
154
  reader.addEventListener('load', function () {
155
155
  that.logger.debug('[CONV-FOOTER] addEventListener load', reader.result);
156
156
  that.isFileSelected = true;
@@ -90,23 +90,48 @@ export class EyeeyeCatcherCardComponent implements OnInit {
90
90
  }
91
91
  }
92
92
 
93
+ // checkIsEmoji() {
94
+ // let title = this.g.CALLOUT_TITLE_PLACEHOLDER.trim();
95
+ // if (this.g.calloutTitle && this.g.calloutTitle !== '') {
96
+ // title = this.g.calloutTitle.trim();
97
+ // }
98
+ // this.title = title;
99
+ // const regex = emojiRegex();
100
+ // let match: any;
101
+ // // this.logger.debug('[EYEEYE-CATCHER-CARD]-->regex, emojiRegex', regex, emojiRegex)
102
+ // while (match = regex.exec(title)) {
103
+ // const emoji = match[0];
104
+ // this.logger.debug('[EYEEYE-CATCHER-CARD]--> match', match)
105
+ // if (title.indexOf(emoji) === 0) {
106
+ // this.title = title.replace(emoji, '');
107
+ // this.emoticon = emoji;
108
+ // }
109
+ // break;
110
+ // }
111
+ // }
112
+
113
+
93
114
  checkIsEmoji() {
94
115
  let title = this.g.CALLOUT_TITLE_PLACEHOLDER.trim();
95
116
  if (this.g.calloutTitle && this.g.calloutTitle !== '') {
96
117
  title = this.g.calloutTitle.trim();
97
118
  }
119
+
120
+ // Reset emoticon
121
+ this.emoticon = null;
98
122
  this.title = title;
99
- const regex = emojiRegex();
100
- let match: any;
101
- // this.logger.debug('[EYEEYE-CATCHER-CARD]-->regex, emojiRegex', regex, emojiRegex)
102
- while (match = regex.exec(title)) {
123
+
124
+ // Regex per emoji Unicode (compatibile)
125
+ const regex = /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/;
126
+ const match = title.match(regex);
127
+
128
+ if (match) {
103
129
  const emoji = match[0];
104
- this.logger.debug('[EYEEYE-CATCHER-CARD]--> match', match)
105
- if (title.indexOf(emoji) === 0) {
106
- this.title = title.replace(emoji, '');
107
- this.emoticon = emoji;
108
- }
109
- break;
130
+ this.logger.debug('[EYEEYE-CATCHER-CARD]--> emoji trovata:', emoji);
131
+
132
+ // Estrai la prima emoji trovata e rimuovila dal testo
133
+ this.title = title.replace(emoji, '').trim();
134
+ this.emoticon = emoji;
110
135
  }
111
136
  }
112
137
 
@@ -164,6 +189,3 @@ export class EyeeyeCatcherCardComponent implements OnInit {
164
189
  }
165
190
 
166
191
 
167
- export default function emojiRegex(): RegExp {
168
- return /(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u2700-\u27BF])|(?:\u24C2)|(?:[\u1F680-\u1F6FF])/g;
169
- }
@@ -2,7 +2,7 @@
2
2
  .c21-icon-avatar {
3
3
  position: relative;
4
4
  padding: 0;
5
- margin: 10px 0 0 0;
5
+ margin: 0px 0 0 0;
6
6
  height: var(--avatar-height);
7
7
  width: var(--avatar-width);
8
8
  min-height: var(--avatar-height);
@@ -9,7 +9,7 @@
9
9
  --font-family: #{var(--button-in-msg-font-family)};
10
10
  --buttonBorderColor: #{var(--button-border-color)};
11
11
  --buttonColor: #{var(--button-color)};
12
- --buttonBackgroundColor: #{var(--button-background-color)};
12
+ --buttonBackgroundColor: #ffffff; //#{var(--button-background-color)};
13
13
  }
14
14
 
15
15
  .button-in-msg {
@@ -9,7 +9,7 @@
9
9
  --font-family: #{var(--button-in-msg-font-family)};
10
10
  --buttonBorderColor: #{var(--button-border-color)};
11
11
  --buttonColor: #{var(--button-color)};
12
- --buttonBackgroundColor: #{var(--button-background-color)};
12
+ --buttonBackgroundColor: #ffffff; //#{var(--button-background-color)};
13
13
  }
14
14
 
15
15
 
@@ -9,7 +9,7 @@
9
9
  --font-family: #{var(--button-in-msg-font-family)};
10
10
  --buttonBorderColor: #{var(--button-border-color)};
11
11
  // --buttonColor: #{var(--buttonColor)};
12
- --buttonBackgroundColor: #{var(--button-background-color)};
12
+ --buttonBackgroundColor: #ffffff; //#{var(--button-background-color)};
13
13
  --buttonColor: #{var(--button-color)};
14
14
  }
15
15
 
@@ -1,5 +1,6 @@
1
1
  import { Pipe, PipeTransform } from '@angular/core';
2
2
  import { marked } from 'marked';
3
+ import { BLOCKED_DOMAINS } from '../utils/utils';
3
4
 
4
5
 
5
6
  @Pipe({
@@ -10,6 +11,55 @@ export class MarkedPipe implements PipeTransform {
10
11
  transform(value: any): any {
11
12
  const renderer = new marked.Renderer();
12
13
  renderer.link = function({ href, title, tokens }) {
14
+ // Normalizza l'href per evitare falsi negativi
15
+ const normalized = (href || '').trim().toLowerCase();
16
+ // Pattern pericolosi da cercare nell'intero URL (non solo all'inizio)
17
+ const dangerousPatterns = [
18
+ /javascript:/i, // javascript: protocol
19
+ /data:/i, // data: protocol
20
+ /vbscript:/i, // vbscript: protocol
21
+ /on\w+\s*=/i, // event handlers (onclick, onload, etc.)
22
+ /alert\s*\(/i, // alert() function
23
+ /eval\s*\(/i, // eval() function
24
+ /document\./i, // document object access
25
+ /window\./i, // window object access
26
+ /\.appendChild\s*\(/i, // DOM manipulation
27
+ /\.createElement\s*\(/i, // DOM creation
28
+ /<script/i, // script tags
29
+ /<\/script>/i, // closing script tags
30
+ /function\s*\(/i, // function definitions
31
+ /\(function/i, // IIFE patterns
32
+ /setTimeout\s*\(/i, // setTimeout
33
+ /setInterval\s*\(/i, // setInterval
34
+ /location\./i, // location object manipulation
35
+ /history\./i, // history object manipulation
36
+ /localStorage\./i, // localStorage access
37
+ /sessionStorage\./i, // sessionStorage access
38
+ /cookie/i, // cookie manipulation
39
+ /fetch\s*\(/i, // fetch API
40
+ /XMLHttpRequest/i, // XHR
41
+ /FormData/i, // FormData
42
+ /Blob\s*\(/i, // Blob constructor
43
+ /FileReader/i, // FileReader
44
+ /crypto\./i, // crypto object
45
+ /btoa\s*\(/i, // base64 encoding
46
+ /atob\s*\(/i, // base64 decoding
47
+ /decodeURI/i, // URI decoding
48
+ /encodeURI/i, // URI encoding
49
+ /String\.fromCharCode/i, // character code conversion
50
+ /unescape\s*\(/i, // unescape function
51
+ /escape\s*\(/i // escape function
52
+ ];
53
+
54
+ // Controlla se l'URL contiene pattern pericolosi
55
+ const isDangerous = dangerousPatterns.some(pattern => pattern.test(normalized));
56
+ if (isDangerous) {
57
+ // Ritorna solo il testo come stringa, niente <a>
58
+ return tokens ? tokens.map(token => token.raw).join('') : href || '';
59
+ }
60
+
61
+ // tokens = this.cleanInput(href);
62
+
13
63
  const text = tokens
14
64
  ? tokens.map(token => token.raw).join('')
15
65
  : href; // fallback se tokens non c'è
@@ -17,7 +67,7 @@ export class MarkedPipe implements PipeTransform {
17
67
 
18
68
  return `<a href="${href}" target="_blank" rel="noopener noreferrer">${text}</a>`;
19
69
  };
20
-
70
+
21
71
  marked.setOptions({
22
72
  renderer,
23
73
  gfm: true,
@@ -35,4 +85,73 @@ export class MarkedPipe implements PipeTransform {
35
85
  return value;
36
86
  }
37
87
 
88
+
89
+ private cleanInput(input: string): string {
90
+ if (!input) return '';
91
+ let cleaned = (input || '').trim().toLowerCase();
92
+
93
+ BLOCKED_DOMAINS.forEach(domain => {
94
+ const escapedDomain = domain.replace(/\./g, '\\.');
95
+ // Pattern che copre TUTTI i casi
96
+ const comprehensivePattern = new RegExp(
97
+ `(\\[([^\\]]*)\\]\\([^)]*(?:https?://)?(?:www\\.)?${escapedDomain}(?:/[^)]*)?\\))|((?:https?://)?(?:www\\.)?${escapedDomain}(?:/\\S*)?)`,
98
+ 'gi'
99
+ );
100
+
101
+ cleaned = cleaned.replace(comprehensivePattern, (match, p1, p2, p3) => {
102
+ // Se è un link markdown [text](url), mantieni il testo
103
+ if (p2) return `${p2} 🔒`;
104
+ // Se è un URL diretto, sostituisci con dominio + 🔒
105
+ if (p3) return `${domain} 🔒`;
106
+ return match;
107
+ });
108
+
109
+ });
110
+
111
+
112
+ // Pattern che sostituisce i link pericolosi con solo il testo
113
+ const dangerousLinkPatterns = [
114
+ // Sostituisce [text](javascript:...) con "text"
115
+ /\[([^\]]*)\]\(javascript:[^)]*\)/gi,
116
+ /\[([^\]]*)\]\(data:[^)]*\)/gi,
117
+ /\[([^\]]*)\]\(vbscript:[^)]*\)/gi,
118
+ /\[([^\]]*)\]\([^)]*alert\([^)]*\)/gi
119
+ ];
120
+
121
+ dangerousLinkPatterns.forEach(pattern => {
122
+ cleaned = cleaned.replace(pattern, '$1'); // $1 = il testo del link
123
+ });
124
+
125
+ // Pattern generali per sicurezza (rimuovono completamente)
126
+ const generalPatterns = [
127
+ /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
128
+ /<script[^>]*>[\s\S]*?<\/script>/gi, // Script multi-linea
129
+ /<script[^>]*>.*?<\/script>/gi, // Script single-line
130
+ /<script[^>]*>/gi, // Solo tag di apertura
131
+ /<\/script>/gi, // Solo tag di chiusura
132
+ /javascript:/gi,
133
+ /vbscript:/gi,
134
+ /data:/gi,
135
+ /on\w+\s*=/gi,
136
+ /alert\(/gi,
137
+ /eval\(/gi,
138
+ /document\./gi,
139
+ /window\./gi,
140
+ /\(function\s*\(\)\s*\{/gi,
141
+ /\.appendChild\(/gi,
142
+ /\.createElement\(/gi,
143
+ /\.getElementsByTagName\(/gi,
144
+
145
+ // ✅ PATTERN PER FUNZIONI IIFE (Immediately Invoked Function Expression):
146
+ /\(function\s*\(\s*\)\s*\{[\s\S]*?\}\)\(\s*\)\s*;/gi,
147
+ /\(function\s*\(\)\s*\{/gi
148
+ ];
149
+
150
+ generalPatterns.forEach(pattern => {
151
+ cleaned = cleaned.replace(pattern, '');
152
+ });
153
+
154
+ return cleaned;
155
+ }
156
+
38
157
  }
@@ -406,7 +406,7 @@ export class Globals {
406
406
  /**enable user to set a telegram number to chat with */
407
407
  this.telegramUsername = ''
408
408
  /**enable auto disconnect from messaging after a defined amount of time (s)*/
409
- this.fileUploadAccept = "image/*,.pdf,.txt,.mp3"
409
+ this.fileUploadAccept = '';//image/*,.pdf,.txt,.mp3,.xls,.xlsx"
410
410
  this.disconnetTime = 0
411
411
 
412
412
  this.showWaitTime = true;
@@ -379,17 +379,23 @@ export function getUnique(arr, comp) {
379
379
  .filter(e => arr[e]).map(e => arr[e]);
380
380
  }
381
381
 
382
- export function checkAcceptedFile(fileType, fileUploadAccept ): boolean{
382
+ export function checkAcceptedFile(nameFile, fileType, fileUploadAccept): boolean {
383
+ console.log('checkAcceptedFile ------------>', fileType, fileUploadAccept);
383
384
 
385
+ const fileExtension = getFileExtension(nameFile);
386
+ console.log('[CONV-FOOTER] fileExtension: ', fileExtension);
387
+
384
388
  if (fileUploadAccept === '*/*') {
385
- return true
389
+ return true;
386
390
  }
391
+
387
392
  // Dividi la stringa fileUploadAccept in un array di tipi accettati
388
393
  const acceptedTypes = fileUploadAccept.split(',');
389
394
 
390
395
  // Verifica se il tipo di file è accettato
391
396
  return acceptedTypes.some((accept) => {
392
397
  accept = accept.trim();
398
+
393
399
  // Controlla per i tipi MIME con wildcard, come image/*
394
400
  if (accept.endsWith('/*')) {
395
401
  const baseMimeType = fileType.split('/')[0]; // Ottieni la parte principale del MIME type
@@ -402,9 +408,39 @@ export function checkAcceptedFile(fileType, fileUploadAccept ): boolean{
402
408
  }
403
409
 
404
410
  // Controlla per le estensioni di file specifiche come .pdf o .txt
405
- return fileType === getMimeTypeFromExtension(accept);
411
+ const expectedMimeType = getMimeTypeFromExtension(accept);
412
+ if (expectedMimeType && fileType === expectedMimeType) {
413
+ return true;
414
+ }
415
+
416
+ // // Controlla se l'estensione del file corrisponde direttamente
417
+ // if (accept.startsWith('.') && fileExtension === accept.substring(1)) {
418
+ // return true;
419
+ // }
420
+
421
+ // Controlla se l'estensione del file corrisponde direttamente
422
+ // Dividi la stringa fileUploadAccept in un array di tipi accettati
423
+ const acceptedTypes = fileUploadAccept.split(',');
424
+ //verifica se l'estensione del file è accettata
425
+ if (acceptedTypes.includes(fileExtension)) {
426
+ return true;
427
+ }
428
+
429
+ return false;
406
430
  });
431
+ }
432
+
407
433
 
434
+ /**
435
+ * Estrae l'estensione del file dal nome
436
+ * @param filename Nome del file
437
+ * @returns Estensione in lowercase (es. 'xlsx', 'pdf', 'jpg')
438
+ */
439
+ export function getFileExtension(filename: string): string {
440
+ if (!filename || filename.indexOf('.') === -1) {
441
+ return '';
442
+ }
443
+ return filename.split('.').pop()?.toLowerCase() || '';
408
444
  }
409
445
 
410
446
  function getMimeTypeFromExtension(extension: string): string {
@@ -420,8 +456,37 @@ function getMimeTypeFromExtension(extension: string): string {
420
456
  '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
421
457
  '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
422
458
  '.xls': 'application/vnd.ms-excel',
423
- '.wav' : 'audio/wav'
459
+ '.wav': 'audio/wav'
424
460
  // Aggiungi altri tipi MIME se necessario
425
461
  };
426
462
  return mimeTypes[extension] || '';
427
463
  }
464
+
465
+ export const BLOCKED_DOMAINS = [
466
+ // DOMINI MALEVOLI NOTI
467
+ 'attacker.me', 'evil.com', 'malicious.site', 'hacker.com', 'phishing.com',
468
+ 'malware.com', 'ransomware.com', 'trojan.com', 'virus.com', 'spyware.com',
469
+
470
+ // DOMINI DI PHISHING
471
+ 'phish.com', 'stealer.com', 'credential-thief.com', 'login-stealer.com',
472
+ 'password-stealer.com', 'banking-phish.com', 'paypal-phish.com',
473
+
474
+ // DOMINI DI SPAM
475
+ 'spam.com', 'spammer.com', 'bulk-email.com', 'unsolicited.com',
476
+
477
+ // DOMINI TRUFFA
478
+ 'scam.com', 'fraud.com', 'fake.com', 'counterfeit.com', 'hoax.com',
479
+
480
+ // DOMINI EXPLOIT
481
+ 'exploit.com', 'vulnerability.com', 'zero-day.com', 'payload.com',
482
+ 'shellcode.com', 'backdoor.com', 'rootkit.com',
483
+
484
+ // DOMINI BOTNET
485
+ 'botnet.com', 'zombie-pc.com', 'command-control.com', 'c2-server.com',
486
+
487
+ // DOMINI ADWARE/MALVERTISING
488
+ 'adware.com', 'malvertising.com', 'popup-ads.com', 'unwanted-ads.com',
489
+
490
+ // DOMINI GENERICI PERICOLOSI
491
+ 'danger.com', 'unsafe.com', 'insecure.com', 'threat.com', 'risk.com',
492
+ ]
@@ -387,9 +387,9 @@ export class FirebaseConversationHandler extends ConversationHandlerService {
387
387
  complement = INFO_SUPPORT_USER_ADDED_COMPLEMENT;
388
388
  } else {
389
389
 
390
- if (message.attributes.messagelabel.parameters.fullname) {
390
+ if (message.attributes.messagelabel.parameters.firstname) {
391
391
  // other user has been added to the group (and he has a fullname)
392
- subject = message.attributes.messagelabel.parameters.fullname;
392
+ subject = message.attributes.messagelabel.parameters.firstname;
393
393
  verb = INFO_SUPPORT_USER_ADDED_VERB;
394
394
  complement = INFO_SUPPORT_USER_ADDED_COMPLEMENT;
395
395
  } else {
@@ -382,14 +382,14 @@ export class MQTTConversationHandler extends ConversationHandlerService {
382
382
  verb = INFO_SUPPORT_USER_ADDED_YOU_VERB;
383
383
  complement = INFO_SUPPORT_USER_ADDED_COMPLEMENT;
384
384
  } else {
385
- if (message.attributes.messagelabel.parameters.fullname) {
385
+ if (message.attributes.messagelabel.parameters.firstname) {
386
386
  // other user has been added to the group (and he has a fullname)
387
- subject = message.attributes.messagelabel.parameters.fullname;
387
+ subject = message.attributes.messagelabel.parameters.firstname;
388
388
  verb = INFO_SUPPORT_USER_ADDED_VERB;
389
389
  complement = INFO_SUPPORT_USER_ADDED_COMPLEMENT;
390
390
  } else {
391
391
  // other user has been added to the group (and he has not a fullname, so use hes useruid)
392
- subject = message.attributes.messagelabel.parameters.member_id;
392
+ subject = message.attributes.messagelabel.parameters.firstname;
393
393
  verb = INFO_SUPPORT_USER_ADDED_VERB;
394
394
  complement = INFO_SUPPORT_USER_ADDED_COMPLEMENT;
395
395
  }
@@ -620,6 +620,10 @@ function componentFromStr(numStr, percent) {
620
620
  export function isAllowedUrlInText(text: string, allowedUrls: string[]) {
621
621
  const urlsInMessage = extractUrls(text);
622
622
 
623
+ if (urlsInMessage.length === 0) {
624
+ return true; // Nessun URL => testo ammesso
625
+ }
626
+
623
627
  const allowedPatterns = allowedUrls.map((url) => {
624
628
  try {
625
629
  // Prova a estrarre il dominio da una URL completa