@meetelise/chat 1.55.3 → 1.56.0

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
@@ -11,6 +11,11 @@ import { shouldCreateAsDevState } from "./shouldCreateAsDevState";
11
11
  import { WidgetType } from "./main/MEChat";
12
12
  import { BuildingWebchatView } from "./fetchBuildingWebchatView";
13
13
  import { LeadSourceMultitouchClient } from "./WebComponent/LeadSourceMultitouchClient";
14
+ import {
15
+ getWebchatPageContext,
16
+ parseWebchatNavigation,
17
+ WebchatNavigation,
18
+ } from "./webchatNavigation";
14
19
 
15
20
  interface TokenResponse {
16
21
  auth: {
@@ -29,6 +34,7 @@ enum MessageType {
29
34
  noReply = "no-reply",
30
35
  text = "text",
31
36
  media = "media",
37
+ navigation = "navigation",
32
38
  }
33
39
 
34
40
  interface RawPubnubMessage {
@@ -43,6 +49,7 @@ interface RawPubnubMessage {
43
49
  order?: number;
44
50
  stream_id?: string;
45
51
  media?: ChatMediaFile[];
52
+ navigation?: unknown;
46
53
  };
47
54
  publisher: string;
48
55
  subscription: string;
@@ -73,7 +80,18 @@ export interface SimpleMediaChatMessage {
73
80
  type: SimpleMessageTypes.media;
74
81
  }
75
82
 
76
- export type SimpleChatMessage = SimpleTextChatMessage | SimpleMediaChatMessage;
83
+ /** A "Take me to {label}" button the agent sends after a reply's text. */
84
+ export interface SimpleNavigationChatMessage {
85
+ timestamp: number;
86
+ navigation: WebchatNavigation;
87
+ isLeadMessage: boolean;
88
+ type: SimpleMessageTypes.navigation;
89
+ }
90
+
91
+ export type SimpleChatMessage =
92
+ | SimpleTextChatMessage
93
+ | SimpleMediaChatMessage
94
+ | SimpleNavigationChatMessage;
77
95
 
78
96
  export const isSimpleTextChatMessage = (
79
97
  message: SimpleChatMessage
@@ -87,9 +105,16 @@ export const isSimpleMediaChatMessage = (
87
105
  return message.type === SimpleMessageTypes.media;
88
106
  };
89
107
 
108
+ export const isSimpleNavigationChatMessage = (
109
+ message: SimpleChatMessage
110
+ ): message is SimpleNavigationChatMessage => {
111
+ return message.type === SimpleMessageTypes.navigation;
112
+ };
113
+
90
114
  export enum SimpleMessageTypes {
91
115
  text = "text",
92
116
  media = "media",
117
+ navigation = "navigation",
93
118
  }
94
119
 
95
120
  class MyPubnub {
@@ -111,6 +136,22 @@ class MyPubnub {
111
136
 
112
137
  private widgetType: WidgetType = WidgetType.Default;
113
138
 
139
+ /**
140
+ * Org-level webchat (`centralConvo: true`): the session is org-anchored, so it
141
+ * must come from /webchat/init and never from the per-lead v1 token fallback.
142
+ */
143
+ centralConvo = false;
144
+
145
+ /** The numeric id of the building the snippet names, sent as `declaredBuildingId`. */
146
+ declaredBuildingId: number | null = null;
147
+
148
+ /**
149
+ * The localStorage key the conversation (lead id) is saved under. It is the
150
+ * building slug, except in org-level webchat, where the conversation stays
151
+ * org-level even after the widget adopts the org's triage general building.
152
+ */
153
+ chatStorageSlug: string;
154
+
114
155
  pubnub: Pubnub | null = null;
115
156
  leadUserId = "";
116
157
  channel = "";
@@ -193,6 +234,7 @@ class MyPubnub {
193
234
  widgetType: WidgetType = WidgetType.Default
194
235
  ) {
195
236
  this.buildingSlug = buildingSlug;
237
+ this.chatStorageSlug = buildingSlug;
196
238
  this.buildingWebchatView = buildingDetails;
197
239
  this.orgSlug = orgSlug;
198
240
  this.leadSource = leadSource;
@@ -212,12 +254,20 @@ class MyPubnub {
212
254
  lead_id: string;
213
255
  channel: string;
214
256
  expires_in_seconds: number;
257
+ /**
258
+ * Org-level webchat only: the org's triage general building, which the
259
+ * widget then runs as.
260
+ */
261
+ building_slug?: string;
215
262
  } | null> {
216
263
  try {
217
- const payload: Record<string, string> = {
264
+ const payload: Record<string, string | boolean> = {
218
265
  building_slug: buildingSlug,
219
266
  org_slug: orgSlug,
220
267
  };
268
+ if (this.centralConvo) {
269
+ payload.central_convo = true;
270
+ }
221
271
  if (existingLeadId) {
222
272
  payload.lead_id = existingLeadId;
223
273
  }
@@ -244,6 +294,18 @@ class MyPubnub {
244
294
  }
245
295
  }
246
296
 
297
+ /**
298
+ * Org-level webchat: run as the building /webchat/init resolved (the org's
299
+ * triage general building), so messages carry its id, slug and AI user.
300
+ */
301
+ adoptBuilding(
302
+ buildingSlug: string,
303
+ buildingDetails: BuildingWebchatView
304
+ ): void {
305
+ this.buildingSlug = buildingSlug;
306
+ this.buildingWebchatView = buildingDetails;
307
+ }
308
+
247
309
  private checkAndHandleDelayStreamingMessages = ({
248
310
  delayToFetchHistoryInMs = 10000,
249
311
  }: { delayToFetchHistoryInMs?: number } = {}): void => {
@@ -407,6 +469,10 @@ class MyPubnub {
407
469
  return response.data;
408
470
  }
409
471
 
472
+ // Org-level sessions only exist through /webchat/init, so there is no
473
+ // v1 session to fall back to.
474
+ if (this.centralConvo) return null;
475
+
410
476
  // v1 fallback for returning users with existing localStorage sessions
411
477
  const response = await axios.get(
412
478
  `${
@@ -665,7 +731,7 @@ class MyPubnub {
665
731
  if (!this.pubnub) {
666
732
  // ONLY create/gets a chat session if user actually wants to chat
667
733
  const chatStorageKey = createChatStorageKey(
668
- this.buildingSlug,
734
+ this.chatStorageSlug,
669
735
  true,
670
736
  this.leadUserId
671
737
  );
@@ -728,6 +794,8 @@ class MyPubnub {
728
794
  leadSource: this.leadSource,
729
795
  widgetType: this.widgetType,
730
796
  isDevState: shouldCreateAsDevState(location.href),
797
+ // Read by the backend only for orgs in the org-level webchat rollout.
798
+ ...getWebchatPageContext(this.declaredBuildingId),
731
799
  lead_sources_with_timestamps: (
732
800
  this.leadSourceMultitouchClient?.getSafeLeadSourceTouchpointsWithDefault(
733
801
  { includedUserSelectedLeadSource: null }
@@ -815,14 +883,28 @@ class MyPubnub {
815
883
  const uniqueMessages = this.removeDuplicatedRawPubnubMessages(messages);
816
884
 
817
885
  uniqueMessages.forEach((message: RawPubnubMessage) => {
818
- // Translate media messages
819
- if (message.message.messageType === "media") {
886
+ if (message.message.messageType === MessageType.navigation) {
887
+ // A "Take me there" button; dropped if its url or label is unusable.
888
+ const navigation = parseWebchatNavigation(message.message.navigation);
889
+ if (navigation) {
890
+ parsedMessages.push({
891
+ timestamp: message.timetoken,
892
+ navigation,
893
+ isLeadMessage: false,
894
+ type: SimpleMessageTypes.navigation,
895
+ });
896
+ }
897
+ } else if (message.message.messageType === "media") {
898
+ // Translate media messages
820
899
  parsedMessages.push({
821
900
  timestamp: message.timetoken,
822
901
  media: message.message.media ?? [],
823
902
  isLeadMessage: this.isLeadMessage(message),
824
903
  type: SimpleMessageTypes.media,
825
904
  });
905
+ } else if (typeof message.message.text !== "string") {
906
+ // A message type this widget doesn't know, with no text to show.
907
+ return;
826
908
  } else if (
827
909
  // Translate non-streaming messages
828
910
  !message.message.stream_id ||
@@ -76,6 +76,7 @@ import {
76
76
  shouldOpenTourSchedulerFromUrl,
77
77
  storeGoogleReserveReferral,
78
78
  } from "../googleReserve";
79
+ import { consumeReopenChatAfterNavigation } from "../webchatNavigation";
79
80
 
80
81
  @customElement("me-chat")
81
82
  export class MEChat extends LitElement {
@@ -136,6 +137,19 @@ export class MEChat extends LitElement {
136
137
  private orgSlug = "";
137
138
  @property({ type: Boolean })
138
139
  private centralConvo = false;
140
+ /**
141
+ * Org-level webchat only: the building the snippet names (`building` in
142
+ * `MEChat.start`). It doesn't change config or the conversation, which stay
143
+ * org-anchored; its id is sent with visitor messages as `declaredBuildingId`.
144
+ */
145
+ @property({ type: String })
146
+ private declaredBuildingSlug = "";
147
+ /**
148
+ * Org-level webchat (`centralConvo` started without a building). Stays true
149
+ * after the widget adopts the org's triage general building from
150
+ * /webchat/init, which sets `buildingSlug`.
151
+ */
152
+ private orgLevel = false;
139
153
  @property({ type: Object })
140
154
  launcherStyles: StyleInfo = {};
141
155
  @property()
@@ -284,26 +298,30 @@ export class MEChat extends LitElement {
284
298
  private setupWebchat = async (): Promise<void> => {
285
299
  if (!this.orgSlug) return;
286
300
  if (!this.centralConvo && !this.buildingSlug) return;
301
+ if (this.centralConvo && !this.buildingSlug) this.orgLevel = true;
287
302
  const buildingWebchatView = this.centralConvo
288
303
  ? await fetchOrgCentralWebchatView(this.orgSlug)
289
304
  : await fetchBuildingWebchatView(this.orgSlug, this.buildingSlug);
290
305
 
291
- if (Math.random() < 0.2) {
306
+ // Both post per-building data, keyed by the building slug.
307
+ if (!this.isOrgMode()) {
308
+ if (Math.random() < 0.2) {
309
+ try {
310
+ updateRentgrataToken(this.buildingSlug, this.orgSlug);
311
+ } catch (e) {
312
+ // eslint-disable-next-line no-console
313
+ console.warn("Error updating rentgrata token", e);
314
+ }
315
+ }
316
+
292
317
  try {
293
- updateRentgrataToken(this.buildingSlug, this.orgSlug);
318
+ logCdnVersion(this.orgSlug, this.buildingSlug);
294
319
  } catch (e) {
295
320
  // eslint-disable-next-line no-console
296
- console.warn("Error updating rentgrata token", e);
321
+ console.warn("Error logging CDN version", e);
297
322
  }
298
323
  }
299
324
 
300
- try {
301
- logCdnVersion(this.orgSlug, this.buildingSlug);
302
- } catch (e) {
303
- // eslint-disable-next-line no-console
304
- console.warn("Error logging CDN version", e);
305
- }
306
-
307
325
  const shouldShowWebchat = await getShouldShowWebchat(
308
326
  buildingWebchatView,
309
327
  this.buildingSlug,
@@ -325,6 +343,9 @@ export class MEChat extends LitElement {
325
343
  }
326
344
  this.buildingWebchatView = buildingWebchatView;
327
345
 
346
+ // A "Take me there" click on the previous page.
347
+ const reopenAfterNavigation = consumeReopenChatAfterNavigation();
348
+
328
349
  await this.initializeChatVariables();
329
350
  await this.handleChatInitializeAnalytics();
330
351
  await this.setBuildingDerivedInfo();
@@ -333,7 +354,13 @@ export class MEChat extends LitElement {
333
354
 
334
355
  storeGoogleReserveReferral(this.buildingWebchatView?.id ?? 0);
335
356
 
336
- if (
357
+ if (reopenAfterNavigation) {
358
+ // Reopen the chat the visitor was using. initializeChatVariables has
359
+ // already loaded the saved conversation, so it is drawn on open.
360
+ this.displayPubnubChat = true;
361
+ this.hideLauncher = true;
362
+ this.hasMounted = true;
363
+ } else if (
337
364
  shouldOpenTourSchedulerFromUrl() &&
338
365
  (this.enabledChatWidgets.sstDesktop || this.enabledChatWidgets.sstMobile)
339
366
  ) {
@@ -355,7 +382,9 @@ export class MEChat extends LitElement {
355
382
  setBuildingDerivedInfo = async (): Promise<void> => {
356
383
  const buildingDetails = this.buildingWebchatView;
357
384
  if (!buildingDetails) return;
358
- if (buildingDetails && this.orgSlug) {
385
+ // Org-level webchat runs on the org's own pages, so it leaves their links,
386
+ // forms and phone numbers alone rather than pointing them at its building.
387
+ if (buildingDetails && this.orgSlug && !this.orgLevel) {
359
388
  overrideContactUsForm(
360
389
  this.orgSlug,
361
390
  this.LeadSourceClient?.chatId ?? null,
@@ -385,7 +414,10 @@ export class MEChat extends LitElement {
385
414
  bedroomPreferenceRequired,
386
415
  brokerIntakeEnabled,
387
416
  ] = await Promise.all([
388
- fetchLeadSources(this.orgSlug, this.buildingSlug),
417
+ // Per-building endpoint; org mode has no building to ask about.
418
+ this.isOrgMode()
419
+ ? Promise.resolve([])
420
+ : fetchLeadSources(this.orgSlug, this.buildingSlug),
389
421
  fetchFeatureFlagUseApplicationsLinkReplacement(this.buildingSlug),
390
422
  fetchBuildingAip(this.buildingSlug),
391
423
  fetchTrackedRolloutBedroomPreferenceRequired(this.buildingSlug),
@@ -405,7 +437,10 @@ export class MEChat extends LitElement {
405
437
  this.brokerIntakeEnabled = brokerIntakeEnabled;
406
438
 
407
439
  // The backend is cached for ~4 hours, so falling back to the existing api request until the cache propagates all the changes
408
- const buildingPhoneNumber = buildingDetails.textWithUsPhoneNumber
440
+ // Org-level webchat never offers Text Us, so it needs no number for it.
441
+ const buildingPhoneNumber = this.orgLevel
442
+ ? null
443
+ : buildingDetails.textWithUsPhoneNumber
409
444
  ? buildingDetails.textWithUsPhoneNumber
410
445
  : await getBuildingPhoneNumber(this.buildingSlug);
411
446
 
@@ -435,9 +470,15 @@ export class MEChat extends LitElement {
435
470
  calcMobile: shouldShowCalcMobile,
436
471
  };
437
472
 
438
- this.enabledChatWidgets = getWidgetOverrides(baseEnabledWidgets);
473
+ this.enabledChatWidgets = this.withOrgLevelContactOptions(
474
+ getWidgetOverrides(baseEnabledWidgets),
475
+ this.buildingWebchatView?.phoneNumber ?? ""
476
+ );
439
477
 
440
- if (
478
+ if (this.orgLevel) {
479
+ // Org-level webchat leaves the org site's tour links alone too; they'd
480
+ // otherwise open the triage general building's scheduler.
481
+ } else if (
441
482
  this.enabledChatWidgets.sstDesktop ||
442
483
  this.enabledChatWidgets.sstMobile ||
443
484
  this.orgSlug === "radco"
@@ -501,7 +542,8 @@ export class MEChat extends LitElement {
501
542
 
502
543
  const buildingsPhoneNumber = this.buildingWebchatView?.phoneNumber ?? "";
503
544
  let phoneNumberForSource = null;
504
- if (featureFlagUseDNI) {
545
+ // Org-level webchat calls its building's own number, never a DNI number.
546
+ if (featureFlagUseDNI && !this.orgLevel) {
505
547
  phoneNumberForSource = await fetchPhoneNumberFromSource(
506
548
  this.buildingSlug,
507
549
  this.LeadSourceClient?.leadSource ?? null
@@ -560,12 +602,18 @@ export class MEChat extends LitElement {
560
602
  this.leadSources.push(currentLeadSource);
561
603
  }
562
604
  }
563
- getRawAvailabilities(buildingDetails.id); // we're not using this here, just want to cache the result
605
+ if (!this.isOrgMode()) {
606
+ getRawAvailabilities(buildingDetails.id); // we're not using this here, just want to cache the result
607
+ }
564
608
  const isApplicationLinkReplacementEnabled =
565
609
  (featureFlagUseApplicationsLinkReplacement ||
566
610
  this.buildingWebchatView?.isAip) &&
567
611
  this.buildingWebchatView?.isLiveOnApplications;
568
- if (isApplicationLinkReplacementEnabled && this.buildingSlug) {
612
+ if (
613
+ isApplicationLinkReplacementEnabled &&
614
+ this.buildingSlug &&
615
+ !this.orgLevel
616
+ ) {
569
617
  const totalReplacements = replaceSelectButtonsWithNewLink(
570
618
  `https://applications.eliseai.com/building/${this.buildingSlug}/units`,
571
619
  this.orgSlug,
@@ -586,7 +634,7 @@ export class MEChat extends LitElement {
586
634
  }
587
635
  }
588
636
 
589
- if (this.buildingSlug && this.orgSlug) {
637
+ if (this.buildingSlug && this.orgSlug && !this.orgLevel) {
590
638
  try {
591
639
  replaceOutdatedApplicationLinks(this.buildingSlug);
592
640
  } catch {
@@ -597,6 +645,7 @@ export class MEChat extends LitElement {
597
645
 
598
646
  if (
599
647
  this.buildingSlug &&
648
+ !this.orgLevel &&
600
649
  !!this.buildingWebchatView?.isLiveOnApplications &&
601
650
  this.buildingWebchatView?.applicationLink &&
602
651
  shouldOverwriteCheckAvail(this.orgSlug)
@@ -613,9 +662,10 @@ export class MEChat extends LitElement {
613
662
  if (!this.buildingWebchatView) return;
614
663
 
615
664
  // important to distinguish between a user who has never opened the chat and a user who has opened the chat but closed it
616
- const currentChatStorageKey = getChatStorageKey(this.buildingSlug);
665
+ const chatStorageSlug = this.chatStorageSlug();
666
+ const currentChatStorageKey = getChatStorageKey(chatStorageSlug);
617
667
  const isReturningUser = isChatKeyValid(
618
- this.buildingSlug,
668
+ chatStorageSlug,
619
669
  currentChatStorageKey
620
670
  );
621
671
 
@@ -623,7 +673,7 @@ export class MEChat extends LitElement {
623
673
  // temporary client-generated one (will be overwritten by server session below).
624
674
  const updatedChatStorageKey = isReturningUser
625
675
  ? currentChatStorageKey
626
- : createChatStorageKey(this.buildingSlug, false);
676
+ : createChatStorageKey(chatStorageSlug, false);
627
677
 
628
678
  if (!updatedChatStorageKey.leadId) {
629
679
  throw new Error("Lead ID is null");
@@ -646,6 +696,17 @@ export class MEChat extends LitElement {
646
696
  this.LeadSourceClient,
647
697
  this.widgetType
648
698
  );
699
+ this.myPubnub.chatStorageSlug = chatStorageSlug;
700
+ if (this.centralConvo) {
701
+ const myPubnub = this.myPubnub;
702
+ myPubnub.centralConvo = true;
703
+ // Not awaited: only needed once the visitor sends a message.
704
+ void this.fetchDeclaredBuildingId().then((id) => {
705
+ myPubnub.declaredBuildingId = id;
706
+ });
707
+ } else {
708
+ this.myPubnub.declaredBuildingId = this.buildingWebchatView.id;
709
+ }
649
710
 
650
711
  // Always create/resume a server-side session so the client has a fresh
651
712
  // sessionToken and never falls back to the insecure V1 token endpoint.
@@ -658,10 +719,13 @@ export class MEChat extends LitElement {
658
719
  if (session) {
659
720
  // Persist the server-provided leadId to localStorage, preserving initiatedChat flag
660
721
  createChatStorageKey(
661
- this.buildingSlug,
722
+ chatStorageSlug,
662
723
  updatedChatStorageKey.initiatedChat,
663
724
  session.lead_id
664
725
  );
726
+ if (this.isOrgMode() && session.building_slug) {
727
+ await this.adoptBuilding(session.building_slug);
728
+ }
665
729
  }
666
730
 
667
731
  this.LeadSourceClient.chatId = this.myPubnub.channel;
@@ -693,12 +757,77 @@ export class MEChat extends LitElement {
693
757
  }
694
758
  // Re-read from localStorage so initializePubnub gets the server-provided
695
759
  // leadId (not the stale reference captured before initSession).
696
- const finalChatStorageKey = getChatStorageKey(this.buildingSlug);
760
+ const finalChatStorageKey = getChatStorageKey(chatStorageSlug);
697
761
  if (finalChatStorageKey.initiatedChat) {
698
762
  await this.myPubnub.initializePubnub(finalChatStorageKey);
699
763
  }
700
764
  };
701
765
 
766
+ /**
767
+ * Org-level webchat with no building: config comes from the org, and there is
768
+ * no building slug for the per-building endpoints.
769
+ */
770
+ private isOrgMode = (): boolean => this.centralConvo && !this.buildingSlug;
771
+
772
+ /** Where the conversation is saved: org-level webchat keeps one per org. */
773
+ private chatStorageSlug = (): string =>
774
+ this.orgLevel ? "" : this.buildingSlug;
775
+
776
+ /**
777
+ * Org-level webchat: /webchat/init chose the org's triage general building,
778
+ * so run as a widget for that building from here on (its config, and its
779
+ * slug for the per-building endpoints). The conversation stays the org-level
780
+ * one /webchat/init returned. If its config can't be loaded, stay in org mode.
781
+ */
782
+ private adoptBuilding = async (buildingSlug: string): Promise<void> => {
783
+ try {
784
+ const view = await fetchBuildingWebchatView(this.orgSlug, buildingSlug);
785
+ if (!view?.id) return;
786
+ this.buildingSlug = buildingSlug;
787
+ this.buildingWebchatView = view;
788
+ this.myPubnub?.adoptBuilding(buildingSlug, view);
789
+ } catch (e) {
790
+ // eslint-disable-next-line no-console
791
+ console.warn("Error loading the org-level webchat building", e);
792
+ }
793
+ };
794
+
795
+ /**
796
+ * Org-level webchat offers no Email or Text Us, and offers Call Us only with
797
+ * its building's number (the org's triage line).
798
+ */
799
+ private withOrgLevelContactOptions = <
800
+ T extends typeof this.enabledChatWidgets
801
+ >(
802
+ widgets: T,
803
+ callNumber: string
804
+ ): T => {
805
+ if (!this.orgLevel) return widgets;
806
+ return {
807
+ ...widgets,
808
+ emailDesktop: false,
809
+ emailMobile: false,
810
+ textDesktop: false,
811
+ textMobile: false,
812
+ callDesktop: widgets.callDesktop && !!callNumber,
813
+ callMobile: widgets.callMobile && !!callNumber,
814
+ };
815
+ };
816
+
817
+ /** The numeric id of the snippet's building in org-level webchat, if it names one. */
818
+ private fetchDeclaredBuildingId = async (): Promise<number | null> => {
819
+ if (!this.declaredBuildingSlug || !this.orgSlug) return null;
820
+ try {
821
+ const view = await fetchBuildingWebchatView(
822
+ this.orgSlug,
823
+ this.declaredBuildingSlug
824
+ );
825
+ return typeof view?.id === "number" && view.id > 0 ? view.id : null;
826
+ } catch {
827
+ return null;
828
+ }
829
+ };
830
+
702
831
  private shouldAutoOpenChat = (buildingHasAutoOpen: boolean): boolean => {
703
832
  const alreadyAutoOpenedTimestamp = sessionStorage.getItem(
704
833
  "alreadyAutoOpenedTimestamp"
@@ -728,7 +857,9 @@ export class MEChat extends LitElement {
728
857
 
729
858
  public async restartConversation(): Promise<void> {
730
859
  this.myPubnub?.handleDisconnect();
731
- clearChatStorageKey(this.buildingSlug);
860
+ clearChatStorageKey(this.chatStorageSlug());
861
+ // Org-level webchat asks /webchat/init for its building again.
862
+ if (this.orgLevel) this.buildingSlug = "";
732
863
  this.myPubnub = null;
733
864
  this.displayPubnubChat = false;
734
865
  await this.setupWebchat();
@@ -981,6 +1112,7 @@ export class MEChat extends LitElement {
981
1112
  .myPubnub=${this.myPubnub}
982
1113
  .orgSlug=${this.orgSlug}
983
1114
  .buildingSlug=${this.buildingSlug}
1115
+ .centralConvo=${this.centralConvo}
984
1116
  .widgetType=${WidgetType.Default}
985
1117
  .building=${this.buildingWebchatView}
986
1118
  .primaryColor=${this.primaryColor}
@@ -6,6 +6,7 @@ import MyPubnub, {
6
6
  SimpleMessageTypes,
7
7
  SimpleChatMessage,
8
8
  isSimpleMediaChatMessage,
9
+ isSimpleNavigationChatMessage,
9
10
  isSimpleTextChatMessage,
10
11
  } from "../MyPubnub";
11
12
  import {
@@ -21,6 +22,7 @@ import { leasingChatStyles } from "./leasing-chat-styles";
21
22
  import { pubnubChatStyles } from "./pubnub-chat-styles";
22
23
  import "./pubnub-message";
23
24
  import "./pubnub-media";
25
+ import "./pubnub-navigation";
24
26
  import classNames from "classnames";
25
27
  import { differenceInDays } from "date-fns";
26
28
  import { WidgetType } from "../main/MEChat";
@@ -79,6 +81,10 @@ export class PubnubChat extends LitElement {
79
81
  @property({ attribute: true })
80
82
  buildingSlug: string | undefined;
81
83
 
84
+ /** Org-level webchat, which has no building slug. */
85
+ @property({ attribute: true })
86
+ centralConvo = false;
87
+
82
88
  @property({ attribute: true })
83
89
  widgetType: WidgetType = WidgetType.Default;
84
90
 
@@ -243,7 +249,7 @@ export class PubnubChat extends LitElement {
243
249
  };
244
250
 
245
251
  render(): TemplateResult {
246
- if (!this.buildingSlug) {
252
+ if (!this.buildingSlug && !this.centralConvo) {
247
253
  return html``; // error should be here
248
254
  }
249
255
 
@@ -376,10 +382,14 @@ export class PubnubChat extends LitElement {
376
382
  role="article"
377
383
  aria-label="${message.isLeadMessage
378
384
  ? "You"
379
- : "Elise"}: ${isSimpleTextChatMessage(message)
380
- ? message.message
381
- : "Media message"}"
385
+ : "Elise"}: ${messageAriaLabel(message)}"
382
386
  >
387
+ ${isSimpleNavigationChatMessage(message)
388
+ ? html`<pubnub-navigation
389
+ .message=${message}
390
+ .onMount=${() => this.scrollToChatBottom()}
391
+ ></pubnub-navigation>`
392
+ : ""}
383
393
  ${isSimpleMediaChatMessage(message)
384
394
  ? html` <pubnub-media
385
395
  .message=${message}
@@ -551,10 +561,14 @@ export class PubnubChat extends LitElement {
551
561
  role="article"
552
562
  aria-label="${message.isLeadMessage
553
563
  ? "You"
554
- : "Elise"}: ${isSimpleTextChatMessage(message)
555
- ? message.message
556
- : "Media message"}"
564
+ : "Elise"}: ${messageAriaLabel(message)}"
557
565
  >
566
+ ${isSimpleNavigationChatMessage(message)
567
+ ? html`<pubnub-navigation
568
+ .message=${message}
569
+ .onMount=${() => this.scrollToChatBottom()}
570
+ ></pubnub-navigation>`
571
+ : ""}
558
572
  ${isSimpleMediaChatMessage(message)
559
573
  ? html` <pubnub-media
560
574
  .message=${message}
@@ -750,10 +764,14 @@ export class PubnubChat extends LitElement {
750
764
  role="article"
751
765
  aria-label="${message.isLeadMessage
752
766
  ? "You"
753
- : "Elise"}: ${isSimpleTextChatMessage(message)
754
- ? message.message
755
- : "Media message"}"
767
+ : "Elise"}: ${messageAriaLabel(message)}"
756
768
  >
769
+ ${isSimpleNavigationChatMessage(message)
770
+ ? html`<pubnub-navigation
771
+ .message=${message}
772
+ .onMount=${() => this.scrollToChatBottom()}
773
+ ></pubnub-navigation>`
774
+ : ""}
757
775
  ${isSimpleMediaChatMessage(message)
758
776
  ? html` <pubnub-media
759
777
  .message=${message}
@@ -851,3 +869,11 @@ export class PubnubChat extends LitElement {
851
869
  `;
852
870
  }
853
871
  }
872
+
873
+ const messageAriaLabel = (message: SimpleChatMessage): string => {
874
+ if (isSimpleTextChatMessage(message)) return message.message;
875
+ if (isSimpleNavigationChatMessage(message)) {
876
+ return `Take me to ${message.navigation.label}`;
877
+ }
878
+ return "Media message";
879
+ };