@morgan-and-morgan/intake-agent 1.0.0-alpha.1 → 1.0.0-alpha.11
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/README.md +11 -1
- package/dist/button-text-us.png +0 -0
- package/dist/consultation-prompt.webp +0 -0
- package/dist/mm-intake-widget.js +284 -112
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -21,6 +21,10 @@ placeholder, don't bump it by hand.
|
|
|
21
21
|
| `release/**` | `x.y.z-beta.N` | `beta` |
|
|
22
22
|
| `main` | `x.y.z` | `latest` |
|
|
23
23
|
|
|
24
|
+
Each bundle also carries its matching chat environment: development for
|
|
25
|
+
`alpha`, UAT for `beta`, and production for `latest`. `data-chat-url` remains
|
|
26
|
+
an explicit override.
|
|
27
|
+
|
|
24
28
|
`fix:` bumps the patch, `feat:` the minor, a `BREAKING CHANGE:` footer the
|
|
25
29
|
major. Every release is tagged `intake-agent-vx.y.z`.
|
|
26
30
|
|
|
@@ -28,5 +32,11 @@ major. Every release is tagged `intake-agent-vx.y.z`.
|
|
|
28
32
|
|
|
29
33
|
| Attribute | Default | Purpose |
|
|
30
34
|
| --- | --- | --- |
|
|
31
|
-
| `data-chat-url` |
|
|
35
|
+
| `data-chat-url` | Release environment URL | Chat iframe target |
|
|
32
36
|
| `data-auto-open` | `false` | Open the chat immediately after mount |
|
|
37
|
+
|
|
38
|
+
The consultation modal reads `window.drupalSettings.mxPopup`, falling back to
|
|
39
|
+
`window.mxChatbotSettings.mxPopup` when the Drupal field is absent. Only the
|
|
40
|
+
boolean `false` suppresses the modal, including calls to `showPrompt()`.
|
|
41
|
+
Missing settings preserve the usual display rules. Settings are read when the
|
|
42
|
+
modal is about to appear; the launcher and chat remain available.
|
|
Binary file
|
|
Binary file
|
package/dist/mm-intake-widget.js
CHANGED
|
@@ -1,45 +1,133 @@
|
|
|
1
1
|
(function() {
|
|
2
2
|
"use strict";
|
|
3
|
-
const DEFAULT_CHAT_PATH = "/embed/intake";
|
|
4
3
|
const readLoaderConfig = (script, baseUri) => {
|
|
5
4
|
var _a;
|
|
6
5
|
const scriptUrl = new URL(script.src, baseUri);
|
|
7
|
-
const
|
|
6
|
+
const bundledChatUrl = "https://chat.mx2.dev/embed/intake";
|
|
7
|
+
const chatUrl = new URL((_a = script.dataset.chatUrl) != null ? _a : bundledChatUrl, scriptUrl).href;
|
|
8
8
|
return {
|
|
9
|
+
assetBaseUrl: new URL(".", scriptUrl).href,
|
|
9
10
|
autoOpen: script.dataset.autoOpen === "true",
|
|
10
11
|
chatOrigin: new URL(chatUrl).origin,
|
|
11
12
|
chatUrl
|
|
12
13
|
};
|
|
13
14
|
};
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const ATTRIBUTION_FIELD_MAX_LENGTH = {
|
|
16
|
+
tracked_landing_page: 4096,
|
|
17
|
+
tracked_landing_timestamp: 255,
|
|
18
|
+
conversion_page: 255,
|
|
19
|
+
ga_client_id: 255,
|
|
20
|
+
gclid_value: 255,
|
|
21
|
+
marketing_source: 255,
|
|
22
|
+
marketing_sub_source: 255
|
|
23
|
+
};
|
|
24
|
+
const ATTRIBUTION_FIELDS = Object.keys(ATTRIBUTION_FIELD_MAX_LENGTH);
|
|
25
|
+
const sanitizeIntakeChatAttribution = (value) => {
|
|
26
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const record = value;
|
|
30
|
+
const sanitized = {};
|
|
31
|
+
for (const field of ATTRIBUTION_FIELDS) {
|
|
32
|
+
const fieldValue = record[field];
|
|
33
|
+
const trimmed = typeof fieldValue === "string" ? fieldValue.trim() : "";
|
|
34
|
+
if (trimmed !== "") {
|
|
35
|
+
sanitized[field] = trimmed.slice(0, ATTRIBUTION_FIELD_MAX_LENGTH[field]);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return Object.keys(sanitized).length === 0 ? null : sanitized;
|
|
39
|
+
};
|
|
40
|
+
const LANDING_PAGE_COOKIES = ["trackedLandingPage", "tracked_landing_page"];
|
|
41
|
+
const LANDING_TIMESTAMP_COOKIES = ["trackedLandingPageTimestamp", "tracked_landing_timestamp"];
|
|
42
|
+
const readTimestampCookie = (cookie) => {
|
|
43
|
+
const raw = readFirstCookie(cookie, LANDING_TIMESTAMP_COOKIES);
|
|
44
|
+
return raw === void 0 ? void 0 : decodeCookieValue(raw);
|
|
45
|
+
};
|
|
46
|
+
const readCookie = (cookie, name) => {
|
|
47
|
+
var _a;
|
|
48
|
+
const raw = (_a = cookie.split(";").map((entry) => entry.trim()).find((entry) => entry.startsWith(`${name}=`))) == null ? void 0 : _a.slice(name.length + 1);
|
|
49
|
+
return raw === void 0 || raw === "" ? void 0 : raw;
|
|
50
|
+
};
|
|
51
|
+
const readFirstCookie = (cookie, names) => {
|
|
52
|
+
for (const name of names) {
|
|
53
|
+
const raw = readCookie(cookie, name);
|
|
54
|
+
if (raw !== void 0) {
|
|
55
|
+
return raw;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return void 0;
|
|
59
|
+
};
|
|
60
|
+
const decodeCookieValue = (raw) => {
|
|
17
61
|
try {
|
|
18
|
-
return
|
|
62
|
+
return decodeURIComponent(raw);
|
|
19
63
|
} catch {
|
|
20
|
-
return
|
|
64
|
+
return raw;
|
|
21
65
|
}
|
|
22
66
|
};
|
|
23
|
-
const
|
|
67
|
+
const parseUrl = (value) => {
|
|
24
68
|
try {
|
|
25
|
-
|
|
69
|
+
return new URL(value);
|
|
26
70
|
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const readLandingPage = (cookie) => {
|
|
75
|
+
const raw = readFirstCookie(cookie, LANDING_PAGE_COOKIES);
|
|
76
|
+
if (raw === void 0 || parseUrl(raw) !== null) {
|
|
77
|
+
return raw;
|
|
78
|
+
}
|
|
79
|
+
const decoded = decodeCookieValue(raw);
|
|
80
|
+
return parseUrl(decoded) === null ? raw : decoded;
|
|
81
|
+
};
|
|
82
|
+
const readGaClientId = (cookie) => {
|
|
83
|
+
const raw = readCookie(cookie, "_ga");
|
|
84
|
+
if (raw === void 0) {
|
|
85
|
+
return void 0;
|
|
27
86
|
}
|
|
87
|
+
const value = decodeCookieValue(raw);
|
|
88
|
+
const parts = value.split(".");
|
|
89
|
+
return parts.length >= 4 ? parts.slice(-2).join(".") : value;
|
|
28
90
|
};
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
91
|
+
const readGclid = (landingUrl) => {
|
|
92
|
+
var _a, _b;
|
|
93
|
+
return landingUrl === void 0 ? void 0 : (_b = (_a = parseUrl(landingUrl)) == null ? void 0 : _a.searchParams.get("gclid")) != null ? _b : void 0;
|
|
94
|
+
};
|
|
95
|
+
const readPathname = (url) => {
|
|
96
|
+
var _a;
|
|
97
|
+
return (_a = parseUrl(url)) == null ? void 0 : _a.pathname;
|
|
98
|
+
};
|
|
99
|
+
const buildHostAttribution = (currentUrl = globalThis.location.href, cookie = document.cookie) => {
|
|
100
|
+
var _a;
|
|
101
|
+
const landingPage = readLandingPage(cookie);
|
|
102
|
+
return (_a = sanitizeIntakeChatAttribution({
|
|
103
|
+
tracked_landing_page: landingPage,
|
|
104
|
+
tracked_landing_timestamp: readTimestampCookie(cookie),
|
|
105
|
+
conversion_page: readPathname(currentUrl),
|
|
106
|
+
ga_client_id: readGaClientId(cookie),
|
|
107
|
+
gclid_value: readGclid(landingPage)
|
|
108
|
+
})) != null ? _a : {};
|
|
109
|
+
};
|
|
110
|
+
const OPEN_STATE_KEY = "mm-intake-widget-open";
|
|
111
|
+
const readOpenState = (storage = sessionStorage) => {
|
|
32
112
|
try {
|
|
33
|
-
return storage.getItem(
|
|
113
|
+
return storage.getItem(OPEN_STATE_KEY) === "true";
|
|
34
114
|
} catch {
|
|
35
115
|
return false;
|
|
36
116
|
}
|
|
37
117
|
};
|
|
38
|
-
const
|
|
118
|
+
const writeOpenState = (isOpen, storage = sessionStorage) => {
|
|
119
|
+
try {
|
|
120
|
+
storage.setItem(OPEN_STATE_KEY, String(isOpen));
|
|
121
|
+
} catch {
|
|
122
|
+
}
|
|
123
|
+
};
|
|
39
124
|
const EMBED_PROTOCOL_VERSION = 1;
|
|
40
125
|
const EMBED_MESSAGE_TYPE = Object.freeze({
|
|
41
126
|
HOST_READY: "mm:intake:host-ready",
|
|
42
127
|
CHILD_READY: "mm:intake:child-ready",
|
|
128
|
+
// Host asks the embedded chat to put the caret in its composer. Kept
|
|
129
|
+
// separate from HOST_READY so signing-card handshakes never steal focus.
|
|
130
|
+
FOCUS_COMPOSER: "mm:intake:focus-composer",
|
|
43
131
|
SIGNING_REQUESTED: "mm:intake:signing-requested",
|
|
44
132
|
SIGNING_RECORDED: "mm:intake:signing-recorded",
|
|
45
133
|
// Chat asks the host page for a signed-in token; the host answers with
|
|
@@ -57,11 +145,18 @@
|
|
|
57
145
|
// belongs to the focused field, so losing it means the keyboard is already
|
|
58
146
|
// leaving — which the host's own visual viewport only admits ~500ms later.
|
|
59
147
|
CARET_STATE: "mm:intake:caret-state",
|
|
60
|
-
// Chat tells the host an inbound AI or operator message just landed
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
// the panel was closed at the time.
|
|
64
|
-
MESSAGE_RECEIVED: "mm:intake:message-received"
|
|
148
|
+
// Chat tells the host an inbound AI or operator message just landed. Kept in
|
|
149
|
+
// the protocol for a future unread affordance; the TEXT US sticky has no badge
|
|
150
|
+
// today. Payload-free: only the chat knows a message arrived while only the
|
|
151
|
+
// host knows whether the panel was closed at the time.
|
|
152
|
+
MESSAGE_RECEIVED: "mm:intake:message-received",
|
|
153
|
+
// Chat asks the host for a fresh attribution snapshot right before a /chat
|
|
154
|
+
// turn; the host answers with ATTRIBUTION as `{ attribution }`. The snapshot
|
|
155
|
+
// sent with HOST_READY is read once at mount and races the marketing site's
|
|
156
|
+
// async first-touch cookie write (MX2-26181), so the landing URL — and the
|
|
157
|
+
// gclid parsed out of it — can only be caught by re-reading at send time.
|
|
158
|
+
GET_ATTRIBUTION: "mm:intake:get-attribution",
|
|
159
|
+
ATTRIBUTION: "mm:intake:attribution"
|
|
65
160
|
});
|
|
66
161
|
const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
67
162
|
const isEmbedProtocolMessage = (value, type) => isRecord(value) && value.type === type && value.version === EMBED_PROTOCOL_VERSION && isRecord(value.payload);
|
|
@@ -116,6 +211,13 @@
|
|
|
116
211
|
return null;
|
|
117
212
|
}
|
|
118
213
|
};
|
|
214
|
+
const hasCompletedSigningEvent = (currentUrl = globalThis.location.href) => {
|
|
215
|
+
try {
|
|
216
|
+
return new URL(currentUrl).searchParams.get("event") === SIGNING_COMPLETE_EVENT;
|
|
217
|
+
} catch {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
119
221
|
const LUCIDE_ICON_PATHS = {
|
|
120
222
|
"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"/>',
|
|
121
223
|
"x": '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
|
|
@@ -130,98 +232,75 @@
|
|
|
130
232
|
};
|
|
131
233
|
const FULL_PAGE_PANEL_QUERY = "(max-width:600px),(max-height:500px)";
|
|
132
234
|
const lucideIcon = (name, className, strokeWidth = 2) => `<svg class="${className}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="${String(strokeWidth)}" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">${LUCIDE_ICON_PATHS[name]}</svg>`;
|
|
133
|
-
const CHAT_ICON = lucideIcon("message-circle", "mm-icon mm-icon-chat");
|
|
134
235
|
const OPEN_CLOSE_ICON = lucideIcon("x", "mm-icon mm-icon-close");
|
|
236
|
+
const PROMPT_CLOSE_ICON = lucideIcon("x", "mm-prompt-close-icon", 2.5);
|
|
135
237
|
const WIDGET_STYLES = [
|
|
136
238
|
':host{all:initial;color-scheme:light;--mm-yellow:#fdeb01;--mm-yellow-hover:#f5de00;--mm-yellow-pressed:#e6cc00;--mm-blue:#115099;--mm-blue-hover:#0b3568;--mm-ink:#111;--mm-white:#fff;--mm-surface-blue:#f3f8ff;--mm-chrome:#e8eef2;--mm-line:#e2e8f0;--mm-muted:#545454;--mm-green:#0f9d58;--mm-font:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif}',
|
|
137
239
|
"*,*::before,*::after{box-sizing:border-box}",
|
|
138
240
|
"button{font:inherit}",
|
|
139
|
-
"@keyframes mm-launcher-in{from{opacity:0;transform:
|
|
241
|
+
"@keyframes mm-launcher-in{from{opacity:0;transform:translateX(8px) scale(.94)}to{opacity:1;transform:none}}",
|
|
242
|
+
"@keyframes mm-close-in{from{opacity:0;transform:scale(.85)}to{opacity:1;transform:scale(1)}}",
|
|
140
243
|
"@keyframes mm-panel-in{from{opacity:0;transform:translateY(10px) scale(.98)}to{opacity:1;transform:none}}",
|
|
141
244
|
"@keyframes mm-panel-out{from{opacity:1;transform:none}to{opacity:0;transform:translateY(10px) scale(.98)}}",
|
|
142
|
-
"@keyframes mm-
|
|
143
|
-
|
|
144
|
-
// .4 opacity, pulse to full with a 1.2x scale, each dot a beat behind the last.
|
|
245
|
+
"@keyframes mm-prompt-backdrop-in{from{opacity:0}to{opacity:1}}",
|
|
246
|
+
"@keyframes mm-prompt-card-in{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:none}}",
|
|
145
247
|
"@keyframes mm-dot{0%,100%{opacity:.4;transform:scale(1)}50%{opacity:1;transform:scale(1.2)}}",
|
|
146
|
-
//
|
|
147
|
-
//
|
|
248
|
+
// Closed: LivePerson TEXT US sticky under the chat layer. Open: swap for a
|
|
249
|
+
// blue X disc above the chat so there is always a clear close control.
|
|
250
|
+
// Same asset as forthepeople.com (button-text-us-white-bg), already rotated
|
|
251
|
+
// 90deg CW like GTM. Placement matches #messaging_gbm: top 42%, ~38px thick,
|
|
252
|
+
// no translateY(-50%). right:10px keeps a small inset from the viewport edge.
|
|
148
253
|
// Against the host page the near-max z-index is only the fallback for
|
|
149
254
|
// browsers without the Popover API — everywhere else the mount rides the top
|
|
150
255
|
// layer (widget.ts) and outranks any host z-index.
|
|
151
|
-
".mm-launcher{position:fixed;right:
|
|
256
|
+
".mm-launcher{position:fixed;right:10px;top:42%;z-index:2147482999;display:grid;place-items:center;width:auto;height:auto;padding:0;border:0;border-radius:0;outline:none;background:transparent;color:var(--mm-ink);box-shadow:none;cursor:pointer;user-select:none;-webkit-user-select:none;-webkit-user-drag:none;transform:none;animation:mm-launcher-in .28s cubic-bezier(.23,1,.32,1) both;transition:transform .15s cubic-bezier(.23,1,.32,1),filter .15s ease,background-color .15s ease}",
|
|
152
257
|
".mm-launcher *{user-select:none;-webkit-user-select:none;-webkit-user-drag:none}",
|
|
153
|
-
".mm-launcher:hover{
|
|
154
|
-
".mm-launcher:active{
|
|
258
|
+
".mm-launcher:hover{transform:translateX(-2px);filter:drop-shadow(0 4px 10px rgba(17,17,17,.18))}",
|
|
259
|
+
".mm-launcher:active{transform:none;filter:none}",
|
|
155
260
|
".mm-launcher:focus-visible{outline:3px solid var(--mm-blue);outline-offset:3px}",
|
|
156
|
-
".mm-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
'.mm-launcher[aria-expanded="true"]
|
|
261
|
+
".mm-text-us{display:block;width:38px;height:auto;pointer-events:none}",
|
|
262
|
+
".mm-icon{position:absolute;top:50%;left:50%;display:none;width:22px;height:22px;pointer-events:none;transform:translate(-50%,-50%)}",
|
|
263
|
+
// Open on desktop: X sits on the chat's top-right corner (chat is bottom:24,
|
|
264
|
+
// height min(640px, 100vh - 48px)).
|
|
265
|
+
'.mm-launcher[aria-expanded="true"]{top:calc(100vh - 24px - min(640px,100vh - 48px) + 12px);right:36px;bottom:auto;z-index:2147483001;width:44px;height:44px;border-radius:999px;background:var(--mm-blue);color:var(--mm-white);box-shadow:0 4px 14px rgba(17,17,17,.2);transform:none;animation:mm-close-in .2s cubic-bezier(.23,1,.32,1) both;filter:none}',
|
|
266
|
+
'.mm-launcher[aria-expanded="true"]:hover,.mm-launcher[aria-expanded="true"]:active{transform:none;filter:none;background:var(--mm-blue-hover)}',
|
|
267
|
+
'.mm-launcher[aria-expanded="true"] .mm-text-us{display:none}',
|
|
161
268
|
'.mm-launcher[aria-expanded="true"] .mm-icon-close{display:block}',
|
|
162
|
-
|
|
163
|
-
// tint. Measured on iOS 26: while the panel is full-page, Safari takes the
|
|
164
|
-
// colour of its own status-bar and URL-bar bands straight off this background
|
|
165
|
-
// — ahead of the host page's `theme-color` and root background, which it
|
|
166
|
-
// ignores outright once the chat frame has committed a navigation. So this is
|
|
167
|
-
// the lever that decides what the bands look like, and the bands sit right
|
|
168
|
-
// against the chat's header and trust line.
|
|
169
|
-
// Two flat halves rather than one colour, because the two bands are sampled
|
|
170
|
-
// independently: the top one always sits against the chat header, while the
|
|
171
|
-
// bottom one sits against the trust line or, with a keyboard up, the composer.
|
|
172
|
-
".mm-chat{position:fixed;right:24px;bottom:100px;z-index:2147483000;display:flex;flex-direction:column;width:min(400px,calc(100vw - 32px));height:min(640px,calc(100vh - 132px));overflow:hidden;border:1px solid var(--mm-line);border-radius:20px;background:linear-gradient(to bottom,var(--mm-chrome) 0 50%,var(--mm-panel-bottom,var(--mm-chrome)) 50% 100%);box-shadow:0 5px 25px rgba(0,0,0,.2);animation:mm-panel-in .18s ease-out both;font-family:var(--mm-font)}",
|
|
173
|
-
// Keyboard up, the chat drops its trust line and the composer becomes the
|
|
174
|
-
// bottom-most surface, so the bottom band has to follow the composer. Only
|
|
175
|
-
// the bottom: the header is still up there against the top one.
|
|
269
|
+
".mm-chat{position:fixed;right:24px;bottom:24px;z-index:2147483000;display:flex;flex-direction:column;width:min(400px,calc(100vw - 32px));height:min(640px,calc(100vh - 48px));overflow:hidden;border:1px solid var(--mm-line);border-radius:20px;background:linear-gradient(to bottom,var(--mm-chrome) 0 50%,var(--mm-panel-bottom,var(--mm-chrome)) 50% 100%);box-shadow:0 5px 25px rgba(0,0,0,.2);animation:mm-panel-in .18s ease-out both;font-family:var(--mm-font)}",
|
|
176
270
|
'.mm-chat[data-keyboard="open"]{--mm-panel-bottom:var(--mm-surface-blue)}',
|
|
177
271
|
'.mm-chat[data-state="closing"]{pointer-events:none;animation-name:mm-panel-out}',
|
|
178
272
|
'.mm-chat[data-state="closed"]{visibility:hidden;animation-name:none}',
|
|
179
273
|
".mm-frame-wrap{position:relative;min-height:0;flex:1;background:linear-gradient(to bottom,var(--mm-chrome) 0 50%,var(--mm-panel-bottom,var(--mm-chrome)) 50% 100%)}",
|
|
180
|
-
// A chat tone, never white: the frame paints this in the gap between the
|
|
181
|
-
// document's load event and the app's first frame, and on a phone that gap is
|
|
182
|
-
// the whole screen.
|
|
183
274
|
".mm-frame{display:block;width:100%;height:100%;border:0;background:var(--mm-chrome)}",
|
|
184
275
|
".mm-loading{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:5px;background:var(--mm-chrome)}",
|
|
185
276
|
".mm-dot{width:8px;height:8px;border-radius:999px;background:var(--mm-muted);animation:mm-dot 1.2s ease-in-out infinite}",
|
|
186
277
|
".mm-dot:nth-child(2){animation-delay:.15s}",
|
|
187
278
|
".mm-dot:nth-child(3){animation-delay:.3s}",
|
|
279
|
+
".mm-prompt-backdrop{position:fixed;inset:0;z-index:2147483002;display:grid;place-items:center;padding:16px;overscroll-behavior:contain;touch-action:none;background:rgba(0,0,0,.64);animation:mm-prompt-backdrop-in .25s cubic-bezier(.23,1,.32,1) both;font-family:var(--mm-font)}",
|
|
280
|
+
".mm-prompt-card{position:relative;width:min(632px,calc(100vw - 32px),calc((100vh - 32px)*1.26148));aspect-ratio:632/501;animation:mm-prompt-card-in .25s cubic-bezier(.23,1,.32,1) both}",
|
|
281
|
+
".mm-prompt-image{display:block;width:100%;height:100%;object-fit:contain;pointer-events:none}",
|
|
282
|
+
".mm-prompt-open,.mm-prompt-close{position:absolute;border:0;outline:none;background:transparent;cursor:pointer}",
|
|
283
|
+
".mm-prompt-open{inset:0;border-radius:24px}",
|
|
284
|
+
".mm-prompt-close{top:4.5%;right:3.1%;z-index:1;display:grid;place-items:center;width:44px;height:44px;border-radius:999px;color:var(--mm-white)}",
|
|
285
|
+
'.mm-prompt-close::before{content:"";position:absolute;width:28px;height:28px;border-radius:999px;background:#050505;box-shadow:0 1px 4px rgba(0,0,0,.35);transition:background-color .12s ease}',
|
|
286
|
+
".mm-prompt-close-icon{position:relative;display:block;width:16px;height:16px;pointer-events:none}",
|
|
287
|
+
".mm-prompt-close:hover::before{background:#252525}",
|
|
288
|
+
".mm-prompt-open:focus-visible{outline:3px solid var(--mm-yellow);outline-offset:-8px}",
|
|
289
|
+
".mm-prompt-close:focus-visible{outline:none}",
|
|
290
|
+
".mm-prompt-close:focus-visible::before{background:#050505;box-shadow:inset 0 0 0 2px var(--mm-yellow),0 1px 4px rgba(0,0,0,.35)}",
|
|
188
291
|
"[hidden]{display:none!important}",
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
//
|
|
193
|
-
// `min-height:0` on the panel so a short landscape viewport shrinks it all the
|
|
194
|
-
// way down instead of overflowing the screen and clipping the chat's own
|
|
195
|
-
// rotate-your-phone overlay.
|
|
196
|
-
//
|
|
197
|
-
// 16px from the corner is right for a normal host site, which has nothing of
|
|
198
|
-
// its own down there. A host that mounts its own bottom chrome — the client
|
|
199
|
-
// portal and its tab bar — sets `--mm-launcher-bottom` on any ancestor of the
|
|
200
|
-
// mount (custom properties reach into the shadow tree) to lift the launcher
|
|
201
|
-
// clear of it. Only the closed launcher: open, it moves to the top corner and
|
|
202
|
-
// tracks the viewport instead.
|
|
203
|
-
// `left:0;right:0` rather than `width:100vw`: `100vw` counts the classic
|
|
204
|
-
// scrollbar the host page still has behind us, so a right-anchored panel that
|
|
205
|
-
// wide starts a scrollbar's width off the left edge and clips its own header.
|
|
206
|
-
//
|
|
207
|
-
// The shadow fills the strip below the panel. Safari's bottom bar is
|
|
208
|
-
// translucent once it collapses — scrolled down, or any time the keyboard is
|
|
209
|
-
// up — and `visualViewport.height` stops short of it, so the host page shows
|
|
210
|
-
// through underneath the chat. `lvh - svh` is exactly that bar's height, and
|
|
211
|
-
// it is 0 wherever the browser has no collapsing bar, so this costs nothing
|
|
212
|
-
// there. A shadow rather than extra height: the trust line and composer must
|
|
213
|
-
// stay above the bar, not slide under it.
|
|
214
|
-
`@media ${FULL_PAGE_PANEL_QUERY}{.mm-launcher{right:16px;bottom:var(--mm-launcher-bottom,16px)}.mm-launcher[aria-expanded="true"]{top:calc(var(--mm-viewport-top,0px) + 10px);right:10px;bottom:auto;width:44px;height:44px;box-shadow:0 4px 14px rgba(17,17,17,.2)}.mm-launcher[aria-expanded="true"] .mm-icon{width:22px;height:22px}.mm-chat{left:0;right:0;top:var(--mm-viewport-top,auto);bottom:0;width:auto;height:var(--mm-viewport-height,100dvh);min-height:0;border:0;border-radius:0;box-shadow:0 calc(100lvh - 100svh) 0 var(--mm-panel-bottom,var(--mm-chrome))}}`,
|
|
215
|
-
"@media(prefers-reduced-motion:reduce){.mm-launcher,.mm-chat{animation:none;transition:none}.mm-badge::after,.mm-dot{animation:none}}"
|
|
292
|
+
`@media ${FULL_PAGE_PANEL_QUERY}{.mm-launcher[aria-expanded="true"]{top:calc(var(--mm-viewport-top,0px) + 10px);right:10px;bottom:auto;width:44px;height:44px}.mm-chat{left:0;right:0;top:var(--mm-viewport-top,auto);bottom:0;width:auto;height:var(--mm-viewport-height,100dvh);min-height:0;border:0;border-radius:0;box-shadow:0 calc(100lvh - 100svh) 0 var(--mm-panel-bottom,var(--mm-chrome))}}`,
|
|
293
|
+
"@media(forced-colors:active){.mm-prompt-close:focus-visible{outline:2px solid ButtonText;outline-offset:-9px}}",
|
|
294
|
+
"@media(prefers-reduced-motion:reduce){.mm-launcher,.mm-chat{animation:none;transition:none}.mm-prompt-card{animation-name:mm-prompt-backdrop-in}.mm-dot{animation:none}}"
|
|
216
295
|
].join("");
|
|
217
|
-
const
|
|
296
|
+
const widgetMarkup = (launcherImageUrl, promptImageUrl) => [
|
|
218
297
|
`<style>${WIDGET_STYLES}</style>`,
|
|
219
|
-
`<button class="mm-launcher" type="button" aria-label="Open intake chat" aria-expanded="false" draggable="false"
|
|
298
|
+
`<button class="mm-launcher" type="button" aria-label="Open intake chat" aria-expanded="false" draggable="false"><img class="mm-text-us" src="${launcherImageUrl}" alt="" width="38" height="147" decoding="async" />${OPEN_CLOSE_ICON}</button>`,
|
|
220
299
|
'<section class="mm-chat" role="dialog" aria-label="Morgan and Morgan intake chat" inert hidden>',
|
|
221
|
-
// Dots rather than a sentence: the panel is up for a second or two and a line
|
|
222
|
-
// of copy reads like an error the visitor has to act on. The label carries the
|
|
223
|
-
// same information for a screen reader.
|
|
224
300
|
'<div class="mm-frame-wrap"><div class="mm-loading" role="status" aria-label="Loading secure chat"><span class="mm-dot"></span><span class="mm-dot"></span><span class="mm-dot"></span></div><iframe class="mm-frame mm-chat-frame" title="Morgan and Morgan intake chat" referrerpolicy="strict-origin-when-cross-origin"></iframe></div>',
|
|
301
|
+
"</section>",
|
|
302
|
+
'<section class="mm-prompt-backdrop" role="dialog" aria-modal="true" aria-label="Start your free consultation" inert hidden>',
|
|
303
|
+
`<div class="mm-prompt-card"><img class="mm-prompt-image" src="${promptImageUrl}" alt="Our team is available 24/7. Chat with a real person. Click to start your free consultation." width="632" height="501" decoding="async" /><button class="mm-prompt-open" type="button" aria-label="Message us about a free consultation"></button><button class="mm-prompt-close" type="button" aria-label="Dismiss consultation prompt">${PROMPT_CLOSE_ICON}</button></div>`,
|
|
225
304
|
"</section>"
|
|
226
305
|
].join("");
|
|
227
306
|
const requireElement = (shadow, selector) => {
|
|
@@ -231,38 +310,66 @@
|
|
|
231
310
|
}
|
|
232
311
|
return element;
|
|
233
312
|
};
|
|
234
|
-
const renderWidgetShell = (shadow) => {
|
|
235
|
-
shadow.innerHTML =
|
|
313
|
+
const renderWidgetShell = (shadow, launcherImageUrl, promptImageUrl) => {
|
|
314
|
+
shadow.innerHTML = widgetMarkup(launcherImageUrl, promptImageUrl);
|
|
236
315
|
return {
|
|
237
316
|
chatDialog: requireElement(shadow, ".mm-chat"),
|
|
238
317
|
chatFrame: requireElement(shadow, ".mm-chat-frame"),
|
|
239
318
|
chatLoading: requireElement(shadow, ".mm-loading"),
|
|
240
319
|
launcher: requireElement(shadow, ".mm-launcher"),
|
|
241
|
-
|
|
320
|
+
promptBackdrop: requireElement(shadow, ".mm-prompt-backdrop"),
|
|
321
|
+
promptClose: requireElement(shadow, ".mm-prompt-close"),
|
|
322
|
+
promptOpen: requireElement(shadow, ".mm-prompt-open")
|
|
242
323
|
};
|
|
243
324
|
};
|
|
244
325
|
const PANEL_ANIMATION_MS = 180;
|
|
326
|
+
const PROMPT_DELAY_MS = 5e3;
|
|
327
|
+
const PROMPT_COOLDOWN_MS = 24 * 60 * 60 * 1e3;
|
|
328
|
+
const PROMPT_COOLDOWN_KEY = "mm-intake-widget-prompt-dismissed-at";
|
|
329
|
+
const PROMPT_SESSION_KEY = "mm-intake-widget-prompt-dismissed";
|
|
245
330
|
const CHAT_CHROME_COLOR = "#e8eef2";
|
|
246
331
|
const KEYBOARD_MIN_SHRINK_PX = 100;
|
|
247
332
|
const CHAT_READY_FALLBACK_MS = 4e3;
|
|
248
|
-
const getLauncherLabel = (isOpen, hasUnread) => {
|
|
249
|
-
if (isOpen) {
|
|
250
|
-
return "Close intake chat";
|
|
251
|
-
}
|
|
252
|
-
return hasUnread ? "Open intake chat, new message" : "Open intake chat";
|
|
253
|
-
};
|
|
254
333
|
const cleanSigningReturnUrl = () => {
|
|
255
334
|
const cleanedReturnUrl = buildSigningReturnUrl();
|
|
256
335
|
if (cleanedReturnUrl !== null) {
|
|
257
336
|
globalThis.history.replaceState(globalThis.history.state, "", cleanedReturnUrl);
|
|
258
337
|
}
|
|
259
338
|
};
|
|
339
|
+
const hasPromptCooldown = () => {
|
|
340
|
+
try {
|
|
341
|
+
if (globalThis.sessionStorage.getItem(PROMPT_SESSION_KEY) === "true") {
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
} catch {
|
|
345
|
+
}
|
|
346
|
+
try {
|
|
347
|
+
const dismissedAt = Number(globalThis.localStorage.getItem(PROMPT_COOLDOWN_KEY));
|
|
348
|
+
return Number.isFinite(dismissedAt) && dismissedAt > 0 && Date.now() - dismissedAt < PROMPT_COOLDOWN_MS;
|
|
349
|
+
} catch {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
const rememberPromptDismissal = () => {
|
|
354
|
+
try {
|
|
355
|
+
globalThis.sessionStorage.setItem(PROMPT_SESSION_KEY, "true");
|
|
356
|
+
} catch {
|
|
357
|
+
}
|
|
358
|
+
try {
|
|
359
|
+
globalThis.localStorage.setItem(PROMPT_COOLDOWN_KEY, String(Date.now()));
|
|
360
|
+
} catch {
|
|
361
|
+
}
|
|
362
|
+
};
|
|
260
363
|
const mountIntakeWidget = (config, onDestroy) => {
|
|
261
364
|
var _a;
|
|
262
365
|
const mount = document.createElement("div");
|
|
263
366
|
mount.dataset.mmIntakeWidget = "";
|
|
264
367
|
const shadow = mount.attachShadow({ mode: "open" });
|
|
265
|
-
const
|
|
368
|
+
const assetUrl = (fileName) => {
|
|
369
|
+
var _a2;
|
|
370
|
+
return new URL(fileName, (_a2 = config.assetBaseUrl) != null ? _a2 : `${config.chatOrigin}/intake-widget/`).href;
|
|
371
|
+
};
|
|
372
|
+
const elements = renderWidgetShell(shadow, assetUrl("button-text-us.png"), assetUrl("consultation-prompt.webp"));
|
|
266
373
|
document.body.append(mount);
|
|
267
374
|
if (typeof mount.showPopover === "function") {
|
|
268
375
|
mount.setAttribute("popover", "manual");
|
|
@@ -272,26 +379,18 @@
|
|
|
272
379
|
} catch {
|
|
273
380
|
}
|
|
274
381
|
}
|
|
275
|
-
const
|
|
382
|
+
const returnedFromSigning = hasCompletedSigningEvent();
|
|
276
383
|
let chatHasLoaded = false;
|
|
277
384
|
let chatCloseTimer;
|
|
278
385
|
let chatReadyTimer;
|
|
386
|
+
let promptTimer;
|
|
387
|
+
let promptReturnFocus = null;
|
|
279
388
|
let isKeyboardOpen = false;
|
|
280
389
|
let hasReleasedKeyboardEarly = false;
|
|
281
|
-
let hasUnread = readUnreadState();
|
|
282
|
-
const loadUnreadIndicator = () => {
|
|
283
|
-
elements.unreadBadge.hidden = !hasUnread;
|
|
284
|
-
syncLauncherLabel();
|
|
285
|
-
};
|
|
286
|
-
const setHasUnread = (next) => {
|
|
287
|
-
hasUnread = next;
|
|
288
|
-
writeUnreadState(next);
|
|
289
|
-
loadUnreadIndicator();
|
|
290
|
-
};
|
|
291
390
|
const syncLauncherLabel = () => {
|
|
292
391
|
const isOpen = elements.chatDialog.dataset.state === "open";
|
|
293
392
|
elements.launcher.setAttribute("aria-expanded", String(isOpen));
|
|
294
|
-
elements.launcher.setAttribute("aria-label",
|
|
393
|
+
elements.launcher.setAttribute("aria-label", isOpen ? "Close intake chat" : "Open intake chat");
|
|
295
394
|
};
|
|
296
395
|
const postToChat = (message) => {
|
|
297
396
|
var _a2;
|
|
@@ -303,11 +402,25 @@
|
|
|
303
402
|
const hostUrl = buildSigningReturnUrl(currentUrl);
|
|
304
403
|
postToChat(
|
|
305
404
|
createEmbedProtocolMessage(EMBED_MESSAGE_TYPE.HOST_READY, {
|
|
405
|
+
// Marketing attribution (MX2-25162): the marketing site's first-touch
|
|
406
|
+
// cookies and the page the chat opened on are only visible from this
|
|
407
|
+
// page, never from the cross-origin chat frame. The chat forwards them
|
|
408
|
+
// on its first /chat turn and Salesforce builds the intake's
|
|
409
|
+
// Marketing_Tracking_Info__c record from them.
|
|
410
|
+
attribution: buildHostAttribution(),
|
|
306
411
|
...hostUrl === null ? {} : { hostUrl },
|
|
307
|
-
...completedEnvelopeId === null ? {} : { completedEnvelopeId }
|
|
412
|
+
...completedEnvelopeId === null ? {} : { completedEnvelopeId },
|
|
413
|
+
// Sent even without an envelope id: the Salesforce service is what
|
|
414
|
+
// appends `envelope_id` to the return URL, and the prod org has shipped
|
|
415
|
+
// without that append. The chat's signing card falls back to its own
|
|
416
|
+
// envelope id on this flag.
|
|
417
|
+
...hasCompletedSigningEvent(currentUrl) ? { signingComplete: true } : {}
|
|
308
418
|
})
|
|
309
419
|
);
|
|
310
420
|
};
|
|
421
|
+
const focusChatComposer = () => {
|
|
422
|
+
postToChat(createEmbedProtocolMessage(EMBED_MESSAGE_TYPE.FOCUS_COMPOSER));
|
|
423
|
+
};
|
|
311
424
|
const answerAuthTokenRequest = async () => {
|
|
312
425
|
var _a2;
|
|
313
426
|
let token;
|
|
@@ -330,6 +443,33 @@
|
|
|
330
443
|
chatReadyTimer = void 0;
|
|
331
444
|
elements.chatLoading.hidden = true;
|
|
332
445
|
};
|
|
446
|
+
const dismissPrompt = (returnFocus = false) => {
|
|
447
|
+
globalThis.clearTimeout(promptTimer);
|
|
448
|
+
promptTimer = void 0;
|
|
449
|
+
const wasVisible = !elements.promptBackdrop.hidden;
|
|
450
|
+
elements.promptBackdrop.hidden = true;
|
|
451
|
+
elements.promptBackdrop.setAttribute("inert", "");
|
|
452
|
+
rememberPromptDismissal();
|
|
453
|
+
if (returnFocus && wasVisible) {
|
|
454
|
+
const focusTarget = (promptReturnFocus == null ? void 0 : promptReturnFocus.isConnected) === true ? promptReturnFocus : elements.launcher;
|
|
455
|
+
promptReturnFocus = null;
|
|
456
|
+
focusTarget.focus();
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
const showPrompt = () => {
|
|
460
|
+
var _a2, _b, _c;
|
|
461
|
+
globalThis.clearTimeout(promptTimer);
|
|
462
|
+
promptTimer = void 0;
|
|
463
|
+
const host = globalThis;
|
|
464
|
+
const mxPopup = (_c = (_a2 = host.drupalSettings) == null ? void 0 : _a2.mxPopup) != null ? _c : (_b = host.mxChatbotSettings) == null ? void 0 : _b.mxPopup;
|
|
465
|
+
if (mxPopup === false || hasPromptCooldown() || elements.chatDialog.dataset.state === "open") {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
promptReturnFocus = document.activeElement instanceof HTMLElement && document.activeElement !== mount ? document.activeElement : null;
|
|
469
|
+
elements.promptBackdrop.hidden = false;
|
|
470
|
+
elements.promptBackdrop.removeAttribute("inert");
|
|
471
|
+
elements.promptClose.focus();
|
|
472
|
+
};
|
|
333
473
|
const syncViewport = () => {
|
|
334
474
|
const viewport = globalThis.visualViewport;
|
|
335
475
|
if (!viewport || viewport.height === 0) {
|
|
@@ -448,19 +588,21 @@
|
|
|
448
588
|
stopViewportTracking();
|
|
449
589
|
};
|
|
450
590
|
const openChat = () => {
|
|
591
|
+
dismissPrompt();
|
|
451
592
|
globalThis.clearTimeout(chatCloseTimer);
|
|
452
593
|
chatCloseTimer = void 0;
|
|
453
594
|
elements.chatDialog.hidden = false;
|
|
454
595
|
elements.chatDialog.dataset.state = "open";
|
|
455
596
|
elements.chatDialog.removeAttribute("aria-hidden");
|
|
456
597
|
elements.chatDialog.removeAttribute("inert");
|
|
457
|
-
|
|
598
|
+
syncLauncherLabel();
|
|
458
599
|
startViewportTracking();
|
|
459
600
|
ensureChatLoaded();
|
|
460
601
|
elements.chatFrame.focus();
|
|
461
602
|
writeOpenState(true);
|
|
462
603
|
if (chatHasLoaded) {
|
|
463
604
|
postHostReady();
|
|
605
|
+
focusChatComposer();
|
|
464
606
|
}
|
|
465
607
|
};
|
|
466
608
|
const closeChat = () => {
|
|
@@ -496,12 +638,19 @@
|
|
|
496
638
|
if (isEmbedProtocolMessage(message, EMBED_MESSAGE_TYPE.CHILD_READY)) {
|
|
497
639
|
revealChat();
|
|
498
640
|
postHostReady();
|
|
641
|
+
if (elements.chatDialog.dataset.state === "open") {
|
|
642
|
+
focusChatComposer();
|
|
643
|
+
}
|
|
499
644
|
return;
|
|
500
645
|
}
|
|
501
646
|
if (isEmbedProtocolMessage(message, EMBED_MESSAGE_TYPE.AUTH_TOKEN_REQUEST)) {
|
|
502
647
|
void answerAuthTokenRequest();
|
|
503
648
|
return;
|
|
504
649
|
}
|
|
650
|
+
if (isEmbedProtocolMessage(message, EMBED_MESSAGE_TYPE.GET_ATTRIBUTION)) {
|
|
651
|
+
postToChat(createEmbedProtocolMessage(EMBED_MESSAGE_TYPE.ATTRIBUTION, { attribution: buildHostAttribution() }));
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
505
654
|
if (isEmbedProtocolMessage(message, EMBED_MESSAGE_TYPE.CARET_STATE)) {
|
|
506
655
|
if (message.payload.isFocused === true) {
|
|
507
656
|
hasReleasedKeyboardEarly = false;
|
|
@@ -511,9 +660,6 @@
|
|
|
511
660
|
return;
|
|
512
661
|
}
|
|
513
662
|
if (isEmbedProtocolMessage(message, EMBED_MESSAGE_TYPE.MESSAGE_RECEIVED)) {
|
|
514
|
-
if (elements.chatDialog.dataset.state !== "open") {
|
|
515
|
-
setHasUnread(true);
|
|
516
|
-
}
|
|
517
663
|
return;
|
|
518
664
|
}
|
|
519
665
|
if (isEmbedProtocolMessage(message, EMBED_MESSAGE_TYPE.SIGNING_REQUESTED)) {
|
|
@@ -526,9 +672,24 @@
|
|
|
526
672
|
}
|
|
527
673
|
};
|
|
528
674
|
const handleKeydown = (event) => {
|
|
675
|
+
if (event.key === "Tab" && !elements.promptBackdrop.hidden) {
|
|
676
|
+
const { activeElement } = shadow;
|
|
677
|
+
if (!event.shiftKey && activeElement === elements.promptClose) {
|
|
678
|
+
event.preventDefault();
|
|
679
|
+
elements.promptOpen.focus();
|
|
680
|
+
} else if (event.shiftKey && activeElement === elements.promptOpen) {
|
|
681
|
+
event.preventDefault();
|
|
682
|
+
elements.promptClose.focus();
|
|
683
|
+
}
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
529
686
|
if (event.key !== "Escape") {
|
|
530
687
|
return;
|
|
531
688
|
}
|
|
689
|
+
if (!elements.promptBackdrop.hidden) {
|
|
690
|
+
dismissPrompt(true);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
532
693
|
if (elements.chatDialog.dataset.state === "open") {
|
|
533
694
|
closeChat();
|
|
534
695
|
}
|
|
@@ -537,6 +698,7 @@
|
|
|
537
698
|
stopViewportTracking();
|
|
538
699
|
globalThis.clearTimeout(chatCloseTimer);
|
|
539
700
|
globalThis.clearTimeout(chatReadyTimer);
|
|
701
|
+
globalThis.clearTimeout(promptTimer);
|
|
540
702
|
globalThis.removeEventListener("message", handleMessage);
|
|
541
703
|
document.removeEventListener("keydown", handleKeydown);
|
|
542
704
|
mount.remove();
|
|
@@ -549,6 +711,13 @@
|
|
|
549
711
|
openChat();
|
|
550
712
|
}
|
|
551
713
|
});
|
|
714
|
+
elements.promptOpen.addEventListener("click", openChat);
|
|
715
|
+
elements.promptClose.addEventListener("click", () => dismissPrompt(true));
|
|
716
|
+
elements.promptBackdrop.addEventListener("click", (event) => {
|
|
717
|
+
if (event.target === elements.promptBackdrop) {
|
|
718
|
+
dismissPrompt(true);
|
|
719
|
+
}
|
|
720
|
+
});
|
|
552
721
|
elements.chatDialog.addEventListener("animationend", (event) => {
|
|
553
722
|
if (event.target === elements.chatDialog) {
|
|
554
723
|
finishChatClose();
|
|
@@ -562,14 +731,17 @@
|
|
|
562
731
|
});
|
|
563
732
|
globalThis.addEventListener("message", handleMessage);
|
|
564
733
|
document.addEventListener("keydown", handleKeydown);
|
|
565
|
-
|
|
566
|
-
if (
|
|
734
|
+
syncLauncherLabel();
|
|
735
|
+
if (returnedFromSigning || readOpenState()) {
|
|
567
736
|
openChat();
|
|
737
|
+
} else if (!hasPromptCooldown()) {
|
|
738
|
+
promptTimer = globalThis.setTimeout(showPrompt, PROMPT_DELAY_MS);
|
|
568
739
|
}
|
|
569
740
|
return Object.freeze({
|
|
570
741
|
close: closeChat,
|
|
571
742
|
destroy,
|
|
572
743
|
open: openChat,
|
|
744
|
+
showPrompt,
|
|
573
745
|
version: EMBED_PROTOCOL_VERSION
|
|
574
746
|
});
|
|
575
747
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morgan-and-morgan/intake-agent",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.11",
|
|
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"
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
"build": "pnpm run build:bundle",
|
|
23
24
|
"build:bundle": "node scripts/generate-icons.mjs && vite build",
|
|
24
25
|
"generate:icons": "node scripts/generate-icons.mjs",
|
|
26
|
+
"test": "node --test 'src/*.test.mjs'",
|
|
25
27
|
"typecheck": "tsc --noEmit",
|
|
26
28
|
"lint": "eslint \"src/**/*.ts\" --quiet",
|
|
27
29
|
"lint:fix": "eslint \"src/**/*.ts\" --quiet --fix",
|