@ada-support/embed2 1.10.0 → 1.11.0

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.
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Returns a data URI containing the fallback HTML.
3
+ * This is used when the chat iframe fails to load within the timeout period.
4
+ */
5
+ export declare function getFallbackDataURI(): string;
@@ -38,6 +38,7 @@ type ChatFrameProps = PropsFromState & OwnProps & PropsFromDispatch;
38
38
  interface ChatFrameState {
39
39
  isMounted: boolean;
40
40
  closeTransitionTime: number;
41
+ showFallback: boolean;
41
42
  }
42
43
  export declare class ChatFrame extends Component<ChatFrameProps, ChatFrameState> {
43
44
  iframeRef: import("preact").RefObject<any>;
@@ -50,6 +51,10 @@ export declare class ChatFrame extends Component<ChatFrameProps, ChatFrameState>
50
51
  componentDidMount(): void;
51
52
  componentDidUpdate(prevProps: ChatFrameProps): void;
52
53
  componentWillUnmount(): void;
54
+ /**
55
+ * Clears the fallback timeout and ensures cleanup
56
+ */
57
+ clearFallbackTimeout(): void;
53
58
  get darkMode(): boolean;
54
59
  getIframeSrc(): string;
55
60
  get styles(): string;
@@ -59,6 +64,7 @@ export declare class ChatFrame extends Component<ChatFrameProps, ChatFrameState>
59
64
  get chooseStyles(): string;
60
65
  handleScrollLock(): void;
61
66
  collapseIframe(): void;
67
+ handleFallbackMessage(event: MessageEvent): void;
62
68
  /**
63
69
  * Initial body "overflow" style may be set to something other than "auto"
64
70
  * We should remember it, to put back the initial value when we unlock the scrolling
@@ -1 +1,2 @@
1
1
  export declare const ANIMATION_DELAY = 50;
2
+ export declare const CHAT_FALLBACK_DELAY_MS = 5000;
@@ -64,4 +64,5 @@ export interface StartOptionsNoFunction {
64
64
  crossWindowPersistence?: boolean;
65
65
  testMode?: boolean;
66
66
  preload?: boolean;
67
+ showFallbackOnTimeout?: boolean;
67
68
  }
@@ -15919,7 +15919,7 @@ const client = new BrowserClient({
15919
15919
  return event;
15920
15920
  },
15921
15921
  environment: "production",
15922
- release: "1.10.0-3873935",
15922
+ release: "1.11.0-67295d0",
15923
15923
  sampleRate: 0.25,
15924
15924
  autoSessionTracking: false,
15925
15925
  // Integrations don't seem to work with Sentry: https://github.com/getsentry/sentry-javascript/issues/2541
@@ -16496,7 +16496,7 @@ function getEmbedURL(_ref) {
16496
16496
  } else {
16497
16497
  host = `http://${handle}.localhost:9001`;
16498
16498
  }
16499
- return `${host}/embed/${frameName}/${"3873935"}/index.html`;
16499
+ return `${host}/embed/${frameName}/${"67295d0"}/index.html`;
16500
16500
  }
16501
16501
  function constructQueryString(query) {
16502
16502
  return Object.keys(query).map(key => {
@@ -17036,7 +17036,8 @@ const getInitialState = adaSettings => ({
17036
17036
  chatterInLiveChat: false,
17037
17037
  wasCampaignShownButNowClosed: false,
17038
17038
  proactiveCampaignHadMessages: false,
17039
- deviceToken: null
17039
+ deviceToken: null,
17040
+ showFallbackOnTimeout: false
17040
17041
  });
17041
17042
  ;// CONCATENATED MODULE: ./src/common/helpers/get-intro-for-url.ts
17042
17043
  function getProcessedPath(path) {
@@ -17676,9 +17677,9 @@ async function log(message, extra, options) {
17676
17677
  service: "embed",
17677
17678
  env: "production",
17678
17679
  embedVersion: 2,
17679
- version: "1.10.0",
17680
+ version: "1.11.0",
17680
17681
  isNpm: true,
17681
- commitHash: "3873935"
17682
+ commitHash: "67295d0"
17682
17683
  }))
17683
17684
  });
17684
17685
  }
@@ -18720,6 +18721,7 @@ function ButtonFrame_mapStateToProps(storeState) {
18720
18721
  /* harmony default export */ var components_ButtonFrame = (connect(ButtonFrame_mapStateToProps)(ButtonFrame));
18721
18722
  ;// CONCATENATED MODULE: ./src/common/constants/timeouts.ts
18722
18723
  const ANIMATION_DELAY = 50;
18724
+ const CHAT_FALLBACK_DELAY_MS = 5000;
18723
18725
  ;// CONCATENATED MODULE: ./src/common/helpers/is-mobile.ts
18724
18726
  const isMobile = /(iPhone)|(iPod)|(android)|(webOS)/i.exec(navigator.userAgent) !== null;
18725
18727
  ;// CONCATENATED MODULE: ./src/client/components/ChatFrame/chatFrameEvents/chatterEvent/index.ts
@@ -18742,6 +18744,134 @@ function storeChatterEventDataInBrowser(client, payload) {
18742
18744
  }
18743
18745
  ;// CONCATENATED MODULE: ./src/client/components/ChatFrame/chatFrameEvents/index.ts
18744
18746
 
18747
+ ;// CONCATENATED MODULE: ./src/client/components/ChatFrame/fallback-html.ts
18748
+ /**
18749
+ * Fallback HTML shown when the chat iframe fails to load within the timeout period.
18750
+ * This is a self-contained HTML page with inline styles and JavaScript.
18751
+ */
18752
+ const FALLBACK_HTML = `<!DOCTYPE html>
18753
+ <html lang="en">
18754
+ <head>
18755
+ <meta charset="utf-8">
18756
+ <meta name="viewport" content="width=device-width, height=device-height">
18757
+ <meta http-equiv="x-ua-compatible" content="IE=edge">
18758
+ <title>Chat - Technical Difficulties</title>
18759
+ <style>
18760
+ * {
18761
+ box-sizing: border-box;
18762
+ }
18763
+ body {
18764
+ margin: 0;
18765
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
18766
+ -webkit-font-smoothing: antialiased;
18767
+ -moz-osx-font-smoothing: grayscale;
18768
+ }
18769
+ .container {
18770
+ display: flex;
18771
+ flex-direction: column;
18772
+ height: 100vh;
18773
+ background-color: #ffffff;
18774
+ }
18775
+ .content-area {
18776
+ flex: 1;
18777
+ display: flex;
18778
+ align-items: center;
18779
+ justify-content: center;
18780
+ padding: 24px 16px;
18781
+ }
18782
+ .message-container {
18783
+ width: 100%;
18784
+ max-width: 351px;
18785
+ }
18786
+ .message-card {
18787
+ display: flex;
18788
+ flex-direction: column;
18789
+ align-items: center;
18790
+ justify-content: center;
18791
+ padding: 24px 16px;
18792
+ border: 1px solid #e8e8eb;
18793
+ border-radius: 16px;
18794
+ background-color: #ffffff;
18795
+ box-shadow: 0px 4px 14px rgba(0, 0, 0, 0.06);
18796
+ }
18797
+ .icon-container {
18798
+ margin-bottom: 8px;
18799
+ }
18800
+ .icon-container svg {
18801
+ fill: rgba(0, 0, 0, 0.65);
18802
+ display: block;
18803
+ }
18804
+ .message-text {
18805
+ margin: 8px 0 0 0;
18806
+ line-height: 20px;
18807
+ font-size: 1rem;
18808
+ text-align: center;
18809
+ color: rgba(0, 0, 0, 0.95);
18810
+ }
18811
+ .close-button {
18812
+ margin-top: 16px;
18813
+ padding: 12px 24px;
18814
+ font-size: 14px;
18815
+ font-weight: 500;
18816
+ color: #ffffff;
18817
+ background-color: #000000;
18818
+ border: none;
18819
+ border-radius: 8px;
18820
+ cursor: pointer;
18821
+ transition: background-color 0.2s ease;
18822
+ font-family: inherit;
18823
+ }
18824
+ .close-button:hover {
18825
+ background-color: #333333;
18826
+ }
18827
+ .close-button:active {
18828
+ background-color: #1a1a1a;
18829
+ }
18830
+ .close-button:focus {
18831
+ outline: 2px solid #4d90fe;
18832
+ outline-offset: 2px;
18833
+ }
18834
+ </style>
18835
+ </head>
18836
+ <body style="margin:0">
18837
+ <div id="app">
18838
+ <div class="container">
18839
+ <div class="content-area">
18840
+ <div class="message-container">
18841
+ <div class="message-card">
18842
+ <div class="icon-container">
18843
+ <svg xmlns="http://www.w3.org/2000/svg" width="42" height="42" viewBox="0 0 24 24" fill="currentColor">
18844
+ <path d="M12,9a1,1,0,0,0-1,1v3a1,1,0,0,0,2,0V10A1,1,0,0,0,12,9Zm7-7H5A3,3,0,0,0,2,5V15a3,3,0,0,0,3,3H16.59l3.7,3.71A1,1,0,0,0,21,22a.84.84,0,0,0,.38-.08A1,1,0,0,0,22,21V5A3,3,0,0,0,19,2Zm1,16.59-2.29-2.3A1,1,0,0,0,17,16H5a1,1,0,0,1-1-1V5A1,1,0,0,1,5,4H19a1,1,0,0,1,1,1ZM12,6a1,1,0,1,0,1,1A1,1,0,0,0,12,6Z"></path>
18845
+ </svg>
18846
+ </div>
18847
+ <p role="alert" data-testid="default-outage-message" class="message-text">
18848
+ The chat is experiencing technical difficulties and we're working to fix them as soon as possible. Please check back for an update in a few minutes.
18849
+ </p>
18850
+ <button class="close-button" id="closeButton" aria-label="Close chat window">Close</button>
18851
+ </div>
18852
+ </div>
18853
+ </div>
18854
+ </div>
18855
+ </div>
18856
+ <script>
18857
+ document.getElementById('closeButton').addEventListener('click', function() {
18858
+ try {
18859
+ window.parent.postMessage('ADA_FALLBACK_CLOSE', '*');
18860
+ } catch (error) {
18861
+ console.error('Failed to close chat:', error);
18862
+ }
18863
+ });
18864
+ </script>
18865
+ </body>
18866
+ </html>`;
18867
+
18868
+ /**
18869
+ * Returns a data URI containing the fallback HTML.
18870
+ * This is used when the chat iframe fails to load within the timeout period.
18871
+ */
18872
+ function getFallbackDataURI() {
18873
+ return `data:text/html;charset=utf-8,${encodeURIComponent(FALLBACK_HTML)}`;
18874
+ }
18745
18875
  ;// CONCATENATED MODULE: ./src/client/components/ChatFrame/index.tsx
18746
18876
 
18747
18877
 
@@ -18760,6 +18890,7 @@ function storeChatterEventDataInBrowser(client, payload) {
18760
18890
 
18761
18891
 
18762
18892
 
18893
+
18763
18894
  class ChatFrame extends d {
18764
18895
  constructor() {
18765
18896
  super(...arguments);
@@ -18771,7 +18902,8 @@ class ChatFrame extends d {
18771
18902
  _defineProperty(this, "url", this.getIframeSrc());
18772
18903
  _defineProperty(this, "state", {
18773
18904
  isMounted: false,
18774
- closeTransitionTime: 200
18905
+ closeTransitionTime: 200,
18906
+ showFallback: false
18775
18907
  });
18776
18908
  } // This url must be a static property - if getIframeSrc is called repeatedly, the resulting URL may change improperly
18777
18909
  // For example, the skipGreeting query parameter was being changed from true to false, causing the AFM-384 bug
@@ -18792,6 +18924,9 @@ class ChatFrame extends d {
18792
18924
  this.bindChatEventHandlers();
18793
18925
  this.handleScrollLock();
18794
18926
 
18927
+ // Listen for messages from the fallback iframe
18928
+ window.addEventListener("message", this.handleFallbackMessage.bind(this));
18929
+
18795
18930
  // We use this log to track number of chat impressions (times when users see chat iFrame render first time).
18796
18931
  log("Chat frame mount", {
18797
18932
  handle,
@@ -18809,8 +18944,23 @@ class ChatFrame extends d {
18809
18944
  const {
18810
18945
  isDrawerOpen
18811
18946
  } = this.props;
18947
+ const {
18948
+ showFallback
18949
+ } = this.state;
18812
18950
  if (isDrawerOpen !== prevProps.isDrawerOpen) {
18813
18951
  this.handleScrollLock();
18952
+
18953
+ // When opening after fallback was shown, reset state and reload iframe
18954
+ if (isDrawerOpen && !prevProps.isDrawerOpen && showFallback) {
18955
+ this.setState({
18956
+ showFallback: false
18957
+ });
18958
+
18959
+ // Force reload by updating src (frameLoaded will handle timeout setup)
18960
+ if (this.iframeRef.current) {
18961
+ this.iframeRef.current.src = this.url;
18962
+ }
18963
+ }
18814
18964
  }
18815
18965
  }
18816
18966
  componentWillUnmount() {
@@ -18818,6 +18968,17 @@ class ChatFrame extends d {
18818
18968
  messageService
18819
18969
  } = this.props;
18820
18970
  messageService.unregisterChannel("chat");
18971
+ this.clearFallbackTimeout();
18972
+ }
18973
+
18974
+ /**
18975
+ * Clears the fallback timeout and ensures cleanup
18976
+ */
18977
+ clearFallbackTimeout() {
18978
+ if (this.chatRenderTimeout) {
18979
+ clearTimeout(this.chatRenderTimeout);
18980
+ this.chatRenderTimeout = undefined;
18981
+ }
18821
18982
  }
18822
18983
  get darkMode() {
18823
18984
  var _client$ui_settings, _client$ui_settings$c;
@@ -18852,7 +19013,7 @@ class ChatFrame extends d {
18852
19013
  const hostPageUrlParams = new URL(window.location.href).searchParams;
18853
19014
  const smsToken = hostPageUrlParams.get("adaSMSToken");
18854
19015
  const queryParams = {
18855
- embedVersion: "3873935".slice(0, 7),
19016
+ embedVersion: "67295d0".slice(0, 7),
18856
19017
  greeting,
18857
19018
  language,
18858
19019
  skipGreeting,
@@ -19062,6 +19223,19 @@ class ChatFrame extends d {
19062
19223
  this.iframeRef.current.style.width = 0;
19063
19224
  this.iframeRef.current.style.height = 0;
19064
19225
  }
19226
+ handleFallbackMessage(event) {
19227
+ var _this$iframeRef$curre;
19228
+ // Only accept messages from our own iframe
19229
+ if (!event.source || event.source !== ((_this$iframeRef$curre = this.iframeRef.current) === null || _this$iframeRef$curre === void 0 ? void 0 : _this$iframeRef$curre.contentWindow)) {
19230
+ return;
19231
+ }
19232
+ if (event.data === "ADA_FALLBACK_CLOSE") {
19233
+ const {
19234
+ toggleChat
19235
+ } = this.props;
19236
+ toggleChat();
19237
+ }
19238
+ }
19065
19239
 
19066
19240
  /**
19067
19241
  * Initial body "overflow" style may be set to something other than "auto"
@@ -19138,7 +19312,7 @@ class ChatFrame extends d {
19138
19312
  if (!this.channel) {
19139
19313
  throw new AdaEmbedError("`this.channel` is not defined");
19140
19314
  }
19141
- clearTimeout(this.chatRenderTimeout);
19315
+ this.clearFallbackTimeout();
19142
19316
  this.channel.isConnected = true;
19143
19317
  setConnectionState({
19144
19318
  chat: ConnectionState.Done
@@ -19406,33 +19580,72 @@ class ChatFrame extends d {
19406
19580
  });
19407
19581
  }
19408
19582
  frameLoaded() {
19583
+ var _this$iframeRef$curre2;
19409
19584
  const {
19410
19585
  handle,
19411
- adaSettings
19586
+ adaSettings,
19587
+ isDrawerOpen
19412
19588
  } = this.props;
19589
+ const {
19590
+ showFallback
19591
+ } = this.state;
19413
19592
  // SUP-1141- TODO: Remove chatFrameTimeoutCallback
19414
19593
  const {
19415
- chatFrameTimeoutCallback
19594
+ chatFrameTimeoutCallback,
19595
+ showFallbackOnTimeout
19416
19596
  } = adaSettings;
19597
+
19598
+ // Don't start timeout if drawer is closed
19599
+ if (!isDrawerOpen) {
19600
+ return;
19601
+ }
19602
+
19603
+ // Don't start timeout if already showing fallback (prevents redundant timeouts)
19604
+ if (showFallback) {
19605
+ return;
19606
+ }
19607
+
19608
+ // Don't start timeout if loading fallback data URI (would create infinite loop)
19609
+ const currentSrc = (_this$iframeRef$curre2 = this.iframeRef.current) === null || _this$iframeRef$curre2 === void 0 ? void 0 : _this$iframeRef$curre2.src;
19610
+ if (currentSrc !== null && currentSrc !== void 0 && currentSrc.startsWith("data:text/html")) {
19611
+ return;
19612
+ }
19613
+
19614
+ // Clear any existing timeout before starting new one (prevents orphaned timeouts)
19615
+ this.clearFallbackTimeout();
19417
19616
  this.chatRenderTimeout = window.setTimeout(() => {
19418
19617
  var _this$channel;
19419
- log("Chat frame took over 5 seconds to respond", {
19420
- handle,
19421
- embedSettings: adaSettings
19422
- });
19423
- if (!((_this$channel = this.channel) !== null && _this$channel !== void 0 && _this$channel.isConnected)) {
19618
+ const {
19619
+ isDrawerOpen: drawerIsOpen
19620
+ } = this.props;
19621
+
19622
+ // Only show fallback if drawer is STILL open and chat hasn't connected
19623
+ if (!((_this$channel = this.channel) !== null && _this$channel !== void 0 && _this$channel.isConnected) && drawerIsOpen) {
19624
+ log("Chat frame took over 5 seconds to respond", {
19625
+ handle,
19626
+ embedSettings: adaSettings
19627
+ });
19628
+
19424
19629
  // SUP-1141- TODO: Remove chatFrameTimeoutCallback
19425
19630
  if (chatFrameTimeoutCallback) {
19426
19631
  chatFrameTimeoutCallback();
19427
19632
  }
19428
19633
  publishEvent("ada:chat_frame_timeout", undefined);
19634
+ if (showFallbackOnTimeout) {
19635
+ this.setState({
19636
+ showFallback: true
19637
+ });
19638
+ }
19429
19639
  }
19430
- }, 5000);
19640
+ }, CHAT_FALLBACK_DELAY_MS);
19431
19641
  }
19432
19642
  render() {
19433
19643
  const {
19434
19644
  client
19435
19645
  } = this.props;
19646
+ const {
19647
+ showFallback
19648
+ } = this.state;
19436
19649
  const iFrameName = `ada-chat-frame`;
19437
19650
  const sandboxParams = "allow-downloads allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-top-navigation allow-forms allow-modals";
19438
19651
  const roleDescription = () => {
@@ -19441,8 +19654,11 @@ class ChatFrame extends d {
19441
19654
  }
19442
19655
  return `${client.handle.charAt(0).toUpperCase() + client.handle.slice(1)} Chat Window`;
19443
19656
  };
19657
+
19658
+ // Use fallback URL if showFallback is true, otherwise use normal chat URL
19659
+ const iframeSrc = showFallback ? getFallbackDataURI() : this.url;
19444
19660
  return v("iframe", {
19445
- src: this.url,
19661
+ src: iframeSrc,
19446
19662
  scrolling: "no",
19447
19663
  style: this.chooseStyles,
19448
19664
  frameBorder: "0",
@@ -19498,6 +19714,8 @@ function ChatFrame_mapDispatchToProps(dispatch) {
19498
19714
  toggleChat: bindActionCreators(ActionCreators.TOGGLE_CHAT_ACTION, dispatch)
19499
19715
  };
19500
19716
  }
19717
+
19718
+ // eslint-disable-next-line import/no-default-export
19501
19719
  /* harmony default export */ var components_ChatFrame = (connect(ChatFrame_mapStateToProps, ChatFrame_mapDispatchToProps)(ChatFrame));
19502
19720
  ;// CONCATENATED MODULE: ./src/client/components/IntroFrame/index.tsx
19503
19721
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ada-support/embed2",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "",
5
5
  "main": "dist/npm-entry",
6
6
  "typings": "dist/npm-entry/index-npm.d.ts",
@@ -52,8 +52,8 @@
52
52
  "@babel/plugin-transform-runtime": "^7.16.10",
53
53
  "@babel/preset-env": "^7.23.2",
54
54
  "@babel/preset-typescript": "^7.23.2",
55
+ "@testing-library/jest-dom": "^5",
55
56
  "@testing-library/preact": "^2.0.1",
56
- "@types/enzyme": "^3.10.18",
57
57
  "@types/jest": "^27.0.2",
58
58
  "@types/json-stable-stringify": "^1.0.33",
59
59
  "@types/lodash.debounce": "^4.0.6",
@@ -62,15 +62,12 @@
62
62
  "@types/uniqid": "^5.3.2",
63
63
  "@types/webpack": "^5.28.0",
64
64
  "@types/webpack-bundle-analyzer": "^4.4.1",
65
- "@types/webpack-dev-server": "^4.7.2",
66
65
  "babel-loader": "9.1.2",
67
66
  "babel-preset-preact": "^2.0.0",
68
67
  "browserslist": "^4.17.4",
69
- "cypress": "^9.6.1",
68
+ "cypress": "^13.0.0",
70
69
  "depcheck": "^1.4.3",
71
70
  "dotenv": "^8.2.0",
72
- "enzyme": "^3.11.0",
73
- "enzyme-adapter-preact-pure": "^4.0.1",
74
71
  "eslint": "^8.8.0",
75
72
  "eslint-config-prettier": "^8.5.0",
76
73
  "eslint-plugin-chai-friendly": "^0.7.2",
@@ -85,14 +82,14 @@
85
82
  "mock-xmlhttprequest": "^7.0.4",
86
83
  "npm-run-all": "^4.1.5",
87
84
  "prettier": "2.7.1",
88
- "start-server-and-test": "^2.0.4",
85
+ "start-server-and-test": "^2.1.2",
89
86
  "ts-node": "^10.2.1",
90
87
  "tsconfig-paths": "^3.11.0",
91
88
  "typescript": "^5.6.3",
92
89
  "webpack": "^5.76.0",
93
90
  "webpack-bundle-analyzer": "^4.5.0",
94
91
  "webpack-cli": "^5.1.4",
95
- "webpack-dev-server": "^4.3.1",
92
+ "webpack-dev-server": "^5.2.2",
96
93
  "webpack-merge": "^5.8.0"
97
94
  },
98
95
  "dependencies": {
@@ -113,7 +110,8 @@
113
110
  "minimist": ">=1.2.6",
114
111
  "terser": ">=5.14.2",
115
112
  "@babel/traverse": ">=7.23.2",
116
- "semver": ">=7.5.2"
113
+ "semver": ">=7.5.2",
114
+ "form-data": ">=4.0.4"
117
115
  },
118
116
  "files": [
119
117
  "dist/npm-entry"
@@ -125,4 +123,4 @@
125
123
  "publishConfig": {
126
124
  "access": "public"
127
125
  }
128
- }
126
+ }