@meetelise/chat 1.30.0 → 1.30.1

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.
Files changed (227) hide show
  1. package/dist/src/MyPubnub.d.ts +116 -0
  2. package/dist/src/WebComponent/FeeCalculator/components/addon-item/addon-item-styles.d.ts +2 -0
  3. package/dist/src/WebComponent/FeeCalculator/components/addon-item/addon-item.d.ts +22 -0
  4. package/dist/src/WebComponent/FeeCalculator/components/addon-item/index.d.ts +1 -0
  5. package/dist/src/WebComponent/FeeCalculator/components/fee-calculator-layout/fee-calculator-layout-styles.d.ts +2 -0
  6. package/dist/src/WebComponent/FeeCalculator/components/fee-calculator-layout/fee-calculator-layout.d.ts +34 -0
  7. package/dist/src/WebComponent/FeeCalculator/components/fee-card/fee-card-styles.d.ts +2 -0
  8. package/dist/src/WebComponent/FeeCalculator/components/fee-card/fee-card.d.ts +21 -0
  9. package/dist/src/WebComponent/FeeCalculator/components/fee-item/fee-item-styles.d.ts +2 -0
  10. package/dist/src/WebComponent/FeeCalculator/components/fee-item/fee-item.d.ts +13 -0
  11. package/dist/src/WebComponent/FeeCalculator/components/floor-plan-selector/floor-plan-selector-styles.d.ts +2 -0
  12. package/dist/src/WebComponent/FeeCalculator/components/floor-plan-selector/floor-plan-selector.d.ts +33 -0
  13. package/dist/src/WebComponent/FeeCalculator/components/floorplan-image-card/floorplan-image-card-styles.d.ts +2 -0
  14. package/dist/src/WebComponent/FeeCalculator/components/floorplan-image-card/floorplan-image-card.d.ts +18 -0
  15. package/dist/src/WebComponent/FeeCalculator/components/index.d.ts +5 -0
  16. package/dist/src/WebComponent/FeeCalculator/components/promo-card/promo-card-styles.d.ts +2 -0
  17. package/dist/src/WebComponent/FeeCalculator/components/promo-card/promo-card.d.ts +13 -0
  18. package/dist/src/WebComponent/FeeCalculator/constants.d.ts +2 -0
  19. package/dist/src/WebComponent/FeeCalculator/fee-calculator-styles.d.ts +1 -0
  20. package/dist/src/WebComponent/FeeCalculator/fee-calculator.d.ts +59 -0
  21. package/dist/src/WebComponent/FeeCalculator/index.d.ts +2 -0
  22. package/dist/src/WebComponent/FeeCalculator/model/building-fee.d.ts +80 -0
  23. package/dist/src/WebComponent/FeeCalculator/model/transaction-category.d.ts +23 -0
  24. package/dist/src/WebComponent/LeadSourceClient.d.ts +46 -0
  25. package/dist/src/WebComponent/OfficeHours.d.ts +21 -0
  26. package/dist/src/WebComponent/Scheduler/date-picker.d.ts +28 -0
  27. package/dist/src/WebComponent/Scheduler/time-picker.d.ts +14 -0
  28. package/dist/src/WebComponent/Scheduler/tour-scheduler.d.ts +100 -0
  29. package/dist/src/WebComponent/Scheduler/tour-type-option.d.ts +13 -0
  30. package/dist/src/WebComponent/Scheduler/tourSchedulerStyles.d.ts +1 -0
  31. package/dist/src/WebComponent/actions/InputStyles.d.ts +1 -0
  32. package/dist/src/WebComponent/actions/action-confirm-button.d.ts +13 -0
  33. package/dist/src/WebComponent/actions/call-us-window.d.ts +37 -0
  34. package/dist/src/WebComponent/actions/collapse-expand-button.d.ts +8 -0
  35. package/dist/src/WebComponent/actions/details-window.d.ts +14 -0
  36. package/dist/src/WebComponent/actions/email-us-window.d.ts +46 -0
  37. package/dist/src/WebComponent/actions/formatPhoneNumber.d.ts +17 -0
  38. package/dist/src/WebComponent/actions/minimize-expand-button.d.ts +8 -0
  39. package/dist/src/WebComponent/chat-additional-actions.d.ts +28 -0
  40. package/dist/src/WebComponent/health-chat.d.ts +47 -0
  41. package/dist/src/WebComponent/healthchat-styles.d.ts +1 -0
  42. package/dist/src/WebComponent/icons/ApplyOutlineIcon.d.ts +2 -0
  43. package/dist/src/WebComponent/icons/BookTourOutlineIcon.d.ts +2 -0
  44. package/dist/src/WebComponent/icons/CalculatorOutlineIcon.d.ts +2 -0
  45. package/dist/src/WebComponent/icons/ChatOutlineIcon.d.ts +2 -0
  46. package/dist/src/WebComponent/icons/ChevronLeftIcon.d.ts +2 -0
  47. package/dist/src/WebComponent/icons/ChevronRightIcon.d.ts +2 -0
  48. package/dist/src/WebComponent/icons/ContactResidentIcon.d.ts +2 -0
  49. package/dist/src/WebComponent/icons/DollarOutlineIcon.d.ts +3 -0
  50. package/dist/src/WebComponent/icons/EmailOutlineIcon.d.ts +2 -0
  51. package/dist/src/WebComponent/icons/HeyThereEmojiIcon.d.ts +2 -0
  52. package/dist/src/WebComponent/icons/PhoneOutlineIcon.d.ts +2 -0
  53. package/dist/src/WebComponent/icons/SendMessageIcon.d.ts +3 -0
  54. package/dist/src/WebComponent/icons/TourSelfGuidedIcon.d.ts +2 -0
  55. package/dist/src/WebComponent/icons/TourVirtuallyIcon.d.ts +2 -0
  56. package/dist/src/WebComponent/icons/TourWithAgentIcon.d.ts +2 -0
  57. package/dist/src/WebComponent/icons/XOutlineIcon.d.ts +2 -0
  58. package/dist/src/WebComponent/index.d.ts +2 -0
  59. package/dist/src/WebComponent/launcher/Launcher.d.ts +98 -0
  60. package/dist/src/WebComponent/launcher/launcherStyles.d.ts +1 -0
  61. package/dist/src/WebComponent/launcher/mobile-launcher.d.ts +27 -0
  62. package/dist/src/WebComponent/launcher/typeEmojiStyles.d.ts +1 -0
  63. package/dist/src/WebComponent/launcher/typeMiniStyles.d.ts +1 -0
  64. package/dist/src/WebComponent/launcher/typeMobileStyles.d.ts +1 -0
  65. package/dist/src/WebComponent/leasing-chat-styles.d.ts +1 -0
  66. package/dist/src/WebComponent/me-chat.d.ts +91 -0
  67. package/dist/src/WebComponent/me-select.d.ts +24 -0
  68. package/dist/src/WebComponent/mega-loader.d.ts +7 -0
  69. package/dist/src/WebComponent/mini-loader.d.ts +5 -0
  70. package/dist/src/WebComponent/pubnub-chat-styles.d.ts +1 -0
  71. package/dist/src/WebComponent/pubnub-chat.d.ts +49 -0
  72. package/dist/src/WebComponent/pubnub-media.d.ts +14 -0
  73. package/dist/src/WebComponent/pubnub-message-styles.d.ts +1 -0
  74. package/dist/src/WebComponent/pubnub-message.d.ts +39 -0
  75. package/dist/src/WebComponent/simple-launcher/simple-launcher-styles.d.ts +1 -0
  76. package/dist/src/WebComponent/simple-launcher/simple-launcher.d.ts +12 -0
  77. package/dist/src/WebComponent/utilities-chat.d.ts +47 -0
  78. package/dist/src/WebComponent/utilities-styles.d.ts +1 -0
  79. package/dist/src/WebComponent/utils.d.ts +31 -0
  80. package/dist/src/analytics.d.ts +64 -0
  81. package/dist/src/disclaimers.d.ts +8 -0
  82. package/dist/src/fetchBuildingABTestType.d.ts +8 -0
  83. package/dist/src/fetchBuildingInfo.d.ts +57 -0
  84. package/dist/src/fetchBuildingWebchatView.d.ts +123 -0
  85. package/dist/src/fetchFeatureFlag.d.ts +14 -0
  86. package/dist/src/fetchLeadSources.d.ts +4 -0
  87. package/dist/src/fetchPhoneNumberFromSource.d.ts +6 -0
  88. package/dist/src/getAvailabilities.d.ts +45 -0
  89. package/dist/src/getBuildingPhoneNumber.d.ts +1 -0
  90. package/dist/src/getShouldAllowScheduling.d.ts +1 -0
  91. package/dist/src/getShouldShowWebchat.d.ts +3 -0
  92. package/dist/src/getTimezoneString.d.ts +1 -0
  93. package/dist/src/globals.d.ts +1 -0
  94. package/dist/src/gtm.d.ts +6 -0
  95. package/dist/src/handleChatId.d.ts +11 -0
  96. package/dist/src/insertDNIIntoWebsite.d.ts +5 -0
  97. package/dist/src/insertLeadSourceIntoSchedulerLinks.d.ts +4 -0
  98. package/dist/src/main/MEChat.d.ts +74 -0
  99. package/dist/src/main/utils.d.ts +2 -0
  100. package/dist/src/postLeadSources.d.ts +3 -0
  101. package/dist/src/rentgrata.d.ts +4 -0
  102. package/dist/src/replaceSelectButtonsWithNewLink.d.ts +5 -0
  103. package/dist/src/services/fees/calculateQuote.d.ts +52 -0
  104. package/dist/src/services/fees/fetchBuildingFees.d.ts +24 -0
  105. package/dist/src/services/fees/fetchBuildingFloorplans.d.ts +21 -0
  106. package/dist/src/services/fees/utils.d.ts +1 -0
  107. package/dist/src/svgIcons.d.ts +5 -0
  108. package/dist/src/themes.d.ts +5 -0
  109. package/dist/src/types/rest-sdk.types.d.ts +11 -0
  110. package/dist/src/types/webchat-no-show-reason.d.ts +1 -0
  111. package/dist/src/utils.d.ts +13 -0
  112. package/package.json +1 -1
  113. package/src/MyPubnub.ts +792 -0
  114. package/src/WebComponent/FeeCalculator/components/addon-item/addon-item-styles.ts +79 -0
  115. package/src/WebComponent/FeeCalculator/components/addon-item/addon-item.ts +121 -0
  116. package/src/WebComponent/FeeCalculator/components/addon-item/index.ts +1 -0
  117. package/src/WebComponent/FeeCalculator/components/fee-calculator-layout/fee-calculator-layout-styles.ts +127 -0
  118. package/src/WebComponent/FeeCalculator/components/fee-calculator-layout/fee-calculator-layout.ts +191 -0
  119. package/src/WebComponent/FeeCalculator/components/fee-card/fee-card-styles.ts +65 -0
  120. package/src/WebComponent/FeeCalculator/components/fee-card/fee-card.ts +91 -0
  121. package/src/WebComponent/FeeCalculator/components/fee-item/fee-item-styles.ts +44 -0
  122. package/src/WebComponent/FeeCalculator/components/fee-item/fee-item.ts +38 -0
  123. package/src/WebComponent/FeeCalculator/components/floor-plan-selector/floor-plan-selector-styles.ts +144 -0
  124. package/src/WebComponent/FeeCalculator/components/floor-plan-selector/floor-plan-selector.ts +241 -0
  125. package/src/WebComponent/FeeCalculator/components/floorplan-image-card/floorplan-image-card-styles.ts +74 -0
  126. package/src/WebComponent/FeeCalculator/components/floorplan-image-card/floorplan-image-card.ts +72 -0
  127. package/src/WebComponent/FeeCalculator/components/index.ts +5 -0
  128. package/src/WebComponent/FeeCalculator/components/promo-card/promo-card-styles.ts +39 -0
  129. package/src/WebComponent/FeeCalculator/components/promo-card/promo-card.ts +39 -0
  130. package/src/WebComponent/FeeCalculator/constants.ts +3 -0
  131. package/src/WebComponent/FeeCalculator/fee-calculator-styles.ts +334 -0
  132. package/src/WebComponent/FeeCalculator/fee-calculator.ts +369 -0
  133. package/src/WebComponent/FeeCalculator/index.ts +4 -0
  134. package/src/WebComponent/FeeCalculator/model/building-fee.ts +120 -0
  135. package/src/WebComponent/FeeCalculator/model/transaction-category.ts +23 -0
  136. package/src/WebComponent/LeadSourceClient.ts +332 -0
  137. package/src/WebComponent/MEChat.css +5 -0
  138. package/src/WebComponent/OfficeHours.ts +73 -0
  139. package/src/WebComponent/Scheduler/date-picker.ts +405 -0
  140. package/src/WebComponent/Scheduler/time-picker.ts +190 -0
  141. package/src/WebComponent/Scheduler/tour-scheduler.ts +1373 -0
  142. package/src/WebComponent/Scheduler/tour-type-option.ts +112 -0
  143. package/src/WebComponent/Scheduler/tourSchedulerStyles.ts +418 -0
  144. package/src/WebComponent/actions/InputStyles.ts +57 -0
  145. package/src/WebComponent/actions/action-confirm-button.ts +125 -0
  146. package/src/WebComponent/actions/call-us-window.ts +445 -0
  147. package/src/WebComponent/actions/collapse-expand-button.ts +65 -0
  148. package/src/WebComponent/actions/details-window.ts +150 -0
  149. package/src/WebComponent/actions/email-us-window.ts +555 -0
  150. package/src/WebComponent/actions/formatPhoneNumber.ts +72 -0
  151. package/src/WebComponent/actions/minimize-expand-button.ts +93 -0
  152. package/src/WebComponent/chat-additional-actions.ts +135 -0
  153. package/src/WebComponent/health-chat.ts +270 -0
  154. package/src/WebComponent/healthchat-styles.ts +119 -0
  155. package/src/WebComponent/icons/ApplyOutlineIcon.ts +22 -0
  156. package/src/WebComponent/icons/BookTourOutlineIcon.ts +13 -0
  157. package/src/WebComponent/icons/CalculatorOutlineIcon.ts +22 -0
  158. package/src/WebComponent/icons/ChatOutlineIcon.ts +10 -0
  159. package/src/WebComponent/icons/ChevronLeftIcon.ts +7 -0
  160. package/src/WebComponent/icons/ChevronRightIcon.ts +7 -0
  161. package/src/WebComponent/icons/ContactResidentIcon.ts +9 -0
  162. package/src/WebComponent/icons/DollarOutlineIcon.ts +18 -0
  163. package/src/WebComponent/icons/EmailOutlineIcon.ts +7 -0
  164. package/src/WebComponent/icons/HeyThereEmojiIcon.ts +12 -0
  165. package/src/WebComponent/icons/PhoneOutlineIcon.ts +7 -0
  166. package/src/WebComponent/icons/SendMessageIcon.ts +17 -0
  167. package/src/WebComponent/icons/TourSelfGuidedIcon.ts +17 -0
  168. package/src/WebComponent/icons/TourVirtuallyIcon.ts +17 -0
  169. package/src/WebComponent/icons/TourWithAgentIcon.ts +17 -0
  170. package/src/WebComponent/icons/XOutlineIcon.ts +8 -0
  171. package/src/WebComponent/index.ts +2 -0
  172. package/src/WebComponent/launcher/Launcher.ts +1282 -0
  173. package/src/WebComponent/launcher/launcherStyles.ts +500 -0
  174. package/src/WebComponent/launcher/mobile-launcher.ts +162 -0
  175. package/src/WebComponent/launcher/typeEmojiStyles.ts +161 -0
  176. package/src/WebComponent/launcher/typeMiniStyles.ts +60 -0
  177. package/src/WebComponent/launcher/typeMobileStyles.ts +50 -0
  178. package/src/WebComponent/leasing-chat-styles.ts +114 -0
  179. package/src/WebComponent/me-chat.ts +1262 -0
  180. package/src/WebComponent/me-select.ts +322 -0
  181. package/src/WebComponent/mega-loader.ts +36 -0
  182. package/src/WebComponent/mini-loader.ts +28 -0
  183. package/src/WebComponent/pubnub-chat-styles.ts +204 -0
  184. package/src/WebComponent/pubnub-chat.ts +928 -0
  185. package/src/WebComponent/pubnub-media.ts +208 -0
  186. package/src/WebComponent/pubnub-message-styles.ts +54 -0
  187. package/src/WebComponent/pubnub-message.ts +431 -0
  188. package/src/WebComponent/simple-launcher/simple-launcher-styles.ts +34 -0
  189. package/src/WebComponent/simple-launcher/simple-launcher.ts +100 -0
  190. package/src/WebComponent/utilities-chat.ts +270 -0
  191. package/src/WebComponent/utilities-styles.ts +110 -0
  192. package/src/WebComponent/utils.ts +82 -0
  193. package/src/analytics.ts +217 -0
  194. package/src/assetUrls.ts +6 -0
  195. package/src/disclaimers.ts +58 -0
  196. package/src/fetchBuildingABTestType.ts +21 -0
  197. package/src/fetchBuildingInfo.ts +87 -0
  198. package/src/fetchBuildingWebchatView.ts +156 -0
  199. package/src/fetchFeatureFlag.ts +250 -0
  200. package/src/fetchLeadSources.ts +98 -0
  201. package/src/fetchPhoneNumberFromSource.ts +31 -0
  202. package/src/fetchWebchatPreferences.ts +54 -0
  203. package/src/getAvailabilities.ts +179 -0
  204. package/src/getBuildingPhoneNumber.ts +26 -0
  205. package/src/getShouldAllowScheduling.ts +16 -0
  206. package/src/getShouldShowWebchat.ts +114 -0
  207. package/src/getTimezoneString.ts +39 -0
  208. package/src/globals.ts +1 -0
  209. package/src/gtm.ts +17 -0
  210. package/src/handleChatId.ts +101 -0
  211. package/src/insertDNIIntoWebsite.ts +146 -0
  212. package/src/insertLeadSourceIntoSchedulerLinks.ts +71 -0
  213. package/src/main/MEChat.test.ts +110 -0
  214. package/src/main/MEChat.ts +404 -0
  215. package/src/main/utils.ts +70 -0
  216. package/src/postLeadSources.ts +44 -0
  217. package/src/rentgrata.ts +74 -0
  218. package/src/replaceSelectButtonsWithNewLink.ts +69 -0
  219. package/src/services/fees/calculateQuote.ts +135 -0
  220. package/src/services/fees/fetchBuildingFees.ts +63 -0
  221. package/src/services/fees/fetchBuildingFloorplans.ts +74 -0
  222. package/src/services/fees/utils.ts +4 -0
  223. package/src/svgIcons.ts +14 -0
  224. package/src/themes.ts +65 -0
  225. package/src/types/rest-sdk.types.ts +13 -0
  226. package/src/types/webchat-no-show-reason.ts +6 -0
  227. package/src/utils.ts +121 -0
