@chat21/chat21-web-widget 5.1.23 → 5.1.24-rc2

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.
@@ -1,10 +1,10 @@
1
1
  name: Docker Image Community latest CI
2
2
 
3
- on:
4
- push:
3
+ on:
4
+ push:
5
5
  branches: [ master ]
6
- pull_request:
7
- branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
8
 
9
9
  jobs:
10
10
  push_to_registry:
@@ -12,12 +12,22 @@ jobs:
12
12
  runs-on: ubuntu-latest
13
13
 
14
14
  steps:
15
- - uses: actions/checkout@v2
16
- name: Check out the repo
17
- - uses: docker/build-push-action@v1
18
- with:
19
- username: ${{ secrets.DOCKERHUB_USERNAME }}
20
- password: ${{ secrets.DOCKERHUB_TOKEN }}
21
- repository: chat21/chat21-web-widget
22
- push: true
23
- tags: latest
15
+ - name: Check out the repo
16
+ uses: actions/checkout@v2
17
+
18
+ - name: Set up Docker Buildx
19
+ uses: docker/setup-buildx-action@v2
20
+
21
+ - name: Log in to Docker Hub
22
+ uses: docker/login-action@v2
23
+ with:
24
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
25
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
26
+
27
+ - name: Build and push multiarch Docker image
28
+ uses: docker/build-push-action@v3
29
+ with:
30
+ context: .
31
+ push: true
32
+ platforms: linux/amd64,linux/arm64
33
+ tags: chat21/chat21-web-widget:latest
@@ -3,20 +3,30 @@ name: Publish Docker Community image tags
3
3
  on:
4
4
  push:
5
5
  tags:
6
- - '**' # Push events to every tag including hierarchical tags like
7
- jobs:
6
+ - '**' # Trigger su qualsiasi tag
8
7
 
8
+ jobs:
9
9
  push_to_registry:
10
10
  name: Push Docker image to Docker Hub
11
11
  runs-on: ubuntu-latest
12
+
12
13
  steps:
13
- - name: Check out the repo
14
- uses: actions/checkout@v2
15
- - name: Push to Docker Hub
16
- uses: docker/build-push-action@v1
17
- with:
18
- username: ${{ secrets.DOCKERHUB_USERNAME }}
19
- password: ${{ secrets.DOCKERHUB_TOKEN }}
20
- repository: chat21/chat21-web-widget
21
- push: true
22
- tag_with_ref: true
14
+ - name: Check out the repo
15
+ uses: actions/checkout@v2
16
+
17
+ - name: Set up Docker Buildx
18
+ uses: docker/setup-buildx-action@v2
19
+
20
+ - name: Log in to Docker Hub
21
+ uses: docker/login-action@v2
22
+ with:
23
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
24
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
25
+
26
+ - name: Build and push multiarch Docker image
27
+ uses: docker/build-push-action@v3
28
+ with:
29
+ context: .
30
+ push: true
31
+ platforms: linux/amd64,linux/arm64
32
+ tags: chat21/chat21-web-widget:${{ github.ref_name }}
package/CHANGELOG.md CHANGED
@@ -6,19 +6,26 @@
6
6
  ### **Copyrigth**:
7
7
  *Tiledesk SRL*
8
8
 
9
- # 5.1.23
10
- - **changed**: API for upload a file/iamges
9
+ # 5.1.24-rc2
10
+ - **bug fixed**: minor fix in marked pipe to avoid rendering html tags
11
11
 
12
- # 5.1.22
13
- - **changed**: Updated Launch.js for Wix and Shopify by bypassing scrdoc
12
+ # 5.1.24-rc1
13
+ - **security**: hardened Markdown link rendering by blocking dangerous protocols (e.g. `javascript:`, `data:`, `vbscript:`) and preventing unsafe links from being rendered as anchors
14
+ - **changed**: refactored `MarkedPipe` to simplify Markdown parsing, improve link rendering via a custom `marked` renderer, and streamline newline handling (`\\n` → `\n`)
14
15
 
15
- # 5.1.21
16
+ # 5.1.20-rc3
16
17
  - **bug fixed**: saved the widget's size state to local storage (in HP conversations)
17
18
 
18
- # 5.1.20
19
+ # 5.1.20-rc2
20
+ - **changed**: API for upload a file/iamges
19
21
  - **changed**: marked pipe do not render /n
20
22
 
23
+ # 5.1.20-rc1
24
+ - **changed**: API for upload a file/iamges
25
+
21
26
  # 5.1.19
27
+
28
+ # 5.1.19-rc1
22
29
  - **bug fixed**: show bottom scroll button and unread message badge only when I'm not at the bottom of the page
23
30
  - **changed**: allow HTML code to be inserted into messages, but do not parse the code. Ensure coexistence with Markdown.
24
31
  - **bug fixed**: after sending a multi-line message, the text area remains open on multiple lines.
@@ -28,6 +35,7 @@
28
35
  - **changed**: saved the widget's size state to local storage. The parameter flow is (default → storage → settings → URL)
29
36
 
30
37
  # 5.1.18
38
+ # 5.1.15-rc3
31
39
  - **added**: Implemented Shadow DOM in the text component to isolate HTML and Markdown rendering in a safe and protected context
32
40
  - **changed**: Adapted text component styles to support Shadow DOM (removed ::ng-deep, added styles for common markdown elements)
33
41
  - **security**: HTML/Markdown content is now rendered in an isolated Shadow DOM, improving security and preventing interference with the rest of the application
@@ -38,6 +46,12 @@
38
46
  # 5.1.16
39
47
  - **changed**: "close chat" header conversation menu button enabled in chatbot-panel.html
40
48
 
49
+ # 5.1.15-rc2
50
+ - **bug-fixed**: Bug fix for ifame message width
51
+
52
+ # 5.1.15-rc1
53
+ - **changed**: Load local translations before remote ones
54
+
41
55
  # 5.1.15
42
56
  - **changed**: Load local translations before remote ones
43
57
 
@@ -46,14 +60,56 @@
46
60
 
47
61
  # 5.1.13
48
62
  - **bug-fixed**: set default widget size
63
+
64
+ # 5.1.7-rc16
65
+ - **bug-fixed**: set default widget size
66
+
67
+ # 5.1.7-rc15
68
+ - **bug-fixed**: set the color of the buttons with visibility control to the font color
69
+
70
+ # 5.1.7-rc14
71
+ - **bug-fixed**: departmentId and departmentName is incorrect in attributes
72
+
73
+ # 5.1.7-rc13
74
+ - **changed**: Force authentication if ageChangeVisibilityDesktop or PageChangeVisibilityMobile is OPEN
75
+
76
+ # 5.1.7-rc12
77
+ - **changed**: Set the default autoStart value to false
78
+ - **added**: Added the open widget loading spinner
79
+ - **changed**: Load the widget without authentication and display the speech bubble
80
+
81
+ # 5.1.7-rc11
82
+ - **changed**: set default value autoStart false
83
+ - **added**:added loading spinner
84
+
85
+ # 5.1.7-rc10
86
+ - **changed**: load widget without authentication and display the balloon
87
+
88
+ # 5.1.7-rc9
89
+ - **removed**: 'DOMAIN_NOT_ALLOWED' in textarea footer component
90
+
91
+ # 5.1.7-rc8
92
+ # changes in the branch
93
+ - **changed**: Load local translations before remote ones
94
+ - **bug-fixed**: set default widget size
49
95
  - **changed**: Updated the translations of the tooltips in the footer-component
