@meetelise/chat 1.20.90 → 1.20.92
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/package.json +1 -1
- package/public/dist/index.js +79 -77
- package/src/MyPubnub.ts +127 -17
- package/src/WebComponent/me-chat.ts +13 -2
- package/src/WebComponent/pubnub-chat-styles.ts +4 -2
- package/src/WebComponent/pubnub-chat.ts +0 -5
- package/src/analytics.ts +39 -0
- package/src/fetchBuildingInfo.ts +14 -13
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(
|
|
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
|
-
|
|
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(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
218
|
-
|
|
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,23 @@ class MyPubnub {
|
|
|
234
322
|
|
|
235
323
|
await this.withAuthToken(async () => {
|
|
236
324
|
if (!this.pubnub || !this.channel) return;
|
|
325
|
+
|
|
326
|
+
this.eliseResponseTimeout = setTimeout(() => {
|
|
327
|
+
// eslint-disable-next-line no-console
|
|
328
|
+
console.error("Elise AI did not respond in time...");
|
|
329
|
+
sendLoggingEvent({
|
|
330
|
+
logTitle: "PUBNUB_ERROR_ELISEAI_MESSAGE_TIMEOUT",
|
|
331
|
+
logData: {
|
|
332
|
+
channel: this.channel,
|
|
333
|
+
leadUserId: this.leadUserId,
|
|
334
|
+
message,
|
|
335
|
+
},
|
|
336
|
+
logType: LogType.error,
|
|
337
|
+
buildingSlug: this.buildingSlug,
|
|
338
|
+
orgSlug: this.orgSlug,
|
|
339
|
+
});
|
|
340
|
+
}, 30000); // if after 30 seconds, no message - we log error
|
|
341
|
+
|
|
237
342
|
await this.pubnub.publish({
|
|
238
343
|
channel: this.channel,
|
|
239
344
|
message: {
|
|
@@ -269,7 +374,7 @@ class MyPubnub {
|
|
|
269
374
|
buildingSlug: this.buildingSlug,
|
|
270
375
|
};
|
|
271
376
|
};
|
|
272
|
-
getChatStorageKey = (): ChatInfo => {
|
|
377
|
+
getChatStorageKey = (createNewIfNotExist = true): ChatInfo => {
|
|
273
378
|
const eliseaiLocalStorageValue = localStorage.getItem(
|
|
274
379
|
"com.eliseai.webchat.slug=" + this.buildingSlug
|
|
275
380
|
);
|
|
@@ -300,7 +405,12 @@ class MyPubnub {
|
|
|
300
405
|
}
|
|
301
406
|
}
|
|
302
407
|
|
|
303
|
-
return this.createChatStorageKey();
|
|
408
|
+
if (createNewIfNotExist) return this.createChatStorageKey();
|
|
409
|
+
return {
|
|
410
|
+
leadId: null,
|
|
411
|
+
timestamp: null,
|
|
412
|
+
buildingSlug: null,
|
|
413
|
+
};
|
|
304
414
|
};
|
|
305
415
|
isChatKeyValid = (storageValueDeconstructed: ChatInfo): boolean => {
|
|
306
416
|
if (
|
|
@@ -484,9 +484,20 @@ export class MEChat extends LitElement {
|
|
|
484
484
|
initializePubnubVariables = async (): Promise<void> => {
|
|
485
485
|
await this.setBuildingDerivedInfo();
|
|
486
486
|
if (!this.building) return;
|
|
487
|
+
if (this.building.conversationMaintenanceMode) {
|
|
488
|
+
// eslint-disable-next-line no-console
|
|
489
|
+
console.warn(
|
|
490
|
+
"MeetElise Chat is in maintenance mode. Chat icon will not appear."
|
|
491
|
+
);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
487
494
|
|
|
488
|
-
this.myPubnub = new MyPubnub(
|
|
489
|
-
|
|
495
|
+
this.myPubnub = new MyPubnub(
|
|
496
|
+
this.buildingSlug,
|
|
497
|
+
this.building,
|
|
498
|
+
this.orgSlug
|
|
499
|
+
);
|
|
500
|
+
if (this.myPubnub.isChatKeyValid(this.myPubnub.getChatStorageKey(false))) {
|
|
490
501
|
await this.myPubnub.initializePubnub();
|
|
491
502
|
}
|
|
492
503
|
this.attachOnClickToLauncher();
|
|
@@ -4,7 +4,7 @@ export const pubnubChatStyles = css`
|
|
|
4
4
|
#pubnub-chat-container {
|
|
5
5
|
position: fixed;
|
|
6
6
|
|
|
7
|
-
z-index:
|
|
7
|
+
z-index: 100001;
|
|
8
8
|
display: flex;
|
|
9
9
|
align-items: center;
|
|
10
10
|
|
|
@@ -127,7 +127,7 @@ export const pubnubChatStyles = css`
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
#loading-message {
|
|
130
|
-
padding:
|
|
130
|
+
padding: 12px;
|
|
131
131
|
}
|
|
132
132
|
.loading-dot {
|
|
133
133
|
width: 6px;
|
|
@@ -178,6 +178,7 @@ export const pubnubChatStyles = css`
|
|
|
178
178
|
box-sizing: border-box;
|
|
179
179
|
gap: 16px;
|
|
180
180
|
padding: 24px;
|
|
181
|
+
z-index: 100001;
|
|
181
182
|
}
|
|
182
183
|
#message-input {
|
|
183
184
|
height: 40px;
|
|
@@ -189,6 +190,7 @@ export const pubnubChatStyles = css`
|
|
|
189
190
|
border: none;
|
|
190
191
|
color: white;
|
|
191
192
|
background: none;
|
|
193
|
+
z-index: 1000000000000000000000000001;
|
|
192
194
|
}
|
|
193
195
|
#message-input:focus {
|
|
194
196
|
outline: none;
|
|
@@ -74,11 +74,6 @@ export class PubnubChat extends LitElement {
|
|
|
74
74
|
}
|
|
75
75
|
);
|
|
76
76
|
this.onMount();
|
|
77
|
-
// TODO (erol): scroll to bottom on all children load
|
|
78
|
-
// this is a hacky way to do it
|
|
79
|
-
setTimeout(() => {
|
|
80
|
-
this.scrollToChatBottom();
|
|
81
|
-
}, 1000);
|
|
82
77
|
}
|
|
83
78
|
|
|
84
79
|
scrollToChatBottom = (): void => {
|
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
|
+
};
|
package/src/fetchBuildingInfo.ts
CHANGED
|
@@ -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
|
|
53
|
-
const
|
|
54
|
-
const
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
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;
|