@meetelise/chat 1.20.134 → 1.20.136

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,14 +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
6
  import addHours from "date-fns/addHours";
9
7
  import isAfter from "date-fns/isAfter";
10
- import formatISO from "date-fns/formatISO";
11
- import isBefore from "date-fns/isBefore";
12
8
  import { LogType, sendLoggingEvent } from "./analytics";
13
9
 
10
+ import { createChatStorageKey, getChatStorageKey } from "./handleChatIds";
11
+
14
12
  interface TokenResponse {
15
13
  auth: {
16
14
  result: {
@@ -40,7 +38,7 @@ export interface ChatMessage {
40
38
  timetoken: number;
41
39
  }
42
40
 
43
- interface ChatInfo {
41
+ export interface ChatInfo {
44
42
  leadId: string | null;
45
43
  timestamp: Date | null;
46
44
  buildingSlug: string | null;
@@ -116,7 +114,7 @@ class MyPubnub {
116
114
  }
117
115
 
118
116
  async initializePubnub(): Promise<Pubnub | undefined> {
119
- const storedChatKeyValues = this.getChatStorageKey();
117
+ const storedChatKeyValues = getChatStorageKey(true, this.buildingSlug);
120
118
  if (!storedChatKeyValues.leadId) {
121
119
  // eslint-disable-next-line no-console
122
120
  sendLoggingEvent({
@@ -145,9 +143,8 @@ class MyPubnub {
145
143
  addHours(new Date(), this.ttlHours)
146
144
  )
147
145
  ) {
148
- this.createChatStorageKey(this.leadUserId);
146
+ createChatStorageKey(this.buildingSlug, this.leadUserId);
149
147
  }
150
-
151
148
  const pubnubToken = await this.fetchToken(this.leadUserId, this.channel);
152
149
  if (!pubnubToken) return;
153
150
 
@@ -382,80 +379,6 @@ class MyPubnub {
382
379
  isLeadMessage = (message: ChatMessage): boolean =>
383
380
  message.publisher.includes("lead_") &&
384
381
  message.message.customType === "lead_message";
385
-
386
- clearChatStorageKey = (): void =>
387
- localStorage.removeItem("com.eliseai.webchat.slug=" + this.buildingSlug);
388
-
389
- createChatStorageKey = (existingUserId?: string): ChatInfo => {
390
- const storageTimestamp = formatISO(new Date());
391
- const leadUserId = existingUserId ?? `lead_${uuid()}_${this.buildingSlug}`;
392
- localStorage.setItem(
393
- "com.eliseai.webchat.slug=" + this.buildingSlug,
394
- JSON.stringify({
395
- buildingSlug: this.buildingSlug,
396
- leadId: leadUserId,
397
- timestamp: storageTimestamp,
398
- })
399
- );
400
- return {
401
- leadId: leadUserId,
402
- timestamp: parseISO(storageTimestamp),
403
- buildingSlug: this.buildingSlug,
404
- };
405
- };
406
- getChatStorageKey = (createNewIfNotExist = true): ChatInfo => {
407
- const eliseaiLocalStorageValue = localStorage.getItem(
408
- "com.eliseai.webchat.slug=" + this.buildingSlug
409
- );
410
- if (eliseaiLocalStorageValue) {
411
- try {
412
- const eliseaiLocalStorageValueParsed = JSON.parse(
413
- eliseaiLocalStorageValue
414
- );
415
- const lsBuildingSlug = eliseaiLocalStorageValueParsed.buildingSlug;
416
- const lsLeadId = eliseaiLocalStorageValueParsed.leadId;
417
- const lsExpiration = new Date(eliseaiLocalStorageValueParsed.timestamp);
418
-
419
- if (
420
- this.isChatKeyValid({
421
- leadId: lsLeadId,
422
- timestamp: lsExpiration,
423
- buildingSlug: lsBuildingSlug,
424
- })
425
- )
426
- return {
427
- leadId: lsLeadId,
428
- timestamp: lsExpiration,
429
- buildingSlug: lsBuildingSlug,
430
- };
431
- } catch (_) {
432
- // eslint-disable-next-line no-console
433
- console.warn("Error getting chat storage key");
434
- }
435
- }
436
-
437
- if (createNewIfNotExist) return this.createChatStorageKey();
438
- return {
439
- leadId: null,
440
- timestamp: null,
441
- buildingSlug: null,
442
- };
443
- };
444
- isChatKeyValid = (storageValueDeconstructed: ChatInfo): boolean => {
445
- if (
446
- storageValueDeconstructed.buildingSlug !== this.buildingSlug ||
447
- !storageValueDeconstructed.leadId ||
448
- !storageValueDeconstructed.timestamp
449
- ) {
450
- return false;
451
- }
452
-
453
- const expirationDate = addHours(
454
- storageValueDeconstructed.timestamp,
455
- this.ttlHours
456
- );
457
- return !isBefore(expirationDate, Date.now());
458
- };
459
382
  }
460
383
 
461
384
  export default MyPubnub;
@@ -39,7 +39,12 @@ import { insertDNIIntoWebsite } from "../insertDNIIntoWebsite";
39
39
  import "./actions/minimize-expand-button";
40
40
  import "./launcher/mobile-launcher";
41
41
  import "./pubnub-chat";
42
- import { getRegisteredPhoneNumbers } from "../getRegisteredPhoneNumbers";
42
+ import { getBuildingPhoneNumber } from "../getBuildingPhoneNumber";
43
+ import {
44
+ clearChatStorageKey,
45
+ getChatStorageKey,
46
+ isChatKeyValid,
47
+ } from "../handleChatIds";
43
48
 
44
49
  @customElement("me-chat")
45
50
  export class MEChat extends LitElement {
@@ -210,7 +215,7 @@ export class MEChat extends LitElement {
210
215
  this.currentLeadSource = currentLeadSource;
211
216
  this.featureFlagShowDropdown = featureFlagShowDropdown;
212
217
 
213
- const registeredPhoneNumbers = await getRegisteredPhoneNumbers(building.id);
218
+ const buildingPhoneNumber = await getBuildingPhoneNumber(this.buildingSlug);
214
219
 
215
220
  this.enabledChatWidgets = {
216
221
  call:
@@ -229,7 +234,7 @@ export class MEChat extends LitElement {
229
234
  (webchatSettings && !webchatSettings.isGlobalDefault
230
235
  ? webchatSettings.config.shouldShowText
231
236
  : this.building?.chatWidgets?.includes("SMS") ?? true) &&
232
- registeredPhoneNumbers.length > 0,
237
+ !!buildingPhoneNumber,
233
238
  sst:
234
239
  webchatSettings && !webchatSettings.isGlobalDefault
235
240
  ? webchatSettings.config.shouldShowSst
@@ -316,7 +321,13 @@ export class MEChat extends LitElement {
316
321
  this.orgSlug,
317
322
  this.currentLeadSource
318
323
  );
319
- if (this.myPubnub.isChatKeyValid(this.myPubnub.getChatStorageKey(false))) {
324
+ if (
325
+ // handle if we already have a valid session to open up to
326
+ isChatKeyValid(
327
+ getChatStorageKey(false, this.buildingSlug),
328
+ this.buildingSlug
329
+ )
330
+ ) {
320
331
  await this.myPubnub.initializePubnub();
321
332
  }
322
333
  this.attachOnClickToLauncher();
@@ -343,18 +354,22 @@ export class MEChat extends LitElement {
343
354
  };
344
355
 
345
356
  private async handleChatInitializeAnalytics(): Promise<void> {
357
+ // Although we may create the chat id here, we DO NOT create a channel here. We only create a channel when the user
358
+ // actually sends a message. This is to prevent unnecessary channels from being created.
346
359
  this.analytics = new Analytics(
347
360
  this.orgSlug,
348
361
  this.buildingSlug,
349
- this.myPubnub?.channel ?? "", // this will be empty if the user does not have a current chat session.
362
+ `webchat_lead_${getChatStorageKey(true, this.buildingSlug).leadId}`, // important to get unique visitors to the website
350
363
  this.currentLeadSource
351
364
  );
365
+ // ping both the load and the heartbeat events for legacy support
366
+ this.analytics.ping("load");
352
367
  this.analytics.ping("webchat_heartbeat");
353
368
  }
354
369
 
355
370
  public async restartConversation(): Promise<void> {
356
371
  this.myPubnub?.handleDisconnect();
357
- this.myPubnub?.clearChatStorageKey();
372
+ clearChatStorageKey(this.buildingSlug);
358
373
  this.myPubnub = null;
359
374
  this.displayPubnubChat = false;
360
375
  await this.initializeChatVariables();
@@ -0,0 +1,26 @@
1
+ import axios from "axios";
2
+ import { LogType, sendLoggingEvent } from "./analytics";
3
+
4
+ export const getBuildingPhoneNumber = async (
5
+ buildingSlug: string
6
+ ): Promise<string | null> => {
7
+ try {
8
+ const host = "https://app.meetelise.com";
9
+ const webchatPreferencesResponse = await axios.get(
10
+ `${host}/platformApi/webchat/${buildingSlug}/phone-number`
11
+ );
12
+ if (webchatPreferencesResponse.data) {
13
+ return webchatPreferencesResponse.data;
14
+ }
15
+ } catch (error) {
16
+ sendLoggingEvent({
17
+ logType: LogType.error,
18
+ buildingSlug,
19
+ logTitle: "[ERROR_GETTING_PHONE_NUMBERS]",
20
+ logData: { error },
21
+ });
22
+
23
+ return null;
24
+ }
25
+ return null;
26
+ };
@@ -0,0 +1,98 @@
1
+ import isBefore from "date-fns/isBefore";
2
+ import addHours from "date-fns/addHours";
3
+ import { ChatInfo } from "./MyPubnub";
4
+
5
+ import parseISO from "date-fns/parseISO";
6
+ import { v4 as uuid } from "uuid";
7
+
8
+ import formatISO from "date-fns/formatISO";
9
+
10
+ export const ttlHoursChat = 24;
11
+
12
+ export const isChatKeyValid = (
13
+ storageValueDeconstructed: ChatInfo,
14
+ buildingSlug: string
15
+ ): boolean => {
16
+ if (
17
+ storageValueDeconstructed.buildingSlug !== buildingSlug ||
18
+ !storageValueDeconstructed.leadId ||
19
+ !storageValueDeconstructed.timestamp
20
+ ) {
21
+ return false;
22
+ }
23
+
24
+ const expirationDate = addHours(
25
+ storageValueDeconstructed.timestamp,
26
+ ttlHoursChat
27
+ );
28
+ return !isBefore(expirationDate, Date.now());
29
+ };
30
+
31
+ export const getChatStorageKey = (
32
+ createNewIfNotExist = true,
33
+ buildingSlug: string
34
+ ): ChatInfo => {
35
+ const eliseaiLocalStorageValue = localStorage.getItem(
36
+ "com.eliseai.webchat.slug=" + buildingSlug
37
+ );
38
+ if (eliseaiLocalStorageValue) {
39
+ try {
40
+ const eliseaiLocalStorageValueParsed = JSON.parse(
41
+ eliseaiLocalStorageValue
42
+ );
43
+ const lsBuildingSlug = eliseaiLocalStorageValueParsed.buildingSlug;
44
+ const lsLeadId = eliseaiLocalStorageValueParsed.leadId;
45
+ const lsExpiration = new Date(eliseaiLocalStorageValueParsed.timestamp);
46
+
47
+ if (
48
+ isChatKeyValid(
49
+ {
50
+ leadId: lsLeadId,
51
+ timestamp: lsExpiration,
52
+ buildingSlug: lsBuildingSlug,
53
+ },
54
+ buildingSlug
55
+ )
56
+ )
57
+ return {
58
+ leadId: lsLeadId,
59
+ timestamp: lsExpiration,
60
+ buildingSlug: lsBuildingSlug,
61
+ };
62
+ } catch (_) {
63
+ // eslint-disable-next-line no-console
64
+ console.warn("Error getting chat storage key");
65
+ }
66
+ }
67
+
68
+ if (createNewIfNotExist) return createChatStorageKey(buildingSlug);
69
+ return {
70
+ leadId: null,
71
+ timestamp: null,
72
+ buildingSlug: null,
73
+ };
74
+ };
75
+
76
+ export const clearChatStorageKey = (buildingSlug: string): void =>
77
+ localStorage.removeItem("com.eliseai.webchat.slug=" + buildingSlug);
78
+
79
+ export const createChatStorageKey = (
80
+ buildingSlug: string,
81
+ existingUserId?: string
82
+ ): ChatInfo => {
83
+ const storageTimestamp = formatISO(new Date());
84
+ const leadUserId = existingUserId ?? `lead_${uuid()}_${buildingSlug}`;
85
+ localStorage.setItem(
86
+ "com.eliseai.webchat.slug=" + buildingSlug,
87
+ JSON.stringify({
88
+ buildingSlug: buildingSlug,
89
+ leadId: leadUserId,
90
+ timestamp: storageTimestamp,
91
+ })
92
+ );
93
+ return {
94
+ leadId: leadUserId,
95
+ timestamp: parseISO(storageTimestamp),
96
+ buildingSlug: buildingSlug,
97
+ };
98
+ };
@@ -1,66 +0,0 @@
1
- import axios from "axios";
2
- import { LogType, sendLoggingEvent } from "./analytics";
3
-
4
- export interface ManagementSmsNumber {
5
- id: number;
6
- userId: number;
7
- timeCreated: string;
8
- active: number;
9
- phoneNumber: string;
10
- phoneNumberType: string;
11
- description: string;
12
- api: string;
13
- buildingIds: number[];
14
- source: string;
15
- deleted: number;
16
- buildingGroupId: number;
17
- ivrRecordLink: number;
18
- ivrFlowType: number;
19
- numberToForwardTo: string | null;
20
- customTimeout: number;
21
- customOptionsRecording: string | null;
22
- customOptionsUrl: string | null;
23
- voiceMailRecipient: string | null;
24
- callCenterNumber: string | null;
25
- initialPauseInSeconds: number;
26
- voiceUrl: string;
27
- alternativeBuildingId: string | null;
28
- timezone: string | null;
29
- buildingId: number;
30
- ivrFlowConfiguration: {
31
- flowName: string;
32
- callCenterNumber: string;
33
- leasingOfficeNumber: string;
34
- marketingSourceCallCenterNumber: string | null;
35
- globalCallCenterNumber: string | null;
36
- buildingIntroMp3Url: string | null;
37
- buildingOptionsMp3Url: string | null;
38
- textYouShortlyMp3Url: string | null;
39
- };
40
- v2Number: boolean;
41
- }
42
-
43
- const registeredPhoneNumbersCache: {
44
- [buildingId: number]: ManagementSmsNumber[];
45
- } = {};
46
-
47
- export const getRegisteredPhoneNumbers = async (
48
- buildingId: number
49
- ): Promise<ManagementSmsNumber[]> => {
50
- if (registeredPhoneNumbersCache[buildingId]) {
51
- return registeredPhoneNumbersCache[buildingId];
52
- }
53
- try {
54
- const url = `https://app.meetelise.com/sms_management_numbers/webchat/building/${buildingId}/numbers`;
55
- const result = await axios.get<ManagementSmsNumber[]>(url);
56
- registeredPhoneNumbersCache[buildingId] = result.data;
57
- return result.data;
58
- } catch (error) {
59
- sendLoggingEvent({
60
- logTitle: "ERROR_LOADING_PHONE_NUMBERS",
61
- logData: { error, buildingId },
62
- logType: LogType.error,
63
- });
64
- return [];
65
- }
66
- };