50
96
  - **changed**: Refactored the network-offline component and made it generic for displaying errors (now error-alert.component)
51
- - **bug-fixed**: set the color of the buttons with visibility control to the font color (setButtonColors function)
97
+
98
+ # 5.1.7-rc7
99
+ - **bug-fixed**: button new_conversation always appear. added subscription to conversationAdded
100
+
101
+ # 5.1.7-rc6
102
+ - **added**: Added MAX_ATTACHMENT_ERROR error message when uploading a file larger than 10 MB
52
103
 
53
104
  # 5.1.12
54
105
  - **bug-fixed**: check showEmojiFooterButton to enable/disable emojii
55
106
  - **bug-fixed**: markdown is fired as an emojii and blocked by isEmojii check fn
56
107
 
108
+ # 5.1.7-rc7
109
+ - **bug-fixed**: button new_conversation always appear. added subscription to conversationAdded
110
+
111
+ # 5.1.7-rc6
112
+ - **added**: Added MAX_ATTACHMENT_ERROR error message when uploading a file larger than 10 MB
57
113
 
58
114
  # 5.1.7-rc5
59
115
  - **bug-fixed**: bug fixed BUTTON STYLES
package/Dockerfile CHANGED
@@ -1,7 +1,7 @@
1
1
  ### STAGE 1: Build ###
2
2
 
3
3
  # We label our stage as ‘builder’
4
- FROM node:20.12.2-alpine3.19 as builder
4
+ FROM --platform=$BUILDPLATFORM node:20.12.2-alpine3.19 as builder
5
5
 
6
6
  COPY package.json package-lock.json ./
7
7
 
@@ -15,12 +15,11 @@ COPY . .
15
15
 
16
16
  ## Build the angular app in production mode and store the artifacts in dist folder
17
17
 
18
- RUN npx ng build --configuration="prod" --output-path=dist --base-href=./ --output-hashing=none
19
18
 
19
+ RUN npx ng build --configuration="prod" --output-path=dist --base-href=./ --output-hashing=none
20
20
 
21
21
  ### STAGE 2: Setup ###
22
-
23
- FROM nginx:1.14.1-alpine
22
+ FROM --platform=$BUILDPLATFORM nginx:1.14.1-alpine
24
23
 
25
24
  ## Copy our default nginx config
26
25
  COPY nginx.conf /etc/nginx/nginx.conf
@@ -33,4 +32,4 @@ COPY --from=builder /ng-app/dist/browser /usr/share/nginx/html
33
32
 
34
33
  RUN echo "Chat21 Web Widget Started!!"
35
34
 
36
- CMD ["/bin/sh", "-c", "envsubst < /usr/share/nginx/html/widget-config-template.json > /usr/share/nginx/html/widget-config.json && exec nginx -g 'daemon off;'"]
35
+ CMD ["/bin/sh", "-c", "envsubst < /usr/share/nginx/html/widget-config-template.json > /usr/share/nginx/html/widget-config.json && exec nginx -g 'daemon off;'"]
package/angular.json CHANGED
@@ -44,7 +44,8 @@
44
44
  "src/environments/real_data/widget-config-docker.json",
45
45
  "src/environments/real_data/widget-config-native-mqtt.json",
46
46
  "src/environments/real_data/widget-config-native-prod.json",
47
- "src/environments/real_data/widget-config-aws-stage.json"
47
+ "src/environments/real_data/widget-config-aws-stage.json",
48
+ "src/environments/real_data/widget-config-aws-aruba.json"
48
49
  ],
49
50
  "styles": [
50
51
  "src/app/sass/styles.scss"
@@ -2,8 +2,16 @@
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.pre.ts src/environments/environment.pre.ts
8
+
5
9
  ng build --configuration="pre" --aot=true --base-href
6
10
 
11
+ ### SET HASHING : START ###
12
+ cp ./src/launch_template.js ./dist/browser/launch.js
13
+ node ./src/build_launch.js
14
+ ### SET HASHING : END ###
7
15
 
8
16
  # ########## --->>>> NATIVE-MQTT folder START <<<<<------ ########## #
9
17
 
@@ -23,15 +31,17 @@ ng build --configuration="pre" --aot=true --base-href
23
31
 
24
32
 
25
33
  # ########## --->>>> FIREBASE folder START <<<<<------ ########## #
26
- cd dist
27
- aws s3 sync . s3://tiledesk-widget-pre/v5/$version/ --cache-control max-age=300
28
- aws s3 sync . s3://tiledesk-widget-pre/v5/ --cache-control max-age=300
29
- cd ..
30
-
31
- #aws cloudfront create-invalidation --distribution-id E3EJDWEHY08CZZ --paths "/*"
32
- cd ..
34
+ cd dist/browser
35
+ aws s3 sync . s3://tiledesk-widget-pre/v5/$version/ --cache-control max-age=300 --exclude='launch.js' #7days
36
+ aws s3 sync . s3://tiledesk-widget-pre/v5/$version/ --cache-control "no-store,no-cache,private" --exclude='*' --include='launch.js'
37
+ aws s3 sync . s3://tiledesk-widget-pre/v5/ --cache-control max-age=300 --exclude='launch.js' #7days
38
+ aws s3 sync . s3://tiledesk-widget-pre/v5/ --cache-control "no-store,no-cache,private" --exclude='*' --include='launch.js'
39
+ cd ../..
33
40
 
34
41
  aws cloudfront create-invalidation --distribution-id E2V5O0YPR61V8P --paths "/*"
42
+
43
+ git restore src/environments/environment.pre.ts
44
+
35
45
  # echo new version deployed $NEW_VER/$NEW_BUILD/ on s3://tiledesk-widget-pre/v2
36
46
  echo new version deployed $version/ on s3://tiledesk-widget-pre/v5 and s3://tiledesk-widget-pre/v5/$version/
37
47
  echo available on https://s3.eu-west-1.amazonaws.com/tiledesk-widget-pre/v5/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.23",
4
+ "version": "5.1.24-rc2",
5
5
  "license": "MIT",
6
6
  "homepage": "https://www.tiledesk.com",
7
7
  "repository": {
@@ -145,5 +145,12 @@
145
145
  <chat-launcher-button *ngIf="isInitialized"
146
146
  (onButtonClicked)="onOpenCloseWidget($event)">
147
147
  </chat-launcher-button>
148
-
148
+
149
+ <!-- Loading Spinner -->
150
+ <div *ngIf="loading" class="tiledesk-loading-overlay">
151
+ <div class="tiledesk-loading-spinner">
152
+ <div class="spinner-circle"></div>
153
+ <div class="loading-text">{{ translationMap.get('LABEL_LOADING') }}</div>
154
+ </div>
155
+ </div>
149
156
  </div>
@@ -761,4 +761,63 @@ chat-root {
761
761
  }
762
762
  }
763
763
 
764
+ // ========= BEGIN: LOADING SPINNER ========= //
765
+ .tiledesk-loading-overlay {
766
+ position: absolute;
767
+ top: 0;
768
+ left: 0;
769
+ width: 100%;
770
+ height: 100%;
771
+ background-color: rgba(248, 249, 250, 0.95);
772
+ display: flex;
773
+ justify-content: center;
774
+ align-items: center;
775
+ z-index: 9999;
776
+ backdrop-filter: blur(2px);
777
+ }
778
+
779
+ .tiledesk-loading-spinner {
780
+ display: flex;
781
+ flex-direction: column;
782
+ align-items: center;
783
+ gap: 16px;
784
+ }
785
+
786
+ .spinner-circle {
787
+ width: 50px;
788
+ height: 50px;
789
+ border: 4px solid rgba(59, 130, 246, 0.2);
790
+ border-top-color: #3b82f6;
791
+ border-radius: 50%;
792
+ animation: spin 0.8s linear infinite;
793
+ }
794
+
795
+ @keyframes spin {
796
+ 0% {
797
+ transform: rotate(0deg);
798
+ }
799
+ 100% {
800
+ transform: rotate(360deg);
801
+ }
802
+ }
803
+
804
+ .loading-text {
805
+ font-family: var(--font-family-bubble-message, 'Roboto', 'Google Sans', Helvetica, Arial, sans-serif);
806
+ font-size: 14px;
807
+ font-weight: 500;
808
+ color: #3b82f6;
809
+ letter-spacing: 0.5px;
810
+ animation: pulse 1.5s ease-in-out infinite;
811
+ }
812
+
813
+ @keyframes pulse {
814
+ 0%, 100% {
815
+ opacity: 1;
816
+ }
817
+ 50% {
818
+ opacity: 0.5;
819
+ }
820
+ }
821
+ // ========= END: LOADING SPINNER ========= //
822
+
764
823
  }
