@morgan-and-morgan/intake-agent 1.1.1 → 1.2.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.
@@ -11,6 +11,92 @@
11
11
  chatUrl
12
12
  };
13
13
  };
14
+ const ATTRIBUTION_FIELD_MAX_LENGTH = {
15
+ tracked_landing_page: 4096,
16
+ tracked_landing_timestamp: 255,
17
+ conversion_page: 255,
18
+ ga_client_id: 255,
19
+ gclid_value: 255,
20
+ marketing_source: 255,
21
+ marketing_sub_source: 255
22
+ };
23
+ const ATTRIBUTION_FIELDS = Object.keys(ATTRIBUTION_FIELD_MAX_LENGTH);
24
+ const sanitizeIntakeChatAttribution = (value) => {
25
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
26
+ return null;
27
+ }
28
+ const record = value;
29
+ const sanitized = {};
30
+ for (const field of ATTRIBUTION_FIELDS) {
31
+ const fieldValue = record[field];
32
+ const trimmed = typeof fieldValue === "string" ? fieldValue.trim() : "";
33
+ if (trimmed !== "") {
34
+ sanitized[field] = trimmed.slice(0, ATTRIBUTION_FIELD_MAX_LENGTH[field]);
35
+ }
36
+ }
37
+ return Object.keys(sanitized).length === 0 ? null : sanitized;
38
+ };
39
+ const LANDING_PAGE_COOKIE = "tracked_landing_page";
40
+ const LANDING_TIMESTAMP_COOKIE = "tracked_landing_timestamp";
41
+ const readTimestampCookie = (cookie) => {
42
+ const raw = readCookie(cookie, LANDING_TIMESTAMP_COOKIE);
43
+ return raw === void 0 ? void 0 : decodeCookieValue(raw);
44
+ };
45
+ const readCookie = (cookie, name) => {
46
+ var _a;
47
+ const raw = (_a = cookie.split(";").map((entry) => entry.trim()).find((entry) => entry.startsWith(`${name}=`))) == null ? void 0 : _a.slice(name.length + 1);
48
+ return raw === void 0 || raw === "" ? void 0 : raw;
49
+ };
50
+ const decodeCookieValue = (raw) => {
51
+ try {
52
+ return decodeURIComponent(raw);
53
+ } catch {
54
+ return raw;
55
+ }
56
+ };
57
+ const parseUrl = (value) => {
58
+ try {
59
+ return new URL(value);
60
+ } catch {
61
+ return null;
62
+ }
63
+ };
64
+ const readLandingPage = (cookie) => {
65
+ const raw = readCookie(cookie, LANDING_PAGE_COOKIE);
66
+ if (raw === void 0 || parseUrl(raw) !== null) {
67
+ return raw;
68
+ }
69
+ const decoded = decodeCookieValue(raw);
70
+ return parseUrl(decoded) === null ? raw : decoded;
71
+ };
72
+ const readGaClientId = (cookie) => {
73
+ const raw = readCookie(cookie, "_ga");
74
+ if (raw === void 0) {
75
+ return void 0;
76
+ }
77
+ const value = decodeCookieValue(raw);
78
+ const parts = value.split(".");
79
+ return parts.length >= 4 ? parts.slice(-2).join(".") : value;
80
+ };
81
+ const readGclid = (landingUrl) => {
82
+ var _a, _b;
83
+ return landingUrl === void 0 ? void 0 : (_b = (_a = parseUrl(landingUrl)) == null ? void 0 : _a.searchParams.get("gclid")) != null ? _b : void 0;
84
+ };
85
+ const readPathname = (url) => {
86
+ var _a;
87
+ return (_a = parseUrl(url)) == null ? void 0 : _a.pathname;
88
+ };
89
+ const buildHostAttribution = (currentUrl = globalThis.location.href, cookie = document.cookie) => {
90
+ var _a;
91
+ const landingPage = readLandingPage(cookie);
92
+ return (_a = sanitizeIntakeChatAttribution({
93
+ tracked_landing_page: landingPage,
94
+ tracked_landing_timestamp: readTimestampCookie(cookie),
95
+ conversion_page: readPathname(currentUrl),
96
+ ga_client_id: readGaClientId(cookie),
97
+ gclid_value: readGclid(landingPage)
98
+ })) != null ? _a : {};
99
+ };
14
100
  const OPEN_STATE_KEY = "mm-intake-widget-open";
15
101
  const readOpenState = (storage = sessionStorage) => {
16
102
  try {
@@ -105,6 +191,13 @@
105
191
  return null;
106
192
  }
107
193
  };
194
+ const hasCompletedSigningEvent = (currentUrl = globalThis.location.href) => {
195
+ try {
196
+ return new URL(currentUrl).searchParams.get("event") === SIGNING_COMPLETE_EVENT;
197
+ } catch {
198
+ return false;
199
+ }
200
+ };
108
201
  const LUCIDE_ICON_PATHS = {
109
202
  "message-circle": '<path d="M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719"/>',
110
203
  "x": '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
@@ -213,7 +306,7 @@
213
306
  } catch {
214
307
  }
215
308
  }
216
- const completedSigningEnvelopeId = readCompletedSigningEnvelopeId();
309
+ const returnedFromSigning = hasCompletedSigningEvent();
217
310
  let chatHasLoaded = false;
218
311
  let chatCloseTimer;
219
312
  let chatReadyTimer;
@@ -234,8 +327,19 @@
234
327
  const hostUrl = buildSigningReturnUrl(currentUrl);
235
328
  postToChat(
236
329
  createEmbedProtocolMessage(EMBED_MESSAGE_TYPE.HOST_READY, {
330
+ // Marketing attribution (MX2-25162): the marketing site's first-touch
331
+ // cookies and the page the chat opened on are only visible from this
332
+ // page, never from the cross-origin chat frame. The chat forwards them
333
+ // on its first /chat turn and Salesforce builds the intake's
334
+ // Marketing_Tracking_Info__c record from them.
335
+ attribution: buildHostAttribution(),
237
336
  ...hostUrl === null ? {} : { hostUrl },
238
- ...completedEnvelopeId === null ? {} : { completedEnvelopeId }
337
+ ...completedEnvelopeId === null ? {} : { completedEnvelopeId },
338
+ // Sent even without an envelope id: the Salesforce service is what
339
+ // appends `envelope_id` to the return URL, and the prod org has shipped
340
+ // without that append. The chat's signing card falls back to its own
341
+ // envelope id on this flag.
342
+ ...hasCompletedSigningEvent(currentUrl) ? { signingComplete: true } : {}
239
343
  })
240
344
  );
241
345
  };
@@ -491,7 +595,7 @@
491
595
  globalThis.addEventListener("message", handleMessage);
492
596
  document.addEventListener("keydown", handleKeydown);
493
597
  syncLauncherLabel();
494
- if (completedSigningEnvelopeId !== null || readOpenState()) {
598
+ if (returnedFromSigning || readOpenState()) {
495
599
  openChat();
496
600
  }
497
601
  return Object.freeze({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morgan-and-morgan/intake-agent",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Embeddable chat widget loader.",
5
5
  "license": "UNLICENSED",
6
6
  "files": [
@@ -11,6 +11,7 @@
11
11
  "exports": {
12
12
  ".": "./dist/mm-intake-widget.js",
13
13
  "./widget": "./src/widget.ts",
14
+ "./attribution": "./src/attribution.ts",
14
15
  "./protocol": "./src/protocol.ts",
15
16
  "./signing": "./src/signing.ts",
16
17
  "./loader-config": "./src/loader-config.ts"