@meetelise/chat 1.20.85 → 1.20.87

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
@@ -3,6 +3,12 @@ import Pubnub, { ListenerParameters, MessageEvent } from "pubnub";
3
3
 
4
4
  import axios from "axios";
5
5
  import { Building } from "./fetchBuildingInfo";
6
+ import parseISO from "date-fns/parseISO";
7
+ import { v4 as uuid } from "uuid";
8
+ import addHours from "date-fns/addHours";
9
+ import isAfter from "date-fns/isAfter";
10
+ import formatISO from "date-fns/formatISO";
11
+ import isBefore from "date-fns/isBefore";
6
12
 
7
13
  interface TokenResponse {
8
14
  auth: {
@@ -27,12 +33,18 @@ export interface ChatMessage {
27
33
  timetoken: number;
28
34
  }
29
35
 
36
+ interface ChatInfo {
37
+ leadId: string | null;
38
+ timestamp: Date | null;
39
+ buildingSlug: string | null;
40
+ }
41
+
30
42
  class MyPubnub {
31
43
  private apiHost = "https://app.meetelise.com";
32
44
 
33
45
  private building: Building | null = null;
34
46
  private buildingSlug: string;
35
- private orgSlug: string;
47
+ private ttlHours = 24;
36
48
 
37
49
  pubnub: Pubnub | null = null;
38
50
  leadUserId = "";
@@ -65,12 +77,7 @@ class MyPubnub {
65
77
  };
66
78
  isLoadingMessages = false;
67
79
 
68
- constructor(
69
- orgSlug: string,
70
- buildingSlug: string,
71
- buildingDetails: Building
72
- ) {
73
- this.orgSlug = orgSlug;
80
+ constructor(buildingSlug: string, buildingDetails: Building) {
74
81
  this.buildingSlug = buildingSlug;
75
82
  this.building = buildingDetails;
76
83
  }
@@ -85,14 +92,28 @@ class MyPubnub {
85
92
  }
86
93
 
87
94
  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);
95
+ const storedChatKeyValues = this.getChatStorageKey();
96
+ if (!storedChatKeyValues.leadId) {
97
+ // eslint-disable-next-line no-console
98
+ console.error("Error getting chat storage key...");
99
+ return;
100
+ }
92
101
 
93
- this.leadUserId = `lead_${uniqueUserId}_${this.buildingSlug}`;
102
+ this.leadUserId = storedChatKeyValues.leadId;
94
103
  this.channel = `webchat_${this.leadUserId}`;
95
104
 
105
+ // If the user comes back to the page after closing it out, we get a new token to persist
106
+ // the chat for another this.ttlHours hours.
107
+ if (
108
+ storedChatKeyValues.timestamp &&
109
+ isAfter(
110
+ storedChatKeyValues.timestamp,
111
+ addHours(new Date(), this.ttlHours)
112
+ )
113
+ ) {
114
+ this.createChatStorageKey(this.leadUserId);
115
+ }
116
+
96
117
  const pubnubToken = await this.fetchToken(this.leadUserId, this.channel);
97
118
 
98
119
  // These keys are OK to expose live, the authKey generated by the BE is what
@@ -221,7 +242,6 @@ class MyPubnub {
221
242
  buildingId: this.building?.id,
222
243
  buildingSlug: this.buildingSlug,
223
244
  userId: this.building?.userId, // this userid is actually the AI user!
224
- // leadSource: DEFAULT_LEAD_SOURCE,
225
245
  },
226
246
  });
227
247
  });
