@mrrocket/sdk 0.16.0 → 0.18.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +11 -2
  3. package/dist/api-client.d.ts +7 -1
  4. package/dist/api-client.js.map +1 -1
  5. package/dist/browser/mrrocket-flows.js +16 -2
  6. package/dist/browser/mrrocket-messenger.js +43 -44
  7. package/dist/browser/mrrocket.js +1 -1
  8. package/dist/browser.d.ts +1 -1
  9. package/dist/browser.js +2 -1
  10. package/dist/browser.js.map +1 -1
  11. package/dist/client.d.ts +23 -3
  12. package/dist/client.js +15 -1
  13. package/dist/client.js.map +1 -1
  14. package/dist/context.d.ts +3 -1
  15. package/dist/context.js.map +1 -1
  16. package/dist/core.d.ts +3 -1
  17. package/dist/core.js +1 -1
  18. package/dist/core.js.map +1 -1
  19. package/dist/finish-web.d.ts +18 -0
  20. package/dist/finish-web.js +139 -0
  21. package/dist/finish-web.js.map +1 -0
  22. package/dist/flows-ui.d.ts +1 -10
  23. package/dist/flows-ui.js +165 -16
  24. package/dist/flows-ui.js.map +1 -1
  25. package/dist/flows-web.d.ts +8 -1
  26. package/dist/flows-web.js +8 -0
  27. package/dist/flows-web.js.map +1 -1
  28. package/dist/flows.d.ts +203 -5
  29. package/dist/flows.js +306 -15
  30. package/dist/flows.js.map +1 -1
  31. package/dist/icons.d.ts +24 -0
  32. package/dist/icons.js +61 -0
  33. package/dist/icons.js.map +1 -0
  34. package/dist/index.d.ts +4 -2
  35. package/dist/index.js +10 -1
  36. package/dist/index.js.map +1 -1
  37. package/dist/messenger-ui.d.ts +10 -2
  38. package/dist/messenger-ui.js +134 -98
  39. package/dist/messenger-ui.js.map +1 -1
  40. package/dist/onboarding-state.d.ts +23 -0
  41. package/dist/onboarding-state.js +43 -0
  42. package/dist/onboarding-state.js.map +1 -0
  43. package/dist/react-native/index.d.cts +216 -15
  44. package/dist/react-native/index.d.ts +216 -15
  45. package/dist/react-native/index.js +880 -270
  46. package/dist/react-native/index.js.map +1 -1
  47. package/dist/styles-css.d.ts +1 -1
  48. package/dist/styles-css.js +40 -41
  49. package/dist/styles-css.js.map +1 -1
  50. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The web messenger, after Crisp's: a round launcher (hovered, a small card offers "Chat" and "Search") and a panel
2
+ * The web messenger, after Crisp's: a round launcher (a click opens it, no card on hover) and a panel
3
3
  * with a bar on top (Home, a "Chat · Help" switch while there are both, a ⋮ menu to minimize or widen it). Home shows the greeting,
4
4
  * the latest chat and the help search; Chat is the chat with the team, answered in MRRocket › Support by the owner or
5
5
  * the Customer experience agent; Help lists the product's published help articles by collection, searchable, read in