@@ -106,6 +106,10 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
106
106
 
107
107
  forceDisconnect: boolean = false;
108
108
 
109
+ //network status
110
+ isOnline: boolean = true;
111
+ loading: boolean = false;
112
+
109
113
  // alert error message
110
114
  isShowErrorMessage: boolean = false;
111
115
  errorMessage: string = '';
@@ -149,7 +153,7 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
149
153
  this.logger.info('[APP-CONF]---------------- ngAfterViewInit: APP.COMPONENT ---------------- ')
150
154
 
151
155
  // Initialize translation map and enable buttons
152
- const keys = ['MAXIMIZE', 'MINIMIZE', 'CENTER', 'BUTTON_CLOSE_TO_ICON'];
156
+ const keys = ['MAXIMIZE', 'MINIMIZE', 'CENTER', 'BUTTON_CLOSE_TO_ICON', 'LABEL_LOADING'];
153
157
  this.translationMap = this.translateService.translateLanguage(keys);
154
158
  this.isButtonsDisabled = false;
155
159
 
@@ -316,8 +320,15 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
316
320
  this.g.setIsOpen(isOpen)
317
321
  this.appStorageService.setItem('isOpen', isOpen)
318
322
  }
319
-
320
-
323
+
324
+ if(this.g.onPageChangeVisibilityDesktop === 'last'){
325
+ this.logger.debug('[APP-COMP2] ------this.g.isOpen: ', this.g.isOpen)
326
+ if(this.g.isOpen){
327
+ this.g.autoStart = true;
328
+ }
329
+ }
330
+
331
+
321
332
  /**CHECK IF JWT IS IN URL PARAMETERS */
322
333
  this.logger.debug('[APP-COMP] check if token is passed throw url: ', this.g.jwt);
323
334
  if (this.g.jwt) {
@@ -354,10 +365,6 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
354
365
  this.subscriptions.push(obsSettingsService);
355
366
  this.globalSettingsService.initWidgetParamiters(this.g, this.el);
356
367
 
357
- // SET AUDIO
358
- this.audio = new Audio();
359
- this.audio.src = this.g.baseLocation + URL_SOUND_LIST_CONVERSATION;
360
- this.audio.load();
361
368
  }
362
369
 
363
370
  private initAll() {
@@ -448,7 +455,7 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
448
455
  that.triggerOnAuthStateChanged(that.stateLoggedUser);
449
456
  that.logger.debug('[APP-COMP] 1 - IMPOSTO STATO CONNESSO UTENTE ', autoStart);
450
457
 
451
-
458
+ this.initAudioNotification()
452
459
 
453
460
  new Promise(async (resolve, reject)=> {
454
461
  that.typingService.initialize(this.g.tenant);
@@ -474,26 +481,30 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
474
481
  that.listenToWidgetClick()
475
482
  }
476
483
 
484
+ /** DDP IF AUTOSTART IS FALSE, SHOW WIDGET */
485
+ if(!autoStart){
486
+ that.logger.info('[APP-COMP] AUTOSTART IS FALSE AND LOGGED SUCCESSFULLY ');
487
+ this.g.setParameter('isShown', true, true);
488
+ }
477
489
 
478
490
 
479
491
  } else if (state && state === AUTH_STATE_OFFLINE && !this.forceDisconnect) {
480
492
  /** non sono loggato */
481
493
  that.logger.info('[APP-COMP] OFFLINE - NO CURRENT USER AUTENTICATE: ');
482
494
  that.g.setParameter('isLogged', false);
483
- that.hideWidget();
495
+ // that.hideWidget();
484
496
  // that.g.setParameter('isShown', false, true);
485
497
  that.triggerOnAuthStateChanged(that.stateLoggedUser);
486
- if (autoStart) {
498
+ if (autoStart || this.g.onPageChangeVisibilityDesktop === 'open' || this.g.onPageChangeVisibilityMobile === 'open') {
487
499
  that.authenticate();
488
500
  }
489
- }else if(state && state === AUTH_STATE_CLOSE ){
501
+ } else if(state && state === AUTH_STATE_CLOSE ){
490
502
  that.logger.info('[APP-COMP] CLOSE - CHANNEL CLOSED: ', this.chatManager);
491
503
  if(this.g.recipientId){
492
504
  this.chatManager.removeConversationHandler(this.g.recipientId)
493
505
  this.g.recipientId = null;
494
506
  }
495
- }
496
-
507
+ }
497
508
 
498
509
  });
499
510
  this.subscriptions.push(subAuthStateChanged);
@@ -738,6 +749,8 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
738
749
  // divWidgetContainer.style.display = 'block';
