@meetelise/chat 1.53.0 → 1.53.2

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.
@@ -780,6 +780,7 @@ export class TourScheduler extends LitElement {
780
780
  ).map((source) => snakify({ ...source })),
781
781
  google_rwg_token: googleReferral?.rwgToken,
782
782
  google_referral_building_id: googleReferral?.buildingId,
783
+ google_environment: googleReferral?.environment,
783
784
  };
784
785
 
785
786
  const url = `https://app.meetelise.com/platformApi/state/create/v2/scheduleMe`;
@@ -107,21 +107,25 @@ export class PubnubMessage extends LitElement {
107
107
  const allWords = this.preserveHTMLTags(sanitizedLine);
108
108
  if (!allWords) return html``;
109
109
  return html`${allWords.map((word) => {
110
- if (hyperlinks.find((obj) => obj.id === word)) {
111
- // EliseAI made hyperlink
112
- const link = hyperlinks.find((obj) => obj.id === word);
113
- if (!link) return;
114
- imagesToDisplay.push({
115
- redirectTo: link?.link,
116
- source: link?.link,
110
+ const markedLinks = hyperlinks.filter((obj) =>
111
+ word.includes(obj.id)
112
+ );
113
+ if (markedLinks.length) {
114
+ // EliseAI made hyperlink, possibly inside markdown formatting like <b>
115
+ let wordWithLinks = word;
116
+ markedLinks.forEach((link) => {
117
+ imagesToDisplay.push({
118
+ redirectTo: link.link,
119
+ source: link.link,
120
+ });
121
+ wordWithLinks = wordWithLinks.replace(
122
+ link.id,
123
+ this.hyperlinkHTML(link.link, link.hyperlink)
124
+ );
117
125
  });
118
- return html`<a
119
- class="redirect-link"
120
- href="${link?.link}"
121
- target="_blank"
122
- rel="noopener noreferrer"
123
- >${link?.hyperlink}
124
- </a>`;
126
+ return html`${unsafeHTML(
127
+ DOMPurify.sanitize(wordWithLinks, { ADD_ATTR: ["target"] })
128
+ )} `;
125
129
  }
126
130
  if (urlRegex.test(word)) {
127
131
  // general url
@@ -145,6 +149,16 @@ export class PubnubMessage extends LitElement {
145
149
  });
146
150
  }
147
151
 
152
+ private hyperlinkHTML(link: string, hyperlink: string): string {
153
+ const anchor = document.createElement("a");
154
+ anchor.className = "redirect-link";
155
+ anchor.href = link;
156
+ anchor.target = "_blank";
157
+ anchor.rel = "noopener noreferrer";
158
+ anchor.textContent = hyperlink;
159
+ return anchor.outerHTML;
160
+ }
161
+
148
162
  private preserveHTMLTags(text: string): string[] {
149
163
  const tagRegex = /(<\/?[a-z][^>]*>[^<]*<\/?[a-z]*>|[^\s]+[.,?!]?|\S)/gi;
150
164
  const splitTags = text.match(tagRegex);
@@ -22,26 +22,33 @@ describe("Google Reserve", () => {
22
22
  visit(originalSearch);
23
23
  });
24
24
 
25
- it("remembers a click from a Google listing, keeping the token intact", () => {
26
- visit("?elise_open=tour&rwg_token=AFd1x%3D%3D");
25
+ it("remembers a click from our Google feed, keeping the token intact", () => {
26
+ visit("?elise_open=tour&elise_google_env=production&rwg_token=AFd1x%3D%3D");
27
27
  storeGoogleReserveReferral(7);
28
28
  visit("");
29
29
 
30
30
  expect(readGoogleReserveReferral()).to.include({
31
31
  rwgToken: "AFd1x==",
32
+ environment: "production",
32
33
  buildingId: 7,
33
34
  });
34
35
  });
35
36
 
36
- it("has no referral without a token or once it has expired", () => {
37
+ it("has no referral without our feed's token or once it has expired", () => {
37
38
  visit("?utm_source=website");
38
39
  storeGoogleReserveReferral(7);
39
40
  expect(readGoogleReserveReferral()).to.equal(null);
40
41
 
42
+ // Another integration's Google link.
43
+ visit("?rwg_token=AFd1x%3D%3D");
44
+ storeGoogleReserveReferral(7);
45
+ expect(readGoogleReserveReferral()).to.equal(null);
46
+
41
47
  localStorage.setItem(
42
48
  STORAGE_KEY,
43
49
  JSON.stringify({
44
50
  rwgToken: "AFd1x==",
51
+ environment: "production",
45
52
  buildingId: 7,
46
53
  expiresAt: Date.now() - 1,
47
54
  })
@@ -3,9 +3,12 @@ const REFERRAL_TTL_MS = 30 * 24 * 60 * 60 * 1000;
3
3
  const REFERRAL_STORAGE_KEY = "com.eliseai.webchat.googlereserve.referral";
4
4
  // Google "Book" links to a customer's page add this so the widget opens the tour scheduler.
5
5
  const OPEN_PARAM = "elise_open";
6
+ // Our Google feed tags its links with the Actions Center environment.
7
+ const ENVIRONMENT_PARAM = "elise_google_env";
6
8
 
7
9
  type GoogleReserveReferral = {
8
10
  rwgToken: string;
11
+ environment: string;
9
12
  buildingId: number;
10
13
  expiresAt: number;
11
14
  };
@@ -16,14 +19,19 @@ export const shouldOpenTourSchedulerFromUrl = (): boolean =>
16
19
  /**
17
20
  * Remembers a click from a building's Google listing, which arrives with an
18
21
  * `rwg_token` query param, so a booking within 30 days can be reported back.
22
+ * Only links from our feed count: other integrations' links carry an
23
+ * `rwg_token` too.
19
24
  */
20
25
  export const storeGoogleReserveReferral = (buildingId: number): void => {
21
- const rwgToken = new URLSearchParams(window.location.search).get("rwg_token");
22
- if (!rwgToken || !buildingId) {
26
+ const params = new URLSearchParams(window.location.search);
27
+ const rwgToken = params.get("rwg_token");
28
+ const environment = params.get(ENVIRONMENT_PARAM);
29
+ if (!rwgToken || !environment || !buildingId) {
23
30
  return;
24
31
  }
25
32
  const referral: GoogleReserveReferral = {
26
33
  rwgToken,
34
+ environment,
27
35
  buildingId,
28
36
  expiresAt: Date.now() + REFERRAL_TTL_MS,
29
37
  };
@@ -0,0 +1,44 @@
1
+ import { expect } from "@esm-bundle/chai";
2
+ import "../public/dist/index";
3
+ import type { SimpleTextChatMessage } from "./MyPubnub";
4
+ import type { PubnubMessage } from "./WebComponent/pubnub-message";
5
+
6
+ declare const it: (name: string, callback: () => void | Promise<void>) => void;
7
+
8
+ const renderMessage = async (message: string): Promise<ShadowRoot> => {
9
+ const element = document.createElement("pubnub-message") as PubnubMessage;
10
+ element.message = {
11
+ timestamp: 100,
12
+ message,
13
+ isLeadMessage: false,
14
+ chunks: [{ text: message, order: 0, isDone: true }],
15
+ type: "text" as SimpleTextChatMessage["type"],
16
+ };
17
+ document.body.appendChild(element);
18
+ await element.updateComplete;
19
+ await element.updateComplete;
20
+ return element.shadowRoot as ShadowRoot;
21
+ };
22
+
23
+ it("renders bolded and plain markdown links as links to the community", async () => {
24
+ const root = await renderMessage(
25
+ "- **[Alder House](https://www.clg-test.com/alder)** apartment 109\n" +
26
+ "- [Cedar Row](https://www.clg-test.com/cedar) apartment 103"
27
+ );
28
+
29
+ const boldLink = root.querySelector("b a.redirect-link");
30
+ expect(boldLink?.getAttribute("href")).to.equal(
31
+ "https://www.clg-test.com/alder"
32
+ );
33
+ expect(boldLink?.getAttribute("target")).to.equal("_blank");
34
+ expect(boldLink?.textContent?.trim()).to.equal("Alder House");
35
+
36
+ const plainLink = Array.from(root.querySelectorAll("a.redirect-link")).find(
37
+ (anchor) => anchor.textContent?.trim() === "Cedar Row"
38
+ );
39
+ expect(plainLink?.getAttribute("href")).to.equal(
40
+ "https://www.clg-test.com/cedar"
41
+ );
42
+ expect(plainLink?.getAttribute("target")).to.equal("_blank");
43
+ expect(root.textContent).not.to.match(/[0-9a-f]{32}/);
44
+ });