@@ -0,0 +1,792 @@
1
+ import { AxiosError } from "axios";
2
+ import Pubnub, { ListenerParameters, MessageEvent } from "pubnub";
3
+
4
+ import axios from "axios";
5
+ import { LogType, sendLoggingEvent } from "./analytics";
6
+ import { ChatStorageKey, createChatStorageKey } from "./handleChatId";
7
+ import LeadSourceClient from "./WebComponent/LeadSourceClient";
8
+ import { pushGtmEvent } from "./gtm";
9
+ import { isContainingEmail } from "./utils";
10
+ import { WidgetType } from "./main/MEChat";
11
+ import { BuildingWebchatView } from "./fetchBuildingWebchatView";
12
+
13
+ interface TokenResponse {
14
+ auth: {
15
+ result: {
16
+ token: string;
17
+ };
18
+ };
19
+ keys: {
20
+ subscribe_key: string;
21
+ publish_key: string;
22
+ };
23
+ }
24
+
25
+ // This is what we expect from our BE
26
+ enum MessageType {
27
+ noReply = "no-reply",
28
+ text = "text",
29
+ media = "media",
30
+ }
31
+
32
+ interface RawPubnubMessage {
33
+ channel: string;
34
+ message: {
35
+ // all the possible options from our BE
36
+ text: string;
37
+ customType: string;
38
+ messageType?: MessageType;
39
+ is_streaming?: boolean;
40
+ is_done?: boolean;
41
+ order?: number;
42
+ stream_id?: string;
43
+ media?: ChatMediaFile[];
44
+ };
45
+ publisher: string;
46
+ subscription: string;
47
+ timetoken: number;
48
+ }
49
+
50
+ export interface SimpleTextChatMessage {
51
+ timestamp: number;
52
+ message: string;
53
+ isLeadMessage: boolean;
54
+ chunks: {
55
+ text: string;
56
+ order: number;
57
+ isDone: boolean;
58
+ }[];
59
+ type: SimpleMessageTypes.text;
60
+ }
61
+
62
+ interface ChatMediaFile {
63
+ title: string | null;
64
+ description: string | null;
65
+ url: string | null;
66
+ }
67
+ export interface SimpleMediaChatMessage {
68
+ timestamp: number;
69
+ media: ChatMediaFile[];
70
+ isLeadMessage: boolean;
71
+ type: SimpleMessageTypes.media;
72
+ }
73
+
74
+ export type SimpleChatMessage = SimpleTextChatMessage | SimpleMediaChatMessage;
75
+
76
+ export const isSimpleTextChatMessage = (
77
+ message: SimpleChatMessage
78
+ ): message is SimpleTextChatMessage => {
79
+ return message.type === SimpleMessageTypes.text;
80
+ };
81
+
82
+ export const isSimpleMediaChatMessage = (
83
+ message: SimpleChatMessage
84
+ ): message is SimpleMediaChatMessage => {
85
+ return message.type === SimpleMessageTypes.media;
86
+ };
87
+
88
+ export enum SimpleMessageTypes {
89
+ text = "text",
90
+ media = "media",
91
+ }
92
+
93
+ class MyPubnub {
94
+ private apiHost = "https://app.meetelise.com";
95
+
96
+ private leadSourceClient: LeadSourceClient | null = null;
97
+ private buildingWebchatView: BuildingWebchatView | null = null;
98
+ private buildingSlug: string;
99
+ private orgSlug: string;
100
+
101
+ private eliseResponseTimeout: NodeJS.Timeout | null = null;
102
+
103
+ private isWaitingForStreamingMessages = false;
104
+
105
+ private widgetType: WidgetType = WidgetType.Default;
106
+
107
+ pubnub: Pubnub | null = null;
108
+ leadUserId = "";
109
+ channel = "";
110
+ leadSource: string | null = null;
111
+
112
+ isCurrentlyStreamingMessage = false;
113
+
114
+ chatListener:
115
+ | ((res: { messages: SimpleChatMessage[]; isLoading: boolean }) => void)
116
+ | null = null;
117
+
118
+ rawPubnubMessages: RawPubnubMessage[] = [];
119
+ simpleChatMessages: SimpleChatMessage[] = [];
120
+
121
+ listenerParams: ListenerParameters = {
122
+ message: (messageEvent: MessageEvent) => {
123
+ const newMessageEventRaw: RawPubnubMessage = {
124
+ channel: messageEvent.channel,
125
+ message: messageEvent.message,
126
+ publisher: messageEvent.publisher,
127
+ subscription: messageEvent.subscription,
128
+ timetoken: +messageEvent.timetoken,
129
+ };
130
+
131
+ this.rawPubnubMessages = [...this.rawPubnubMessages, newMessageEventRaw];
132
+
133
+ this.markEveryPreviousNoReplyMessageDone(this.rawPubnubMessages);
134
+ this.rawPubnubMessages = this.rawPubnubMessages.filter(
135
+ (m) => m.message.messageType !== MessageType.noReply
136
+ );
137
+ this.simpleChatMessages =
138
+ this.translatePubnubMessagesIntoSimpleChatMessages(
139
+ this.rawPubnubMessages
140
+ );
141
+
142
+ this.checkAndHandleDelayStreamingMessages();
143
+
144
+ const isWaitingForEliseResponse =
145
+ messageEvent.publisher !== "eliseai" &&
146
+ messageEvent.publisher !== "elise_health_ai";
147
+ this.chatListener?.({
148
+ messages: this.simpleChatMessages,
149
+ isLoading: isWaitingForEliseResponse,
150
+ });
151
+ if (!isWaitingForEliseResponse && this.eliseResponseTimeout) {
152
+ clearTimeout(this.eliseResponseTimeout);
153
+ }
154
+ this.isLoadingMessages = isWaitingForEliseResponse;
155
+
156
+ if (messageEvent.message.customType === "ai_message") {
157
+ const containsConfirmedTour = this.simpleChatMessages.some(
158
+ (message) => {
159
+ return (
160
+ message.type === SimpleMessageTypes.text &&
161
+ message.message.includes("confirmed for your tour")
162
+ );
163
+ }
164
+ );
165
+ if (containsConfirmedTour) {
166
+ pushGtmEvent("scheduledTourEvent", {
167
+ tourDetails: messageEvent.message,
168
+ buildingId: this.buildingWebchatView?.id,
169
+ buildingSlug: this.buildingSlug,
170
+ orgSlug: this.orgSlug,
171
+ leadUserId: this.leadUserId,
172
+ channel: this.channel,
173
+ });
174
+ }
175
+ }
176
+
177
+ this.checkAndHandleGTMForLeadEmail(messageEvent);
178
+ },
179
+ };
180
+ isLoadingMessages = false;
181
+ isFirstChatMessageSent = false;
182
+
183
+ private streamingMessageTimeout: NodeJS.Timeout | null = null;
184
+
185
+ constructor(
186
+ buildingSlug: string,
187
+ buildingDetails: BuildingWebchatView | null,
188
+ orgSlug: string,
189
+ leadSource: string | null = null,
190
+ leadUserId: string,
191
+ leadSourceClient: LeadSourceClient | null = null,
192
+ widgetType: WidgetType = WidgetType.Default
193
+ ) {
194
+ this.buildingSlug = buildingSlug;
195
+ this.buildingWebchatView = buildingDetails;
196
+ this.orgSlug = orgSlug;
197
+ this.leadSource = leadSource;
198
+ this.leadUserId = leadUserId;
199
+ this.channel = `webchat_${leadUserId}`;
200
+ this.leadSourceClient = leadSourceClient;
201
+ this.widgetType = widgetType;
202
+ }
203
+
204
+ private checkAndHandleDelayStreamingMessages = ({
205
+ delayToFetchHistoryInMs = 10000,
206
+ }: { delayToFetchHistoryInMs?: number } = {}): void => {
207
+ if (this.streamingMessageTimeout) {
208
+ clearTimeout(this.streamingMessageTimeout);
209
+ this.streamingMessageTimeout = null;
210
+ }
211
+
212
+ const hasStreamingMessages = this.simpleChatMessages.some((message) => {
213
+ if (!isSimpleTextChatMessage(message)) return false;
214
+ const messageChunkMarkedAsDone = message.chunks.find(
215
+ (chunk) => chunk.isDone
216
+ );
217
+ if (!messageChunkMarkedAsDone) return true;
218
+ return message.chunks.length === messageChunkMarkedAsDone.order;
219
+ });
220
+
221
+ if (hasStreamingMessages) {
222
+ this.streamingMessageTimeout = setTimeout(() => {
223
+ sendLoggingEvent({
224
+ logTitle: "PUBNUB_WARN_DELAYED_STREAMING_MESSAGES",
225
+ logData: { channel: this.channel },
226
+ logType: LogType.warn,
227
+ buildingSlug: this.buildingSlug,
228
+ orgSlug: this.orgSlug,
229
+ });
230
+ // eslint-disable-next-line no-console
231
+ console.warn(
232
+ `PUBNUB_WARN_DELAYED_STREAMING_MESSAGES: ${delayToFetchHistoryInMs}`
233
+ );
234
+ this.getChannelHistory();
235
+ }, delayToFetchHistoryInMs);
236
+ }
237
+ };
238
+
239
+ checkAndHandleGTMForLeadEmail = (messageEvent: Pubnub.MessageEvent): void => {
240
+ try {
241
+ if (messageEvent.message.customType !== "lead_message") {
242
+ return;
243
+ }
244
+ if (!isContainingEmail(messageEvent.message.text)) {
245
+ return;
246
+ }
247
+ pushGtmEvent("leadProvidedEmail", {
248
+ buildingId: this.buildingWebchatView?.id,
249
+ buildingSlug: this.buildingSlug,
250
+ orgSlug: this.orgSlug,
251
+ leadUserId: this.leadUserId,
252
+ channel: this.channel,
253
+ });
254
+ } catch (error) {
255
+ return;
256
+ }
257
+ };
258
+
259
+ addChatListener(
260
+ listener: (response: {
261
+ messages: SimpleChatMessage[];
262
+ isLoading: boolean;
263
+ }) => void
264
+ ): void {
265
+ this.chatListener = listener;
266
+ }
267
+
268
+ async initializePubnub(
269
+ chatStorageKey: ChatStorageKey
270
+ ): Promise<Pubnub | undefined> {
271
+ if (!chatStorageKey.leadId) return;
272
+ this.leadUserId = chatStorageKey.leadId;
273
+
274
+ const pubnubToken = await this.fetchToken(this.leadUserId, this.channel);
275
+ if (!pubnubToken) return;
276
+
277
+ // These keys are OK to expose live, the authKey generated by the BE is what
278
+ // is used to authenticate the user. Ideally, should also add rate limiting
279
+ // and/or IP whitelisting to the BE endpoint that generates the token!!
280
+ this.pubnub = new Pubnub({
281
+ publishKey: pubnubToken.keys.publish_key,
282
+ subscribeKey: pubnubToken.keys.subscribe_key,
283
+ userId: this.leadUserId,
284
+ authKey: pubnubToken.auth.result.token,
285
+ origin: "meetelise.pubnubapi.com",
286
+ });
287
+ this.withAuthToken(() => new Promise(() => this.handleChatListeners()));
288
+ await this.withAuthToken(() => this.getChannelHistory());
289
+ return this.pubnub;
290
+ }
291
+ async fetchToken(
292
+ lead: string,
293
+ channel: string
294
+ ): Promise<TokenResponse | null> {
295
+ try {
296
+ const response = await axios.get(
297
+ `${
298
+ this.apiHost
299
+ }/platformApi/webchat/pn/request-token?user_id=${lead}&channel=${channel}&${
300
+ this.widgetType === "healthcare" ? `industry=health_care` : ""
301
+ }`
302
+ );
303
+ return response.data;
304
+ } catch (error) {
305
+ if (this.buildingWebchatView) {
306
+ sendLoggingEvent({
307
+ logTitle: "PUBNUB_ERROR_FETCHING_TOKEN",
308
+ logData: { error },
309
+ logType: LogType.error,
310
+ buildingSlug: this.buildingSlug,
311
+ orgSlug: this.orgSlug,
312
+ });
313
+ }
314
+ }
315
+ return null;
316
+ }
317
+ async fetchChannelExists(channel: string): Promise<boolean> {
318
+ try {
319
+ const response = await axios.get(
320
+ `${this.apiHost}/platformApi/webchat/check-channel-exists?channel_name=${channel}`
321
+ );
322
+ return response.data;
323
+ } catch (error) {
324
+ if (this.buildingWebchatView) {
325
+ sendLoggingEvent({
326
+ logTitle: "PUBNUB_ERROR_FETCHING_CHANNEL_EXISTS",
327
+ logData: { error },
328
+ logType: LogType.error,
329
+ buildingSlug: this.buildingSlug,
330
+ orgSlug: this.orgSlug,
331
+ });
332
+ }
333
+ }
334
+ return false;
335
+ }
336
+
337
+ async withAuthToken(apiRequestFunc: () => Promise<void>): Promise<void> {
338
+ try {
339
+ await apiRequestFunc();
340
+ } catch (error: unknown) {
341
+ // only want to retry with new token if the error is a 403
342
+ if (
343
+ error instanceof AxiosError &&
344
+ error &&
345
+ error.response &&
346
+ error.response.status === 403
347
+ ) {
348
+ try {
349
+ if (!this.pubnub || !this.leadUserId || !this.channel) return;
350
+
351
+ const newToken = await this.fetchToken(this.leadUserId, this.channel);
352
+ if (!newToken) return;
353
+
354
+ this.pubnub.setAuthKey(newToken.auth.result.token);
355
+
356
+ await apiRequestFunc();
357
+ } catch (retryError) {
358
+ if (this.buildingWebchatView) {
359
+ sendLoggingEvent({
360
+ logTitle: "PUBNUB_ERROR_REFETCHING_TOKEN",
361
+ logData: {
362
+ retryError,
363
+ },
364
+ logType: LogType.error,
365
+ buildingSlug: this.buildingSlug,
366
+ orgSlug: this.orgSlug,
367
+ });
368
+ }
369
+ }
370
+ }
371
+ }
372
+ }
373
+
374
+ private markEveryPreviousNoReplyMessageDone = (
375
+ messages: RawPubnubMessage[]
376
+ ): void => {
377
+ for (let i = messages.length - 1; i >= 0; i--) {
378
+ const currMsg = messages[i]?.message;
379
+ const prevMsg = i > 0 && messages[i - 1]?.message;
380
+
381
+ if (currMsg?.messageType === MessageType.noReply && prevMsg) {
382
+ prevMsg.is_done = true;
383
+ }
384
+ }
385
+ };
386
+
387
+ private checkMessageChunkValid = (message: RawPubnubMessage): boolean => {
388
+ try {
389
+ return (
390
+ !!message.message &&
391
+ !!message.message.text &&
392
+ message.message.text.length > 0 &&
393
+ !!message.message.customType
394
+ );
395
+ } catch (error) {
396
+ return false;
397
+ }
398
+ };
399
+
400
+ async getChannelHistory(maxTotalMessageChunksToFetch = 1500): Promise<void> {
401
+ try {
402
+ let allMessages: RawPubnubMessage[] = [];
403
+ let startTimeToken: string | number | null = null;
404
+ const maxCountPerFetch = 100;
405
+ for (
406
+ let totalCount = 0;
407
+ totalCount < maxTotalMessageChunksToFetch;
408
+ totalCount += maxCountPerFetch
409
+ ) {
410
+ const response: Pubnub.FetchMessagesResponse = await new Promise(
411
+ (resolve, reject) => {
412
+ if (!this.pubnub || !this.channel) return [];
413
+ const countToFetch = Math.min(
414
+ maxCountPerFetch,
415
+ maxTotalMessageChunksToFetch - totalCount
416
+ );
417
+ this.pubnub.fetchMessages(
418
+ {
419
+ channels: [this.channel],
420
+ count: countToFetch,
421
+ start: startTimeToken ?? undefined,
422
+ },
423
+ (status, response) => {
424
+ if (status.error) {
425
+ reject(status);
426
+ } else {
427
+ resolve(response);
428
+ }
429
+ }
430
+ );
431
+ }
432
+ );
433
+
434
+ const messages = response.channels[this.channel];
435
+ if (!messages) {
436
+ break;
437
+ }
438
+ messages.sort((a, b) => +a.timetoken - +b.timetoken);
439
+
440
+ if (!messages || messages.length === 0) {
441
+ break;
442
+ }
443
+ if (this.channel && Object.keys(response.channels).length !== 0) {
444
+ const currentChannelMessages = response.channels[this.channel];
445
+ const parsedCurrentChannelMessages: RawPubnubMessage[] = [];
446
+ currentChannelMessages.forEach((message) => {
447
+ if (message.uuid) {
448
+ parsedCurrentChannelMessages.push({
449
+ channel: message.channel,
450
+ message: message.message,
451
+ publisher: message.uuid,
452
+ subscription: message.channel,
453
+ timetoken: +message.timetoken,
454
+ });
455
+ }
456
+ });
457
+
458
+ this.markEveryPreviousNoReplyMessageDone(
459
+ parsedCurrentChannelMessages
460
+ );
461
+ allMessages = allMessages.concat(
462
+ parsedCurrentChannelMessages.filter(
463
+ (m) => m.message.messageType !== MessageType.noReply
464
+ )
465
+ );
466
+ }
467
+ startTimeToken = messages[0].timetoken;
468
+
469
+ if (
470
+ allMessages.length >= maxTotalMessageChunksToFetch ||
471
+ messages.length < maxCountPerFetch
472
+ ) {
473
+ break;
474
+ }
475
+ }
476
+ this.rawPubnubMessages = allMessages.slice(
477
+ 0,
478
+ maxTotalMessageChunksToFetch
479
+ );
480
+
481
+ this.simpleChatMessages =
482
+ this.translatePubnubMessagesIntoSimpleChatMessages(
483
+ this.rawPubnubMessages
484
+ );
485
+
486
+ this.chatListener?.({
487
+ messages: this.simpleChatMessages,
488
+ isLoading: false,
489
+ });
490
+ } catch (error) {
491
+ if (this.buildingWebchatView) {
492
+ sendLoggingEvent({
493
+ logTitle: "PUBNUB_WARN_FETCHING_HISTORY",
494
+ logData: { error },
495
+ logType: LogType.warn,
496
+ buildingSlug: this.buildingSlug,
497
+ orgSlug: this.orgSlug,
498
+ });
499
+ }
500
+ }
501
+ }
502
+
503
+ handleChatListeners = (): void => {
504
+ if (!this.pubnub || !this.channel) return;
505
+ try {
506
+ this.pubnub.subscribe({ channels: [this.channel] });
507
+ this.pubnub.addListener(this.listenerParams);
508
+ } catch (error) {
509
+ if (this.buildingWebchatView) {
510
+ sendLoggingEvent({
511
+ logTitle: "PUBNUB_ERROR_ADDING_LISTENER",
512
+ logData: {
513
+ error,
514
+ channel: this.channel,
515
+ leadUserId: this.leadUserId,
516
+ website: location.href,
517
+ },
518
+ logType: LogType.error,
519
+ buildingSlug: this.buildingSlug,
520
+ orgSlug: this.orgSlug,
521
+ });
522
+ }
523
+ }
524
+ };
525
+ handleDisconnect = (): void => {
526
+ if (this.eliseResponseTimeout) {
527
+ clearTimeout(this.eliseResponseTimeout);
528
+ this.eliseResponseTimeout = null;
529
+ }
530
+ if (this.streamingMessageTimeout) {
531
+ clearTimeout(this.streamingMessageTimeout);
532
+ this.streamingMessageTimeout = null;
533
+ }
534
+ this.removeChatListeners();
535
+ };
536
+ removeChatListeners = (): void => {
537
+ if (this.pubnub && this.channel) {
538
+ this.pubnub.unsubscribe({ channels: [this.channel] });
539
+ this.pubnub.removeListener(this.listenerParams);
540
+ }
541
+ };
542
+
543
+ sendMessage = async (message: string): Promise<void> => {
544
+ if (message) {
545
+ if (!this.pubnub) {
546
+ // ONLY create/gets a chat session if user actually wants to chat
547
+ const chatStorageKey = createChatStorageKey(
548
+ this.buildingSlug,
549
+ true,
550
+ this.leadUserId
551
+ );
552
+ const myPubnub = await this.initializePubnub(chatStorageKey);
553
+ if (!myPubnub) return;
554
+ }
555
+
556
+ await this.withAuthToken(async () => {
557
+ if (!this.pubnub || !this.channel) return;
558
+
559
+ if (this.eliseResponseTimeout) {
560
+ clearTimeout(this.eliseResponseTimeout);
561
+ this.eliseResponseTimeout = null;
562
+ }
563
+ this.eliseResponseTimeout = setTimeout(() => {
564
+ // eslint-disable-next-line no-console
565
+ console.error("Elise AI did not respond in time...");
566
+ if (this.buildingWebchatView) {
567
+ sendLoggingEvent({
568
+ logTitle: "PUBNUB_ERROR_ELISEAI_MESSAGE_TIMEOUT",
569
+ logData: {
570
+ channel: this.channel,
571
+ leadUserId: this.leadUserId,
572
+ message,
573
+ },
574
+ logType: LogType.error,
575
+ buildingSlug: this.buildingSlug,
576
+ orgSlug: this.orgSlug,
577
+ });
578
+ }
579
+ }, 90000); // if after 90 seconds, no message - we log error
580
+
581
+ if (this.simpleChatMessages.length === 0) {
582
+ this.leadSourceClient?.checkAndHandleForLogLeadSource({
583
+ webchatAction: "chat",
584
+ stateId: null,
585
+ });
586
+ }
587
+
588
+ if (this.widgetType === WidgetType.Utilities) {
589
+ await this.pubnub.publish({
590
+ channel: this.channel,
591
+ message: {
592
+ text: message,
593
+ customType: "lead_message",
594
+ widgetType: this.widgetType,
595
+ isDevState: this.shouldCreateAsDevState(),
596
+ orgSlug: this.orgSlug,
597
+ },
598
+ });
599
+ } else {
600
+ await this.pubnub.publish({
601
+ channel: this.channel,
602
+ message: {
603
+ text: message,
604
+ customType: "lead_message",
605
+ buildingId: this.buildingWebchatView?.id,
606
+ buildingSlug: this.buildingSlug,
607
+ userId: this.buildingWebchatView?.userId, // this userid is actually the AI user!
608
+ leadSource: this.leadSource,
609
+ widgetType: this.widgetType,
610
+ isDevState: this.shouldCreateAsDevState(),
611
+ },
612
+ });
613
+ }
614
+
615
+ if (!this.isFirstChatMessageSent) {
616
+ pushGtmEvent("firstChatMessageSent", {
617
+ message,
618
+ buildingId: this.buildingWebchatView?.id,
619
+ buildingSlug: this.buildingSlug,
620
+ orgSlug: this.orgSlug,
621
+ leadUserId: this.leadUserId,
622
+ channel: this.channel,
623
+ });
624
+ this.isFirstChatMessageSent = true;
625
+ }
626
+
627
+ pushGtmEvent("chatMessageSent", {
628
+ message,
629
+ buildingId: this.buildingWebchatView?.id,
630
+ buildingSlug: this.buildingSlug,
631
+ orgSlug: this.orgSlug,
632
+ leadUserId: this.leadUserId,
633
+ channel: this.channel,
634
+ });
635
+ });
636
+
637
+ if (this.isLoadingMessages === false) this.isLoadingMessages = true;
638
+ }
639
+ };
640
+ private shouldCreateAsDevState(): boolean {
641
+ return (
642
+ location.href.startsWith("https://app.meetelise.com/settings/widgets") ||
643
+ location.href.includes("demo/utilities")
644
+ );
645
+ }
646
+
647
+ isLeadMessage = (message: RawPubnubMessage): boolean =>
648
+ message.publisher.includes("lead_") &&
649
+ message.message.customType === "lead_message";
650
+
651
+ private removeDuplicatedRawPubnubMessages = (
652
+ messages: RawPubnubMessage[]
653
+ ): RawPubnubMessage[] => {
654
+ /**
655
+ * Occassionally, we get some duplicates from pubnub.
656
+ * We remove them by using a set of keys made from the streamId, order, and messageType.
657
+ *
658
+ * Not 100% guaranteed its on pubnub's side
659
+ */
660
+
661
+ const seen = new Set();
662
+ const uniqueMessages = messages.filter((message) => {
663
+ if (
664
+ message.message.customType === "lead_message" ||
665
+ !message.message.stream_id ||
666
+ !message.message.order
667
+ )
668
+ return true;
669
+ const key = `${message.message.stream_id}-${message.message.order}-${message.message.messageType}`;
670
+ if (seen.has(key)) {
671
+ sendLoggingEvent({
672
+ logTitle: "PUBNUB_WARN_DUPLICATE_MESSAGE",
673
+ logData: { message, channel: this.channel },
674
+ logType: LogType.warn,
675
+ buildingSlug: this.buildingSlug,
676
+ orgSlug: this.orgSlug,
677
+ });
678
+ return false;
679
+ }
680
+ seen.add(key);
681
+ return true;
682
+ });
683
+ return uniqueMessages;
684
+ };
685
+
686
+ translatePubnubMessagesIntoSimpleChatMessages = (
687
+ messages: RawPubnubMessage[]
688
+ ): SimpleChatMessage[] => {
689
+ const parsedMessages: SimpleChatMessage[] = [];
690
+ const streamingIdToMessageChunks: {
691
+ [streamId: string]: RawPubnubMessage[];
692
+ } = {};
693
+
694
+ const uniqueMessages = this.removeDuplicatedRawPubnubMessages(messages);
695
+
696
+ uniqueMessages.forEach((message: RawPubnubMessage) => {
697
+ // Translate media messages
698
+ if (message.message.messageType === "media") {
699
+ parsedMessages.push({
700
+ timestamp: message.timetoken,
701
+ media: message.message.media ?? [],
702
+ isLeadMessage: this.isLeadMessage(message),
703
+ type: SimpleMessageTypes.media,
704
+ });
705
+ } else if (
706
+ // Translate non-streaming messages
707
+ !message.message.stream_id ||
708
+ (message.message.is_done && message.message.order === 0)
709
+ ) {
710
+ parsedMessages.push({
711
+ timestamp: message.timetoken,
712
+ message: message.message.text,
713
+ isLeadMessage: this.isLeadMessage(message),
714
+ chunks: [
715
+ {
716
+ text: message.message.text,
717
+ order: 0,
718
+ isDone: true,
719
+ },
720
+ ],
721
+ type: SimpleMessageTypes.text,
722
+ });
723
+ } else {
724
+ // Translate streaming messages
725
+ if (streamingIdToMessageChunks[message.message.stream_id]) {
726
+ streamingIdToMessageChunks[message.message.stream_id].push(message);
727
+ } else {
728
+ streamingIdToMessageChunks[message.message.stream_id] = [message];
729
+ }
730
+ }
731
+ });
732
+ Object.keys(streamingIdToMessageChunks).forEach((streamId) => {
733
+ const messages = streamingIdToMessageChunks[streamId];
734
+ const sortedChunks = this.getConsecutiveChunks(
735
+ messages.sort((a, b) => (a.message.order ?? 0) - (b.message.order ?? 0))
736
+ );
737
+ const text = sortedChunks.map((message) => message.message.text).join("");
738
+ const firstMessage = sortedChunks[0];
739
+ const newMessage: SimpleTextChatMessage = {
740
+ timestamp: firstMessage.timetoken,
741
+ message: text,
742
+ isLeadMessage: this.isLeadMessage(firstMessage),
743
+ chunks: sortedChunks.map((message) => ({
744
+ text: message.message.text,
745
+ order: message.message.order ?? 0,
746
+ isDone: message.message.is_done ?? false,
747
+ })),
748
+ type: SimpleMessageTypes.text,
749
+ };
750
+ this.isCurrentlyStreamingMessage =
751
+ this.isMessageStillStreamingChunks(sortedChunks);
752
+ parsedMessages.push(newMessage);
753
+ });
754
+ parsedMessages.sort((a, b) => a.timestamp - b.timestamp);
755
+ return parsedMessages;
756
+ };
757
+
758
+ private getConsecutiveChunks = (
759
+ rawPubnubMessages: RawPubnubMessage[]
760
+ ): RawPubnubMessage[] => {
761
+ const sortedRawPubnubMessages = rawPubnubMessages.sort(
762
+ (a, b) => (a.message.order ?? 0) - (b.message.order ?? 0)
763
+ );
764
+ const consecutiveMessages = [];
765
+
766
+ // get the LOWEST order that exists
767
+ let expectedOrder = sortedRawPubnubMessages[0].message.order ?? 0;
768
+ for (const message of sortedRawPubnubMessages) {
769
+ if ((message.message.order ?? -1) === expectedOrder) {
770
+ consecutiveMessages.push(message);
771
+ expectedOrder++;
772
+ } else {
773
+ break;
774
+ }
775
+ }
776
+ return consecutiveMessages;
777
+ };
778
+
779
+ private isMessageStillStreamingChunks = (
780
+ rawPubnubMessages: RawPubnubMessage[]
781
+ ): boolean => {
782
+ const messageChunkMarkedAsDone = rawPubnubMessages.find(
783
+ (message) => message.message.is_done
784
+ );
785
+ if (!messageChunkMarkedAsDone) return true;
786
+
787
+ // We check to see if ALL the chunks have been received
788
+ return rawPubnubMessages.length === messageChunkMarkedAsDone.message.order;
789
+ };
790
+ }
791
+
792
+ export default MyPubnub;