739
750
  // }
740
751
  // }, 500);
752
+
753
+ this.loading = false;
741
754
  }
742
755
  // ========= end:: START UI ============//
743
756
 
@@ -846,7 +859,13 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
846
859
  this.appStorageService.setItem('attributes', JSON.stringify(attributes));
847
860
  return attributes;
848
861
  }
849
-
862
+
863
+ // SET AUDIO
864
+ private initAudioNotification(){
865
+ this.audio = new Audio();
866
+ this.audio.src = this.g.baseLocation + URL_SOUND_LIST_CONVERSATION;
867
+ this.audio.load();
868
+ }
850
869
 
851
870
  private async initConversationsHandler(tenant: string, senderId: string) {
852
871
  this.logger.debug('[APP-COMP] initialize: ListConversationsComponent');
@@ -1605,23 +1624,46 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
1605
1624
  this.f21_close();
1606
1625
  }
1607
1626
 
1627
+
1628
+ /** DDP reload widget */
1629
+ async reloadWidget() {
1630
+ this.openCloseWidget();
1631
+ this.logger.debug('[APP-COMP-1] AAA - hideWidget');
1632
+ await Promise.all([
1633
+ this.authenticate(),
1634
+ // this.initAll()
1635
+ ]);
1636
+ this.logger.debug('[APP-COMP-1] CCC - showWidget');
1637
+ }
1638
+
1639
+
1608
1640
  /**
1609
1641
  * LAUNCHER BUTTON:
1610
1642
  * onClick button open/close widget
1611
1643
  */
