@morgan-and-morgan/intake-agent 1.0.0-alpha.1 → 1.0.0-alpha.10
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 +303 -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;
|
|
27
72
|
}
|
|
28
73
|
};
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
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;
|
|
86
|
+
}
|
|
87
|
+
const value = decodeCookieValue(raw);
|
|
88
|
+
const parts = value.split(".");
|
|
89
|
+
return parts.length >= 4 ? parts.slice(-2).join(".") : value;
|
|
90
|
+
};
|
|
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,84 @@
|
|
|
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
|
+
};
|
|
363
|
+
const playPromptSound = async () => {
|
|
364
|
+
try {
|
|
365
|
+
const context = new AudioContext();
|
|
366
|
+
const oscillator = context.createOscillator();
|
|
367
|
+
const gain = context.createGain();
|
|
368
|
+
oscillator.frequency.setValueAtTime(660, context.currentTime);
|
|
369
|
+
oscillator.frequency.exponentialRampToValueAtTime(880, context.currentTime + 0.16);
|
|
370
|
+
gain.gain.setValueAtTime(0.08, context.currentTime);
|
|
371
|
+
gain.gain.exponentialRampToValueAtTime(1e-3, context.currentTime + 0.2);
|
|
372
|
+
oscillator.connect(gain);
|
|
373
|
+
gain.connect(context.destination);
|
|
374
|
+
oscillator.start();
|
|
375
|
+
oscillator.stop(context.currentTime + 0.2);
|
|
376
|
+
oscillator.addEventListener("ended", () => void context.close());
|
|
377
|
+
await context.resume();
|
|
378
|
+
} catch {
|
|
379
|
+
}
|
|
380
|
+
};
|
|
260
381
|
const mountIntakeWidget = (config, onDestroy) => {
|
|
261
382
|
var _a;
|
|
262
383
|
const mount = document.createElement("div");
|
|
263
384
|
mount.dataset.mmIntakeWidget = "";
|
|
264
385
|
const shadow = mount.attachShadow({ mode: "open" });
|
|
265
|
-
const
|
|
386
|
+
const assetUrl = (fileName) => {
|
|
387
|
+
var _a2;
|
|
388
|
+
return new URL(fileName, (_a2 = config.assetBaseUrl) != null ? _a2 : `${config.chatOrigin}/intake-widget/`).href;
|
|
389
|
+
};
|
|
390
|
+
const elements = renderWidgetShell(shadow, assetUrl("button-text-us.png"), assetUrl("consultation-prompt.webp"));
|
|
266
391
|
document.body.append(mount);
|
|
267
392
|
if (typeof mount.showPopover === "function") {
|
|
268
393
|
mount.setAttribute("popover", "manual");
|
|
@@ -272,26 +397,18 @@
|
|
|
272
397
|
} catch {
|
|
273
398
|
}
|
|
274
399
|
}
|
|
275
|
-
const
|
|
400
|
+
const returnedFromSigning = hasCompletedSigningEvent();
|
|
276
401
|
let chatHasLoaded = false;
|
|
277
402
|
let chatCloseTimer;
|
|
278
403
|
let chatReadyTimer;
|
|
404
|
+
let promptTimer;
|
|
405
|
+
let promptReturnFocus = null;
|
|
279
406
|
let isKeyboardOpen = false;
|
|
280
407
|
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
408
|
const syncLauncherLabel = () => {
|
|
292
409
|
const isOpen = elements.chatDialog.dataset.state === "open";
|
|
293
410
|
elements.launcher.setAttribute("aria-expanded", String(isOpen));
|
|
294
|
-
elements.launcher.setAttribute("aria-label",
|
|
411
|
+
elements.launcher.setAttribute("aria-label", isOpen ? "Close intake chat" : "Open intake chat");
|
|
295
412
|
};
|
|
296
413
|
const postToChat = (message) => {
|
|
297
414
|
var _a2;
|
|
@@ -303,11 +420,25 @@
|
|
|
303
420
|
const hostUrl = buildSigningReturnUrl(currentUrl);
|
|
304
421
|
postToChat(
|
|
305
422
|
createEmbedProtocolMessage(EMBED_MESSAGE_TYPE.HOST_READY, {
|
|
423
|
+
// Marketing attribution (MX2-25162): the marketing site's first-touch
|
|
424
|
+
// cookies and the page the chat opened on are only visible from this
|
|
425
|
+
// page, never from the cross-origin chat frame. The chat forwards them
|
|
426
|
+
// on its first /chat turn and Salesforce builds the intake's
|
|
427
|
+
// Marketing_Tracking_Info__c record from them.
|
|
428
|
+
attribution: buildHostAttribution(),
|
|
306
429
|
...hostUrl === null ? {} : { hostUrl },
|
|
307
|
-
...completedEnvelopeId === null ? {} : { completedEnvelopeId }
|
|
430
|
+
...completedEnvelopeId === null ? {} : { completedEnvelopeId },
|
|
431
|
+
// Sent even without an envelope id: the Salesforce service is what
|
|
432
|
+
// appends `envelope_id` to the return URL, and the prod org has shipped
|
|
433
|
+
// without that append. The chat's signing card falls back to its own
|
|
434
|
+
// envelope id on this flag.
|
|
435
|
+
...hasCompletedSigningEvent(currentUrl) ? { signingComplete: true } : {}
|
|
308
436
|
})
|
|
309
437
|
);
|
|
310
438
|
};
|
|
439
|
+
const focusChatComposer = () => {
|
|
440
|
+
postToChat(createEmbedProtocolMessage(EMBED_MESSAGE_TYPE.FOCUS_COMPOSER));
|
|
441
|
+
};
|
|
311
442
|
const answerAuthTokenRequest = async () => {
|
|
312
443
|
var _a2;
|
|
313
444
|
let token;
|
|
@@ -330,6 +461,34 @@
|
|
|
330
461
|
chatReadyTimer = void 0;
|
|
331
462
|
elements.chatLoading.hidden = true;
|
|
332
463
|
};
|
|
464
|
+
const dismissPrompt = (returnFocus = false) => {
|
|
465
|
+
globalThis.clearTimeout(promptTimer);
|
|
466
|
+
promptTimer = void 0;
|
|
467
|
+
const wasVisible = !elements.promptBackdrop.hidden;
|
|
468
|
+
elements.promptBackdrop.hidden = true;
|
|
469
|
+
elements.promptBackdrop.setAttribute("inert", "");
|
|
470
|
+
rememberPromptDismissal();
|
|
471
|
+
if (returnFocus && wasVisible) {
|
|
472
|
+
const focusTarget = (promptReturnFocus == null ? void 0 : promptReturnFocus.isConnected) === true ? promptReturnFocus : elements.launcher;
|
|
473
|
+
promptReturnFocus = null;
|
|
474
|
+
focusTarget.focus();
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
const showPrompt = () => {
|
|
478
|
+
var _a2, _b, _c;
|
|
479
|
+
globalThis.clearTimeout(promptTimer);
|
|
480
|
+
promptTimer = void 0;
|
|
481
|
+
const host = globalThis;
|
|
482
|
+
const mxPopup = (_c = (_a2 = host.drupalSettings) == null ? void 0 : _a2.mxPopup) != null ? _c : (_b = host.mxChatbotSettings) == null ? void 0 : _b.mxPopup;
|
|
483
|
+
if (mxPopup === false || hasPromptCooldown() || elements.chatDialog.dataset.state === "open") {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
promptReturnFocus = document.activeElement instanceof HTMLElement && document.activeElement !== mount ? document.activeElement : null;
|
|
487
|
+
elements.promptBackdrop.hidden = false;
|
|
488
|
+
elements.promptBackdrop.removeAttribute("inert");
|
|
489
|
+
elements.promptClose.focus();
|
|
490
|
+
void playPromptSound();
|
|
491
|
+
};
|
|
333
492
|
const syncViewport = () => {
|
|
334
493
|
const viewport = globalThis.visualViewport;
|
|
335
494
|
if (!viewport || viewport.height === 0) {
|
|
@@ -448,19 +607,21 @@
|
|
|
448
607
|
stopViewportTracking();
|
|
449
608
|
};
|
|
450
609
|
const openChat = () => {
|
|
610
|
+
dismissPrompt();
|
|
451
611
|
globalThis.clearTimeout(chatCloseTimer);
|
|
452
612
|
chatCloseTimer = void 0;
|
|
453
613
|
elements.chatDialog.hidden = false;
|
|
454
614
|
elements.chatDialog.dataset.state = "open";
|
|
455
615
|
elements.chatDialog.removeAttribute("aria-hidden");
|
|
456
616
|
elements.chatDialog.removeAttribute("inert");
|
|
457
|
-
|
|
617
|
+
syncLauncherLabel();
|
|
458
618
|
startViewportTracking();
|
|
459
619
|
ensureChatLoaded();
|
|
460
620
|
elements.chatFrame.focus();
|
|
461
621
|
writeOpenState(true);
|
|
462
622
|
if (chatHasLoaded) {
|
|
463
623
|
postHostReady();
|
|
624
|
+
focusChatComposer();
|
|
464
625
|
}
|
|
465
626
|
};
|
|
466
627
|
const closeChat = () => {
|
|
@@ -496,12 +657,19 @@
|
|
|
496
657
|
if (isEmbedProtocolMessage(message, EMBED_MESSAGE_TYPE.CHILD_READY)) {
|
|
497
658
|
revealChat();
|
|
498
659
|
postHostReady();
|
|
660
|
+
if (elements.chatDialog.dataset.state === "open") {
|
|
661
|
+
focusChatComposer();
|
|
662
|
+
}
|
|
499
663
|
return;
|
|
500
664
|
}
|
|
501
665
|
if (isEmbedProtocolMessage(message, EMBED_MESSAGE_TYPE.AUTH_TOKEN_REQUEST)) {
|
|
502
666
|
void answerAuthTokenRequest();
|
|
503
667
|
return;
|
|
504
668
|
}
|
|
669
|
+
if (isEmbedProtocolMessage(message, EMBED_MESSAGE_TYPE.GET_ATTRIBUTION)) {
|
|
670
|
+
postToChat(createEmbedProtocolMessage(EMBED_MESSAGE_TYPE.ATTRIBUTION, { attribution: buildHostAttribution() }));
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
505
673
|
if (isEmbedProtocolMessage(message, EMBED_MESSAGE_TYPE.CARET_STATE)) {
|
|
506
674
|
if (message.payload.isFocused === true) {
|
|
507
675
|
hasReleasedKeyboardEarly = false;
|
|
@@ -511,9 +679,6 @@
|
|
|
511
679
|
return;
|
|
512
680
|
}
|
|
513
681
|
if (isEmbedProtocolMessage(message, EMBED_MESSAGE_TYPE.MESSAGE_RECEIVED)) {
|
|
514
|
-
if (elements.chatDialog.dataset.state !== "open") {
|
|
515
|
-
setHasUnread(true);
|
|
516
|
-
}
|
|
517
682
|
return;
|
|
518
683
|
}
|
|
519
684
|
if (isEmbedProtocolMessage(message, EMBED_MESSAGE_TYPE.SIGNING_REQUESTED)) {
|
|
@@ -526,9 +691,24 @@
|
|
|
526
691
|
}
|
|
527
692
|
};
|
|
528
693
|
const handleKeydown = (event) => {
|
|
694
|
+
if (event.key === "Tab" && !elements.promptBackdrop.hidden) {
|
|
695
|
+
const { activeElement } = shadow;
|
|
696
|
+
if (!event.shiftKey && activeElement === elements.promptClose) {
|
|
697
|
+
event.preventDefault();
|
|
698
|
+
elements.promptOpen.focus();
|
|
699
|
+
} else if (event.shiftKey && activeElement === elements.promptOpen) {
|
|
700
|
+
event.preventDefault();
|
|
701
|
+
elements.promptClose.focus();
|
|
702
|
+
}
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
529
705
|
if (event.key !== "Escape") {
|
|
530
706
|
return;
|
|
531
707
|
}
|
|
708
|
+
if (!elements.promptBackdrop.hidden) {
|
|
709
|
+
dismissPrompt(true);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
532
712
|
if (elements.chatDialog.dataset.state === "open") {
|
|
533
713
|
closeChat();
|
|
534
714
|
}
|
|
@@ -537,6 +717,7 @@
|
|
|
537
717
|
stopViewportTracking();
|
|
538
718
|
globalThis.clearTimeout(chatCloseTimer);
|
|
539
719
|
globalThis.clearTimeout(chatReadyTimer);
|
|
720
|
+
globalThis.clearTimeout(promptTimer);
|
|
540
721
|
globalThis.removeEventListener("message", handleMessage);
|
|
541
722
|
document.removeEventListener("keydown", handleKeydown);
|
|
542
723
|
mount.remove();
|
|
@@ -549,6 +730,13 @@
|
|
|
549
730
|
openChat();
|
|
550
731
|
}
|
|
551
732
|
});
|
|
733
|
+
elements.promptOpen.addEventListener("click", openChat);
|
|
734
|
+
elements.promptClose.addEventListener("click", () => dismissPrompt(true));
|
|
735
|
+
elements.promptBackdrop.addEventListener("click", (event) => {
|
|
736
|
+
if (event.target === elements.promptBackdrop) {
|
|
737
|
+
dismissPrompt(true);
|
|
738
|
+
}
|
|
739
|
+
});
|
|
552
740
|
elements.chatDialog.addEventListener("animationend", (event) => {
|
|
553
741
|
if (event.target === elements.chatDialog) {
|
|
554
742
|
finishChatClose();
|
|
@@ -562,14 +750,17 @@
|
|
|
562
750
|
});
|
|
563
751
|
globalThis.addEventListener("message", handleMessage);
|
|
564
752
|
document.addEventListener("keydown", handleKeydown);
|
|
565
|
-
|
|
566
|
-
if (
|
|
753
|
+
syncLauncherLabel();
|
|
754
|
+
if (returnedFromSigning || readOpenState()) {
|
|
567
755
|
openChat();
|
|
756
|
+
} else if (!hasPromptCooldown()) {
|
|
757
|
+
promptTimer = globalThis.setTimeout(showPrompt, PROMPT_DELAY_MS);
|
|
568
758
|
}
|
|
569
759
|
return Object.freeze({
|
|
570
760
|
close: closeChat,
|
|
571
761
|
destroy,
|
|
572
762
|
open: openChat,
|
|
763
|
+
showPrompt,
|
|
573
764
|
version: EMBED_PROTOCOL_VERSION
|
|
574
765
|
});
|
|
575
766
|
};
|
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.10",
|
|
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",
|