@@ -57,8 +57,14 @@ export interface MessengerUiOptions {
57
57
  header?: "plain" | "color";
58
58
  /** "Powered by MRRocket" at the bottom; MRRocket's server says whether the plan may hide it. */
59
59
  branding?: boolean;
60
- /** The product's icon at the top left of Home (MRRocket sends the product's favicon); without it the title's first letter. */
60
+ /** The logo at the top left of Home (MRRocket sends the uploaded logo, else the favicon); without it the title's first letter. */
61
61
  logo?: string;
62
+ /** The round mark in the chat's head (MRRocket sends the favicon); without it the title's first letter. */
63
+ icon?: string;
64
+ /** The AI agent's name on its answers (default "AI agent"). */
65
+ agentName?: string;
66
+ /** The AI agent's picture beside its answers. */
67
+ agentPhoto?: string;
62
68
  /** MRRocket's own preview: mount inside this element (positioned) instead of the page. */
63
69
  container?: HTMLElement;
64
70
  /** MRRocket's own preview: answers from here instead of the server. */
@@ -82,5 +88,7 @@ export interface MessengerHandle {
82
88
  /**
83
89
  * Mounts the messenger with its look from MRRocket (Support › Messenger) underneath what the code passes: an option the
84
90
  * code sets wins, one it leaves out comes from MRRocket, then the default. Calls made before the look arrived wait.
91
+ * A repeat visit starts with the look this browser saw last; a look that arrives later than that (changed, or slower
92
+ * than LOOK_WAIT_MS) replaces the launcher while the panel is closed, or once it closes.
85
93
  */
86
94
  export declare const mountMessenger: (o: MessengerUiOptions) => MessengerHandle;
@@ -13,9 +13,9 @@ var POLL_OPEN_MS = 4e3;
13
13
  var POLL_WAITING_MS = 8e3;
14
14
  var POLL_IDLE_MS = 3e4;
15
15
  var WAITING_FOR_MS = 10 * 6e4;
16
+ var SLOW_SEND_MS = 1500;
16
17
  var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
17
18
  var SEEN_KEY = "ckw_msgr_seen";
18
- var whoLabel = { visitor: "You", team: "Team", ai: "AI agent", auto: "Team" };
19
19
  var readSeen = () => {
20
20
  try {
21
21
  return window.localStorage.getItem(SEEN_KEY) ?? "";
@@ -167,20 +167,52 @@ var blockOf = (b) => {
167
167
  }
168
168
  };
169
169
  var LOOK_WAIT_MS = 2500;
170
+ var LOOK_KEY = "ckw_msgr_look:";
171
+ var readCachedLook = (apiKey) => {
172
+ try {
173
+ const raw = window.localStorage.getItem(LOOK_KEY + apiKey);
174
+ return raw ? JSON.parse(raw) : null;
175
+ } catch {
176
+ return null;
177
+ }
178
+ };
179
+ var writeCachedLook = (apiKey, look) => {
180
+ try {
181
+ window.localStorage.setItem(LOOK_KEY + apiKey, JSON.stringify(look));
182
+ } catch {
183
+ }
184
+ };
170
185
  var mountMessenger = (o) => {
171
186
  let real = null;
187
+ let shown = "";
172
188
  let gone = false;
173
189
  const queued = [];
174
190
  const later = (fn) => real ? fn(real) : queued.push(fn);
175
191
  const ctrl = new AbortController();
192
+ const caching = !o.client && !!o.apiKey;
176
193
  const start = (look) => {
177
- if (real || gone) return;
194
+ if (gone) return;
195
+ const key = JSON.stringify(look ?? {});
196
+ if (real && key === shown) return;
197
+ if (real?.isOpen()) {
198
+ real.onClose(() => start(look));
199
+ return;
200
+ }
201
+ real?.destroy();
202
+ shown = key;
178
203
  const defined = Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
179
204
  real = build({ ...look ?? {}, ...defined, accent: o.accent ?? look?.accent, theme: o.theme ?? look?.theme, branding: look?.branding ?? true });
180
205
  for (const fn of queued.splice(0)) fn(real);
181
206
  };
182
- const timer = setTimeout(() => start(null), LOOK_WAIT_MS);
183
- (o.client ?? createApi({ host: o.host, apiKey: o.apiKey, distinctId: o.distinctId(), user: o.user(), userHash: o.userHash?.() })).messengerLook(ctrl.signal).then((look) => start(look)).catch(() => start(null)).finally(() => clearTimeout(timer));
207
+ const cached = caching ? readCachedLook(o.apiKey) : null;
208
+ if (cached) start(cached);
209
+ const timer = cached ? void 0 : setTimeout(() => start(null), LOOK_WAIT_MS);
210
+ (o.client ?? createApi({ host: o.host, apiKey: o.apiKey, distinctId: o.distinctId(), user: o.user(), userHash: o.userHash?.() })).messengerLook(ctrl.signal).then((look) => {
211
+ if (caching && look) writeCachedLook(o.apiKey, look);
212
+ start(look);
213
+ }).catch(() => {
214
+ if (!real) start(null);
215
+ }).finally(() => clearTimeout(timer));
184
216
  return {
185
217
  open: (v) => later((h) => h.open(v)),
186
218
  close: () => real?.close(),
@@ -203,6 +235,7 @@ var build = (o) => {
203
235
  const label = o.label ?? "";
204
236
  const reply = replyLine(o.replyTime);
205
237
  const initial = (title.trim()[0] ?? "?").toUpperCase();
238
+ const whoLabel = { visitor: "You", team: "Team", ai: o.agentName?.trim() || "AI agent", auto: "Team" };
206
239
  const launcherOn = o.launcher ?? true;
207
240
  const helpOn = o.help ?? true;
208
241
  const soundOn = o.sound ?? true;
@@ -223,11 +256,15 @@ var build = (o) => {
223
256
  o.glow?.slice(0, 2).forEach((c, i) => /^#[0-9a-f]{6}$/i.test(c) && root.style.setProperty(`--ckw-glow${i + 2}`, c));
224
257
  (o.container ?? document.body).appendChild(root);
225
258
  let open = false;
259
+ let afterClose = null;
226
260
  let view = "home";
227
261
  let thread = null;
228
262
  let loaded = false;
229
263
  let error = "";
230
- let pending = null;
264
+ const sayable = (e) => e.status === 401 ? "The chat is not available right now. Please try again later." : e.message;
265
+ let outbox = [];
266
+ let outboxSeq = 0;
267
+ let flushing = false;
231
268
  let lastKnown = null;
232
269
  let unseen = [];
233
270
  let previewGone = false;
@@ -268,13 +305,22 @@ var build = (o) => {
268
305
  const id = lastTeamId(thread);
269
306
  if (id) writeSeen(id);
270
307
  };
271
- const avatar = (size) => el("span", { class: `ckw-msgr-av${size ? " ckw-msgr-av-sm" : ""}`, "aria-hidden": "true" }, initial);
308
+ const letter = (size) => el("span", { class: `ckw-msgr-av${size ? " ckw-msgr-av-sm" : ""}`, "aria-hidden": "true" }, initial);
309
+ const avatar = (size) => {
310
+ if (!o.icon) return letter(size);
311
+ const av = el("span", { class: `ckw-msgr-av ckw-msgr-av-img${size ? " ckw-msgr-av-sm" : ""}`, "aria-hidden": "true" });
312
+ const img = el("img", { src: o.icon, alt: "" });
313
+ img.addEventListener("error", () => av.replaceWith(letter(size)));
314
+ av.append(img);
315
+ return av;
316
+ };
272
317
  const logo = el("span", { class: "ckw-msgr-logo", "aria-hidden": "true" });
273
318
  if (o.logo) {
274
319
  const img = el("img", { src: o.logo, alt: "" });
275
320
  img.addEventListener("error", () => logo.replaceChildren(avatar("sm")));
276
321
  logo.append(img);
277
322
  } else logo.append(avatar("sm"));
323
+ const agentFace = () => o.agentPhoto ? el("img", { class: "ckw-msg-face", src: o.agentPhoto, alt: "" }) : null;
278
324
  const sky = el("div", { class: "ckw-msgr-sky", "aria-hidden": "true" });
279
325
  const leaving = /* @__PURE__ */ new Map();
280
326
  const enter = (node, parent = root) => {
@@ -326,7 +372,6 @@ var build = (o) => {
326
372
  const drawPreview = () => {
327
373
  const m = unseen.at(-1);
328
374
  if (!previewShown() || !m) return leave(prev);
329
- hideTeaser(true);
330
375
  const x = el("button", { type: "button", class: "ckw-msgr-preview-x", "aria-label": "Dismiss" }, icon(I.cross, 14));
331
376
  x.addEventListener("click", (e) => {
332
377
  e.stopPropagation();
@@ -347,43 +392,8 @@ var build = (o) => {
347
392
  prev.replaceChildren(card, x);
348
393
  enter(prev);
349
394
  };
350
- const teaser = el("div", { class: "ckw-msgr-teaser", "data-pos": position });
351
- let teaserTimer;
352
- const showTeaser = () => {
353
- clearTimeout(teaserTimer);
354
- if (open || previewShown() || !launcherOn) return;
355
- const chat = el("button", { type: "button", class: "ckw-msgr-teaser-b", "data-main": "" }, icon(I.chat), el("span", {}, "Chat"));
356
- chat.addEventListener("click", () => show("messages"));
357
- const buttons = [chat];
358
- if (helpOn && hasArticles) {
359
- const search = el("button", { type: "button", class: "ckw-msgr-teaser-b" }, icon(I.search), el("span", {}, "Search"));
360
- search.addEventListener("click", () => show("help"));
361
- buttons.push(search);
362
- }
363
- teaser.replaceChildren(
364
- el(
365
- "div",
366
- { class: "ckw-msgr-teaser-top" },
367
- el("span", { class: "ckw-msgr-teaser-t" }, el("b", {}, greeting), el("span", { class: "ckw-msgr-online" }, reply)),
368
- avatar()
369
- ),
370
- el("div", { class: "ckw-msgr-teaser-row" }, ...buttons)
371
- );
372
- enter(teaser);
373
- };
374
- function hideTeaser(now = false) {
375
- clearTimeout(teaserTimer);
376
- if (now) return leave(teaser, 140);
377
- teaserTimer = setTimeout(() => leave(teaser, 140), 260);
378
- }
379
- const hoverable = (e) => e.pointerType === "mouse";
380
- for (const node of [fab, teaser]) {
381
- node.addEventListener("pointerenter", (e) => hoverable(e) && (node === fab ? showTeaser() : clearTimeout(teaserTimer)));
382
- node.addEventListener("pointerleave", (e) => hoverable(e) && hideTeaser());
383
- }
384
395
  fab.addEventListener("click", () => {
385
396
  chime.unlock();
386
- hideTeaser(true);
387
397
  if (open) close();
388
398
  else show(unseen.length ? "messages" : "home");
389
399
  });
@@ -477,7 +487,7 @@ var build = (o) => {
477
487
  };
478
488
  const form = el("form", { class: "ckw-msgr-compose" });
479
489
  const err = el("p", { class: "ckw-msgr-error", role: "alert" });
480
- const email = el("input", { class: "ckw-input", type: "email", inputmode: "email", autocomplete: "email", placeholder: "Your email (optional), so we can follow up", "aria-label": "Your email (optional)", maxlength: "200" });
490
+ const email = el("input", { class: "ckw-input ckw-msgr-email", type: "email", inputmode: "email", autocomplete: "email", placeholder: "Email for our reply (optional)", "aria-label": "Your email (optional)", maxlength: "200", "data-1p-ignore": "", "data-lpignore": "true", "data-bwignore": "", "data-form-type": "other" });
481
491
  const row = el("div", { class: "ckw-msgr-box" });
482
492
  const text = el("textarea", { class: "ckw-textarea ckw-msgr-input", rows: "1", placeholder: "Write your message\u2026", "aria-label": "Message", maxlength: "4000" });
483
493
  const send = el("button", { type: "submit", class: "ckw-msgr-send", "aria-label": "Send" }, icon(I.up));
@@ -638,7 +648,7 @@ var build = (o) => {
638
648
  const list = el("ol", { class: "ckw-msgs", "aria-live": "polite", "aria-label": "Messages" });
639
649
  const needEmail = () => askEmail && !o.user()?.email && !thread?.hasEmail;
640
650
  const emailOk = () => !email.value.trim() || EMAIL.test(email.value.trim());
641
- const canSend = () => (!!text.value.trim() || picked.length > 0) && !pending && emailOk();
651
+ const canSend = () => (!!text.value.trim() || picked.length > 0) && emailOk();
642
652
  const go = (v) => {
643
653
  const changed = JSON.stringify(v) !== JSON.stringify(view);
644
654
  view = v;
@@ -660,6 +670,7 @@ var build = (o) => {
660
670
  return img;
661
671
  };
662
672
  const bubble = (m, isPending = false) => {
673
+ const slow = isPending && Date.now() - Date.parse(m.at) >= SLOW_SEND_MS;
663
674
  const li = el("li", { class: "ckw-msg", "data-from": m.from === "visitor" ? "me" : "them" }, m.body || !m.attachments?.length ? el("div", { class: "ckw-bubble" }, m.body) : null);
664
675
  if (isPending) li.setAttribute("aria-busy", "true");
665
676
  if (m.attachments?.length)
@@ -677,7 +688,7 @@ var build = (o) => {
677
688
  chip.addEventListener("click", () => go({ article: a.id }));
678
689
  li.append(chip);
679
690
  }
680
- li.append(el("span", { class: "ckw-msg-meta" }, `${m.from !== "visitor" ? `${whoLabel[m.from]} \xB7 ` : ""}${isPending ? "Sending\u2026" : timeAgo(m.at)}`));
691
+ li.append(el("span", { class: "ckw-msg-meta" }, m.from === "ai" ? agentFace() : null, `${m.from !== "visitor" ? `${whoLabel[m.from]} \xB7 ` : ""}${slow ? "Sending\u2026" : timeAgo(m.at)}`));
681
692
  return li;
682
693
  };
683
694
  const articleRow = (a) => {
@@ -748,7 +759,7 @@ var build = (o) => {
748
759
  askIn.value = "";
749
760
  askGo.disabled = true;
750
761
  go("messages");
751
- if (canSend()) void submit();
762
+ if (canSend()) submit();
752
763
  });
753
764
  const drawHome = () => {
754
765
  const first = o.user()?.name?.trim().split(/\s+/)[0];
@@ -878,19 +889,19 @@ var build = (o) => {
878
889
  const drawMessages = () => {
879
890
  const msgs = thread?.messages ?? [];
880
891
  const parts = [];
881
- if (!loaded) parts.push(el("div", { class: "ckw-skel" }));
882
- else {
883
- const sig = `${msgs.map((m) => m.id).join(",")}|${pending ? `${pending.body}+${pending.attachments?.length ?? 0}` : ""}|${Math.floor(Date.now() / 6e4)}`;
892
+ {
893
+ const waiting = outbox.map((o2) => o2.message);
894
+ const sig = `${msgs.map((m) => m.id).join(",")}|${waiting.map((m) => `${m.id}:${Date.now() - Date.parse(m.at) >= SLOW_SEND_MS}`).join(",")}|${Math.floor(Date.now() / 6e4)}`;
884
895
  if (sig !== listSig) {
885
896
  listSig = sig;
886
- const greet = el("li", { class: "ckw-msg", "data-from": "them" }, el("div", { class: "ckw-bubble" }, greeting));
887
- const items = [greet, ...msgs.map((m) => bubble(m)), ...pending ? [bubble(pending, true)] : []];
897
+ const greet = msgs.length || waiting.length ? [] : [el("li", { class: "ckw-msg", "data-from": "them" }, el("div", { class: "ckw-bubble" }, greeting))];
898
+ const items = [...greet, ...msgs.map((m) => bubble(m)), ...waiting.map((m) => bubble(m, true))];
888
899
  if (listCount >= 0) items.slice(listCount).forEach((li) => li.setAttribute("data-new", ""));
889
900
  listCount = items.length;
890
901
  list.replaceChildren(...items);
891
902
  }
892
903
  parts.push(list);
893
- const top = !msgs.some((m) => m.from === "visitor") && !pending ? (articles ?? []).slice(0, 3) : [];
904
+ const top = !msgs.some((m) => m.from === "visitor") && !outbox.length ? (articles ?? []).slice(0, 3) : [];
894
905
  if (top.length) {
895
906
  if (suggestions.childNodes.length !== top.length)
896
907
  suggestions.replaceChildren(
@@ -903,9 +914,9 @@ var build = (o) => {
903
914
  parts.push(suggestions);
904
915
  }
905
916
  }
906
- if (thread?.conversation?.status === "solved" && !pending) parts.push(el("p", { class: "ckw-fine ckw-msgr-note" }, "This conversation is closed. Write to start a new one."));
917
+ if (thread?.conversation?.status === "solved" && !outbox.length) parts.push(el("p", { class: "ckw-fine ckw-msgr-note" }, "This conversation is closed. Write to start a new one."));
907
918
  if (body.childNodes.length !== parts.length || parts.some((x, i) => body.childNodes[i] !== x)) body.replaceChildren(...parts);
908
- const count = msgs.length + (pending ? 1 : 0);
919
+ const count = msgs.length + outbox.length;
909
920
  if (count !== drawn) {
910
921
  drawn = count;
911
922
  body.scrollTop = body.scrollHeight;
@@ -916,7 +927,7 @@ var build = (o) => {
916
927
  if (needEmail()) {
917
928
  if (emailOk()) email.removeAttribute("aria-invalid");
918
929
  else email.setAttribute("aria-invalid", "true");
919
- if (!email.isConnected) form.insertBefore(email, tray.isConnected ? tray : row);
930
+ if (!email.isConnected) row.insertBefore(email, text);
920
931
  } else email.remove();
921
932
  drawTray();
922
933
  send.disabled = !canSend();
@@ -936,15 +947,18 @@ var build = (o) => {
936
947
  if (pop.childNodes.length !== kids.length || kids.some((k, i) => pop.childNodes[i] !== k)) pop.replaceChildren(...kids);
937
948
  };
938
949
  const tick = async () => {
939
- ctrl?.abort();
940
- ctrl = new AbortController();
950
+ if (ctrl) return;
951
+ const mine = ctrl = new AbortController();
941
952
  try {
942
- take(await api().chat(ctrl.signal));
953
+ take(await api().chat(mine.signal));
943
954
  error = "";
944
955
  } catch (e) {
945
956
  if (e.name === "AbortError") return;
946
- if (open && view === "messages") error = e.message;
957
+ if (open && view === "messages") error = sayable(e);
958
+ } finally {
959
+ if (ctrl === mine) ctrl = null;
947
960
  }
961
+ if (!loaded) listCount = -1;
948
962
  loaded = true;
949
963
  draw();
950
964
  drawFab();
@@ -963,58 +977,75 @@ var build = (o) => {
963
977
  else clearTimeout(poll);
964
978
  };
965
979
  document.addEventListener("visibilitychange", onVisible);
966
- const submit = async () => {
980
+ const flush = async () => {
981
+ if (flushing) return;
982
+ flushing = true;
983
+ while (outbox.length) {
984
+ const { message, files, email: given } = outbox[0];
985
+ await Promise.all(files.map((p) => p.up));
986
+ const ids = files.filter((p) => p.id).map((p) => p.id);
987
+ try {
988
+ if (!message.body && !ids.length) throw new Error(error || "The file could not be sent.");
989
+ const sent = await api().sendChat({
990
+ body: message.body,
991
+ email: given,
992
+ url: window.location.href.slice(0, 500),
993
+ ...ids.length ? { attachments: ids } : {},
994
+ // A little context for a bug report; the server reads browser and system from the user agent.
995
+ context: { viewport: `${window.innerWidth}\xD7${window.innerHeight}` }
996
+ });
997
+ ctrl?.abort();
998
+ ctrl = null;
999
+ take(sent);
1000
+ outbox.shift();
1001
+ for (const p of files) {
1002
+ localUrls.delete(p.key);
1003
+ URL.revokeObjectURL(p.local);
1004
+ }
1005
+ } catch (e) {
1006
+ const back = outbox.splice(0);
1007
+ text.value = [...back.map((b) => b.message.body), text.value.trim()].filter(Boolean).join("\n");
1008
+ picked = [...back.flatMap((b) => b.files.filter((p) => p.id)), ...picked];
1009
+ for (const b of back) for (const p of b.files) localUrls.delete(p.key);
1010
+ error = sayable(e);
1011
+ }
1012
+ draw();
1013
+ }
1014
+ flushing = false;
1015
+ schedule();
1016
+ };
1017
+ const submit = () => {
967
1018
  const msg = text.value.trim();
968
1019
  const files = picked;
969
- if (!msg && !files.length || pending || !emailOk()) return;
1020
+ if (!msg && !files.length || !emailOk()) return;
970
1021
  chime.unlock();
971
1022
  for (const p of files) localUrls.set(p.key, p.local);
972
- pending = {
973
- id: "pending",
974
- from: "visitor",
975
- body: msg,
976
- at: (/* @__PURE__ */ new Date()).toISOString(),
977
- ...files.length ? { attachments: files.map((p) => ({ id: p.key, name: p.name, type: p.type, size: p.size })) } : {}
978
- };
1023
+ outbox.push({
1024
+ message: {
1025
+ id: `pending-${++outboxSeq}`,
1026
+ from: "visitor",
1027
+ body: msg,
1028
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1029
+ ...files.length ? { attachments: files.map((p) => ({ id: p.key, name: p.name, type: p.type, size: p.size })) } : {}
1030
+ },
1031
+ files,
1032
+ email: needEmail() && email.value.trim() ? email.value.trim() : void 0
1033
+ });
979
1034
  text.value = "";
980
1035
  picked = [];
981
1036
  error = "";
982
1037
  draw();
983
- await Promise.all(files.map((p) => p.up));
984
- const ids = files.filter((p) => p.id).map((p) => p.id);
985
- let sent = false;
986
- try {
987
- if (!msg && !ids.length) throw new Error(error || "The file could not be sent.");
988
- take(
989
- await api().sendChat({
990
- body: msg,
991
- email: needEmail() && email.value.trim() ? email.value.trim() : void 0,
992
- url: window.location.href.slice(0, 500),
993
- ...ids.length ? { attachments: ids } : {},
994
- // A little context for a bug report; the server reads browser and system from the user agent.
995
- context: { viewport: `${window.innerWidth}\xD7${window.innerHeight}` }
996
- })
997
- );
998
- sent = true;
999
- } catch (e) {
1000
- text.value = msg;
1001
- picked = files.filter((p) => p.id);
1002
- error = e.message;
1003
- }
1004
- pending = null;
1005
- for (const p of files) localUrls.delete(p.key);
1006
- draw();
1007
- if (sent) for (const p of files) URL.revokeObjectURL(p.local);
1008
- schedule();
1038
+ setTimeout(draw, SLOW_SEND_MS + 50);
1039
+ void flush();
1009
1040
  };
1010
1041
  form.addEventListener("submit", (e) => {
1011
1042
  e.preventDefault();
1012
- void submit();
1043
+ submit();
1013
1044
  });
1014
1045
  text.addEventListener("keydown", (e) => {
1015
1046
  if (e.key === "Enter" && !e.shiftKey && !e.isComposing) {
1016
1047
  e.preventDefault();
1017
- void submit();
1048
+ submit();
1018
1049
  }
1019
1050
  });
1020
1051
  text.addEventListener("input", () => send.disabled = !canSend());
@@ -1046,7 +1077,6 @@ var build = (o) => {
1046
1077
  view = v === "help" && !helpOn ? "home" : v;
1047
1078
  drawn = -1;
1048
1079
  previewGone = true;
1049
- hideTeaser(true);
1050
1080
  pop.toggleAttribute("data-still", still);
1051
1081
  enter(pop);
1052
1082
  if (was) replay(body, "ckw-msgr-enter");
@@ -1067,6 +1097,9 @@ var build = (o) => {
1067
1097
  drawFab();
1068
1098
  schedule();
1069
1099
  if (launcherOn) fab.focus();
1100
+ const fn = afterClose;
1101
+ afterClose = null;
1102
+ if (fn) setTimeout(fn, 220);
1070
1103
  }
1071
1104
  pop.addEventListener("click", () => amenuOpen() && closeAmenu());
1072
1105
  const onKey = (e) => {
@@ -1088,9 +1121,12 @@ var build = (o) => {
1088
1121
  open: (v) => show(v ?? "home"),
1089
1122
  close,
1090
1123
  toggle: () => open ? close() : show(),
1124
+ isOpen: () => open,
1125
+ onClose: (fn) => {
1126
+ afterClose = fn;
1127
+ },
1091
1128
  destroy: () => {
1092
1129
  clearTimeout(poll);
1093
- clearTimeout(teaserTimer);
1094
1130
  for (const t of leaving.values()) clearTimeout(t);
1095
1131
  ctrl?.abort();
1096
1132
  for (const p of picked) forget(p);