1612
1644
  onOpenCloseWidget($event) {
1645
+ this.logger.debug('[APP-COMP] onOpenCloseWidget', $event, this.g.isLogged);
1646
+ if(!this.g.isLogged){
1647
+ this.reloadWidget();
1648
+ } else {
1649
+ this.openCloseWidget();
1650
+ }
1651
+ }
1652
+
1653
+ /** DDP show widget */
1654
+ openCloseWidget() {
1613
1655
  this.g.setParameter('displayEyeCatcherCard', 'none');
1614
1656
  // const conversationActive: ConversationModel = JSON.parse(this.appStorageService.getItem('activeConversation'));
1615
1657
  const recipientId : string = this.appStorageService.getItem('recipientId')
1616
1658
  this.g.setParameter('recipientId', recipientId);
1617
1659
  this.logger.debug('[APP-COMP] openCloseWidget', recipientId, this.g.isOpen, this.g.startFromHome);
1660
+
1618
1661
  if (this.g.isOpen === false) {
1619
1662
  if(this.forceDisconnect){
1620
1663
  this.logger.log('[FORCE] onOpenCloseWidget --> reconnect', this.forceDisconnect)
1621
1664
  this.messagingAuthService.createCustomToken(this.g.tiledeskToken)
1622
1665
  this.forceDisconnect = false;
1623
1666
  }
1624
-
1625
1667
  if (!recipientId) {
1626
1668
  if(this.g.singleConversation){
1627
1669
  this.isOpenHome = false;
@@ -1641,29 +1683,22 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
1641
1683
  this.isOpenHome = false;
1642
1684
  this.isOpenConversation = true;
1643
1685
  this.startUI()
1644
- // this.isOpenSelectionDepartment = false;
1645
1686
  }
1646
- // if (!conversationActive && !this.g.startFromHome) {
1647
- // this.isOpenHome = false;
1648
- // this.isOpenConversation = true;
1649
- // this.startNwConversation();
1650
- // } else if (conversationActive) {
1651
- // this.isOpenHome = false;
1652
- // this.isOpenConversation = true;
1653
- // }
1654
- // this.g.startFromHome = true;
1655
1687
  this.triggerOnOpenEvent();
1656
-
1657
1688
  } else {
1658
1689
  this.triggerOnCloseEvent();
1659
1690
  }
1660
1691
  //change status to the widget
1661
1692
  this.g.setIsOpen(!this.g.isOpen);
1662
1693
  this.appStorageService.setItem('isOpen', this.g.isOpen);
1663
-
1694
+ //show loading if widget is open and user is not logged
1695
+ if(this.g.isOpen === true && !this.g.isLogged){
1696
+ this.loading = true;
1697
+ }
1664
1698
  // this.saveBadgeNewConverstionNumber();
1665
1699
  }
1666
1700
 
1701
+
1667
1702
  /**
1668
1703
  * MODAL SELECTION DEPARTMENT:
1669
1704
  * selected department
@@ -470,7 +470,7 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
470
470
  return this.isConversationArchived;
471
471
  }
472
472
 
473
- //FALLBACK TO TILEDESK
473
+ // //FALLBACK TO TILEDESK
474
474
  const requests_list = await this.tiledeskRequestService.getMyRequests().catch(err => {
475
475
  this.logger.error('[CONV-COMP] getConversationDetail: error getting request from Tiledesk', err);
476
476
  this.isConversationArchived=true
@@ -488,9 +488,9 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
488
488
  return this.isConversationArchived
489
489
  }
490
490
 
491
- this.isConversationArchived = true;
492
- return null;
493
- }
491
+ this.isConversationArchived = false;
492
+ return null;
493
+ }
494
494
 
495
495
  /**
496
496
  * this.g.recipientId:
@@ -840,6 +840,20 @@ export class ConversationComponent implements OnInit, AfterViewInit, OnChanges {
840
840
  this.subscriptions.push(subscribe);
841
841
  }
842
842
 
843
+ subscribtionKey = 'conversationsAdded';
844
+ subscribtion = this.subscriptions.find(item => item.key === subscribtionKey);
845
+ if(!subscribtion){
846
+
847
+ subscribtion = this.chatManager.conversationsHandlerService.conversationChanged.pipe(takeUntil(this.unsubscribe$)).subscribe((conversation) => {
848
+ this.logger.debug('[CONV-COMP] ***** DATAIL conversationsChanged *****', conversation, this.conversationWith, this.isConversationArchived);
849
+ if(conversation && conversation.recipient === this.conversationId){
850
+ this.isConversationArchived = false
851
+ }
852
+ });
853
+ const subscribe = {key: subscribtionKey, value: subscribtion };
854
+ this.subscriptions.push(subscribe);
855
+ }
856
+
843
857
  subscribtionKey = 'messageWait';
844
858
  subscribtion = this.subscriptions.find(item => item.key === subscribtionKey);
845
859
  if (!subscribtion) {
@@ -419,3 +419,28 @@ textarea:active{
419
419
  border: none;
420
420
  // margin: -2px -2px 0px;
421
421
  }
422
+
423
+
424
+ // aggiungi un'animazione di fade in e fade out quando .star-rating-widget è visibile con transition
425
+ .star-rating-widget {
426
+ transition: all 0.5s ease-in-out;
427
+ }
428
+
429
+ .star-rating-widget {
430
+ position: absolute;
431
+ left: 0;
432
+ right: 0;
433
+ bottom: -52px;
434
+ height: 100%;
435
+ width: 100%;
436
+ flex-direction: row;
437
+ justify-content: center;
438
+ background-color: rgb(255, 255, 255);
439
+ flex-wrap: nowrap;
440
+ &.active {
441
+ bottom: 0px;
442
+ }
443
+ &.inactive {
444
+ bottom: -52px;
445
+ }
446
+ }
@@ -87,6 +87,7 @@ export class ConversationFooterComponent implements OnInit, OnChanges {
87
87
 
88
88
  file_size_limit = FILE_SIZE_LIMIT;
89
89
  attachmentTooltip: string = '';
90
+ isErrorNetwork: boolean = false;
90
91
 
91
92
 
92
93
  convertColorToRGBA = convertColorToRGBA;
@@ -1,7 +1,7 @@
1
1
  <!-- tabindex="000"-->
2
2
 
3
3
  <button aflauncherbutton #aflauncherbutton id="c21-launcher-button" class="c21-button-clean scale-in-center"
4
- *ngIf="g.isLogged == true && g.isOpen == false"
4
+ *ngIf="g.isOpen == false"
5
5
  [ngClass]="{'c21-align-left' : g.align === 'left', 'c21-align-right' : g.align !== 'left'}"
6
6
  [ngStyle]="{ 'background-color': g.baloonImage? null: g.themeColor, 'bottom': g.marginY+'px!important', 'left':(g.align==='left')?g.marginX+'px!important':'', 'right':(g.align==='right')?g.marginX+'px!important':'', 'width': g.launcherWidth, 'height': g.launcherHeight, 'border-radius': g.baloonShape}"
7
7
  (click)="openCloseWidget()"
@@ -67,8 +67,9 @@ export class LauncherButtonComponent implements OnInit, AfterViewInit {
67
67
  // this.g.isOpen = !this.g.isOpen;
68
68
  // this.g.setIsOpen(!this.g.isOpen);
69
69
  // this.appStorageService.setItem('isOpen', this.g.isOpen);
70
- this.onButtonClicked.emit( this.g.isOpen );
71
- }
70
+
71
+ }
72
+ this.onButtonClicked.emit( this.g.isOpen );
72
73
  }
73
74
 
74
75
  }
@@ -1,184 +1,77 @@
1
1
  import { Pipe, PipeTransform } from '@angular/core';
2
- import { marked } from 'marked';
3
- import { BLOCKED_DOMAINS } from '../utils/utils';
4
- import { htmlEntities } from 'src/chat21-core/utils/utils';
5
-
2
+ import { marked, Tokens } from 'marked';
6
3
 
7
4
  @Pipe({
8
5
  name: 'marked'
9
6
  })
10
-
11
7
  export class MarkedPipe implements PipeTransform {
12
- transform(value: any): any {
13
- // Security hardening:
14
- // - Do not allow raw HTML from chat messages to be interpreted as DOM.
15
- // - Keep Markdown working (marked will generate the needed HTML tags).
16
- // This makes inputs like "<h1>Title</h1>" render exactly as typed.
8
+
9
+ transform(value: any): string {
10
+
17
11
  const input =
18
12
  typeof value === 'string'
19
13
  ? value
20
14
  : (value === null || value === undefined) ? '' : String(value);
21
-
22
- // Converti i \n letterali in newline reali prima di htmlEntities
23
- // così il markdown con breaks: true li renderizzerà correttamente
15
+
24
16
  const inputWithNewlines = input.replace(/\\n/g, '\n');
25
-
26
- // Proteggi i > usati per i blockquote markdown (all'inizio di riga)
27
- // sostituendoli temporaneamente con un placeholder
28
- const BLOCKQUOTE_PLACEHOLDER = '___MARKDOWN_BLOCKQUOTE___';
29
- const protectedInput = inputWithNewlines.replace(/^(\s*)>/gm, (match, spaces) => {
30
- return spaces + BLOCKQUOTE_PLACEHOLDER;
31
- });
32
-
33
- // Applica htmlEntities (che codificherà tutti gli altri >)
34
- let safeInput = htmlEntities(protectedInput);
35
-
36
- // Ripristina i > dei blockquote
37
- safeInput = safeInput.replace(new RegExp(BLOCKQUOTE_PLACEHOLDER, 'g'), '>');
38
17
 
39
18
  const renderer = new marked.Renderer();
40
- renderer.link = function({ href, title, tokens }) {
41
- // Normalizza l'href per evitare falsi negativi
42
- const normalized = (href || '').trim().toLowerCase();
43
- // Pattern pericolosi da cercare nell'intero URL (non solo all'inizio)
44
- const dangerousPatterns = [
45
- /javascript:/i, // javascript: protocol
46
- /data:/i, // data: protocol
47
- /vbscript:/i, // vbscript: protocol
48
- /on\w+\s*=/i, // event handlers (onclick, onload, etc.)
49
- /alert\s*\(/i, // alert() function
50
- /eval\s*\(/i, // eval() function
51
- /document\./i, // document object access
52
- /window\./i, // window object access
53
- /\.appendChild\s*\(/i, // DOM manipulation
54
- /\.createElement\s*\(/i, // DOM creation
55
- /<script/i, // script tags
56
- /<\/script>/i, // closing script tags
57
- /function\s*\(/i, // function definitions
58
- /\(function/i, // IIFE patterns
59
- /setTimeout\s*\(/i, // setTimeout
60
- /setInterval\s*\(/i, // setInterval
61
- /location\./i, // location object manipulation
62
- /history\./i, // history object manipulation
63
- /localStorage\./i, // localStorage access
64
- /sessionStorage\./i, // sessionStorage access
65
- /cookie/i, // cookie manipulation
66
- /fetch\s*\(/i, // fetch API
67
- /XMLHttpRequest/i, // XHR
68
- /FormData/i, // FormData
69
- /Blob\s*\(/i, // Blob constructor
70
- /FileReader/i, // FileReader
71
- /crypto\./i, // crypto object
72
- /btoa\s*\(/i, // base64 encoding
73
- /atob\s*\(/i, // base64 decoding
74
- /decodeURI/i, // URI decoding
75
- /encodeURI/i, // URI encoding
76
- /String\.fromCharCode/i, // character code conversion
77
- /unescape\s*\(/i, // unescape function
78
- /escape\s*\(/i // escape function
79
- ];
80
-
81
- // Controlla se l'URL contiene pattern pericolosi
82
- const isDangerous = dangerousPatterns.some(pattern => pattern.test(normalized));
83
- if (isDangerous) {
84
- // Ritorna solo il testo come stringa, niente <a>
85
- return tokens ? tokens.map(token => token.raw).join('') : href || '';
86
- }
87
19
 
88
- // tokens = this.cleanInput(href);
20
+ /* --------------------------------------------------
21
+ 🔐 1. NON renderizzare HTML raw
22
+ -------------------------------------------------- */
23
+ renderer.html = function(token: Tokens.HTML | Tokens.Tag): string {
24
+ const html = 'text' in token ? token.text : '';
89
25
 
90
- const text = tokens
91
- ? tokens.map(token => token.raw).join('')
92
- : href; // fallback se tokens non c'è
93
- if (!href) return text;
94
-
95
- return `<a href="${href}" target="_blank" rel="noopener noreferrer">${text}</a>`;
26
+ return html
27
+ .replace(/&/g, '&amp;')
28
+ .replace(/</g, '&lt;')
29
+ .replace(/>/g, '&gt;');
96
30
  };
97
-
98
- marked.setOptions({
99
- renderer,
100
- gfm: true,
101
- breaks: true
102
- });
103
31
 
104
- if (safeInput && safeInput.length > 0) {
105
- try {
106
- return marked.parse(safeInput);
107
- } catch (err) {
108
- console.error('Errore nel parsing markdown:', err);
109
- return safeInput;
110
- }
111
- }
112
- return safeInput;
113
- }
32
+ /* --------------------------------------------------
33
+ 🔐 2. Link sicuri
34
+ -------------------------------------------------- */
35
+ const originalLinkRenderer = renderer.link.bind(renderer);
114
36
 
