@mrrocket/sdk 0.15.0 → 0.17.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 (71) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +47 -3
  3. package/dist/api-client.d.ts +12 -5
  4. package/dist/api-client.js +12 -8
  5. package/dist/api-client.js.map +1 -1
  6. package/dist/api.d.ts +4 -1
  7. package/dist/api.js +2 -1
  8. package/dist/api.js.map +1 -1
  9. package/dist/batch.d.ts +6 -4
  10. package/dist/batch.js +3 -3
  11. package/dist/batch.js.map +1 -1
  12. package/dist/browser/mrrocket-flows.js +9 -2
  13. package/dist/browser/mrrocket-forms.js +1 -1
  14. package/dist/browser/mrrocket-messenger.js +42 -43
  15. package/dist/browser/mrrocket-push.js +1 -1
  16. package/dist/browser/mrrocket-replay.js +3 -3
  17. package/dist/browser/mrrocket.js +1 -1
  18. package/dist/client.d.ts +10 -2
  19. package/dist/client.js +36 -15
  20. package/dist/client.js.map +1 -1
  21. package/dist/context.d.ts +5 -1
  22. package/dist/context.js.map +1 -1
  23. package/dist/core.d.ts +1 -1
  24. package/dist/core.js +1 -1
  25. package/dist/core.js.map +1 -1
  26. package/dist/errors.d.ts +5 -1
  27. package/dist/errors.js +8 -2
  28. package/dist/errors.js.map +1 -1
  29. package/dist/flows-ui.d.ts +1 -1
  30. package/dist/flows-ui.js +64 -14
  31. package/dist/flows-ui.js.map +1 -1
  32. package/dist/flows-web.d.ts +2 -0
  33. package/dist/flows-web.js +4 -1
  34. package/dist/flows-web.js.map +1 -1
  35. package/dist/flows.d.ts +59 -3
  36. package/dist/flows.js +88 -7
  37. package/dist/flows.js.map +1 -1
  38. package/dist/forms.d.ts +4 -0
  39. package/dist/forms.js +4 -1
  40. package/dist/forms.js.map +1 -1
  41. package/dist/identity.d.ts +42 -0
  42. package/dist/identity.js +24 -0
  43. package/dist/identity.js.map +1 -0
  44. package/dist/index.d.ts +3 -1
  45. package/dist/index.js +5 -1
  46. package/dist/index.js.map +1 -1
  47. package/dist/messenger-ui.d.ts +12 -2
  48. package/dist/messenger-ui.js +58 -47
  49. package/dist/messenger-ui.js.map +1 -1
  50. package/dist/messenger.js +3 -2
  51. package/dist/messenger.js.map +1 -1
  52. package/dist/push-web.d.ts +2 -13
  53. package/dist/push-web.js +4 -2
  54. package/dist/push-web.js.map +1 -1
  55. package/dist/react-native/index.d.cts +105 -14
  56. package/dist/react-native/index.d.ts +105 -14
  57. package/dist/react-native/index.js +238 -55
  58. package/dist/react-native/index.js.map +1 -1
  59. package/dist/replay.d.ts +4 -1
  60. package/dist/replay.js +8 -2
  61. package/dist/replay.js.map +1 -1
  62. package/dist/server.d.ts +2 -0
  63. package/dist/server.js +2 -0
  64. package/dist/server.js.map +1 -1
  65. package/dist/styles-css.d.ts +1 -1
  66. package/dist/styles-css.js +40 -41
  67. package/dist/styles-css.js.map +1 -1
  68. package/dist/user-hash.d.ts +27 -0
  69. package/dist/user-hash.js +24 -0
  70. package/dist/user-hash.js.map +1 -0
  71. package/package.json +5 -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
@@ -23,6 +23,8 @@ export interface MessengerUiOptions {
23
23
  name?: string;
24
24
  email?: string;
25
25
  } | undefined;