@@ -231,6 +251,69 @@ class MyPubnub {
231
251
  isLeadMessage = (message: ChatMessage): boolean =>
232
252
  message.publisher.includes("lead_") &&
233
253
  message.message.customType === "lead_message";
254
+
255
+ createChatStorageKey = (existingUserId?: string): ChatInfo => {
256
+ const storageTimestamp = formatISO(new Date());
257
+ const leadUserId = existingUserId ?? `lead_${uuid()}_${this.buildingSlug}`;
258
+ localStorage.setItem(
259
+ "com.eliseai.webchat.slug=" + this.buildingSlug,
260
+ JSON.stringify({
261
+ buildingSlug: this.buildingSlug,
262
+ leadId: leadUserId,
263
+ timestamp: storageTimestamp,
264
+ })
265
+ );
266
+ return {
267
+ leadId: leadUserId,
268
+ timestamp: parseISO(storageTimestamp),
269
+ buildingSlug: this.buildingSlug,
270
+ };
271
+ };
272
+ getChatStorageKey = (): ChatInfo => {
273
+ const eliseaiLocalStorageValue = localStorage.getItem(
274
+ "com.eliseai.webchat.slug=" + this.buildingSlug
275
+ );
276
+ if (eliseaiLocalStorageValue) {
277
+ try {
278
+ const eliseaiLocalStorageValueParsed = JSON.parse(
279
+ eliseaiLocalStorageValue
280
+ );
281
+ const lsBuildingSlug = eliseaiLocalStorageValueParsed.buildingSlug;
282
+ const lsLeadId = eliseaiLocalStorageValueParsed.leadId;
283
+ const lsExpiration = new Date(eliseaiLocalStorageValueParsed.timestamp);
284
+
285
+ if (
286
+ this.isChatKeyValid({
287
+ leadId: lsLeadId,
288
+ timestamp: lsExpiration,
289
+ buildingSlug: lsBuildingSlug,
290
+ })
291
+ )
292
+ return {
293
+ leadId: lsLeadId,
294
+ timestamp: lsExpiration,
295
+ buildingSlug: lsBuildingSlug,
296
+ };
297
+ } catch (_) {
298
+ // eslint-disable-next-line no-console
299
+ console.warn("Error getting chat storage key");
300
+ }
301
+ }
302
+
303
+ return this.createChatStorageKey();
304
+ };
305
+ isChatKeyValid = (storageValueDeconstructed: ChatInfo): boolean => {
306
+ if (
307
+ storageValueDeconstructed.buildingSlug !== this.buildingSlug ||
308
+ !storageValueDeconstructed.leadId ||
309
+ !storageValueDeconstructed.timestamp
310
+ ) {
311
+ return false;
312
+ }
313
+
314
+ const expirationDate = addHours(new Date(), this.ttlHours);
315
+ return isBefore(storageValueDeconstructed.timestamp, expirationDate);
316
+ };
234
317
  }
235
318
 
236
319
  export default MyPubnub;