37
+ const dangerousProtocols = [
38
+ /^javascript:/i,
39
+ /^data:/i,
40
+ /^vbscript:/i
41
+ ];
115
42
 
116
- private cleanInput(input: string): string {
117
- if (!input) return '';
118
- let cleaned = (input || '').trim().toLowerCase();
119
-
120
- BLOCKED_DOMAINS.forEach(domain => {
121
- const escapedDomain = domain.replace(/\./g, '\\.');
122
- // Pattern che copre TUTTI i casi
123
- const comprehensivePattern = new RegExp(
124
- `(\\[([^\\]]*)\\]\\([^)]*(?:https?://)?(?:www\\.)?${escapedDomain}(?:/[^)]*)?\\))|((?:https?://)?(?:www\\.)?${escapedDomain}(?:/\\S*)?)`,
125
- 'gi'
126
- );
127
-
128
- cleaned = cleaned.replace(comprehensivePattern, (match, p1, p2, p3) => {
129
- // Se è un link markdown [text](url), mantieni il testo
130
- if (p2) return `${p2} 🔒`;
131
- // Se è un URL diretto, sostituisci con dominio + 🔒
132
- if (p3) return `${domain} 🔒`;
133
- return match;
134
- });
135
-
136
- });
43
+ renderer.link = function({ href, title, tokens }) {
137
44
 
45
+ const normalized = (href || '').trim();
138
46
 
139
- // Pattern che sostituisce i link pericolosi con solo il testo
140
- const dangerousLinkPatterns = [
141
- // Sostituisce [text](javascript:...) con "text"
142
- /\[([^\]]*)\]\(javascript:[^)]*\)/gi,
143
- /\[([^\]]*)\]\(data:[^)]*\)/gi,
144
- /\[([^\]]*)\]\(vbscript:[^)]*\)/gi,
145
- /\[([^\]]*)\]\([^)]*alert\([^)]*\)/gi
146
- ];
47
+ const isDangerous = dangerousProtocols.some(pattern =>
48
+ pattern.test(normalized)
49
+ );
147
50
 
148
- dangerousLinkPatterns.forEach(pattern => {
149
- cleaned = cleaned.replace(pattern, '$1'); // $1 = il testo del link
150
- });
51
+ if (isDangerous) {
52
+ return tokens ? tokens.map(t => t.raw).join('') : href || '';
53
+ }
151
54
 
