@meetelise/chat 1.20.91 → 1.20.93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/MyPubnub.ts CHANGED
@@ -9,6 +9,7 @@ import addHours from "date-fns/addHours";
9
9
  import isAfter from "date-fns/isAfter";
10
10
  import formatISO from "date-fns/formatISO";
11
11
  import isBefore from "date-fns/isBefore";
12
+ import { LogType, sendLoggingEvent } from "./analytics";
12
13
 
13
14
  interface TokenResponse {
14
15
  auth: {
@@ -44,8 +45,11 @@ class MyPubnub {
44
45
 
45
46
  private building: Building | null = null;
46
47
  private buildingSlug: string;
48
+ private orgSlug: string;
47
49
  private ttlHours = 24;
48
50
 
51
+ private eliseResponseTimeout: NodeJS.Timeout | null = null;
52
+
49
53
  pubnub: Pubnub | null = null;
50
54
  leadUserId = "";
51
55
  channel = "";
@@ -72,14 +76,22 @@ class MyPubnub {
72
76
  messages: this.messages,
73
77
  isLoading: isWaitingForEliseResponse,
74
78
  });
79
+ if (!isWaitingForEliseResponse && this.eliseResponseTimeout) {
80
+ clearTimeout(this.eliseResponseTimeout);
81
+ }
75
82
  this.isLoadingMessages = isWaitingForEliseResponse;
76
83
  },
77
84
  };
78
85
  isLoadingMessages = false;
79
86
 