@@ -400,30 +400,35 @@ export class MEChat extends LitElement {
400
400
  (talkjsPopupElement as HTMLElement).style.zIndex = "99999999999";
401
401
  this.popup = popup;
402
402
 
403
- if (this.shouldAutoOpenChatWidget(building.autoOpenChatWidget)) {
403
+ if (this.shouldAutoOpenChat(building.autoOpenChatWidget)) {
404
404
  this.popup.show();
405
405
  this.hideLauncher = true;
406
406
  this.hasMounted = true;
407
- sessionStorage.setItem(
408
- "autoOpenedTimestamp",
409
- formatISO(addMinutes(new Date(), 15))
410
- );
407
+ this.updateAlreadyAutoOpenedTimestamp();
411
408
  this.analytics?.ping("autoOpen");
412
409
  }
413
410
  };
414
411
 
415
- private shouldAutoOpenChatWidget = (
416
- buildingHasAutoOpen: boolean
417
- ): boolean => {
418
- const autoOpenedTimestamp = sessionStorage.getItem("autoOpenedTimestamp");
412
+ private shouldAutoOpenChat = (buildingHasAutoOpen: boolean): boolean => {
413
+ const alreadyAutoOpenedTimestamp = sessionStorage.getItem(
414
+ "alreadyAutoOpenedTimestamp"
415
+ ); // we dont want to autoopen on EVERY single page load, so we'll only do it once every 15 minutes max
419
416
  const shouldAutoOpen =
420
- !autoOpenedTimestamp ||
421
- (autoOpenedTimestamp && isPast(parseISO(autoOpenedTimestamp)));
422
- if (buildingHasAutoOpen && shouldAutoOpen && !isMobile()) {
423
- return true;
424
- }
425
- return false;
417
+ !alreadyAutoOpenedTimestamp ||
418
+ !(
419
+ alreadyAutoOpenedTimestamp &&
420
+ !isPast(parseISO(alreadyAutoOpenedTimestamp))
421
+ );
422
+ return !!buildingHasAutoOpen && !!shouldAutoOpen && !isMobile();
426
423
  };
424
+
425
+ private updateAlreadyAutoOpenedTimestamp = (): void => {
426
+ sessionStorage.setItem(
427
+ "alreadyAutoOpenedTimestamp",
428
+ formatISO(addMinutes(new Date(), 15))
429
+ );
430
+ };
431
+
427
432
  /**
428
433
  * Remove the instance from the screen.
429
434
  *
@@ -479,22 +484,16 @@ export class MEChat extends LitElement {
479
484
  await this.setBuildingDerivedInfo();
480
485
  if (!this.building) return;
481
486
 
482
- const pubnubRaw = new MyPubnub(
483
- this.orgSlug,
484
- this.buildingSlug,
485
- this.building
486
- );
487
- await pubnubRaw.initializePubnub();
488
- this.myPubnub = pubnubRaw;
487
+ this.myPubnub = new MyPubnub(this.buildingSlug, this.building);
488
+ if (this.myPubnub.isChatKeyValid(this.myPubnub.getChatStorageKey())) {
489
+ await this.myPubnub.initializePubnub();
490
+ }
489
491
  this.attachOnClickToLauncher();
490
- if (this.shouldAutoOpenChatWidget(this.building.autoOpenChatWidget)) {
492
+ if (this.shouldAutoOpenChat(this.building.autoOpenChatWidget)) {
491
493
  this.displayPubnubChat = true;
492
494
  this.hideLauncher = true;
493
495
  this.hasMounted = true;
494
- sessionStorage.setItem(
495
- "autoOpenedTimestamp",
496
- formatISO(addMinutes(new Date(), 15))
497
- );
496
+ this.updateAlreadyAutoOpenedTimestamp();
498
497
  }
499
498
  this.isLoading = false;
500
499
  };
@@ -589,13 +588,11 @@ export class MEChat extends LitElement {
589
588
  </div>
590
589
 
591
590
  ${
592
- this.chatProvider === ChatProviders.PUBNUB &&
593
- this.displayPubnubChat &&
594
- this.myPubnub
591
+ this.chatProvider === ChatProviders.PUBNUB && this.displayPubnubChat
595
592
  ? html`
596
593
  <pubnub-chat
597
594
  id="pubnub-chat"
598
- .channel=${this.myPubnub.channel}
595
+ .channel=${this.myPubnub?.channel}
599
596
  .myPubnub=${this.myPubnub}
600
597
  .buildingSlug=${this.buildingSlug}
601
598
  .building=${this.building}
@@ -607,6 +604,7 @@ export class MEChat extends LitElement {
607
604
  .onMount=${() => {
608
605
  this.adjustTopHeaderContactCoords();
609
606
  }}
607
+ .isMobile=${this.isMobile}
610
608
  ></pubnub-chat>
611
609
  ${this.renderChatAdditionalActions(
612
610
  "chatAdditionalActionsPubnub",
@@ -3,22 +3,33 @@ import { css } from "lit";
3
3
  export const pubnubChatStyles = css`
4
4
  #pubnub-chat-container {
5
5
  position: fixed;
6
- right: 0px;
7
- bottom: 110px;
6
+
8
7
  z-index: 100000;
9
8
  display: flex;
10
9
  align-items: center;
11
10
 
12
- width: 340px;
13
- height: 600px;
14
-
15
11
  display: flex;
16
12
  flex-direction: column;
17
- border-radius: 24px 24px 0px 24px;
18
13
  overflow: hidden;
19
14
 
20
15
  box-shadow: rgba(50, 50, 93, 0.25) 0px 2px 5px -1px,
21
16
  rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;
17
+
18
+ font-family: Poppins, "Open Sans", "Helvetica", sans-serif;
19
+ }
20
+ .pubnub-container__mobile {
21
+ right: 0px;
22
+ bottom: 0px;
23
+ width: 100%;
24
+ height: 100%;
25
+ border-radius: 0px;
26
+ }
27
+ .pubnub-container__desktop {
28
+ right: 0px;
29
+ bottom: 110px;
30
+ width: 340px;
31
+ height: 600px;
32
+ border-radius: 24px 24px 0px 24px;
22
33
  }
23
34
  #header {
24
35
  height: 70px;
@@ -37,7 +48,6 @@ export const pubnubChatStyles = css`
37
48
  #header-text {
38
49
  font-size: 14px;
39
50
  font-weight: bold;
40
- font-family: Poppins, "Open Sans", "Helvetica", sans-serif;
41
51
  }
42
52
  #exit-chat-bttn {
43
53
  display: flex;
@@ -66,6 +76,11 @@ export const pubnubChatStyles = css`
66
76
  position: relative;
67
77
  padding: 0px 16px;
68
78
  gap: 16px;
79
+ list-style: none;
80
+ }
81
+
82
+ .disclaimer-message {
83
+ font-size: 12px;
69
84
  }
70
85
 
71
86
  .message-container {
@@ -93,8 +108,7 @@ export const pubnubChatStyles = css`
93
108
  word-break: break-word;
94
109
  margin: 0;
95
110
  padding: 6px 12px;
96
- line-height: 150%;
97
- font-family: Poppins, "Open Sans", "Helvetica", sans-serif;
111
+ line-height: 130%;
98
112
  }
99
113
 
100
114
  .loading-dot {
@@ -157,7 +171,6 @@ export const pubnubChatStyles = css`
157
171
  border: none;
158
172
  color: white;
159
173
  background: none;
160
- font-family: Poppins, "Open Sans", "Helvetica", sans-serif;
161
174
  }
162
175
  #message-input:focus {
163
176
  outline: none;
@@ -1,5 +1,6 @@
1
1
  import { html, LitElement, TemplateResult } from "lit";
2
2
  import { customElement, property, query, state } from "lit/decorators.js";
3
+ import { classMap } from "lit/directives/class-map.js";
3
4
  import { styleMap } from "lit/directives/style-map.js";
4
5
  import { Building } from "../fetchBuildingInfo";
5
6
  import MyPubnub, { ChatMessage } from "../MyPubnub";
@@ -42,6 +43,9 @@ export class PubnubChat extends LitElement {
42
43
  @state()
43
44
  messages: ChatMessage[] = [];
44
45
 
46
+ @state()
47
+ isMobile = false;
48
+
45
49
  @state()
46
50
  isLoadingMessages = false;
47
51
 
@@ -71,23 +75,24 @@ export class PubnubChat extends LitElement {
71
75
  }
72
76
 
73
77
  render(): TemplateResult {
74
- if (
75
- !this.channel ||
76
- !this.myPubnub ||
77
- !this.buildingSlug ||
78
- !this.building
79
- ) {
78
+ if (!this.buildingSlug || !this.building) {
80
79
  return html``; // error here
81
80
  }
82
81
  return html`
83
- <div id="pubnub-chat-container">
82
+ <div
83
+ id="pubnub-chat-container"
84
+ class=${classMap({
85
+ ["pubnub-container__mobile"]: this.isMobile,
86
+ ["pubnub-container__desktop"]: !this.isMobile,
87
+ })}
88
+ >
84
89
  <div
85
90
  id="header"
86
91
  style=${styleMap({
87
92
  background:
88
93
  this.brandColor !== defaultBrandColor
89
94
  ? hexToAlmostWhite(this.brandColor, 0.6)
90
- : this.brandColor,
95
+ : undefined,
91
96
  })}
92
97
  >
93
98
  <div id="header-text">${this.building.name}</div>
@@ -97,6 +102,11 @@ export class PubnubChat extends LitElement {
97
102
  </div>
98
103
  <div id="conversation-body">
99
104
  <ul id="message-thread-list">
105
+ <li class="message-container disclaimer-message">
106
+ By interacting with this system, you acknowledge that EliseAI may
107
+ log and retain any actions that you take and information that you
108
+ provide. Replies may be computer or human-generated.
109
+ </li>
100
110
  ${this.building.welcomeMessage
101
111
  ? html` <li class="message-container ai-message">
102
112
  <p class="message-text">${this.building.welcomeMessage}</p>