@meetelise/chat 1.20.83 → 1.20.85
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 +3 -1
- package/public/demo/index.html +8 -2
- package/public/dist/index.js +514 -229
- package/public/dist/index.js.LICENSE.txt +17 -0
- package/src/MyPubnub.ts +236 -0
- package/src/WebComponent/Scheduler/tour-scheduler.ts +29 -2
- package/src/WebComponent/actions/email-us-window.ts +9 -0
- package/src/WebComponent/launcher/Launcher.ts +4 -0
- package/src/WebComponent/me-chat.ts +194 -67
- package/src/WebComponent/pubnub-chat-styles.ts +172 -0
- package/src/WebComponent/pubnub-chat.ts +155 -0
- package/src/disclaimers.ts +56 -0
- package/src/fetchFeatureFlag.ts +21 -0
- package/src/svgIcons.ts +9 -0
|
@@ -10,6 +10,23 @@ object-assign
|
|
|
10
10
|
http://jedwatson.github.io/classnames
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
/*! *****************************************************************************
|
|
14
|
+
Copyright (c) Microsoft Corporation.
|
|
15
|
+
|
|
16
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
17
|
+
purpose with or without fee is hereby granted.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
20
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
21
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
22
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
23
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
24
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
25
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
26
|
+
***************************************************************************** */
|
|
27
|
+
|
|
28
|
+
/*! lil-uuid - v0.1 - MIT License - https://github.com/lil-js/uuid */
|
|
29
|
+
|
|
13
30
|
/**
|
|
14
31
|
* @license
|
|
15
32
|
* Copyright 2017 Google LLC
|
package/src/MyPubnub.ts
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { AxiosError } from "axios";
|
|
2
|
+
import Pubnub, { ListenerParameters, MessageEvent } from "pubnub";
|
|
3
|
+
|
|
4
|
+
import axios from "axios";
|
|
5
|
+
import { Building } from "./fetchBuildingInfo";
|
|
6
|
+
|
|
7
|
+
interface TokenResponse {
|
|
8
|
+
auth: {
|
|
9
|
+
result: {
|
|
10
|
+
token: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
keys: {
|
|
14
|
+
subscribe_key: string;
|
|
15
|
+
publish_key: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ChatMessage {
|
|
20
|
+
channel: string;
|
|
21
|
+
message: {
|
|
22
|
+
text: string;
|
|
23
|
+
customType: string;
|
|
24
|
+
};
|
|
25
|
+
publisher: string;
|
|
26
|
+
subscription: string;
|
|
27
|
+
timetoken: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class MyPubnub {
|
|
31
|
+
private apiHost = "https://app.meetelise.com";
|
|
32
|
+
|
|
33
|
+
private building: Building | null = null;
|
|
34
|
+
private buildingSlug: string;
|
|
35
|
+
private orgSlug: string;
|
|
36
|
+
|
|
37
|
+
pubnub: Pubnub | null = null;
|
|
38
|
+
leadUserId = "";
|
|
39
|
+
channel = "";
|
|
40
|
+
|
|
41
|
+
chatListener:
|
|
42
|
+
| ((res: { messages: ChatMessage[]; isLoading: boolean }) => void)
|
|
43
|
+
| null = null;
|
|
44
|
+
|
|
45
|
+
messages: ChatMessage[] = [];
|
|
46
|
+
listenerParams: ListenerParameters = {
|
|
47
|
+
message: (messageEvent: MessageEvent) => {
|
|
48
|
+
this.messages = [
|
|
49
|
+
...this.messages,
|
|
50
|
+
{
|
|
51
|
+
channel: messageEvent.channel,
|
|
52
|
+
message: messageEvent.message,
|
|
53
|
+
publisher: messageEvent.publisher,
|
|
54
|
+
subscription: messageEvent.subscription,
|
|
55
|
+
timetoken: +messageEvent.timetoken,
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
const isWaitingForEliseResponse = messageEvent.publisher !== "eliseai";
|
|
59
|
+
this.chatListener?.({
|
|
60
|
+
messages: this.messages,
|
|
61
|
+
isLoading: isWaitingForEliseResponse,
|
|
62
|
+
});
|
|
63
|
+
this.isLoadingMessages = isWaitingForEliseResponse;
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
isLoadingMessages = false;
|
|
67
|
+
|
|
68
|
+
constructor(
|
|
69
|
+
orgSlug: string,
|
|
70
|
+
buildingSlug: string,
|
|
71
|
+
buildingDetails: Building
|
|
72
|
+
) {
|
|
73
|
+
this.orgSlug = orgSlug;
|
|
74
|
+
this.buildingSlug = buildingSlug;
|
|
75
|
+
this.building = buildingDetails;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
addChatListener(
|
|
79
|
+
listener: (response: {
|
|
80
|
+
messages: ChatMessage[];
|
|
81
|
+
isLoading: boolean;
|
|
82
|
+
}) => void
|
|
83
|
+
): void {
|
|
84
|
+
this.chatListener = listener;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async initializePubnub(): Promise<Pubnub | undefined> {
|
|
88
|
+
//const currentChatId = getChatID(this.orgSlug, this.buildingSlug);
|
|
89
|
+
const existingUserId = localStorage.getItem("_uetsid"); // apartments.com user_id
|
|
90
|
+
const uniqueUserId =
|
|
91
|
+
existingUserId ?? Math.floor(Math.random() * 1000000000);
|
|
92
|
+
|
|
93
|
+
this.leadUserId = `lead_${uniqueUserId}_${this.buildingSlug}`;
|
|
94
|
+
this.channel = `webchat_${this.leadUserId}`;
|
|
95
|
+
|
|
96
|
+
const pubnubToken = await this.fetchToken(this.leadUserId, this.channel);
|
|
97
|
+
|
|
98
|
+
// These keys are OK to expose live, the authKey generated by the BE is what
|
|
99
|
+
// is used to authenticate the user. Ideally, should also add rate limiting
|
|
100
|
+
// and/or IP whitelisting to the BE endpoint that generates the token.
|
|
101
|
+
this.pubnub = new Pubnub({
|
|
102
|
+
publishKey: pubnubToken.keys.publish_key,
|
|
103
|
+
subscribeKey: pubnubToken.keys.subscribe_key,
|
|
104
|
+
userId: this.leadUserId,
|
|
105
|
+
authKey: pubnubToken.auth.result.token,
|
|
106
|
+
});
|
|
107
|
+
this.withAuthToken(() => new Promise(() => this.handleChatListeners()));
|
|
108
|
+
await this.withAuthToken(() => this.getChannelHistory());
|
|
109
|
+
return this.pubnub;
|
|
110
|
+
}
|
|
111
|
+
async fetchToken(lead: string, channel: string): Promise<TokenResponse> {
|
|
112
|
+
const response = await axios.get(
|
|
113
|
+
`${this.apiHost}/platformApi/webchat/pn/request-token?user_id=${lead}&channel=${channel}`
|
|
114
|
+
);
|
|
115
|
+
return response.data;
|
|
116
|
+
}
|
|
117
|
+
async fetchChannelExists(channel: string): Promise<boolean> {
|
|
118
|
+
const response = await axios.get(
|
|
119
|
+
`${this.apiHost}/platformApi/webchat/check-channel-exists?channel_name=${channel}`
|
|
120
|
+
);
|
|
121
|
+
return response.data;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async withAuthToken(apiRequestFunc: () => Promise<void>): Promise<void> {
|
|
125
|
+
try {
|
|
126
|
+
await apiRequestFunc();
|
|
127
|
+
} catch (error: unknown) {
|
|
128
|
+
// only want to retry with new token if the error is a 403
|
|
129
|
+
if (
|
|
130
|
+
error instanceof AxiosError &&
|
|
131
|
+
error &&
|
|
132
|
+
error.response &&
|
|
133
|
+
error.response.status === 403
|
|
134
|
+
) {
|
|
135
|
+
try {
|
|
136
|
+
if (!this.pubnub || !this.leadUserId || !this.channel) return;
|
|
137
|
+
|
|
138
|
+
const newToken = await this.fetchToken(this.leadUserId, this.channel);
|
|
139
|
+
if (!newToken) return;
|
|
140
|
+
|
|
141
|
+
this.pubnub.setAuthKey(newToken.auth.result.token);
|
|
142
|
+
|
|
143
|
+
await apiRequestFunc();
|
|
144
|
+
} catch (retryError) {
|
|
145
|
+
//onsole.error(retryError);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async getChannelHistory(): Promise<void> {
|
|
152
|
+
try {
|
|
153
|
+
const response: Pubnub.FetchMessagesResponse = await new Promise(
|
|
154
|
+
(resolve, reject) => {
|
|
155
|
+
if (!this.pubnub || !this.channel) return;
|
|
156
|
+
this.pubnub.fetchMessages(
|
|
157
|
+
{
|
|
158
|
+
channels: [this.channel],
|
|
159
|
+
count: 100,
|
|
160
|
+
},
|
|
161
|
+
(status, response) => {
|
|
162
|
+
if (status.error) {
|
|
163
|
+
reject(status);
|
|
164
|
+
} else {
|
|
165
|
+
resolve(response);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
if (this.channel && Object.keys(response.channels).length !== 0) {
|
|
173
|
+
const currentChannelMessages = response.channels[this.channel];
|
|
174
|
+
const parsedCurrentChannelMessages: ChatMessage[] = [];
|
|
175
|
+
currentChannelMessages.forEach((message) => {
|
|
176
|
+
if (message.uuid) {
|
|
177
|
+
parsedCurrentChannelMessages.push({
|
|
178
|
+
channel: message.channel,
|
|
179
|
+
message: message.message,
|
|
180
|
+
publisher: message.uuid,
|
|
181
|
+
subscription: message.channel,
|
|
182
|
+
timetoken: +message.timetoken,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
this.messages = parsedCurrentChannelMessages;
|
|
187
|
+
this.chatListener?.({ messages: this.messages, isLoading: false });
|
|
188
|
+
}
|
|
189
|
+
} catch (error) {
|
|
190
|
+
// Handle the error here
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
handleChatListeners = (): void => {
|
|
195
|
+
if (!this.pubnub || !this.channel) return;
|
|
196
|
+
this.pubnub.subscribe({ channels: [this.channel] });
|
|
197
|
+
this.pubnub.addListener(this.listenerParams);
|
|
198
|
+
};
|
|
199
|
+
removeChatListeners = (): void => {
|
|
200
|
+
if (this.pubnub && this.channel) {
|
|
201
|
+
this.pubnub.unsubscribe({ channels: [this.channel] });
|
|
202
|
+
this.pubnub.removeListener(this.listenerParams);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
sendMessage = async (message: string): Promise<void> => {
|
|
207
|
+
if (message) {
|
|
208
|
+
if (!this.pubnub) {
|
|
209
|
+
// ONLY create/gets a chat session if user actually wants to chat
|
|
210
|
+
const myPubnub = await this.initializePubnub();
|
|
211
|
+
if (!myPubnub) return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
await this.withAuthToken(async () => {
|
|
215
|
+
if (!this.pubnub || !this.channel) return;
|
|
216
|
+
await this.pubnub.publish({
|
|
217
|
+
channel: this.channel,
|
|
218
|
+
message: {
|
|
219
|
+
text: message,
|
|
220
|
+
customType: "lead_message",
|
|
221
|
+
buildingId: this.building?.id,
|
|
222
|
+
buildingSlug: this.buildingSlug,
|
|
223
|
+
userId: this.building?.userId, // this userid is actually the AI user!
|
|
224
|
+
// leadSource: DEFAULT_LEAD_SOURCE,
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
if (this.isLoadingMessages === false) this.isLoadingMessages = true;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
isLeadMessage = (message: ChatMessage): boolean =>
|
|
232
|
+
message.publisher.includes("lead_") &&
|
|
233
|
+
message.message.customType === "lead_message";
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export default MyPubnub;
|
|
@@ -33,6 +33,7 @@ import { classMap } from "lit/directives/class-map.js";
|
|
|
33
33
|
import postLeadSources from "../../postLeadSources";
|
|
34
34
|
import { FeatureFlagsShowDropdown } from "../../fetchFeatureFlag";
|
|
35
35
|
import { pushGtmEvent } from "../../gtm";
|
|
36
|
+
import disclaimer from "../../disclaimers";
|
|
36
37
|
|
|
37
38
|
const getHumanReadableLayout = (layout: string) => {
|
|
38
39
|
if (layout == "studio") return "Studio";
|
|
@@ -72,6 +73,10 @@ export class TourScheduler extends LitElement {
|
|
|
72
73
|
virtualToursLink = "";
|
|
73
74
|
@property({ attribute: true })
|
|
74
75
|
orgSlug = "";
|
|
76
|
+
|
|
77
|
+
@property({ attribute: true })
|
|
78
|
+
buildingName = "";
|
|
79
|
+
|
|
75
80
|
onCloseClicked?: (e: MouseEvent) => void;
|
|
76
81
|
|
|
77
82
|
@state()
|
|
@@ -621,7 +626,7 @@ export class TourScheduler extends LitElement {
|
|
|
621
626
|
margin: 0;
|
|
622
627
|
}
|
|
623
628
|
|
|
624
|
-
|
|
629
|
+
.explanation {
|
|
625
630
|
font-weight: 400;
|
|
626
631
|
font-size: 12px;
|
|
627
632
|
grid-row: 7;
|
|
@@ -630,6 +635,9 @@ export class TourScheduler extends LitElement {
|
|
|
630
635
|
margin: 0;
|
|
631
636
|
padding-top: 32px;
|
|
632
637
|
}
|
|
638
|
+
.withDisclaimer {
|
|
639
|
+
padding-top: 16px;
|
|
640
|
+
}
|
|
633
641
|
|
|
634
642
|
#schedule {
|
|
635
643
|
width: 145px;
|
|
@@ -1336,10 +1344,20 @@ export class TourScheduler extends LitElement {
|
|
|
1336
1344
|
: html`${this.tourTypeMenu()} ${this.dateAndTimeMenu()}
|
|
1337
1345
|
${this.userInfoAndLayoutMenu()}
|
|
1338
1346
|
<hr />
|
|
1339
|
-
<p
|
|
1347
|
+
<p
|
|
1348
|
+
class=${this.phoneInput?.value || this.emailInput?.value
|
|
1349
|
+
? "explanation withDisclaimer"
|
|
1350
|
+
: "explanation"}
|
|
1351
|
+
>
|
|
1340
1352
|
We’ll send a confirmation and any follow-ups to your email
|
|
1341
1353
|
address.
|
|
1354
|
+
${disclaimer({
|
|
1355
|
+
buildingName: this.buildingName,
|
|
1356
|
+
phoneNumberInput: this.phoneInput?.value,
|
|
1357
|
+
emailInput: this.emailInput?.value,
|
|
1358
|
+
})}
|
|
1342
1359
|
</p>
|
|
1360
|
+
|
|
1343
1361
|
<action-confirm-button
|
|
1344
1362
|
id="schedule"
|
|
1345
1363
|
.onClick=${this.submit}
|
|
@@ -1383,6 +1401,15 @@ export class TourScheduler extends LitElement {
|
|
|
1383
1401
|
${currentPage.nextButtonText}
|
|
1384
1402
|
</button>`
|
|
1385
1403
|
: currentPage.renderNextButton()}`}
|
|
1404
|
+
${this.mobilePageIndex + 1 === this.mobilePages.length
|
|
1405
|
+
? html`
|
|
1406
|
+
${disclaimer({
|
|
1407
|
+
buildingName: this.buildingName,
|
|
1408
|
+
phoneNumberInput: this.phoneInput?.value,
|
|
1409
|
+
emailInput: this.emailInput?.value,
|
|
1410
|
+
})}
|
|
1411
|
+
`
|
|
1412
|
+
: html``}
|
|
1386
1413
|
</div>
|
|
1387
1414
|
`;
|
|
1388
1415
|
}
|
|
@@ -16,6 +16,7 @@ import { InputStyles } from "./InputStyles";
|
|
|
16
16
|
import axios from "axios";
|
|
17
17
|
import { FeatureFlagsShowDropdown } from "../../fetchFeatureFlag";
|
|
18
18
|
import { pushGtmEvent } from "../../gtm";
|
|
19
|
+
import disclaimer from "../../disclaimers";
|
|
19
20
|
|
|
20
21
|
@customElement("email-us-window")
|
|
21
22
|
export class EmailUsWindow extends LitElement {
|
|
@@ -100,6 +101,8 @@ export class EmailUsWindow extends LitElement {
|
|
|
100
101
|
buildingSlug = "";
|
|
101
102
|
@property({ attribute: true })
|
|
102
103
|
orgSlug = "";
|
|
104
|
+
@property({ attribute: true })
|
|
105
|
+
buildingName = "";
|
|
103
106
|
|
|
104
107
|
@property({ attribute: true })
|
|
105
108
|
featureFlagShowDropdown = "";
|
|
@@ -446,6 +449,12 @@ export class EmailUsWindow extends LitElement {
|
|
|
446
449
|
`
|
|
447
450
|
: ""}
|
|
448
451
|
</div>
|
|
452
|
+
|
|
453
|
+
${disclaimer({
|
|
454
|
+
buildingName: this.buildingName,
|
|
455
|
+
phoneNumberInput: this.phoneNumber,
|
|
456
|
+
emailInput: this.email,
|
|
457
|
+
})}
|
|
449
458
|
</details-window>
|
|
450
459
|
`;
|
|
451
460
|
};
|
|
@@ -60,6 +60,8 @@ export class Launcher extends LitElement {
|
|
|
60
60
|
@property()
|
|
61
61
|
phoneNumber = "";
|
|
62
62
|
@property({ attribute: true })
|
|
63
|
+
buildingName = "";
|
|
64
|
+
@property({ attribute: true })
|
|
63
65
|
chatId = "";
|
|
64
66
|
@property({ attribute: true })
|
|
65
67
|
chatCallUsHeader = "";
|
|
@@ -528,6 +530,7 @@ export class Launcher extends LitElement {
|
|
|
528
530
|
featureFlagShowDropdown="${this.featureFlagShowDropdown}"
|
|
529
531
|
${ref(this.emailUsWindowRef)}
|
|
530
532
|
.buildingId=${this.buildingId}
|
|
533
|
+
.buildingName=${this.buildingName}
|
|
531
534
|
>
|
|
532
535
|
</email-us-window>
|
|
533
536
|
</div>`
|
|
@@ -559,6 +562,7 @@ export class Launcher extends LitElement {
|
|
|
559
562
|
.tourTypeOptions=${this.tourTypeOptions}
|
|
560
563
|
buildingId=${this.buildingId}
|
|
561
564
|
featureFlagShowDropdown="${this.featureFlagShowDropdown}"
|
|
565
|
+
.buildingName=${this.buildingName}
|
|
562
566
|
${ref(this.tourSchedulerRef)}
|
|
563
567
|
></tour-scheduler>
|
|
564
568
|
</div>`
|