26
+ /** The identity hash of the visitor id now (0.16.0), when the site gave one to identify. */
27
+ userHash?: () => string | undefined;
26
28
  /** Show the round launcher (default true). Without it the panel opens only through `open()`. */
27
29
  launcher?: boolean;
28
30
  /** Where the launcher sits (default bottom right); the panel and the cards open from there. */
@@ -55,8 +57,14 @@ export interface MessengerUiOptions {
55
57
  header?: "plain" | "color";
56
58
  /** "Powered by MRRocket" at the bottom; MRRocket's server says whether the plan may hide it. */
57
59
  branding?: boolean;
58
- /** 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. */
59
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;
60
68
  /** MRRocket's own preview: mount inside this element (positioned) instead of the page. */
61
69
  container?: HTMLElement;
62
70
  /** MRRocket's own preview: answers from here instead of the server. */
@@ -80,5 +88,7 @@ export interface MessengerHandle {
80
88
  /**
81
89
  * Mounts the messenger with its look from MRRocket (Support › Messenger) underneath what the code passes: an option the
82
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.
83
93
  */
84
94
  export declare const mountMessenger: (o: MessengerUiOptions) => MessengerHandle;
@@ -15,7 +15,6 @@ var POLL_IDLE_MS = 3e4;
15
15
  var WAITING_FOR_MS = 10 * 6e4;
16
16
  var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
17
17
  var SEEN_KEY = "ckw_msgr_seen";
18
- var whoLabel = { visitor: "You", team: "Team", ai: "AI agent", auto: "Team" };
19
18
  var readSeen = () => {
20
19
  try {
21
20
  return window.localStorage.getItem(SEEN_KEY) ?? "";
@@ -167,20 +166,52 @@ var blockOf = (b) => {
167
166
  }
168
167
  };
169
168
  var LOOK_WAIT_MS = 2500;
169
+ var LOOK_KEY = "ckw_msgr_look:";
170
+ var readCachedLook = (apiKey) => {
171
+ try {
172
+ const raw = window.localStorage.getItem(LOOK_KEY + apiKey);
173
+ return raw ? JSON.parse(raw) : null;
174
+ } catch {
175
+ return null;
176
+ }
177
+ };
178
+ var writeCachedLook = (apiKey, look) => {
179
+ try {
180
+ window.localStorage.setItem(LOOK_KEY + apiKey, JSON.stringify(look));
181
+ } catch {
182
+ }
183
+ };
170
184
  var mountMessenger = (o) => {
171
185
  let real = null;
186
+ let shown = "";
172
187
  let gone = false;
173
188
  const queued = [];
174
189
  const later = (fn) => real ? fn(real) : queued.push(fn);
175
190
  const ctrl = new AbortController();
191
+ const caching = !o.client && !!o.apiKey;
176
192
  const start = (look) => {
177
- if (real || gone) return;
193
+ if (gone) return;
194
+ const key = JSON.stringify(look ?? {});
195
+ if (real && key === shown) return;
196
+ if (real?.isOpen()) {
197
+ real.onClose(() => start(look));
198
+ return;
199
+ }
200
+ real?.destroy();
201
+ shown = key;
178
202
  const defined = Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
179
203
  real = build({ ...look ?? {}, ...defined, accent: o.accent ?? look?.accent, theme: o.theme ?? look?.theme, branding: look?.branding ?? true });
180
204
  for (const fn of queued.splice(0)) fn(real);
181
205
  };
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() })).messengerLook(ctrl.signal).then((look) => start(look)).catch(() => start(null)).finally(() => clearTimeout(timer));
206
+ const cached = caching ? readCachedLook(o.apiKey) : null;
207
+ if (cached) start(cached);
208
+ const timer = cached ? void 0 : setTimeout(() => start(null), LOOK_WAIT_MS);
209
+ (o.client ?? createApi({ host: o.host, apiKey: o.apiKey, distinctId: o.distinctId(), user: o.user(), userHash: o.userHash?.() })).messengerLook(ctrl.signal).then((look) => {
210
+ if (caching && look) writeCachedLook(o.apiKey, look);
211
+ start(look);
212
+ }).catch(() => {
213
+ if (!real) start(null);
214
+ }).finally(() => clearTimeout(timer));
184
215
  return {
185
216
  open: (v) => later((h) => h.open(v)),
186
217
  close: () => real?.close(),
@@ -203,11 +234,12 @@ var build = (o) => {
203
234
  const label = o.label ?? "";
204
235
  const reply = replyLine(o.replyTime);
205
236
  const initial = (title.trim()[0] ?? "?").toUpperCase();
237
+ const whoLabel = { visitor: "You", team: "Team", ai: o.agentName?.trim() || "AI agent", auto: "Team" };
206
238
  const launcherOn = o.launcher ?? true;
207
239
  const helpOn = o.help ?? true;
208
240
  const soundOn = o.sound ?? true;
209
241
  const previewOn = o.preview ?? true;
210
- const api = () => o.client ?? createApi({ host: o.host, apiKey: o.apiKey, distinctId: o.distinctId(), user: o.user() });
242
+ const api = () => o.client ?? createApi({ host: o.host, apiKey: o.apiKey, distinctId: o.distinctId(), user: o.user(), userHash: o.userHash?.() });
211
243
  const root = el("div", {
212
244
  class: "ckw-root",
213
245
  "data-theme": o.theme ?? "auto",
@@ -223,6 +255,7 @@ var build = (o) => {
223
255
  o.glow?.slice(0, 2).forEach((c, i) => /^#[0-9a-f]{6}$/i.test(c) && root.style.setProperty(`--ckw-glow${i + 2}`, c));
224
256
  (o.container ?? document.body).appendChild(root);
225
257
  let open = false;
258
+ let afterClose = null;
226
259
  let view = "home";
227
260
  let thread = null;
228
261
  let loaded = false;
@@ -268,13 +301,22 @@ var build = (o) => {
268
301
  const id = lastTeamId(thread);
269
302
  if (id) writeSeen(id);
270
303
  };
271
- const avatar = (size) => el("span", { class: `ckw-msgr-av${size ? " ckw-msgr-av-sm" : ""}`, "aria-hidden": "true" }, initial);
304
+ const letter = (size) => el("span", { class: `ckw-msgr-av${size ? " ckw-msgr-av-sm" : ""}`, "aria-hidden": "true" }, initial);
305
+ const avatar = (size) => {
306
+ if (!o.icon) return letter(size);
307
+ const av = el("span", { class: `ckw-msgr-av ckw-msgr-av-img${size ? " ckw-msgr-av-sm" : ""}`, "aria-hidden": "true" });
308
+ const img = el("img", { src: o.icon, alt: "" });
309
+ img.addEventListener("error", () => av.replaceWith(letter(size)));
310
+ av.append(img);
311
+ return av;
312
+ };
272
313
  const logo = el("span", { class: "ckw-msgr-logo", "aria-hidden": "true" });
273
314
  if (o.logo) {
274
315
  const img = el("img", { src: o.logo, alt: "" });
275
316
  img.addEventListener("error", () => logo.replaceChildren(avatar("sm")));
276
317
  logo.append(img);
277
318
  } else logo.append(avatar("sm"));
319
+ const agentFace = () => o.agentPhoto ? el("img", { class: "ckw-msg-face", src: o.agentPhoto, alt: "" }) : null;
278
320
  const sky = el("div", { class: "ckw-msgr-sky", "aria-hidden": "true" });
279
321
  const leaving = /* @__PURE__ */ new Map();
280
322
  const enter = (node, parent = root) => {
@@ -326,7 +368,6 @@ var build = (o) => {
326
368
  const drawPreview = () => {
327
369
  const m = unseen.at(-1);
328
370
  if (!previewShown() || !m) return leave(prev);
329
- hideTeaser(true);
330
371
  const x = el("button", { type: "button", class: "ckw-msgr-preview-x", "aria-label": "Dismiss" }, icon(I.cross, 14));
331
372
  x.addEventListener("click", (e) => {
332
373
  e.stopPropagation();
@@ -347,43 +388,8 @@ var build = (o) => {
347
388
  prev.replaceChildren(card, x);
348
389
  enter(prev);
349
390
  };
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
391
  fab.addEventListener("click", () => {
385
392
  chime.unlock();
386
- hideTeaser(true);
387
393
  if (open) close();
388
394
  else show(unseen.length ? "messages" : "home");
389
395
  });
@@ -477,7 +483,7 @@ var build = (o) => {
477
483
  };
478
484
  const form = el("form", { class: "ckw-msgr-compose" });
479
485
  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" });
486
+ 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
487
  const row = el("div", { class: "ckw-msgr-box" });
482
488
  const text = el("textarea", { class: "ckw-textarea ckw-msgr-input", rows: "1", placeholder: "Write your message\u2026", "aria-label": "Message", maxlength: "4000" });
483
489
  const send = el("button", { type: "submit", class: "ckw-msgr-send", "aria-label": "Send" }, icon(I.up));
@@ -677,7 +683,7 @@ var build = (o) => {
677
683
  chip.addEventListener("click", () => go({ article: a.id }));
678
684
  li.append(chip);
679
685
  }
680
- li.append(el("span", { class: "ckw-msg-meta" }, `${m.from !== "visitor" ? `${whoLabel[m.from]} \xB7 ` : ""}${isPending ? "Sending\u2026" : timeAgo(m.at)}`));
686
+ li.append(el("span", { class: "ckw-msg-meta" }, m.from === "ai" ? agentFace() : null, `${m.from !== "visitor" ? `${whoLabel[m.from]} \xB7 ` : ""}${isPending ? "Sending\u2026" : timeAgo(m.at)}`));
681
687
  return li;
682
688
  };
683
689
  const articleRow = (a) => {
@@ -916,7 +922,7 @@ var build = (o) => {
916
922
  if (needEmail()) {
917
923
  if (emailOk()) email.removeAttribute("aria-invalid");
918
924
  else email.setAttribute("aria-invalid", "true");
919
- if (!email.isConnected) form.insertBefore(email, tray.isConnected ? tray : row);
925
+ if (!email.isConnected) row.insertBefore(email, text);
920
926
  } else email.remove();
921
927
  drawTray();
922
928
  send.disabled = !canSend();
@@ -1046,7 +1052,6 @@ var build = (o) => {
1046
1052
  view = v === "help" && !helpOn ? "home" : v;
1047
1053
  drawn = -1;
1048
1054
  previewGone = true;
1049
- hideTeaser(true);
1050
1055
  pop.toggleAttribute("data-still", still);
1051
1056
  enter(pop);
1052
1057
  if (was) replay(body, "ckw-msgr-enter");
@@ -1067,6 +1072,9 @@ var build = (o) => {
1067
1072
  drawFab();
1068
1073
  schedule();
1069
1074
  if (launcherOn) fab.focus();
1075
+ const fn = afterClose;
1076
+ afterClose = null;
1077
+ if (fn) setTimeout(fn, 220);
1070
1078
  }
1071
1079
  pop.addEventListener("click", () => amenuOpen() && closeAmenu());
1072
1080
  const onKey = (e) => {
@@ -1088,9 +1096,12 @@ var build = (o) => {
1088
1096
  open: (v) => show(v ?? "home"),
1089
1097
  close,
1090
1098
  toggle: () => open ? close() : show(),
1099
+ isOpen: () => open,
1100
+ onClose: (fn) => {
1101
+ afterClose = fn;
1102
+ },
1091
1103
  destroy: () => {
1092
1104
  clearTimeout(poll);
1093
- clearTimeout(teaserTimer);
1094
1105
  for (const t of leaving.values()) clearTimeout(t);
1095
1106
  ctrl?.abort();
1096
1107
  for (const p of picked) forget(p);