80
- constructor(buildingSlug: string, buildingDetails: Building) {
87
+ constructor(
88
+ buildingSlug: string,
89
+ buildingDetails: Building,
90
+ orgSlug: string
91
+ ) {
81
92
  this.buildingSlug = buildingSlug;
82
93
  this.building = buildingDetails;
94
+ this.orgSlug = orgSlug;
83
95
  }
84
96
 
85
97
  addChatListener(
@@ -95,7 +107,17 @@ class MyPubnub {
95
107
  const storedChatKeyValues = this.getChatStorageKey();
96
108
  if (!storedChatKeyValues.leadId) {
97
109
  // eslint-disable-next-line no-console
98
- console.error("Error getting chat storage key...");
110
+ sendLoggingEvent({
111
+ logTitle: "PUBNUB_ERROR_FETCHING_STORAGE_KEY",
112
+ logData: {
113
+ channel: this.channel,
114
+ eliseaiLocalStorageValue: storedChatKeyValues,
115
+ leadUserId: this.leadUserId,
116
+ },
117
+ logType: LogType.warn,
118
+ buildingSlug: this.buildingSlug,
119
+ orgSlug: this.orgSlug,
120
+ });
99
121
  return;
100
122
  }
101
123
 
@@ -115,6 +137,7 @@ class MyPubnub {
115
137
  }
116
138
 
117
139
  const pubnubToken = await this.fetchToken(this.leadUserId, this.channel);
140
+ if (!pubnubToken) return;
118
141
 
119
142
  // These keys are OK to expose live, the authKey generated by the BE is what
120
143
  // is used to authenticate the user. Ideally, should also add rate limiting
@@ -129,17 +152,42 @@ class MyPubnub {
129
152
  await this.withAuthToken(() => this.getChannelHistory());
130
153
  return this.pubnub;
131
154
  }
132
- async fetchToken(lead: string, channel: string): Promise<TokenResponse> {
133
- const response = await axios.get(
134
- `${this.apiHost}/platformApi/webchat/pn/request-token?user_id=${lead}&channel=${channel}`
135
- );
136
- return response.data;
155
+ async fetchToken(
156
+ lead: string,
157
+ channel: string
158
+ ): Promise<TokenResponse | null> {
159
+ try {
160
+ const response = await axios.get(
161
+ `${this.apiHost}/platformApi/webchat/pn/request-token?user_id=${lead}&channel=${channel}`
162
+ );
163
+ return response.data;
164
+ } catch (error) {
165
+ sendLoggingEvent({
166
+ logTitle: "PUBNUB_ERROR_FETCHING_TOKEN",
167
+ logData: { error },
168
+ logType: LogType.error,
169
+ buildingSlug: this.buildingSlug,
170
+ orgSlug: this.orgSlug,
171
+ });
172
+ }
173
+ return null;
137
174
  }
138
175
  async fetchChannelExists(channel: string): Promise<boolean> {
139
- const response = await axios.get(
140
- `${this.apiHost}/platformApi/webchat/check-channel-exists?channel_name=${channel}`
141
- );
142
- return response.data;
176
+ try {
177
+ const response = await axios.get(
178
+ `${this.apiHost}/platformApi/webchat/check-channel-exists?channel_name=${channel}`
179
+ );
180
+ return response.data;
181
+ } catch (error) {
182
+ sendLoggingEvent({
183
+ logTitle: "PUBNUB_ERROR_FETCHING_CHANNEL_EXISTS",
184
+ logData: { error },
185
+ logType: LogType.error,
186
+ buildingSlug: this.buildingSlug,
187
+ orgSlug: this.orgSlug,
188
+ });
189
+ }
190
+ return false;
143
191
  }
144
192
 
145
193
  async withAuthToken(apiRequestFunc: () => Promise<void>): Promise<void> {
@@ -163,7 +211,15 @@ class MyPubnub {
163
211
 
164
212
  await apiRequestFunc();
165
213
  } catch (retryError) {
166
- //onsole.error(retryError);
214
+ sendLoggingEvent({
215
+ logTitle: "PUBNUB_ERROR_REFETCHING_TOKEN",
216
+ logData: {
217
+ retryError,
218
+ },
219
+ logType: LogType.error,
220
+ buildingSlug: this.buildingSlug,
221
+ orgSlug: this.orgSlug,
222
+ });
167
223
  }
168
224
  }
169
225
  }
@@ -181,6 +237,17 @@ class MyPubnub {
181
237
  },
182
238
  (status, response) => {
183
239
  if (status.error) {
240
+ sendLoggingEvent({
241
+ logTitle: "PUBNUB_ERROR_FETCHING_HISTORY",
242
+ logData: {
243
+ channel: this.channel,
244
+ status: status,
245
+ response: response,
246
+ },
247
+ logType: LogType.error,
248
+ buildingSlug: this.buildingSlug,
249
+ orgSlug: this.orgSlug,
250
+ });
184
251
  reject(status);
185
252
  } else {
186
253
  resolve(response);
@@ -208,14 +275,35 @@ class MyPubnub {
208
275
  this.chatListener?.({ messages: this.messages, isLoading: false });
209
276
  }
210
277
  } catch (error) {
211
- // Handle the error here
278
+ sendLoggingEvent({
279
+ logTitle: "PUBNUB_ERROR_FETCHING_HISTORY",
280
+ logData: { error },
281
+ logType: LogType.error,
282
+ buildingSlug: this.buildingSlug,
283
+ orgSlug: this.orgSlug,
284
+ });
212
285
  }
213
286
  }
214
287
 
215
288
  handleChatListeners = (): void => {
216
289
  if (!this.pubnub || !this.channel) return;
217
- this.pubnub.subscribe({ channels: [this.channel] });
218
- this.pubnub.addListener(this.listenerParams);
290
+ try {
291
+ this.pubnub.subscribe({ channels: [this.channel] });
292
+ this.pubnub.addListener(this.listenerParams);
293
+ } catch (error) {
294
+ sendLoggingEvent({
295
+ logTitle: "PUBNUB_ERROR_ADDING_LISTENER",
296
+ logData: {
297
+ error,
298
+ channel: this.channel,
299
+ leadUserId: this.leadUserId,
300
+ website: location.href,
301
+ },
302
+ logType: LogType.error,
303
+ buildingSlug: this.buildingSlug,
304
+ orgSlug: this.orgSlug,
305
+ });
306
+ }
219
307
  };
220
308
  removeChatListeners = (): void => {
221
309
  if (this.pubnub && this.channel) {
@@ -234,6 +322,27 @@ class MyPubnub {
234
322
 
235
323
  await this.withAuthToken(async () => {
236
324
  if (!this.pubnub || !this.channel) return;
325
+
326
+ if (this.eliseResponseTimeout) {
327
+ clearTimeout(this.eliseResponseTimeout);
328
+ this.eliseResponseTimeout = null;
329
+ }
330
+ this.eliseResponseTimeout = setTimeout(() => {
331
+ // eslint-disable-next-line no-console
332
+ console.error("Elise AI did not respond in time...");
333
+ sendLoggingEvent({
334
+ logTitle: "PUBNUB_ERROR_ELISEAI_MESSAGE_TIMEOUT",
335
+ logData: {
336
+ channel: this.channel,
337
+ leadUserId: this.leadUserId,
338
+ message,
339
+ },
340
+ logType: LogType.error,
341
+ buildingSlug: this.buildingSlug,
342
+ orgSlug: this.orgSlug,
343
+ });
344
+ }, 45000); // if after 45 seconds, no message - we log error
345
+
237
346
  await this.pubnub.publish({
238
347
  channel: this.channel,
239
348
  message: {
@@ -492,7 +492,11 @@ export class MEChat extends LitElement {
492
492
  return;
493
493
  }
494
494
 
495
- this.myPubnub = new MyPubnub(this.buildingSlug, this.building);
495
+ this.myPubnub = new MyPubnub(
496
+ this.buildingSlug,
497
+ this.building,
498
+ this.orgSlug
499
+ );
496
500
  if (this.myPubnub.isChatKeyValid(this.myPubnub.getChatStorageKey(false))) {
497
501
  await this.myPubnub.initializePubnub();
498
502
  }
@@ -190,7 +190,7 @@ export const pubnubChatStyles = css`
190
190
  border: none;
191
191
  color: white;
192
192
  background: none;
193
- z-index: 100001;
193
+ z-index: 1000000000000000000000000001;
194
194
  }
195
195
  #message-input:focus {
196
196
  outline: none;
package/src/analytics.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import axios from "axios";
2
+
1
3
  declare global {
2
4
  interface Window {
3
5
  RCTPCampaign?: { CampaignDetails: { Source: string } };
@@ -86,3 +88,40 @@ export const getCampaignSources = (): CampaignSources => {
86
88
  yardiCampaignSource,
87
89
  };
88
90
  };
91
+
92
+ export enum LogType {
93
+ error = "error",
94
+ info = "info",
95
+ warn = "warn",
96
+ }
97
+
98
+ export const sendLoggingEvent = async ({
99
+ logType,
100
+ buildingSlug,
101
+ orgSlug,
102
+ logTitle,
103
+ logData,
104
+ }: {
105
+ logType?: LogType;
106
+ buildingSlug?: string;
107
+ orgSlug?: string;
108
+ logTitle: string;
109
+ logData: Record<string, unknown>;
110
+ }): Promise<void> => {
111
+ const host = "https://app.meetelise.com";
112
+ await axios.post(
113
+ `${host}/platformApi/webchat/logging`,
114
+ {
115
+ ...logData,
116
+ },
117
+ {
118
+ headers: {
119
+ "Content-Type": "application/json",
120
+ "building-slug": buildingSlug ?? "",
121
+ "org-slug": orgSlug ?? "",
122
+ "log-type": logType ?? LogType.error,
123
+ "log-title": logTitle.toUpperCase().replace(/\s/g, "_"),
124
+ },
125
+ }
126
+ );
127
+ };
@@ -1,3 +1,5 @@
1
+ import axios from "axios";
2
+
1
3
  export interface LabeledOption {
2
4
  label: string;
3
5
  value: string | number;
@@ -49,20 +51,19 @@ export default async function fetchBuildingInfo(
49
51
  buildingSlug: string
50
52
  ): Promise<Building> {
51
53
  const host = "https://app.meetelise.com";
52
- const url = `${host}/api/pub/v1/organization/${orgSlug}/building/${buildingSlug}`;
53
- const response = await fetch(url);
54
- const building: Building = await response.json();
54
+ const buildingUrl = `${host}/api/pub/v1/organization/${orgSlug}/building/${buildingSlug}`;
55
+ const unitsUrl = `${host}/eliseCrmApi/pub/building/${buildingSlug}/units`;
56
+ const layoutsUrl = `${host}/eliseCrmApi/pub/building/${buildingSlug}/layouts`;
57
+
58
+ const [buildingResponse, unitsResponse, layoutsResponse] = await Promise.all([
59
+ axios.get(buildingUrl),
60
+ axios.get(unitsUrl),
61
+ axios.get(layoutsUrl),
62
+ ]);
55
63
 
56
- // HACK
57
- // We will fetch these units/layouts from elise-crm-api and supplement the DTO
58
- const unitsResponse = await fetch(
59
- `${host}/eliseCrmApi/pub/building/${buildingSlug}/units?showAvailableOnly=true`
60
- );
61
- const units: UnitV2[] = await unitsResponse.json();
62
- const layoutsResponse = await fetch(
63
- `${host}/eliseCrmApi/pub/building/${buildingSlug}/layouts`
64
- );
65
- const layouts: string[] = await layoutsResponse.json();
64
+ const building: Building = buildingResponse.data;
65
+ const units: UnitV2[] = unitsResponse.data;
66
+ const layouts: string[] = layoutsResponse.data;
66
67
 
67
68
  building.unitOptionsV2 = units;
68
69
  building.layoutOptionsV2 = layouts;