152
- // Pattern generali per sicurezza (rimuovono completamente)
153
- const generalPatterns = [
154
- /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
155
- /<script[^>]*>[\s\S]*?<\/script>/gi, // Script multi-linea
156
- /<script[^>]*>.*?<\/script>/gi, // Script single-line
157
- /<script[^>]*>/gi, // Solo tag di apertura
158
- /<\/script>/gi, // Solo tag di chiusura
159
- /javascript:/gi,
160
- /vbscript:/gi,
161
- /data:/gi,
162
- /on\w+\s*=/gi,
163
- /alert\(/gi,
164
- /eval\(/gi,
165
- /document\./gi,
166
- /window\./gi,
167
- /\(function\s*\(\)\s*\{/gi,
168
- /\.appendChild\(/gi,
169
- /\.createElement\(/gi,
170
- /\.getElementsByTagName\(/gi,
171
-
172
- // ✅ PATTERN PER FUNZIONI IIFE (Immediately Invoked Function Expression):
173
- /\(function\s*\(\s*\)\s*\{[\s\S]*?\}\)\(\s*\)\s*;/gi,
174
- /\(function\s*\(\)\s*\{/gi
175
- ];
55
+ const html = originalLinkRenderer({ href, title, tokens });
176
56
 
177
- generalPatterns.forEach(pattern => {
178
- cleaned = cleaned.replace(pattern, '');
57
+ // aggiunge sicurezza ai link
58
+ return html.replace(
59
+ '<a ',
60
+ '<a target="_blank" rel="noopener noreferrer" '
61
+ );
62
+ };
63
+
64
+ marked.setOptions({
65
+ renderer,
66
+ gfm: true,
67
+ breaks: true
179
68
  });
180
69
 
181
- return cleaned;
70
+ try {
71
+ return marked.parse(inputWithNewlines) as string;
72
+ } catch (err) {
73
+ console.error('Errore parsing markdown:', err);
74
+ return inputWithNewlines;
75
+ }
182
76
  }
183
-
184
- }
77
+ }
@@ -302,6 +302,7 @@ export class TranslatorService {
302
302
  'CLOSED',
303
303
  'LABEL_PREVIEW',
304
304
  'MAX_ATTACHMENT',
305
+ 'MAX_ATTACHMENT_ERROR',
305
306
  'EMOJI'
306
307
  ];
307
308
 
@@ -358,6 +359,7 @@ export class TranslatorService {
358
359
  globals.LABEL_PREVIEW = res['LABEL_PREVIEW']
359
360
  globals.LABEL_ERROR_FIELD_REQUIRED= res['LABEL_ERROR_FIELD_REQUIRED']
360
361
  globals.MAX_ATTACHMENT = res['MAX_ATTACHMENT']
362
+ globals.MAX_ATTACHMENT_ERROR = res['MAX_ATTACHMENT_ERROR']
361
363
  globals.EMOJI = res['EMOJI']
362
364
 
363
365
 
@@ -247,7 +247,7 @@ export class Globals {
247
247
 
248
248
  // ============ BEGIN: SET EXTERNAL PARAMETERS ==============//
249
249
  this.baseLocation = 'https://widget.tiledesk.com/v2';
250
- this.autoStart = true;
250
+ this.autoStart = false;
251
251
  /** start Authentication and startUI */
252
252
  this.startHidden = false;
253
253
  /** show/hide all widget -> js call: showAllWidget */
@@ -96,5 +96,6 @@
96
96
  "EMOJI_NOT_ELLOWED":"Emoji not allowed",
97
97
  "DOMAIN_NOT_ALLOWED":"URL contains a non-allowed domain",
98
98
  "MAX_ATTACHMENT": "Max allowed size {{FILE_SIZE_LIMIT}}Mb",
99
+ "MAX_ATTACHMENT_ERROR": "The file exceeds the maximum allowed size",
99
100
  "EMOJI": "Emoji"
100
101
  }
@@ -96,5 +96,6 @@
96
96
  "EMOJI_NOT_ELLOWED":"Emoji no permitido",
97
97
  "DOMAIN_NOT_ALLOWED":"La URL contiene un dominio no permitido",
98
98
  "MAX_ATTACHMENT": "Tamaño máximo permitido {{FILE_SIZE_LIMIT}}Mb",
99
+ "MAX_ATTACHMENT_ERROR": "El archivo supera el tamaño máximo permitido",
99
100
  "EMOJI": "Emoji"
100
101
  }
@@ -96,5 +96,6 @@
96
96
  "EMOJI_NOT_ELLOWED":"Emoji non autorisé",
97
97
  "DOMAIN_NOT_ALLOWED":"L'URL contient un domaine non autorisé",
98
98
  "MAX_ATTACHMENT": "Taille maximale autorisée {{FILE_SIZE_LIMIT}}Mo",
99
+ "MAX_ATTACHMENT_ERROR": "Le fichier dépasse la taille maximale autorisée",
99
100
  "EMOJI": "Emoji"
100
101
  }
@@ -94,5 +94,6 @@
94
94
  "EMOJI_NOT_ELLOWED":"Emoji non consentiti",
95
95
  "DOMAIN_NOT_ALLOWED":"L'URL contiene un dominio non consentito",
96
96
  "MAX_ATTACHMENT": "Dimensione massima consentita {{FILE_SIZE_LIMIT}}Mb",
97
+ "MAX_ATTACHMENT_ERROR": "Il file supera la dimensione massima consentita",
97
98
  "EMOJI": "Emoji"
98
99
  }
@@ -71,7 +71,7 @@ export class TiledeskRequestsService {
71
71
 
72
72
  public getMyRequests(): Promise<{ requests: Array<any>}> {
73
73
  this.tiledeskToken = this.appStorage.getItem('tiledeskToken')
74
- const url = this.URL_TILEDESK_REQUEST + '/me?preflight=true'
74
+ const url = this.URL_TILEDESK_REQUEST + 'me?preflight=true'
75
75
  this.logger.log('[TILEDESK-SERVICE] - GET REQUEST url ', url);
76
76
  const httpOptions = {
77
77
  headers: new HttpHeaders({
@@ -773,6 +773,11 @@ export function isAllowedUrlInText(text: string, allowedUrls: string[]) {
773
773
  return nonWhitelistedDomains.length === 0;
774
774
  }
775
775
 
776
+ // function extractUrls(text: string): string[] {
777
+ // const urlRegex = /https?:\/\/[^\s]+/g;
778
+ // return text.match(urlRegex) || [];
779
+ // }
780
+
776
781
  function extractUrls(text: string): string[] {
777
782
  // Rileva URL con o senza protocollo (http/https)
778
783
  const urlRegex = /\b((https?:\/\/)?(www\.)?[a-z0-9.-]+\.[a-z]{2,})(\/[^\s]*)?/gi;
@@ -787,5 +792,3 @@ function extractUrls(text: string): string[] {
787
792
  }
788
793
 
789
794
 
790
-
791
-
@@ -13,7 +13,7 @@
13
13
  bottom: 0px;
14
14
  width: auto;
15
15
  height: auto;
16
- display: none;
16
+ display: block;
17
17
  z-index: 3000000000; /*999999*/;
18
18
  }
19
19
 
@@ -84,12 +84,12 @@
84
84
  max-width: 1024px;
85
85
  max-height: 100%;
86
86
 
87
- transition:
87
+ /* transition:
88
88
  width 300ms,
89
89
  height 300ms,
90
90
  max-height 300ms,
91
91
  transform 300ms cubic-bezier(0, 1.2, 1, 1),
92
- opacity 300ms ease-out;
92
+ opacity 300ms ease-out; */
93
93
  /* per migliorare le prestazioni quando si usa transform */
94
94
  will-change: transform, opacity, width, height;
95
95
  }
@@ -175,14 +175,14 @@
175
175
  }
176
176
 
177
177
  #tiledesk-container.open #tiledeskdiv.min-size {
178
- transition: width 200ms, height 200ms, max-height 200ms, transform 300ms cubic-bezier(0, 1.2, 1, 1), opacity 83ms ease-out;
178
+ /* transition: width 200ms, height 200ms, max-height 200ms, transform 300ms cubic-bezier(0, 1.2, 1, 1), opacity 83ms ease-out; */
179
179
  width: var(--iframeMinWidth);
180
180
  height: var(--iframeMinHeight);
181
181
  }
182
182
 
183
183
  #tiledesk-container.open #tiledeskdiv.max-size {
184
184
  /* transition: width 1s, height 1s; */
185
- transition: width 200ms, height 200ms, max-height 200ms, transform 300ms cubic-bezier(0, 1.2, 1, 1), opacity 83ms ease-out;
185
+ /* transition: width 200ms, height 200ms, max-height 200ms, transform 300ms cubic-bezier(0, 1.2, 1, 1), opacity 83ms ease-out; */
186
186
  width: var(--iframeMaxWidth);
187
187
  height: var(--iframeMaxHeight);
188
188
  }
package/src/launch.js CHANGED
@@ -217,68 +217,13 @@ function loadIframe(tiledeskScriptBaseLocation) {
217
217
 
218
218
  iDiv.appendChild(ifrm);
219
219
 
220
- // Funzione helper per caricare iframe con fallback per compatibilità CSP (Wix, etc.)
221
- // Usa Blob URL come metodo principale (più compatibile con CSP) con fallback a srcdoc e document.write
222
- function loadIframeContent(iframe, htmlContent, baseLocation) {
223
- var isLocalhost = baseLocation.includes('localhost');
224
- var blobUrl = null;
225
-
226
- // Metodo 1: Blob URL (più compatibile con CSP di Wix e altre piattaforme)
227
- // Usa Blob URL come metodo principale perché è meno spesso bloccato da CSP rispetto a srcdoc
228
- if (typeof Blob !== 'undefined' && typeof URL !== 'undefined' && URL.createObjectURL) {
229
- try {
230
- var blob = new Blob([htmlContent], { type: 'text/html;charset=utf-8' });
231
- blobUrl = URL.createObjectURL(blob);
232
- iframe.src = blobUrl;
233
-
234
- // Cleanup del blob URL dopo il caricamento per liberare memoria
235
- var originalOnload = iframe.onload;
236
- iframe.onload = function() {
237
- // Revoca il blob URL dopo un delay per assicurarsi che tutto sia caricato
238
- setTimeout(function() {
239
- if (blobUrl) {
240
- try {
241
- URL.revokeObjectURL(blobUrl);
242
- blobUrl = null;
243
- } catch(e) {
244
- console.warn('Error revoking blob URL:', e);
245
- }
246
- }
247
- }, 1000);
248
- if (originalOnload) originalOnload.call(this);
249
- };
250
- return; // Blob URL impostato con successo
251
- } catch(e) {
252
- console.warn('Blob URL not available, trying srcdoc:', e);
253
- }
254
- }
255
-
256
- // Metodo 2: srcdoc (fallback se Blob URL non disponibile)
257
- // Skip per localhost (usa document.write per compatibilità sviluppo)
258
- if (!isLocalhost && 'srcdoc' in iframe) {
259
- try {
260
- iframe.srcdoc = htmlContent;
261
- return; // srcdoc impostato
262
- } catch(e) {
263
- console.warn('srcdoc not allowed, trying document.write:', e);
264
- }
265
- }
266
-
267
- // Metodo 3: document.write (fallback finale, funziona su localhost e browser vecchi)
268
- if (isLocalhost || (iframe.contentWindow && iframe.contentWindow.document)) {
269
- try {
270
- iframe.contentWindow.document.open();
271
- iframe.contentWindow.document.write(htmlContent);
272
- iframe.contentWindow.document.close();
273
- return; // document.write completato
274
- } catch(e) {
275
- console.error('All iframe loading methods failed:', e);
276
- }
277
- }
220
+ if(tiledeskScriptBaseLocation.includes('localhost')){
221
+ ifrm.contentWindow.document.open();
222
+ ifrm.contentWindow.document.write(srcTileDesk);
223
+ ifrm.contentWindow.document.close();
224
+ }else {
225
+ ifrm.srcdoc = srcTileDesk
278
226
  }
279
-
280
- // Carica il contenuto dell'iframe con fallback automatico
281
- loadIframeContent(ifrm, srcTileDesk, tiledeskScriptBaseLocation);
282
227
 
283
228
 
284
229
  }
@@ -218,68 +218,13 @@ function loadIframe(tiledeskScriptBaseLocation) {
218
218
 
219
219
  iDiv.appendChild(ifrm);
220
220
 
221
- // Funzione helper per caricare iframe con fallback per compatibilità CSP (Wix, etc.)
222
- // Usa Blob URL come metodo principale (più compatibile con CSP) con fallback a srcdoc e document.write
223
- function loadIframeContent(iframe, htmlContent, baseLocation) {
224
- var isLocalhost = baseLocation.includes('localhost');
225
- var blobUrl = null;
226
-
227
- // Metodo 1: Blob URL (più compatibile con CSP di Wix e altre piattaforme)
228
- // Usa Blob URL come metodo principale perché è meno spesso bloccato da CSP rispetto a srcdoc
229
- if (typeof Blob !== 'undefined' && typeof URL !== 'undefined' && URL.createObjectURL) {
230
- try {
231
- var blob = new Blob([htmlContent], { type: 'text/html;charset=utf-8' });
232
- blobUrl = URL.createObjectURL(blob);
233
- iframe.src = blobUrl;
234
-
235
- // Cleanup del blob URL dopo il caricamento per liberare memoria
236
- var originalOnload = iframe.onload;
237
- iframe.onload = function() {
238
- // Revoca il blob URL dopo un delay per assicurarsi che tutto sia caricato
239
- setTimeout(function() {
240
- if (blobUrl) {
241
- try {
242
- URL.revokeObjectURL(blobUrl);
243
- blobUrl = null;
244
- } catch(e) {
245
- console.warn('Error revoking blob URL:', e);
246
- }
247
- }
248
- }, 1000);
249
- if (originalOnload) originalOnload.call(this);
250
- };
251
- return; // Blob URL impostato con successo
252
- } catch(e) {
253
- console.warn('Blob URL not available, trying srcdoc:', e);
254
- }
255
- }
256
-
257
- // Metodo 2: srcdoc (fallback se Blob URL non disponibile)
258
- // Skip per localhost (usa document.write per compatibilità sviluppo)
259
- if (!isLocalhost && 'srcdoc' in iframe) {
260
- try {
261
- iframe.srcdoc = htmlContent;
262
- return; // srcdoc impostato
263
- } catch(e) {
264
- console.warn('srcdoc not allowed, trying document.write:', e);
265
- }
266
- }
267
-
268
- // Metodo 3: document.write (fallback finale, funziona su localhost e browser vecchi)
269
- if (isLocalhost || (iframe.contentWindow && iframe.contentWindow.document)) {
270
- try {
271
- iframe.contentWindow.document.open();
272
- iframe.contentWindow.document.write(htmlContent);
273
- iframe.contentWindow.document.close();
274
- return; // document.write completato
275
- } catch(e) {
276
- console.error('All iframe loading methods failed:', e);
277
- }
278
- }
221
+ if(tiledeskScriptBaseLocation.includes('localhost')){
222
+ ifrm.contentWindow.document.open();
223
+ ifrm.contentWindow.document.write(srcTileDesk);
224
+ ifrm.contentWindow.document.close();
225
+ }else {
226
+ ifrm.srcdoc = srcTileDesk
279
227
  }
280
-
281
- // Carica il contenuto dell'iframe con fallback automatico
282
- loadIframeContent(ifrm, srcTileDesk, tiledeskScriptBaseLocation);
283
228
 
284
229
 
285
230
  }
@@ -1,41 +0,0 @@
1
- # npm version patch
2
- version=`node -e 'console.log(require("./package.json").version)'`
3
- echo "version $version"
4
-
5
- npm i
6
-
7
- cp src/environments/real_data/environment.prod.ts src/environments/environment.prod.ts
8
-
9
- # --build-optimizer=false if localstorage is disabled (webview) appears https://github.com/firebase/angularfire/issues/970
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 ###
17
-
18
- #### FIREBASE #####
19
- # cd dist
20
- # # aws s3 sync . s3://tiledesk-widget/v5/latest/
21
- # aws s3 sync . s3://tiledesk-widget/v5/$version/ --cache-control max-age=300
22
- # aws s3 sync . s3://tiledesk-widget/v5/ --cache-control max-age=300
23
- # cd ..
24
-
25
- # #### MQTT #####
26
- cd dist/browser
27
- # aws s3 sync . s3://tiledesk-widget/v5/latest/
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 ../..
33
-
34
- aws cloudfront create-invalidation --distribution-id E3EJDWEHY08CZZ --paths "/*"
35
-
36
- git restore src/environments/environment.prod.ts
37
-
38
- echo new version deployed $version on s3://tiledesk-widget/v6
39
- echo available on https://s3.eu-west-1.amazonaws.com/tiledesk-widget/v6/index.html
40
- echo https://widget.tiledesk.com/v6/index.html
41
- echo https://widget.tiledesk.com/v6/$version/index.html