@mrrocket/sdk 0.15.0 → 0.16.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 (64) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +36 -1
  3. package/dist/api-client.d.ts +5 -4
  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 +2 -2
  13. package/dist/browser/mrrocket-forms.js +1 -1
  14. package/dist/browser/mrrocket-messenger.js +2 -2
  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-web.d.ts +2 -0
  30. package/dist/flows-web.js +4 -1
  31. package/dist/flows-web.js.map +1 -1
  32. package/dist/flows.d.ts +10 -0
  33. package/dist/flows.js +2 -0
  34. package/dist/flows.js.map +1 -1
  35. package/dist/forms.d.ts +4 -0
  36. package/dist/forms.js +4 -1
  37. package/dist/forms.js.map +1 -1
  38. package/dist/identity.d.ts +42 -0
  39. package/dist/identity.js +24 -0
  40. package/dist/identity.js.map +1 -0
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/messenger-ui.d.ts +2 -0
  44. package/dist/messenger-ui.js +2 -2
  45. package/dist/messenger-ui.js.map +1 -1
  46. package/dist/messenger.js +3 -2
  47. package/dist/messenger.js.map +1 -1
  48. package/dist/push-web.d.ts +2 -13
  49. package/dist/push-web.js +4 -2
  50. package/dist/push-web.js.map +1 -1
  51. package/dist/react-native/index.d.cts +61 -9
  52. package/dist/react-native/index.d.ts +61 -9
  53. package/dist/react-native/index.js +107 -28
  54. package/dist/react-native/index.js.map +1 -1
  55. package/dist/replay.d.ts +4 -1
  56. package/dist/replay.js +8 -2
  57. package/dist/replay.js.map +1 -1
  58. package/dist/server.d.ts +2 -0
  59. package/dist/server.js +2 -0
  60. package/dist/server.js.map +1 -1
  61. package/dist/user-hash.d.ts +27 -0
  62. package/dist/user-hash.js +24 -0
  63. package/dist/user-hash.js.map +1 -0
  64. package/package.json +5 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  All notable changes to this package. It follows semantic versioning; before 1.0 a minor version may change an API.
4
4
 
5
+ ## 0.16.0 (not published yet)
6
+
7
+ - **Identity verification:** your server signs the user id with the product's server key and the SDK sends that signature as `userHash` with every request for the id, so someone who only knows a user's id can no longer read their support chat, register push for them or post feedback and events as them. Server: `userHash(userId, { serverKey? })` from `@mrrocket/sdk/next/server` (WebCrypto only; Node 18+, Edge, Workers; defaults to `MRROCKET_SERVER_KEY`, throws without a key). The formula for other backends: `hex(HMAC-SHA256(key = SHA-256(server key), message = user id))`.
8
+ - **Client:** `identify(id, props, { hash })` in `useCockpit()`, `window.mrrocket.identify` and the React Native client; `<CockpitProvider userHash>` signs the `distinctId` prop from a server layout. The hash belongs to one id: dropped on `reset()` and on `identify` for another id without one; a value that is not 64 hex characters is never sent. React Native keeps it in memory only.
9
+ - **Sent with:** event batches (one top-level `userHash`, checked per event), errors (only when every report is of the signed id), recordings, push register/unregister, in-app messages, surveys (read, seen, answer), the feedback board (list, post, vote, comments, roadmap, a post), article ratings, messenger reads, messages, uploads (form field) and attachment links (query). A 401 is handled like any failed call and the console says once how to fix it; a recording stops on 401.
10
+ - New exports: the type `IdentifyOptions` (Next.js and React Native), `userHash` and `UserHashOptions` (server). `createApi` takes `userHash`.
11
+ - **Store reviews (React Native):** `useReview().request()` / `client.requestReview()` asks for a review at the moment your app picks (the trigger of an in-app request in MRRocket › Reviews › Requests). It asks MRRocket whether this signed-in person may be asked now (a customer within the request's neutral limits, not asked in 120 days; never by rating or mood), then opens the App Store's or Google Play's own rating dialog through `expo-store-review` (new optional peer dependency) and sends `$review_prompt {campaign, store, action: "requested" | "unavailable"}`; only a requested dialog counts as an invitation. Resolves with `"requested" | "not-now" | "unavailable" | "not-signed-in"` (type `NativeReviewResult`). `GET /api/sdk/inapp` answers `review: { campaign, store }` when the person may be asked; the web has no store dialog and ignores it.
12
+ - `sdk.js` budget 15.75 KB gzip (was 15.25).
13
+ - Version reported in event batches: `0.16.0`.
14
+
5
15
  ## 0.14.0 (not published yet)
6
16
 
7
17
  - **Paywalls with pages:** a paywall from MRRocket › Conversion › Paywalls can have several pages (benefits, customer quotes, plans), served as the config's new `paywalls` per placement. `usePaywall().open(placement, { onPurchase, plans, onClose })` (Next.js and React Native, drawn by the provider), `mrrocket.paywall.open(…)` (script tag) show them one after another with Back and dots; the last page hands the chosen plan to `onPurchase`, where the app buys it (StoreKit/Play through RevenueCat or Adapty, Stripe Checkout). MRRocket never takes a payment; only after `onPurchase` resolves is the purchase recorded (`false`: the store sheet was cancelled, the paywall stays). Resolves with `{ outcome: "purchased" | "chosen" | "closed", plan, variant, index }`.
package/README.md CHANGED
@@ -10,7 +10,7 @@ The MRRocket SDK, one package with three ways in:
10
10
 
11
11
  One provider in your layout (or one script tag) gives MRRocket page views, your events, A/B tests, feature flags and errors that the agents can run and read. MRRocket shows every variant of your real pages, because the SDK can render any variant on request.
12
12
 
13
- Status: 0.8.0, MIT licensed, compiled to ESM JavaScript with type declarations (no `transpilePackages`); the React Native entry is CommonJS for Metro. Works with Next.js 15 and 16, React 18 and 19, on Node and on Cloudflare Workers (OpenNext), and with React Native 0.72+ / Expo. The source lives in the MRRocket repo (`prototype/web/packages/cockpit-next`).
13
+ Status: 0.16.0, MIT licensed, compiled to ESM JavaScript with type declarations (no `transpilePackages`); the React Native entry is CommonJS for Metro. Works with Next.js 15 and 16, React 18 and 19, on Node and on Cloudflare Workers (OpenNext), and with React Native 0.72+ / Expo. The source lives in the MRRocket repo (`prototype/web/packages/cockpit-next`).
14
14
 
15
15
  ## Install
16
16
 
@@ -63,6 +63,7 @@ import { useCockpit } from "@mrrocket/sdk/next";
63
63
  const { track, identify } = useCockpit();
64
64
  track("checkout_started", { plan: "team" });
65
65
  identify(user.id, { plan: user.plan });
66
+ identify(user.id, { plan: user.plan }, { hash }); // signed by your server, see 22. Identity verification
66
67
  ```
67
68
 
68
69
  ## 3. A/B tests
@@ -451,6 +452,17 @@ mrrocket.push.subscribe();
451
452
 
452
453
  `register({ ask: false })` / `subscribe({ ask: false })` only send an existing token or subscription again. `reset()` on React Native takes the token back; on the web call `push.unsubscribe()` before `reset()`. A site with its own service worker adds `importScripts("/mrrocket-sw.js")` to it and calls `subscribe({ serviceWorker: false })`. Opening a MRRocket notification records `$push { action: "opened", message }`.
453
454
 
455
+
456
+ **Store reviews (0.16.0, React Native).** An in-app review request (Reviews › Requests) opens the App Store's or Google Play's own rating dialog at the moment your app picks. Install `expo-store-review`, then after `identify()`:
457
+
458
+ ```tsx
459
+ import { useReview } from "@mrrocket/sdk/react-native";
460
+ const review = useReview();
461
+ // Where the request's trigger happens (the third finished challenge, a 7-day streak …):
462
+ await review.request(); // "requested" | "not-now" | "unavailable" | "not-signed-in"
463
+ ```
464
+
465
+ MRRocket answers whether this person may be asked now (a customer within the request's neutral limits, not asked in 120 days; never by rating or mood) and counts only dialogs that were requested (`$review_prompt`). The store decides whether its dialog shows (Apple: at most three times a year). Websites have no store dialog: ask by email there.
454
466
  ## 17. Redirects and redirect tests (0.7.0)
455
467
 
456
468
  Page redirects (Landing pages › Redirect) and redirect tests ("Whole page" in Experiments) come with the config: the SDK sends the visitor on before the page shows, keeping the query and hash, and the page a test's variant lands on records the exposure (`ck_x=<test>:<variant>`, removed from the address). Nothing to add for the script tag (the optional `ck-hide` snippet stops a flash of the old page). In Next.js `<CockpitProvider>` redirects after the first paint; to answer before rendering, use the proxy helper:
@@ -585,6 +597,29 @@ mrrocket.paywall.open("pricing_page", { onPurchase: (plan) => fetch("/checkout",
585
597
  - **Without pages** a paywall keeps working as texts your own paywall marks (`paywall.<placement>.headline`, `.body`, `.cta`).
586
598
  - The script tag loads the paywall dialog from `sdk-flows.js` on the first `open`; `sdk.js` stays within its budget.
587
599
 
600
+ ## 22. Identity verification (0.16.0)
601
+
602
+ After `identify(id)` the SDK sends your app's own user id with every request, and that id is no secret (a Clerk id shows in URLs and logs). On its own it proves nothing: someone who knows it could read that person's support chat, register a push device for them or post feedback as them. Identity verification closes that, like Intercom's: your **server** signs the user id with the product's server key, the SDK sends the signature (`userHash`) with every request for that id, and MRRocket checks it.
603
+
604
+ Next.js, in three lines (the server key is `mrr_sk_…` from MRRocket › Settings › Products › your product › Sign-in, in `MRROCKET_SERVER_KEY`, never `NEXT_PUBLIC_`):
605
+
606
+ ```tsx
607
+ // app/layout.tsx (a server component)
608
+ import { userHash } from "@mrrocket/sdk/next/server";
609
+
610
+ const hash = user ? await userHash(user.id) : undefined;
611
+ // then either: <CockpitProvider distinctId={user?.id} userHash={hash}>
612
+ // or, in the client component that signs people in: identify(user.id, { email: user.email }, { hash })
613
+ ```
614
+
615
+ - **Script tag:** your server renders the hash into the page, the page passes it on: `mrrocket.identify(user.id, { email }, { hash: "…" })`.
616
+ - **React Native:** your backend returns the hash with the signed-in user (in `/me`, say); call `identify(user.id, props, { hash })` on every start. The server key never goes into the app, and the SDK keeps the hash in memory only.
617
+ - **Other languages:** `userHash = lowercase hex( HMAC-SHA256( key = SHA-256(server key) as its 32 raw bytes, message = user id as UTF-8 ) )`. MRRocket stores only the SHA-256 of the server key, which is why that is the key. Node: `crypto.createHmac("sha256", crypto.createHash("sha256").update(serverKey).digest()).update(userId).digest("hex")`; Python: `hmac.new(hashlib.sha256(server_key.encode()).digest(), user_id.encode(), hashlib.sha256).hexdigest()`; PHP: `hash_hmac("sha256", $userId, hash("sha256", $serverKey, true))`; Ruby: `OpenSSL::HMAC.hexdigest("SHA256", Digest::SHA256.digest(server_key), user_id)`.
618
+ - **What carries it:** events (one `userHash` per batch; MRRocket checks it against each event's id), errors, recordings, push sign-up, in-app messages, surveys, the feedback board, help article ratings and the messenger (its messages, uploads and file links). A JSON field next to `distinctId`, a query parameter on reads, a form field on uploads.
619
+ - **One id, one hash:** the hash is forgotten on `reset()` and when `identify` is called for another id without one, so it never goes out with someone else's id. A value that is not 64 hex characters (the server key pasted by mistake) is never sent; the console says so.
620
+ - **Refused:** a hash that does not match is answered 401; with "Only signed users" on (Sign-in settings), so is a signed-in user's id without one. The SDK never throws for it: the request fails like any other and the console says once how to fix it. Turn "Only signed users" on after the app sends hashes.
621
+ - `userHash()` runs on Node 18+ (Next.js provides WebCrypto there), the Edge runtime and Cloudflare Workers; it throws when there is no server key, so a missing setting shows on your server.
622
+
588
623
  ## Privacy and consent
589
624
 
590
625
  ```tsx
@@ -1,7 +1,3 @@
1
- /**
2
- * The SDK's fetch client and the shapes it reads, without React, so the script tag's widgets (src/script) use it too.
3
- * `./api` re-exports all of it with the `useApi` hook.
4
- */
5
1
  export type PostStatus = "review" | "planned" | "progress" | "shipped";
6
2
  export declare const STATUS_LABEL: Record<PostStatus, string>;
7
3
  export interface FeedbackBoardInfo {
@@ -221,6 +217,11 @@ interface Conn {
221
217
  id?: string;
222
218
  [key: string]: unknown;
223
219
  };
220
+ /**
221
+ * The identity hash of `distinctId` (0.16.0, see identity.ts), sent with every request that names the visitor: a
222
+ * JSON field next to `distinctId`, a query parameter, a form field. Leave it out for any other id.
223
+ */
224
+ userHash?: string;
224
225
  }
225
226
  /** The small fetch client behind the widgets. Every read takes an AbortSignal so unmounting cancels it. */
226
227
  export declare const createApi: (c: Conn) => {
@@ -1,4 +1,5 @@
1
1
  // src/api-client.ts
2
+ import { warnUnsigned } from "./identity.js";
2
3
  var STATUS_LABEL = {
3
4
  review: "Under review",
4
5
  planned: "Planned",
@@ -46,6 +47,7 @@ var call = async (url, init, signal) => {
46
47
  throw new ApiError("Could not reach the server. Check your connection and try again.", 0);
47
48
  }
48
49
  if (!res.ok) {
50
+ warnUnsigned(res.status);
49
51
  let detail = "";
50
52
  try {
51
53
  detail = (await res.json()).error ?? "";
@@ -57,21 +59,22 @@ var call = async (url, init, signal) => {
57
59
  };
58
60
  var createApi = (c) => {
59
61
  const base = `${c.host}/api/sdk`;
60
- const who = { key: c.apiKey, distinctId: c.distinctId, user: c.user };
62
+ const who = { key: c.apiKey, distinctId: c.distinctId, user: c.user, userHash: c.userHash };
63
+ const me = { key: c.apiKey, distinctId: c.distinctId, userHash: c.userHash };
61
64
  const post = (path, body, signal) => call(`${base}${path}`, { method: "POST", body: JSON.stringify({ ...who, ...body }) }, signal);
62
65
  return {
63
- list: (o, signal) => call(`${base}/feedback?${qs({ key: c.apiKey, distinctId: c.distinctId, board: o.board, sort: o.sort, q: o.q })}`, void 0, signal),
64
- post: (id, signal) => call(`${base}/feedback/${encodeURIComponent(id)}?${qs({ key: c.apiKey, distinctId: c.distinctId })}`, void 0, signal),
65
- roadmap: (signal) => call(`${base}/feedback/roadmap?${qs({ key: c.apiKey, distinctId: c.distinctId })}`, void 0, signal),
66
+ list: (o, signal) => call(`${base}/feedback?${qs({ ...me, board: o.board, sort: o.sort, q: o.q })}`, void 0, signal),
67
+ post: (id, signal) => call(`${base}/feedback/${encodeURIComponent(id)}?${qs(me)}`, void 0, signal),
68
+ roadmap: (signal) => call(`${base}/feedback/roadmap?${qs(me)}`, void 0, signal),
66
69
  changelog: (signal) => call(`${base}/feedback/changelog?${qs({ key: c.apiKey })}`, void 0, signal),
67
70
  submit: (p) => post("/feedback", p),
68
71
  vote: (id) => post(`/feedback/${encodeURIComponent(id)}/vote`, {}),
69
72
  comments: (id, signal) => call(`${base}/feedback/${encodeURIComponent(id)}/comments?${qs({ key: c.apiKey })}`, void 0, signal),
70
73
  comment: (id, body) => post(`/feedback/${encodeURIComponent(id)}/comments`, { body }),
71
- survey: (url, signal) => call(`${base}/surveys?${qs({ key: c.apiKey, distinctId: c.distinctId, url })}`, void 0, signal),
74
+ survey: (url, signal) => call(`${base}/surveys?${qs({ ...me, url })}`, void 0, signal),
72
75
  seen: (id) => post(`/surveys/${encodeURIComponent(id)}/seen`, { user: void 0 }),
73
76
  respond: (id, r) => post(`/surveys/${encodeURIComponent(id)}/respond`, r),
74
- chat: (signal) => call(`${base}/messages?${qs({ key: c.apiKey, distinctId: c.distinctId })}`, void 0, signal),
77
+ chat: (signal) => call(`${base}/messages?${qs(me)}`, void 0, signal),
75
78
  /** A message; `attachments` are upload ids from `uploadChatFile` (then the body may be empty). */
76
79
  sendChat: (m) => post("/messages", m),
77
80
  /** Uploads one picked file for the next message (pictures and PDFs, at most 10 MB); answers its upload id. */
@@ -79,14 +82,15 @@ var createApi = (c) => {
79
82
  const form = new FormData();
80
83
  form.append("key", c.apiKey);
81
84
  form.append("distinctId", c.distinctId);
85
+ if (c.userHash) form.append("userHash", c.userHash);
82
86
  if ("uri" in file) form.append("file", file);
83
87
  else form.append("file", file, file.name || "file");
84
88
  return call(`${base}/messages/attachments`, { method: "POST", body: form }, signal);
85
89
  },
86
90
  /** Where a sent file of this visitor's chat is read (an image's src, a PDF's link). */
87
- attachmentUrl: (id) => `${base}/messages/attachments/${encodeURIComponent(id)}?${qs({ key: c.apiKey, distinctId: c.distinctId })}`,
91
+ attachmentUrl: (id) => `${base}/messages/attachments/${encodeURIComponent(id)}?${qs(me)}`,
88
92
  articles: (q, signal) => call(`${base}/articles?${qs({ key: c.apiKey, q })}`, void 0, signal),
89
- article: (id, signal) => call(`${base}/articles/${encodeURIComponent(id)}?${qs({ key: c.apiKey, distinctId: c.distinctId })}`, void 0, signal),
93
+ article: (id, signal) => call(`${base}/articles/${encodeURIComponent(id)}?${qs(me)}`, void 0, signal),
90
94
  messengerLook: (signal) => call(`${base}/messenger?${qs({ key: c.apiKey })}`, void 0, signal),
91
95
  rateArticle: (id, helpful) => post(`/articles/${encodeURIComponent(id)}/rate`, { helpful })
92
96
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/api-client.ts"],"sourcesContent":["/**\n * The SDK's fetch client and the shapes it reads, without React, so the script tag's widgets (src/script) use it too.\n * `./api` re-exports all of it with the `useApi` hook.\n */\nexport type PostStatus = \"review\" | \"planned\" | \"progress\" | \"shipped\";\n\nexport const STATUS_LABEL: Record<PostStatus, string> = {\n review: \"Under review\",\n planned: \"Planned\",\n progress: \"In progress\",\n shipped: \"Shipped\",\n};\n\nexport interface FeedbackBoardInfo {\n id: string;\n label: string;\n}\n\nexport interface FeedbackPost {\n id: string;\n title: string;\n body: string;\n board: string;\n status: PostStatus;\n votes: number;\n comments: number;\n voted: boolean;\n createdAt: string;\n /** \"Lena K.\", \"Anonymous\" or \"Team\". Missing from servers before SDK 0.6. */\n author?: string;\n}\n\n/** The product's public face for the board's header. */\nexport interface FeedbackProduct {\n name: string;\n logo: string | null;\n /** The domain's favicon, tried when `logo` does not load. Older servers leave it out. */\n logoFallback?: string | null;\n}\n\nexport interface FeedbackList {\n boards: FeedbackBoardInfo[];\n posts: FeedbackPost[];\n product?: FeedbackProduct;\n}\n\nexport interface FeedbackRoadmap {\n /** `review` (Backlog) comes from servers of SDK 0.6 on. */\n columns: { review?: FeedbackPost[]; planned: FeedbackPost[]; progress: FeedbackPost[]; shipped: FeedbackPost[] };\n}\n\nexport interface ChangelogEntry {\n id: string;\n title: string;\n body: string;\n publishedAt: string;\n}\n\nexport interface FeedbackComment {\n id: string;\n body: string;\n author: string;\n isTeam: boolean;\n postedAt: string;\n}\n\nexport type SurveyQuestionKind = \"nps\" | \"rating\" | \"single\" | \"multi\" | \"text\";\n\nexport interface SurveyQuestion {\n key: string;\n kind: SurveyQuestionKind;\n title: string;\n description?: string;\n required: boolean;\n options?: string[];\n scale?: number;\n low?: string;\n high?: string;\n placeholder?: string;\n}\n\nexport interface SurveyDefinition {\n id: string;\n name: string;\n type: string;\n thanks: string;\n questions: SurveyQuestion[];\n}\n\nexport type SurveyAnswer = number | string | string[];\n\n/** A file in the messenger's chat: a screenshot, a photo or a PDF the visitor sent (servers from SDK 0.15 on). */\nexport interface ChatFile {\n /** From an upload: the signed id a message sends. In a message: the file's own id (`attachmentUrl` reads it). */\n id: string;\n name: string;\n type: \"image/png\" | \"image/jpeg\" | \"image/webp\" | \"image/gif\" | \"application/pdf\";\n /** Bytes. */\n size: number;\n}\n\n/** What the messenger can send: pictures and PDFs, at most 10 MB each, 4 per message. */\nexport const CHAT_FILE_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\", \"application/pdf\"] as const;\nexport const CHAT_FILE_MAX_BYTES = 10 * 1024 * 1024;\nexport const CHAT_FILES_PER_MESSAGE = 4;\n\n/** A file to upload: a Blob or File on the web, the image picker's `{ uri, name, type }` in React Native. */\nexport type ChatUpload = Blob | { uri: string; name: string; type: string };\n\n/** The bug context a visitor message carries; the server fills browser and system from the user agent when left out. */\nexport interface ChatContext {\n browser?: string;\n os?: string;\n /** \"1440×900\". */\n viewport?: string;\n}\n\n/** One message of the messenger's chat. \"visitor\" is the person on the site; the rest come from the product's team. */\nexport interface ChatMessage {\n id: string;\n from: \"visitor\" | \"team\" | \"ai\" | \"auto\";\n body: string;\n at: string;\n /** Help articles an answer drew on (servers from SDK 0.9 on). */\n articles?: { id: string; title: string }[];\n /** Files sent with it (servers from SDK 0.15 on). */\n attachments?: ChatFile[];\n}\n\n/** A published help article in a list (the messenger's Help tab). */\nexport interface ArticleSummary {\n id: string;\n title: string;\n collection: string;\n excerpt: string;\n updatedAt: string;\n}\n\n/** A run of text inside an article block. Links are http(s), mailto or tel only. */\nexport interface ArticleSpan {\n text: string;\n href?: string;\n bold?: boolean;\n italic?: boolean;\n code?: boolean;\n}\n\n/** An article's content as blocks, written with textContent (never parsed as HTML). */\nexport type ArticleBlock =\n | { type: \"h2\" | \"h3\" | \"p\" | \"quote\"; spans: ArticleSpan[] }\n | { type: \"ul\" | \"ol\"; items: ArticleSpan[][] }\n | { type: \"code\"; text: string }\n | { type: \"hr\" };\n\nexport interface Article {\n id: string;\n title: string;\n collection: string;\n updatedAt: string;\n blocks: ArticleBlock[];\n}\n\n/** Where the chat button sits: a corner, the middle of the top or bottom edge, or the middle of a side. */\nexport type MessengerPosition = \"top-left\" | \"top-center\" | \"top-right\" | \"middle-left\" | \"middle-right\" | \"bottom-left\" | \"bottom-center\" | \"bottom-right\";\n\n/** How round the messenger is. */\nexport type MessengerCorners = \"round\" | \"soft\" | \"square\";\n\n/** The radii per corner style, in px: the panel or sheet, message bubbles (and their tail), cards, fields and buttons. */\nexport const CORNERS: Record<MessengerCorners, { panel: number; bubble: number; tail: number; card: number; field: number }> = {\n round: { panel: 22, bubble: 18, tail: 6, card: 16, field: 20 },\n soft: { panel: 14, bubble: 12, tail: 4, card: 10, field: 10 },\n square: { panel: 6, bubble: 6, tail: 2, card: 4, field: 4 },\n};\n\n/** The messenger's look and texts as set in MRRocket › Support › Messenger (servers from SDK 0.9 on). */\nexport interface MessengerLook {\n accent: string;\n /** Up to two more colors blended into Home's background glow (MRRocket from 0.16; older servers leave it out). */\n glow?: string[];\n theme: \"light\" | \"dark\" | \"auto\";\n position: MessengerPosition;\n title: string;\n greeting: string;\n label: string;\n /** How soon the team usually answers (MRRocket from 0.10; older servers leave it out). */\n replyTime?: \"minutes\" | \"hours\" | \"day\";\n /** A round chat button in the app (React Native) too. */\n appLauncher?: boolean;\n help: boolean;\n sound: boolean;\n preview: boolean;\n askEmail: boolean;\n /** How round panel, bubbles, cards and fields are (MRRocket from 0.15; older servers leave it out: round). */\n corners?: MessengerCorners;\n /** The site's own font (the default) or the system's (web only; from 0.15). */\n font?: \"site\" | \"system\";\n /** Home's top: plain like the rest, or in the accent color (from 0.15; older servers leave it out: plain). */\n header?: \"plain\" | \"color\";\n /** \"Powered by MRRocket\" at the bottom; the server sends false only on plans that may hide it (from 0.15). */\n branding?: boolean;\n /** The product's icon (its favicon) at the top left of Home; without it the title's first letter. */\n logo?: string;\n}\n\n/** The link behind \"Powered by MRRocket\". */\nexport const BRAND_URL = \"https://mrrocket.dev/?utm_source=messenger&utm_medium=powered-by\";\n\n/** The line under \"Send us a message\" and in the chat's head. */\nexport const replyLine = (t: MessengerLook[\"replyTime\"]) =>\n t === \"minutes\" ? \"We usually reply in a few minutes\" : t === \"day\" ? \"We usually reply within a day\" : \"We usually reply in a few hours\";\n\n/** Black or white, whichever reads better on the accent (a light custom color gets dark text). */\nexport const inkOn = (hex: string) => {\n const m = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);\n if (!m) return \"#ffffff\";\n const [r, g, b] = [m[1], m[2], m[3]].map((x) => {\n const c = parseInt(x, 16) / 255;\n return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;\n });\n return 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.45 ? \"#111214\" : \"#ffffff\";\n};\n\n/** The visitor's newest chat (open or closed) and its messages. */\nexport interface ChatThread {\n conversation: { id: string; status: \"open\" | \"solved\" } | null;\n messages: ChatMessage[];\n /** The visitor gave an email already: the messenger stops asking. */\n hasEmail: boolean;\n}\n\nexport class ApiError extends Error {\n status: number;\n constructor(message: string, status: number) {\n super(message);\n this.status = status;\n }\n}\n\ninterface Conn {\n host: string;\n apiKey: string;\n distinctId: string;\n user?: { id?: string; [key: string]: unknown };\n}\n\n// Not URLSearchParams: React Native's lacks `set` in older versions, and the apps use this client too.\nconst qs = (params: Record<string, string | undefined>) =>\n Object.entries(params)\n .filter((e): e is [string, string] => !!e[1])\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)\n .join(\"&\");\n\nconst call = async <T>(url: string, init: RequestInit | undefined, signal?: AbortSignal): Promise<T> => {\n let res: Response;\n try {\n res = await fetch(url, {\n ...init,\n signal,\n // A form (a file upload) sets its own multipart type with the boundary.\n headers: typeof init?.body === \"string\" ? { \"content-type\": \"application/json\" } : undefined,\n });\n } catch (e) {\n if ((e as Error).name === \"AbortError\") throw e;\n throw new ApiError(\"Could not reach the server. Check your connection and try again.\", 0);\n }\n if (!res.ok) {\n let detail = \"\";\n try {\n detail = ((await res.json()) as { error?: string }).error ?? \"\";\n } catch {\n // Not JSON; the status is enough.\n }\n throw new ApiError(detail || (res.status === 429 ? \"Too many requests. Try again in a minute.\" : \"Something went wrong. Please try again.\"), res.status);\n }\n return (await res.json()) as T;\n};\n\n/** The small fetch client behind the widgets. Every read takes an AbortSignal so unmounting cancels it. */\nexport const createApi = (c: Conn) => {\n const base = `${c.host}/api/sdk`;\n const who = { key: c.apiKey, distinctId: c.distinctId, user: c.user };\n const post = <T>(path: string, body: Record<string, unknown>, signal?: AbortSignal) =>\n call<T>(`${base}${path}`, { method: \"POST\", body: JSON.stringify({ ...who, ...body }) }, signal);\n return {\n list: (o: { board?: string; sort?: \"top\" | \"new\"; q?: string }, signal?: AbortSignal) =>\n call<FeedbackList>(`${base}/feedback?${qs({ key: c.apiKey, distinctId: c.distinctId, board: o.board, sort: o.sort, q: o.q })}`, undefined, signal),\n post: (id: string, signal?: AbortSignal) =>\n call<FeedbackPost>(`${base}/feedback/${encodeURIComponent(id)}?${qs({ key: c.apiKey, distinctId: c.distinctId })}`, undefined, signal),\n roadmap: (signal?: AbortSignal) => call<FeedbackRoadmap>(`${base}/feedback/roadmap?${qs({ key: c.apiKey, distinctId: c.distinctId })}`, undefined, signal),\n changelog: (signal?: AbortSignal) => call<{ entries: ChangelogEntry[] }>(`${base}/feedback/changelog?${qs({ key: c.apiKey })}`, undefined, signal),\n submit: (p: { title: string; body?: string; board: string }) => post<FeedbackPost>(\"/feedback\", p),\n vote: (id: string) => post<{ voted: boolean; votes: number }>(`/feedback/${encodeURIComponent(id)}/vote`, {}),\n comments: (id: string, signal?: AbortSignal) =>\n call<{ comments: FeedbackComment[] }>(`${base}/feedback/${encodeURIComponent(id)}/comments?${qs({ key: c.apiKey })}`, undefined, signal),\n comment: (id: string, body: string) => post<FeedbackComment>(`/feedback/${encodeURIComponent(id)}/comments`, { body }),\n survey: (url: string, signal?: AbortSignal) =>\n call<{ survey: SurveyDefinition | null }>(`${base}/surveys?${qs({ key: c.apiKey, distinctId: c.distinctId, url })}`, undefined, signal),\n seen: (id: string) => post<unknown>(`/surveys/${encodeURIComponent(id)}/seen`, { user: undefined }),\n respond: (id: string, r: { answers: Record<string, SurveyAnswer>; completed: boolean; seconds: number }) =>\n post<unknown>(`/surveys/${encodeURIComponent(id)}/respond`, r),\n chat: (signal?: AbortSignal) => call<ChatThread>(`${base}/messages?${qs({ key: c.apiKey, distinctId: c.distinctId })}`, undefined, signal),\n /** A message; `attachments` are upload ids from `uploadChatFile` (then the body may be empty). */\n sendChat: (m: { body: string; email?: string; url?: string; attachments?: string[]; context?: ChatContext }) => post<ChatThread>(\"/messages\", m),\n /** Uploads one picked file for the next message (pictures and PDFs, at most 10 MB); answers its upload id. */\n uploadChatFile: (file: ChatUpload, signal?: AbortSignal) => {\n const form = new FormData();\n form.append(\"key\", c.apiKey);\n form.append(\"distinctId\", c.distinctId);\n if (\"uri\" in file) form.append(\"file\", file as unknown as Blob);\n else form.append(\"file\", file, (file as Blob & { name?: string }).name || \"file\");\n return call<ChatFile>(`${base}/messages/attachments`, { method: \"POST\", body: form }, signal);\n },\n /** Where a sent file of this visitor's chat is read (an image's src, a PDF's link). */\n attachmentUrl: (id: string) => `${base}/messages/attachments/${encodeURIComponent(id)}?${qs({ key: c.apiKey, distinctId: c.distinctId })}`,\n articles: (q?: string, signal?: AbortSignal) => call<{ articles: ArticleSummary[] }>(`${base}/articles?${qs({ key: c.apiKey, q })}`, undefined, signal),\n article: (id: string, signal?: AbortSignal) =>\n call<Article>(`${base}/articles/${encodeURIComponent(id)}?${qs({ key: c.apiKey, distinctId: c.distinctId })}`, undefined, signal),\n messengerLook: (signal?: AbortSignal) => call<MessengerLook>(`${base}/messenger?${qs({ key: c.apiKey })}`, undefined, signal),\n rateArticle: (id: string, helpful: boolean) => post<{ counted: boolean }>(`/articles/${encodeURIComponent(id)}/rate`, { helpful }),\n };\n};\n\nexport type Api = ReturnType<typeof createApi>;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/api-client.ts"],"sourcesContent":["/**\n * The SDK's fetch client and the shapes it reads, without React, so the script tag's widgets (src/script) use it too.\n * `./api` re-exports all of it with the `useApi` hook.\n */\nimport { warnUnsigned } from \"./identity\";\n\nexport type PostStatus = \"review\" | \"planned\" | \"progress\" | \"shipped\";\n\nexport const STATUS_LABEL: Record<PostStatus, string> = {\n review: \"Under review\",\n planned: \"Planned\",\n progress: \"In progress\",\n shipped: \"Shipped\",\n};\n\nexport interface FeedbackBoardInfo {\n id: string;\n label: string;\n}\n\nexport interface FeedbackPost {\n id: string;\n title: string;\n body: string;\n board: string;\n status: PostStatus;\n votes: number;\n comments: number;\n voted: boolean;\n createdAt: string;\n /** \"Lena K.\", \"Anonymous\" or \"Team\". Missing from servers before SDK 0.6. */\n author?: string;\n}\n\n/** The product's public face for the board's header. */\nexport interface FeedbackProduct {\n name: string;\n logo: string | null;\n /** The domain's favicon, tried when `logo` does not load. Older servers leave it out. */\n logoFallback?: string | null;\n}\n\nexport interface FeedbackList {\n boards: FeedbackBoardInfo[];\n posts: FeedbackPost[];\n product?: FeedbackProduct;\n}\n\nexport interface FeedbackRoadmap {\n /** `review` (Backlog) comes from servers of SDK 0.6 on. */\n columns: { review?: FeedbackPost[]; planned: FeedbackPost[]; progress: FeedbackPost[]; shipped: FeedbackPost[] };\n}\n\nexport interface ChangelogEntry {\n id: string;\n title: string;\n body: string;\n publishedAt: string;\n}\n\nexport interface FeedbackComment {\n id: string;\n body: string;\n author: string;\n isTeam: boolean;\n postedAt: string;\n}\n\nexport type SurveyQuestionKind = \"nps\" | \"rating\" | \"single\" | \"multi\" | \"text\";\n\nexport interface SurveyQuestion {\n key: string;\n kind: SurveyQuestionKind;\n title: string;\n description?: string;\n required: boolean;\n options?: string[];\n scale?: number;\n low?: string;\n high?: string;\n placeholder?: string;\n}\n\nexport interface SurveyDefinition {\n id: string;\n name: string;\n type: string;\n thanks: string;\n questions: SurveyQuestion[];\n}\n\nexport type SurveyAnswer = number | string | string[];\n\n/** A file in the messenger's chat: a screenshot, a photo or a PDF the visitor sent (servers from SDK 0.15 on). */\nexport interface ChatFile {\n /** From an upload: the signed id a message sends. In a message: the file's own id (`attachmentUrl` reads it). */\n id: string;\n name: string;\n type: \"image/png\" | \"image/jpeg\" | \"image/webp\" | \"image/gif\" | \"application/pdf\";\n /** Bytes. */\n size: number;\n}\n\n/** What the messenger can send: pictures and PDFs, at most 10 MB each, 4 per message. */\nexport const CHAT_FILE_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\", \"application/pdf\"] as const;\nexport const CHAT_FILE_MAX_BYTES = 10 * 1024 * 1024;\nexport const CHAT_FILES_PER_MESSAGE = 4;\n\n/** A file to upload: a Blob or File on the web, the image picker's `{ uri, name, type }` in React Native. */\nexport type ChatUpload = Blob | { uri: string; name: string; type: string };\n\n/** The bug context a visitor message carries; the server fills browser and system from the user agent when left out. */\nexport interface ChatContext {\n browser?: string;\n os?: string;\n /** \"1440×900\". */\n viewport?: string;\n}\n\n/** One message of the messenger's chat. \"visitor\" is the person on the site; the rest come from the product's team. */\nexport interface ChatMessage {\n id: string;\n from: \"visitor\" | \"team\" | \"ai\" | \"auto\";\n body: string;\n at: string;\n /** Help articles an answer drew on (servers from SDK 0.9 on). */\n articles?: { id: string; title: string }[];\n /** Files sent with it (servers from SDK 0.15 on). */\n attachments?: ChatFile[];\n}\n\n/** A published help article in a list (the messenger's Help tab). */\nexport interface ArticleSummary {\n id: string;\n title: string;\n collection: string;\n excerpt: string;\n updatedAt: string;\n}\n\n/** A run of text inside an article block. Links are http(s), mailto or tel only. */\nexport interface ArticleSpan {\n text: string;\n href?: string;\n bold?: boolean;\n italic?: boolean;\n code?: boolean;\n}\n\n/** An article's content as blocks, written with textContent (never parsed as HTML). */\nexport type ArticleBlock =\n | { type: \"h2\" | \"h3\" | \"p\" | \"quote\"; spans: ArticleSpan[] }\n | { type: \"ul\" | \"ol\"; items: ArticleSpan[][] }\n | { type: \"code\"; text: string }\n | { type: \"hr\" };\n\nexport interface Article {\n id: string;\n title: string;\n collection: string;\n updatedAt: string;\n blocks: ArticleBlock[];\n}\n\n/** Where the chat button sits: a corner, the middle of the top or bottom edge, or the middle of a side. */\nexport type MessengerPosition = \"top-left\" | \"top-center\" | \"top-right\" | \"middle-left\" | \"middle-right\" | \"bottom-left\" | \"bottom-center\" | \"bottom-right\";\n\n/** How round the messenger is. */\nexport type MessengerCorners = \"round\" | \"soft\" | \"square\";\n\n/** The radii per corner style, in px: the panel or sheet, message bubbles (and their tail), cards, fields and buttons. */\nexport const CORNERS: Record<MessengerCorners, { panel: number; bubble: number; tail: number; card: number; field: number }> = {\n round: { panel: 22, bubble: 18, tail: 6, card: 16, field: 20 },\n soft: { panel: 14, bubble: 12, tail: 4, card: 10, field: 10 },\n square: { panel: 6, bubble: 6, tail: 2, card: 4, field: 4 },\n};\n\n/** The messenger's look and texts as set in MRRocket › Support › Messenger (servers from SDK 0.9 on). */\nexport interface MessengerLook {\n accent: string;\n /** Up to two more colors blended into Home's background glow (MRRocket from 0.16; older servers leave it out). */\n glow?: string[];\n theme: \"light\" | \"dark\" | \"auto\";\n position: MessengerPosition;\n title: string;\n greeting: string;\n label: string;\n /** How soon the team usually answers (MRRocket from 0.10; older servers leave it out). */\n replyTime?: \"minutes\" | \"hours\" | \"day\";\n /** A round chat button in the app (React Native) too. */\n appLauncher?: boolean;\n help: boolean;\n sound: boolean;\n preview: boolean;\n askEmail: boolean;\n /** How round panel, bubbles, cards and fields are (MRRocket from 0.15; older servers leave it out: round). */\n corners?: MessengerCorners;\n /** The site's own font (the default) or the system's (web only; from 0.15). */\n font?: \"site\" | \"system\";\n /** Home's top: plain like the rest, or in the accent color (from 0.15; older servers leave it out: plain). */\n header?: \"plain\" | \"color\";\n /** \"Powered by MRRocket\" at the bottom; the server sends false only on plans that may hide it (from 0.15). */\n branding?: boolean;\n /** The product's icon (its favicon) at the top left of Home; without it the title's first letter. */\n logo?: string;\n}\n\n/** The link behind \"Powered by MRRocket\". */\nexport const BRAND_URL = \"https://mrrocket.dev/?utm_source=messenger&utm_medium=powered-by\";\n\n/** The line under \"Send us a message\" and in the chat's head. */\nexport const replyLine = (t: MessengerLook[\"replyTime\"]) =>\n t === \"minutes\" ? \"We usually reply in a few minutes\" : t === \"day\" ? \"We usually reply within a day\" : \"We usually reply in a few hours\";\n\n/** Black or white, whichever reads better on the accent (a light custom color gets dark text). */\nexport const inkOn = (hex: string) => {\n const m = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);\n if (!m) return \"#ffffff\";\n const [r, g, b] = [m[1], m[2], m[3]].map((x) => {\n const c = parseInt(x, 16) / 255;\n return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;\n });\n return 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.45 ? \"#111214\" : \"#ffffff\";\n};\n\n/** The visitor's newest chat (open or closed) and its messages. */\nexport interface ChatThread {\n conversation: { id: string; status: \"open\" | \"solved\" } | null;\n messages: ChatMessage[];\n /** The visitor gave an email already: the messenger stops asking. */\n hasEmail: boolean;\n}\n\nexport class ApiError extends Error {\n status: number;\n constructor(message: string, status: number) {\n super(message);\n this.status = status;\n }\n}\n\ninterface Conn {\n host: string;\n apiKey: string;\n distinctId: string;\n user?: { id?: string; [key: string]: unknown };\n /**\n * The identity hash of `distinctId` (0.16.0, see identity.ts), sent with every request that names the visitor: a\n * JSON field next to `distinctId`, a query parameter, a form field. Leave it out for any other id.\n */\n userHash?: string;\n}\n\n// Not URLSearchParams: React Native's lacks `set` in older versions, and the apps use this client too.\nconst qs = (params: Record<string, string | undefined>) =>\n Object.entries(params)\n .filter((e): e is [string, string] => !!e[1])\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)\n .join(\"&\");\n\nconst call = async <T>(url: string, init: RequestInit | undefined, signal?: AbortSignal): Promise<T> => {\n let res: Response;\n try {\n res = await fetch(url, {\n ...init,\n signal,\n // A form (a file upload) sets its own multipart type with the boundary.\n headers: typeof init?.body === \"string\" ? { \"content-type\": \"application/json\" } : undefined,\n });\n } catch (e) {\n if ((e as Error).name === \"AbortError\") throw e;\n throw new ApiError(\"Could not reach the server. Check your connection and try again.\", 0);\n }\n if (!res.ok) {\n // A missing or wrong identity hash: the console says once how to sign the user id.\n warnUnsigned(res.status);\n let detail = \"\";\n try {\n detail = ((await res.json()) as { error?: string }).error ?? \"\";\n } catch {\n // Not JSON; the status is enough.\n }\n throw new ApiError(detail || (res.status === 429 ? \"Too many requests. Try again in a minute.\" : \"Something went wrong. Please try again.\"), res.status);\n }\n return (await res.json()) as T;\n};\n\n/** The small fetch client behind the widgets. Every read takes an AbortSignal so unmounting cancels it. */\nexport const createApi = (c: Conn) => {\n const base = `${c.host}/api/sdk`;\n const who = { key: c.apiKey, distinctId: c.distinctId, user: c.user, userHash: c.userHash };\n // Every query that names the visitor names its hash too.\n const me = { key: c.apiKey, distinctId: c.distinctId, userHash: c.userHash };\n const post = <T>(path: string, body: Record<string, unknown>, signal?: AbortSignal) =>\n call<T>(`${base}${path}`, { method: \"POST\", body: JSON.stringify({ ...who, ...body }) }, signal);\n return {\n list: (o: { board?: string; sort?: \"top\" | \"new\"; q?: string }, signal?: AbortSignal) =>\n call<FeedbackList>(`${base}/feedback?${qs({ ...me, board: o.board, sort: o.sort, q: o.q })}`, undefined, signal),\n post: (id: string, signal?: AbortSignal) =>\n call<FeedbackPost>(`${base}/feedback/${encodeURIComponent(id)}?${qs(me)}`, undefined, signal),\n roadmap: (signal?: AbortSignal) => call<FeedbackRoadmap>(`${base}/feedback/roadmap?${qs(me)}`, undefined, signal),\n changelog: (signal?: AbortSignal) => call<{ entries: ChangelogEntry[] }>(`${base}/feedback/changelog?${qs({ key: c.apiKey })}`, undefined, signal),\n submit: (p: { title: string; body?: string; board: string }) => post<FeedbackPost>(\"/feedback\", p),\n vote: (id: string) => post<{ voted: boolean; votes: number }>(`/feedback/${encodeURIComponent(id)}/vote`, {}),\n comments: (id: string, signal?: AbortSignal) =>\n call<{ comments: FeedbackComment[] }>(`${base}/feedback/${encodeURIComponent(id)}/comments?${qs({ key: c.apiKey })}`, undefined, signal),\n comment: (id: string, body: string) => post<FeedbackComment>(`/feedback/${encodeURIComponent(id)}/comments`, { body }),\n survey: (url: string, signal?: AbortSignal) =>\n call<{ survey: SurveyDefinition | null }>(`${base}/surveys?${qs({ ...me, url })}`, undefined, signal),\n seen: (id: string) => post<unknown>(`/surveys/${encodeURIComponent(id)}/seen`, { user: undefined }),\n respond: (id: string, r: { answers: Record<string, SurveyAnswer>; completed: boolean; seconds: number }) =>\n post<unknown>(`/surveys/${encodeURIComponent(id)}/respond`, r),\n chat: (signal?: AbortSignal) => call<ChatThread>(`${base}/messages?${qs(me)}`, undefined, signal),\n /** A message; `attachments` are upload ids from `uploadChatFile` (then the body may be empty). */\n sendChat: (m: { body: string; email?: string; url?: string; attachments?: string[]; context?: ChatContext }) => post<ChatThread>(\"/messages\", m),\n /** Uploads one picked file for the next message (pictures and PDFs, at most 10 MB); answers its upload id. */\n uploadChatFile: (file: ChatUpload, signal?: AbortSignal) => {\n const form = new FormData();\n form.append(\"key\", c.apiKey);\n form.append(\"distinctId\", c.distinctId);\n if (c.userHash) form.append(\"userHash\", c.userHash);\n if (\"uri\" in file) form.append(\"file\", file as unknown as Blob);\n else form.append(\"file\", file, (file as Blob & { name?: string }).name || \"file\");\n return call<ChatFile>(`${base}/messages/attachments`, { method: \"POST\", body: form }, signal);\n },\n /** Where a sent file of this visitor's chat is read (an image's src, a PDF's link). */\n attachmentUrl: (id: string) => `${base}/messages/attachments/${encodeURIComponent(id)}?${qs(me)}`,\n articles: (q?: string, signal?: AbortSignal) => call<{ articles: ArticleSummary[] }>(`${base}/articles?${qs({ key: c.apiKey, q })}`, undefined, signal),\n article: (id: string, signal?: AbortSignal) =>\n call<Article>(`${base}/articles/${encodeURIComponent(id)}?${qs(me)}`, undefined, signal),\n messengerLook: (signal?: AbortSignal) => call<MessengerLook>(`${base}/messenger?${qs({ key: c.apiKey })}`, undefined, signal),\n rateArticle: (id: string, helpful: boolean) => post<{ counted: boolean }>(`/articles/${encodeURIComponent(id)}/rate`, { helpful }),\n };\n};\n\nexport type Api = ReturnType<typeof createApi>;\n"],"mappings":"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","names":[]}
package/dist/api.d.ts CHANGED
@@ -9,5 +9,8 @@ export interface Writer {
9
9
  email?: string;
10
10
  };
11
11
  }
12
- /** The client for the provider's key, host and visitor, or for `as` when given. */
12
+ /**
13
+ * The client for the provider's key, host and visitor, or for `as` when given. The visitor's identity hash goes along;
14
+ * `as` (a board identity the visitor typed in) is no signed user and goes without.
15
+ */
13
16
  export declare const useApi: (as?: Writer | null) => Api;
package/dist/api.js CHANGED
@@ -7,8 +7,9 @@ var useApi = (as) => {
7
7
  const ctx = useCtx();
8
8
  const distinctId = as?.distinctId ?? ctx.distinctId;
9
9
  const user = as ? as.user : ctx.user;
10
+ const userHash = as ? void 0 : ctx.userHash;
10
11
  const { apiKey, host } = ctx;
11
- return useMemo(() => createApi({ apiKey, host, distinctId, user }), [apiKey, host, distinctId, user]);
12
+ return useMemo(() => createApi({ apiKey, host, distinctId, user, userHash }), [apiKey, host, distinctId, user, userHash]);
12
13
  };
13
14
  export {
14
15
  useApi
package/dist/api.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/api.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { type Api, createApi } from \"./api-client\";\nimport { useCtx } from \"./context\";\n\nexport * from \"./api-client\";\n\n/** Who writes: the provider's visitor, or the name and email a visitor gave the board (see useBoardIdentity). */\nexport interface Writer {\n distinctId: string;\n user?: { id?: string; name?: string; email?: string };\n}\n\n/** The client for the provider's key, host and visitor, or for `as` when given. */\nexport const useApi = (as?: Writer | null): Api => {\n const ctx = useCtx();\n const distinctId = as?.distinctId ?? ctx.distinctId;\n const user = as ? as.user : ctx.user;\n const { apiKey, host } = ctx;\n return useMemo(() => createApi({ apiKey, host, distinctId, user }), [apiKey, host, distinctId, user]);\n};\n"],"mappings":";AAAA,SAAS,eAAe;AACxB,SAAmB,iBAAiB;AACpC,SAAS,cAAc;AAEvB,cAAc;AASP,IAAM,SAAS,CAAC,OAA4B;AAC/C,QAAM,MAAM,OAAO;AACnB,QAAM,aAAa,IAAI,cAAc,IAAI;AACzC,QAAM,OAAO,KAAK,GAAG,OAAO,IAAI;AAChC,QAAM,EAAE,QAAQ,KAAK,IAAI;AACzB,SAAO,QAAQ,MAAM,UAAU,EAAE,QAAQ,MAAM,YAAY,KAAK,CAAC,GAAG,CAAC,QAAQ,MAAM,YAAY,IAAI,CAAC;AACxG;","names":[]}
1
+ {"version":3,"sources":["../src/api.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { type Api, createApi } from \"./api-client\";\nimport { useCtx } from \"./context\";\n\nexport * from \"./api-client\";\n\n/** Who writes: the provider's visitor, or the name and email a visitor gave the board (see useBoardIdentity). */\nexport interface Writer {\n distinctId: string;\n user?: { id?: string; name?: string; email?: string };\n}\n\n/**\n * The client for the provider's key, host and visitor, or for `as` when given. The visitor's identity hash goes along;\n * `as` (a board identity the visitor typed in) is no signed user and goes without.\n */\nexport const useApi = (as?: Writer | null): Api => {\n const ctx = useCtx();\n const distinctId = as?.distinctId ?? ctx.distinctId;\n const user = as ? as.user : ctx.user;\n const userHash = as ? undefined : ctx.userHash;\n const { apiKey, host } = ctx;\n return useMemo(() => createApi({ apiKey, host, distinctId, user, userHash }), [apiKey, host, distinctId, user, userHash]);\n};\n"],"mappings":";AAAA,SAAS,eAAe;AACxB,SAAmB,iBAAiB;AACpC,SAAS,cAAc;AAEvB,cAAc;AAYP,IAAM,SAAS,CAAC,OAA4B;AAC/C,QAAM,MAAM,OAAO;AACnB,QAAM,aAAa,IAAI,cAAc,IAAI;AACzC,QAAM,OAAO,KAAK,GAAG,OAAO,IAAI;AAChC,QAAM,WAAW,KAAK,SAAY,IAAI;AACtC,QAAM,EAAE,QAAQ,KAAK,IAAI;AACzB,SAAO,QAAQ,MAAM,UAAU,EAAE,QAAQ,MAAM,YAAY,MAAM,SAAS,CAAC,GAAG,CAAC,QAAQ,MAAM,YAAY,MAAM,QAAQ,CAAC;AAC5H;","names":[]}
package/dist/batch.d.ts CHANGED
@@ -23,11 +23,13 @@ export interface BatchRequest<E> {
23
23
  }
24
24
  /**
25
25
  * Splits events into request bodies of at most 100 events each, under the size limit. An event too large on its own is
26
- * dropped (it is in no request).
26
+ * dropped (it is in no request). `userHash` (0.16.0) is the identity hash of the signed-in person: one per request, and
27
+ * MRRocket checks it against each event's `distinctId`, so only that person's events count as verified.
27
28
  */
28
- export declare const batchRequests: <E extends CockpitEvent>(key: string, events: E[], sdk: SdkInfo) => BatchRequest<E>[];
29
+ export declare const batchRequests: <E extends CockpitEvent>(key: string, events: E[], sdk: SdkInfo, userHash?: string) => BatchRequest<E>[];
29
30
  /**
30
31
  * Request bodies of at most 100 events each, under the size limit. An event too large on its own is dropped. `sdk` tells
31
- * MRRocket which SDK the site runs and whether it reports errors (Build › Errors and Feature flags show it).
32
+ * MRRocket which SDK the site runs and whether it reports errors (Build › Errors and Feature flags show it); `userHash`
33
+ * as in `batchRequests`.
32
34
  */
33
- export declare const bodies: (key: string, events: CockpitEvent[], sdk: SdkInfo) => string[];
35
+ export declare const bodies: (key: string, events: CockpitEvent[], sdk: SdkInfo, userHash?: string) => string[];
package/dist/batch.js CHANGED
@@ -14,8 +14,8 @@ var byteLength = (s) => {
14
14
  }
15
15
  return n;
16
16
  };
17
- var batchRequests = (key, events, sdk) => {
18
- const head = `{"key":${JSON.stringify(key)},"sdk":${JSON.stringify(sdk)},"events":[`;
17
+ var batchRequests = (key, events, sdk, userHash) => {
18
+ const head = `{"key":${JSON.stringify(key)},${userHash ? `"userHash":${JSON.stringify(userHash)},` : ""}"sdk":${JSON.stringify(sdk)},"events":[`;
19
19
  const empty = byteLength(head) + 2;
20
20
  const out = [];
21
21
  let chunk = [];
@@ -39,7 +39,7 @@ var batchRequests = (key, events, sdk) => {
39
39
  close();
40
40
  return out;
41
41
  };
42
- var bodies = (key, events, sdk) => batchRequests(key, events, sdk).map((r) => r.body);
42
+ var bodies = (key, events, sdk, userHash) => batchRequests(key, events, sdk, userHash).map((r) => r.body);
43
43
  export {
44
44
  MAX_BATCH,
45
45
  MAX_BYTES,
package/dist/batch.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/batch.ts"],"sourcesContent":["/**\n * How events go over the wire to `POST /api/sdk/events`, shared by every runtime (Next.js, the script tag, React\n * Native): request bodies of at most 100 events each, under the server's 64 KB. No React, no browser APIs, no\n * TextEncoder (Hermes on older React Native lacks it), so it runs anywhere.\n */\nimport type { CockpitEvent } from \"./core\";\n\n/** The server takes at most 100 events and 64 KB per request; stay a little under the size. */\nexport const MAX_BATCH = 100;\nexport const MAX_BYTES = 60_000;\n\n/** What a batch says about the SDK that sent it (Build › Errors and Feature flags show it). */\nexport interface SdkInfo {\n v: string;\n errors: boolean;\n /** \"web\" (script tag), \"ios\" or \"android\" (React Native). Missing from the Next.js provider. */\n platform?: string;\n}\n\n/** UTF-8 length of a string, the way TextEncoder counts it (a lone surrogate becomes U+FFFD, three bytes). */\nexport const byteLength = (s: string) => {\n let n = 0;\n for (let i = 0; i < s.length; i++) {\n const c = s.charCodeAt(i);\n if (c < 0x80) n += 1;\n else if (c < 0x800) n += 2;\n else if (c >= 0xd800 && c <= 0xdbff && i + 1 < s.length && (s.charCodeAt(i + 1) & 0xfc00) === 0xdc00) {\n n += 4;\n i++;\n } else n += 3;\n }\n return n;\n};\n\n/** One request: its body and the events in it (so a client can keep the ones that did not arrive). */\nexport interface BatchRequest<E> {\n body: string;\n events: E[];\n}\n\n/**\n * Splits events into request bodies of at most 100 events each, under the size limit. An event too large on its own is\n * dropped (it is in no request).\n */\nexport const batchRequests = <E extends CockpitEvent>(key: string, events: E[], sdk: SdkInfo): BatchRequest<E>[] => {\n const head = `{\"key\":${JSON.stringify(key)},\"sdk\":${JSON.stringify(sdk)},\"events\":[`;\n const empty = byteLength(head) + 2;\n const out: BatchRequest<E>[] = [];\n let chunk: string[] = [];\n let items: E[] = [];\n let size = empty;\n const close = () => {\n if (chunk.length) out.push({ body: `${head}${chunk.join(\",\")}]}`, events: items });\n chunk = [];\n items = [];\n size = empty;\n };\n for (const event of events) {\n const json = JSON.stringify(event);\n const n = byteLength(json) + 1;\n if (empty + n > MAX_BYTES) continue;\n if (chunk.length === MAX_BATCH || size + n > MAX_BYTES) close();\n chunk.push(json);\n items.push(event);\n size += n;\n }\n close();\n return out;\n};\n\n/**\n * Request bodies of at most 100 events each, under the size limit. An event too large on its own is dropped. `sdk` tells\n * MRRocket which SDK the site runs and whether it reports errors (Build › Errors and Feature flags show it).\n */\nexport const bodies = (key: string, events: CockpitEvent[], sdk: SdkInfo) => batchRequests(key, events, sdk).map((r) => r.body);\n"],"mappings":";AAQO,IAAM,YAAY;AAClB,IAAM,YAAY;AAWlB,IAAM,aAAa,CAAC,MAAc;AACrC,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AAC/B,UAAM,IAAI,EAAE,WAAW,CAAC;AACxB,QAAI,IAAI,IAAM,MAAK;AAAA,aACV,IAAI,KAAO,MAAK;AAAA,aAChB,KAAK,SAAU,KAAK,SAAU,IAAI,IAAI,EAAE,WAAW,EAAE,WAAW,IAAI,CAAC,IAAI,WAAY,OAAQ;AAClG,WAAK;AACL;AAAA,IACJ,MAAO,MAAK;AAAA,EAChB;AACA,SAAO;AACX;AAYO,IAAM,gBAAgB,CAAyB,KAAa,QAAa,QAAoC;AAChH,QAAM,OAAO,UAAU,KAAK,UAAU,GAAG,CAAC,UAAU,KAAK,UAAU,GAAG,CAAC;AACvE,QAAM,QAAQ,WAAW,IAAI,IAAI;AACjC,QAAM,MAAyB,CAAC;AAChC,MAAI,QAAkB,CAAC;AACvB,MAAI,QAAa,CAAC;AAClB,MAAI,OAAO;AACX,QAAM,QAAQ,MAAM;AAChB,QAAI,MAAM,OAAQ,KAAI,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,KAAK,GAAG,CAAC,MAAM,QAAQ,MAAM,CAAC;AACjF,YAAQ,CAAC;AACT,YAAQ,CAAC;AACT,WAAO;AAAA,EACX;AACA,aAAW,SAAS,QAAQ;AACxB,UAAM,OAAO,KAAK,UAAU,KAAK;AACjC,UAAM,IAAI,WAAW,IAAI,IAAI;AAC7B,QAAI,QAAQ,IAAI,UAAW;AAC3B,QAAI,MAAM,WAAW,aAAa,OAAO,IAAI,UAAW,OAAM;AAC9D,UAAM,KAAK,IAAI;AACf,UAAM,KAAK,KAAK;AAChB,YAAQ;AAAA,EACZ;AACA,QAAM;AACN,SAAO;AACX;AAMO,IAAM,SAAS,CAAC,KAAa,QAAwB,QAAiB,cAAc,KAAK,QAAQ,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI;","names":[]}
1
+ {"version":3,"sources":["../src/batch.ts"],"sourcesContent":["/**\n * How events go over the wire to `POST /api/sdk/events`, shared by every runtime (Next.js, the script tag, React\n * Native): request bodies of at most 100 events each, under the server's 64 KB. No React, no browser APIs, no\n * TextEncoder (Hermes on older React Native lacks it), so it runs anywhere.\n */\nimport type { CockpitEvent } from \"./core\";\n\n/** The server takes at most 100 events and 64 KB per request; stay a little under the size. */\nexport const MAX_BATCH = 100;\nexport const MAX_BYTES = 60_000;\n\n/** What a batch says about the SDK that sent it (Build › Errors and Feature flags show it). */\nexport interface SdkInfo {\n v: string;\n errors: boolean;\n /** \"web\" (script tag), \"ios\" or \"android\" (React Native). Missing from the Next.js provider. */\n platform?: string;\n}\n\n/** UTF-8 length of a string, the way TextEncoder counts it (a lone surrogate becomes U+FFFD, three bytes). */\nexport const byteLength = (s: string) => {\n let n = 0;\n for (let i = 0; i < s.length; i++) {\n const c = s.charCodeAt(i);\n if (c < 0x80) n += 1;\n else if (c < 0x800) n += 2;\n else if (c >= 0xd800 && c <= 0xdbff && i + 1 < s.length && (s.charCodeAt(i + 1) & 0xfc00) === 0xdc00) {\n n += 4;\n i++;\n } else n += 3;\n }\n return n;\n};\n\n/** One request: its body and the events in it (so a client can keep the ones that did not arrive). */\nexport interface BatchRequest<E> {\n body: string;\n events: E[];\n}\n\n/**\n * Splits events into request bodies of at most 100 events each, under the size limit. An event too large on its own is\n * dropped (it is in no request). `userHash` (0.16.0) is the identity hash of the signed-in person: one per request, and\n * MRRocket checks it against each event's `distinctId`, so only that person's events count as verified.\n */\nexport const batchRequests = <E extends CockpitEvent>(key: string, events: E[], sdk: SdkInfo, userHash?: string): BatchRequest<E>[] => {\n const head = `{\"key\":${JSON.stringify(key)},${userHash ? `\"userHash\":${JSON.stringify(userHash)},` : \"\"}\"sdk\":${JSON.stringify(sdk)},\"events\":[`;\n const empty = byteLength(head) + 2;\n const out: BatchRequest<E>[] = [];\n let chunk: string[] = [];\n let items: E[] = [];\n let size = empty;\n const close = () => {\n if (chunk.length) out.push({ body: `${head}${chunk.join(\",\")}]}`, events: items });\n chunk = [];\n items = [];\n size = empty;\n };\n for (const event of events) {\n const json = JSON.stringify(event);\n const n = byteLength(json) + 1;\n if (empty + n > MAX_BYTES) continue;\n if (chunk.length === MAX_BATCH || size + n > MAX_BYTES) close();\n chunk.push(json);\n items.push(event);\n size += n;\n }\n close();\n return out;\n};\n\n/**\n * Request bodies of at most 100 events each, under the size limit. An event too large on its own is dropped. `sdk` tells\n * MRRocket which SDK the site runs and whether it reports errors (Build › Errors and Feature flags show it); `userHash`\n * as in `batchRequests`.\n */\nexport const bodies = (key: string, events: CockpitEvent[], sdk: SdkInfo, userHash?: string) => batchRequests(key, events, sdk, userHash).map((r) => r.body);\n"],"mappings":";AAQO,IAAM,YAAY;AAClB,IAAM,YAAY;AAWlB,IAAM,aAAa,CAAC,MAAc;AACrC,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AAC/B,UAAM,IAAI,EAAE,WAAW,CAAC;AACxB,QAAI,IAAI,IAAM,MAAK;AAAA,aACV,IAAI,KAAO,MAAK;AAAA,aAChB,KAAK,SAAU,KAAK,SAAU,IAAI,IAAI,EAAE,WAAW,EAAE,WAAW,IAAI,CAAC,IAAI,WAAY,OAAQ;AAClG,WAAK;AACL;AAAA,IACJ,MAAO,MAAK;AAAA,EAChB;AACA,SAAO;AACX;AAaO,IAAM,gBAAgB,CAAyB,KAAa,QAAa,KAAc,aAAyC;AACnI,QAAM,OAAO,UAAU,KAAK,UAAU,GAAG,CAAC,IAAI,WAAW,cAAc,KAAK,UAAU,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,UAAU,GAAG,CAAC;AACnI,QAAM,QAAQ,WAAW,IAAI,IAAI;AACjC,QAAM,MAAyB,CAAC;AAChC,MAAI,QAAkB,CAAC;AACvB,MAAI,QAAa,CAAC;AAClB,MAAI,OAAO;AACX,QAAM,QAAQ,MAAM;AAChB,QAAI,MAAM,OAAQ,KAAI,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,KAAK,GAAG,CAAC,MAAM,QAAQ,MAAM,CAAC;AACjF,YAAQ,CAAC;AACT,YAAQ,CAAC;AACT,WAAO;AAAA,EACX;AACA,aAAW,SAAS,QAAQ;AACxB,UAAM,OAAO,KAAK,UAAU,KAAK;AACjC,UAAM,IAAI,WAAW,IAAI,IAAI;AAC7B,QAAI,QAAQ,IAAI,UAAW;AAC3B,QAAI,MAAM,WAAW,aAAa,OAAO,IAAI,UAAW,OAAM;AAC9D,UAAM,KAAK,IAAI;AACf,UAAM,KAAK,KAAK;AAChB,YAAQ;AAAA,EACZ;AACA,QAAM;AACN,SAAO;AACX;AAOO,IAAM,SAAS,CAAC,KAAa,QAAwB,KAAc,aAAsB,cAAc,KAAK,QAAQ,KAAK,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI;","names":[]}
@@ -1,4 +1,4 @@
1
- (function(){'use strict';var L=(n,e)=>Math.round(n*(1-e/100)*100)/100,T=(n,e)=>{try{return new Intl.NumberFormat(void 0,{style:"currency",currency:e}).format(n)}catch(c){return `${n.toFixed(2)} ${e}`}},A=n=>n.kind==="contact"?"Send to the team":n.kind==="pause"?"Pause instead":n.kind==="downgrade"?"Switch plan":"Accept offer";var U="ckf-styles",D=`
1
+ (function(){'use strict';var L=(n,e)=>Math.round(n*(1-e/100)*100)/100,_=(n,e)=>{try{return new Intl.NumberFormat(void 0,{style:"currency",currency:e}).format(n)}catch(c){return `${n.toFixed(2)} ${e}`}},A=n=>n.kind==="contact"?"Send to the team":n.kind==="pause"?"Pause instead":n.kind==="downgrade"?"Switch plan":"Accept offer";var V="ckf-styles",D=`
2
2
  .ckf-root{--ckf-accent:#3f76ff;--ckf-accent-ink:#fff;--ckf-bg:#fff;--ckf-fg:#1c1d20;--ckf-muted:#5d6068;--ckf-soft:#f4f5f7;--ckf-line:#e4e5e8;--ckf-danger:#d92d20;--ckf-radius:14px;--ckf-font:inherit;--ckf-shadow:0 16px 40px -12px rgba(16,18,24,.28),0 0 0 1px rgba(16,18,24,.07)}
3
3
  .ckf-root[data-theme=dark]{--ckf-bg:#17181b;--ckf-fg:#f1f2f4;--ckf-muted:#a9acb3;--ckf-soft:#232428;--ckf-line:#2e2f35;--ckf-shadow:0 16px 40px -12px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.08)}
4
4
  @media (prefers-color-scheme:dark){.ckf-root[data-theme=auto]{--ckf-bg:#17181b;--ckf-fg:#f1f2f4;--ckf-muted:#a9acb3;--ckf-soft:#232428;--ckf-line:#2e2f35;--ckf-shadow:0 16px 40px -12px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.08)}}
@@ -70,4 +70,4 @@
70
70
  .ckf-banner{animation-name:ckf-fade}
71
71
  @media (max-width:520px){.ckf-scrim{align-items:flex-end;padding:0}.ckf-scrim .ckf-card{max-width:none;border-radius:var(--ckf-radius) var(--ckf-radius) 0 0;max-height:92dvh}.ckf-float{right:12px;bottom:12px}}
72
72
  @media (prefers-reduced-motion:reduce){.ckf-root *{animation:none!important;transition:none!important}}
73
- `,t=(n,e={},...c)=>{let s=document.createElement(n);for(let[g,u]of Object.entries(e))u===void 0||u===false||(typeof u=="function"?s.addEventListener(g.slice(2).toLowerCase(),u):g==="className"?s.className=String(u):s.setAttribute(g,u===true?"":u));for(let g of c)(g||g==="")&&s.append(typeof g=="string"?document.createTextNode(g):g);return s},P=(n,e,c,s={})=>t("button",{type:"button",className:`ckf-btn${e==="secondary"?"":` ckf-${e}`}`,disabled:!!s.disabled,"data-ckf-id":s.id,onClick:()=>c()},n),$=(n,e)=>t("button",{type:"button",className:"ckf-x","aria-label":n,"data-ckf-id":"x",onClick:()=>e()},"\xD7"),V=(n,e)=>e>1?t("span",{className:"ckf-dots",role:"img","aria-label":`Step ${n} of ${e}`},...Array.from({length:e},(c,s)=>t("span",{className:"ckf-dot","data-on":s<n}))):t("span"),W=n=>{var e;return n.type==="cancel"?`c:${n.screen}:${(e=n.round)!=null?e:1}`:n.type==="step"?`s:${n.step.id}`:n.type==="checklist"?`l:${n.flow.id}`:`m:${n.message.id}`},H=n=>{var c;if(!document.getElementById(U)){let s=document.createElement("style");s.id=U,s.textContent=D,document.head.appendChild(s);}let e=t("div",{className:`ckf-root${n.className?` ${n.className}`:""}`,"data-theme":(c=n.theme)!=null?c:"auto"});return n.accent&&e.style.setProperty("--ckf-accent",n.accent),document.body.appendChild(e),e},j=(n,e={})=>{if(typeof document=="undefined")return ()=>{};let c=H(e),s="",g=null,u="",w=null,C=r=>{var m,y,E,M;let o="ckf-title",l=r.busy,d=[];if(r.screen==="survey")d.push(t("h2",{className:"ckf-h",id:o},r.flow.question||"Why are you leaving?"),t("div",{className:"ckf-opts",role:"radiogroup","aria-labelledby":o},...r.flow.reasons.map(a=>t("button",{type:"button",role:"radio",className:"ckf-opt","aria-checked":String(r.reason===a.id),"data-ckf-id":`r:${a.id}`,onClick:()=>n.cancel.pick(a.id)},t("span",{className:"ckf-radio"}),a.label))),t("div",{className:"ckf-btns"},P("Continue","primary",n.cancel.next,{disabled:!r.reason,id:"go"}),P("Never mind, keep my plan","quiet",n.cancel.close)));else if(r.screen==="followup"&&r.followUp){let a=r.followUp,x;if(a.answers.length)x=t("div",{className:"ckf-opts",role:"radiogroup","aria-labelledby":o},...a.answers.map((v,S)=>t("button",{type:"button",role:"radio",className:"ckf-opt","aria-checked":String(r.answer===v),"data-ckf-id":`a:${S}`,onClick:()=>n.cancel.answer(v)},t("span",{className:"ckf-radio"}),v)));else {let v=t("textarea",{className:"ckf-text",placeholder:"Your answer (optional)","aria-labelledby":o,maxlength:"500","data-ckf-id":"answer",onInput:S=>n.cancel.answer(S.target.value)});v.value=(m=r.answer)!=null?m:"",x=v;}let O=(y=r.flow.reasons.find(v=>v.id===r.reason))==null?void 0:y.label;d.push(O?t("p",{className:"ckf-small"},`You said: ${O}`):false,t("h2",{className:"ckf-h",id:o},a.question),x,t("div",{className:"ckf-btns"},P("Continue","primary",n.cancel.next,{id:"go"}),P("Never mind, keep my plan","quiet",n.cancel.close)));}else if(r.screen==="offer"&&r.offer){let a=r.offer,x=(E=r.flow.reasons.find(S=>S.id===r.reason))==null?void 0:E.label,O=false;a.kind==="discount"&&a.percent&&(O=r.price?t("div",{className:"ckf-box"},t("span",{className:"ckf-big"},T(L(r.price.amount,a.percent),r.price.currency)),t("span",{className:"ckf-strike"},T(r.price.amount,r.price.currency)),t("span",{className:"ckf-small"},`/${(M=r.price.interval)!=null?M:"month"}${a.months?` for ${a.months} ${a.months===1?"month":"months"}`:""}`)):t("div",{className:"ckf-box"},t("span",{className:"ckf-big"},`${a.percent}% off`),a.months?t("span",{className:"ckf-small"},`for ${a.months} ${a.months===1?"month":"months"}`):false)),a.kind==="pause"&&a.months&&(O=t("div",{className:"ckf-box"},t("span",{},`Pause for up to ${a.months} ${a.months===1?"month":"months"}. Your data stays.`))),a.kind==="downgrade"&&a.planName&&(O=t("div",{className:"ckf-box"},t("b",{},a.planName)));let v=a.kind==="contact"?t("textarea",{className:"ckf-text",placeholder:"What would make you stay?","aria-label":"Your message",maxlength:"500","data-ckf-id":"comment",onInput:S=>u=S.target.value}):null;v&&(v.value=u),d.push(x?t("p",{className:"ckf-small"},`You said: ${x}`):false,t("h2",{className:"ckf-h",id:o},a.headline),O,v!=null?v:false,t("div",{className:"ckf-btns"},P(l?"One moment\u2026":A(a),"primary",()=>n.cancel.accept(u),{disabled:l,id:"go"}),P("No thanks, continue cancelling","quiet",n.cancel.decline,{disabled:l})));}else {let x=r.steps===1?null:t("textarea",{className:"ckf-text",placeholder:"Anything else we should know? (optional)","aria-label":"Anything else we should know? (optional)",maxlength:"500","data-ckf-id":"comment",onInput:O=>u=O.target.value});x&&(x.value=u),d.push(t("h2",{className:"ckf-h",id:o},r.flow.confirm.title),r.flow.confirm.body?t("p",{className:"ckf-p"},r.flow.confirm.body):false,x!=null?x:false,t("div",{className:"ckf-btns"},P(l?"One moment\u2026":r.flow.confirm.button,"danger",()=>n.cancel.confirm(u),{disabled:l,id:"go"}),P("Keep my plan","secondary",n.cancel.close,{disabled:l})));}return t("div",{className:"ckf-scrim",onClick:a=>a.target===a.currentTarget&&!l&&n.cancel.close()},t("div",{className:"ckf-card",role:"dialog","aria-modal":"true","aria-labelledby":o},t("div",{className:"ckf-top"},V(r.step,r.steps),$("Close",n.cancel.close)),...d,r.error?t("p",{className:"ckf-err",role:"alert"},r.error):false))},p=r=>{let o=r.step,l=t("div",{className:"ckf-card",role:"dialog","aria-modal":o.kind==="modal"?"true":void 0,"aria-labelledby":"ckf-title"},t("div",{className:"ckf-top"},r.total>1?t("p",{className:"ckf-small"},`Step ${r.index+1} of ${r.total}`):t("span"),$("Close the tour",n.onboarding.dismiss)),t("h2",{className:"ckf-h",id:"ckf-title"},o.title),o.body?t("p",{className:"ckf-p"},o.body):false,t("div",{className:"ckf-row"},P(o.button||(r.last?"Done":"Next"),"primary",n.onboarding.next,{id:"go"})));if(o.kind==="modal")return t("div",{className:"ckf-scrim"},l);let d=o.target?i(o.target):null,m=t("div",{className:"ckf-float"},l);if(d){let y=t("div",{className:"ckf-ring","aria-hidden":"true"});return w={float:m,ring:y,target:d},b(m,y,d),t("div",{},y,m)}return m},i=r=>{var l;let o=()=>document.querySelector(`[data-ck-target="${CSS.escape(r)}"]`);return /^[#.[]|[\s>:=]/.test(r)&&(l=f(r))!=null?l:o()},f=r=>{try{return document.querySelector(r)}catch(o){return null}},b=(r,o,l)=>{let d=l.getBoundingClientRect();Object.assign(o.style,{left:`${d.left-4}px`,top:`${d.top-4}px`,width:`${d.width+8}px`,height:`${d.height+8}px`});let m=Math.min(340,window.innerWidth-24),y=Math.max(12,Math.min(d.left,window.innerWidth-m-12)),E=d.bottom+12;Object.assign(r.style,{left:`${y}px`,right:"auto",width:`${m}px`}),E+200<window.innerHeight?Object.assign(r.style,{top:`${E}px`,bottom:"auto"}):Object.assign(r.style,{top:"auto",bottom:`${Math.max(12,window.innerHeight-d.top+12)}px`});},h=r=>{let o=r.items.filter(l=>l.done).length;return t("div",{className:"ckf-float"},t("div",{className:"ckf-card",role:"region","aria-labelledby":"ckf-title"},t("div",{className:"ckf-top"},t("h2",{className:"ckf-h",id:"ckf-title"},r.flow.name),$("Hide the checklist",n.onboarding.dismiss)),t("p",{className:"ckf-small"},`${o} of ${r.items.length} done`),t("div",{className:"ckf-bar",role:"progressbar","aria-valuemin":"0","aria-valuemax":String(r.items.length),"aria-valuenow":String(o)},t("i",{style:`width:${o/r.items.length*100}%`})),t("div",{className:"ckf-opts"},...r.items.map(({step:l,done:d})=>{let m=[t("span",{className:"ckf-tick","aria-hidden":"true"},d?"\u2713":""),t("span",{},t("b",{},l.title),l.body?t("span",{className:"ckf-small"},l.body):false)];return d||l.event?t("div",{className:"ckf-check","data-done":d,role:"listitem"},...m):t("button",{type:"button",className:"ckf-check","aria-label":`Mark \u201C${l.title}\u201D done`,"data-ckf-id":`i:${l.id}`,onClick:()=>n.onboarding.check(l.id)},...m)}))))},N=r=>{let o=r.message,l=o.button?P(o.button,"primary",n.message.click,{id:"go"}):false;return o.style==="banner"?t("div",{className:"ckf-banner",role:"status"},t("span",{},t("b",{},o.title),o.body?t("span",{className:"ckf-small"},o.body):false),l,$("Dismiss",n.message.dismiss)):t("div",{className:"ckf-scrim",onClick:d=>d.target===d.currentTarget&&n.message.dismiss()},t("div",{className:"ckf-card",role:"dialog","aria-modal":"true","aria-labelledby":"ckf-title"},t("div",{className:"ckf-top"},t("span"),$("Dismiss",n.message.dismiss)),t("h2",{className:"ckf-h",id:"ckf-title"},o.title),o.body?t("p",{className:"ckf-p"},o.body):false,l?t("div",{className:"ckf-row"},l):false))},F=()=>{var d,m,y,E,M;let r=n.view(),o=r?W(r):"",l=(m=(d=document.activeElement)==null?void 0:d.closest)!=null&&m.call(d,".ckf-root")?(y=document.activeElement)==null?void 0:y.getAttribute("data-ckf-id"):null;if(o&&!s&&document.activeElement instanceof HTMLElement&&!document.activeElement.closest(".ckf-root")&&(g=document.activeElement),o!==s&&!o.startsWith("c:")&&(u=""),o||(u=""),w=null,c.replaceChildren(...r?[r.type==="cancel"?C(r):r.type==="step"?p(r):r.type==="checklist"?h(r):N(r)]:[]),o!==s){let a=c.querySelector("[aria-modal=true]"),x=(E=a==null?void 0:a.querySelector("[role=radio], [data-ckf-id=go], textarea"))!=null?E:null;x==null||x.focus(),!o&&g&&(g.focus(),g=null);}else l&&((M=c.querySelector(`[data-ckf-id="${CSS.escape(l)}"]`))==null||M.focus());s=o;},k=r=>{if(r.key!=="Escape")return;let o=n.view();(o==null?void 0:o.type)==="cancel"?n.cancel.close():(o==null?void 0:o.type)==="message"?n.message.dismiss():(o==null?void 0:o.type)==="step"&&o.step.kind==="modal"&&n.onboarding.dismiss();},R=()=>{w&&b(w.float,w.ring,w.target);};document.addEventListener("keydown",k),window.addEventListener("resize",R),window.addEventListener("scroll",R,{passive:true,capture:true});let I=n.subscribe(F);return F(),()=>{I(),document.removeEventListener("keydown",k),window.removeEventListener("resize",R),window.removeEventListener("scroll",R,{capture:true}),c.remove();}},K=(n,e={})=>{if(typeof document=="undefined")return ()=>{};let c=H(e),s="",g=null,u=()=>{var h,N,F;let p=n.view(),i=p?`${p.paywall.id}:${p.index}`:"";if(i&&!s&&document.activeElement instanceof HTMLElement&&(g=document.activeElement),!p){c.replaceChildren(),c.removeAttribute("data-still"),s&&(g==null||g.focus()),s="";return}s&&c.setAttribute("data-still","");let f=p.page,b=f.kind==="plans"?t("div",{className:"ckf-opts",role:"radiogroup","aria-label":"Plans"},...p.plans.map(k=>t("button",{type:"button",role:"radio",className:"ckf-opt ckf-plan","aria-checked":String(p.plan===k.id),"data-ckf-id":`p:${k.id}`,onClick:()=>n.pick(k.id)},t("span",{className:"ckf-radio"}),t("span",{},k.name,k.note?t("span",{className:"ckf-small"},k.note):false),t("b",{},k.price)))):false;c.replaceChildren(t("div",{className:"ckf-scrim",onClick:k=>k.target===k.currentTarget&&n.close()},t("div",{className:"ckf-card ckf-pw",role:"dialog","aria-modal":"true","aria-labelledby":"ckf-title"},t("div",{className:"ckf-top"},V(p.index+1,p.total),$("Close",n.close)),t("h2",{className:"ckf-h",id:"ckf-title"},f.title),f.body?t("p",{className:"ckf-p"},f.body):false,f.kind==="benefits"&&((h=f.items)!=null&&h.length)?t("div",{className:"ckf-opts"},...f.items.map(k=>t("p",{className:"ckf-li"},k))):false,f.kind==="proof"&&((N=f.quotes)!=null&&N.length)?t("div",{className:"ckf-opts"},...f.quotes.map(k=>t("blockquote",{className:"ckf-quote"},k.text,k.by?t("cite",{},k.by):false))):false,b,p.error?t("p",{className:"ckf-err",role:"alert"},p.error):false,t("div",{className:"ckf-btns"},P(p.busy?"One moment\u2026":f.button||"Continue","primary",n.next,{disabled:p.busy||f.kind==="plans"&&!p.plan,id:"go"}),p.index?P("Back","quiet",n.back,{disabled:p.busy}):false)))),i!==s&&((F=c.querySelector("[data-ckf-id=go]"))==null||F.focus()),s=i;},w=p=>{n.view()&&p.key==="Escape"&&n.close();};document.addEventListener("keydown",w);let C=n.subscribe(u);return u(),()=>{C(),document.removeEventListener("keydown",w),c.remove();}};var Y=n=>{let e=2166136261;for(let c=0;c<n.length;c++)e^=n.charCodeAt(c),e=Math.imul(e,16777619);return (e>>>0)/4294967296};var q=(n,e)=>{var u,w,C,p;let c=(w=(u=n.variants[0])==null?void 0:u.id)!=null?w:"control";if(n.winner)return n.winner;if(n.status!=="running")return c;let s=Y(`${n.key}:${e}`),g=0;for(let i of n.variants)if(g+=i.weight,s<g)return i.id;return (p=(C=n.variants[n.variants.length-1])==null?void 0:C.id)!=null?p:c};var _="$paywall",B=(n,e,c)=>{var w,C;let s=e.experiment?n==null?void 0:n.experiments.find(p=>p.key===e.experiment):void 0,g=s?q(s,c):(w=e.variants[0])==null?void 0:w.id,u=(C=e.variants.find(p=>p.id===g))!=null?C:e.variants[0];return u?{variant:u.id,pages:u.pages,test:(s==null?void 0:s.status)==="running"&&!s.winner?s.key:void 0}:null},z=n=>{let e=null,c=new Set,s=()=>{for(let i of [...c])try{i();}catch(f){}},g=()=>e?{paywall:e.w.id,placement:e.w.placement,variant:e.variant}:{},u=()=>{e&&!e.seen.has(e.index)&&(e.seen.add(e.index),n.track(_,{...g(),page:e.pages[e.index].id,index:e.index,action:"page"})),s();},w=i=>{var b,h;let f=e;f&&(e=null,i.outcome==="closed"&&((h=(b=f.options).onClose)==null||h.call(b,i)),f.resolve(i),s());},C=i=>{var f;return {outcome:i,index:(f=e==null?void 0:e.index)!=null?f:0,...e?{paywall:e.w.id,variant:e.variant}:{},...i!=="closed"&&e?{plan:e.plans.find(b=>b.id===e.plan)}:{}}},p=()=>{let i=e,f=i==null?void 0:i.plans.find(b=>b.id===i.plan);if(!(!i||!f||i.busy)){if(!i.options.onPurchase)return w(C("chosen"));i.busy=true,i.error=void 0,s(),Promise.resolve().then(()=>{var b,h;return (h=(b=i.options).onPurchase)==null?void 0:h.call(b,f)}).then(b=>{if(i.busy=false,e===i){if(b===false)return s();n.track(_,{...g(),page:i.pages[i.index].id,index:i.index,plan:f.id,action:"purchased"}),w(C("purchased"));}},()=>{i.busy=false,i.error="The purchase did not go through. Please try again.",s();});}};return {open:(i,f={})=>{var o,l,d;if(e)return e.promise;let b=n.config(),h=(o=b==null?void 0:b.paywalls)==null?void 0:o.find(m=>m.placement===i),N=h?B(b,h,n.distinctId()):null;if(!h||!(N!=null&&N.pages.length))return Promise.resolve({outcome:"closed",index:0});let F=new Map(((l=f.plans)!=null?l:[]).map(m=>[m.id,m])),k=h.plans.map(m=>{var y;return (y=F.get(m.id))!=null?y:m}),R=()=>{},I=new Promise(m=>R=m),r=k.some(m=>m.id===h.highlight)?h.highlight:(d=k[0])==null?void 0:d.id;return e={w:h,variant:N.variant,pages:N.pages,plans:k,index:0,plan:r,busy:false,seen:new Set,options:f,resolve:R,promise:I},N.test&&n.track("$exposure",{experiment:N.test,variant:N.variant}),u(),I},view:()=>e?{paywall:e.w,variant:e.variant,page:e.pages[e.index],index:e.index,total:e.pages.length,last:e.index===e.pages.length-1,plans:e.plans,plan:e.plan,busy:e.busy,error:e.error}:null,subscribe:i=>(c.add(i),()=>c.delete(i)),next:()=>{if(!(!e||e.busy)){if(e.pages[e.index].kind==="plans"||e.index===e.pages.length-1)return p();e.index++,e.error=void 0,u();}},back:()=>{!e||e.busy||!e.index||(e.index--,e.error=void 0,s());},pick:i=>{!e||e.busy||!e.plans.some(f=>f.id===i)||(e.plan=i,e.error=void 0,s());},close:()=>{!e||e.busy||(n.track(_,{...g(),index:e.index,action:"closed"}),w(C("closed")));}}};var J=(n,e)=>{let c=z(n);return K(c,e),c};window.__mrrocketFlows={mountFlows:j,paywallHost:J};})();
73
+ `,t=(n,e={},...c)=>{let s=document.createElement(n);for(let[g,u]of Object.entries(e))u===void 0||u===false||(typeof u=="function"?s.addEventListener(g.slice(2).toLowerCase(),u):g==="className"?s.className=String(u):s.setAttribute(g,u===true?"":u));for(let g of c)(g||g==="")&&s.append(typeof g=="string"?document.createTextNode(g):g);return s},P=(n,e,c,s={})=>t("button",{type:"button",className:`ckf-btn${e==="secondary"?"":` ckf-${e}`}`,disabled:!!s.disabled,"data-ckf-id":s.id,onClick:()=>c()},n),$=(n,e)=>t("button",{type:"button",className:"ckf-x","aria-label":n,"data-ckf-id":"x",onClick:()=>e()},"\xD7"),U=(n,e)=>e>1?t("span",{className:"ckf-dots",role:"img","aria-label":`Step ${n} of ${e}`},...Array.from({length:e},(c,s)=>t("span",{className:"ckf-dot","data-on":s<n}))):t("span"),W=n=>{var e;return n.type==="cancel"?`c:${n.screen}:${(e=n.round)!=null?e:1}`:n.type==="step"?`s:${n.step.id}`:n.type==="checklist"?`l:${n.flow.id}`:`m:${n.message.id}`},H=n=>{var c;if(!document.getElementById(V)){let s=document.createElement("style");s.id=V,s.textContent=D,document.head.appendChild(s);}let e=t("div",{className:`ckf-root${n.className?` ${n.className}`:""}`,"data-theme":(c=n.theme)!=null?c:"auto"});return n.accent&&e.style.setProperty("--ckf-accent",n.accent),document.body.appendChild(e),e},j=(n,e={})=>{if(typeof document=="undefined")return ()=>{};let c=H(e),s="",g=null,u="",b=null,C=r=>{var m,y,O,M;let o="ckf-title",l=r.busy,d=[];if(r.screen==="survey")d.push(t("h2",{className:"ckf-h",id:o},r.flow.question||"Why are you leaving?"),t("div",{className:"ckf-opts",role:"radiogroup","aria-labelledby":o},...r.flow.reasons.map(a=>t("button",{type:"button",role:"radio",className:"ckf-opt","aria-checked":String(r.reason===a.id),"data-ckf-id":`r:${a.id}`,onClick:()=>n.cancel.pick(a.id)},t("span",{className:"ckf-radio"}),a.label))),t("div",{className:"ckf-btns"},P("Continue","primary",n.cancel.next,{disabled:!r.reason,id:"go"}),P("Never mind, keep my plan","quiet",n.cancel.close)));else if(r.screen==="followup"&&r.followUp){let a=r.followUp,x;if(a.answers.length)x=t("div",{className:"ckf-opts",role:"radiogroup","aria-labelledby":o},...a.answers.map((v,S)=>t("button",{type:"button",role:"radio",className:"ckf-opt","aria-checked":String(r.answer===v),"data-ckf-id":`a:${S}`,onClick:()=>n.cancel.answer(v)},t("span",{className:"ckf-radio"}),v)));else {let v=t("textarea",{className:"ckf-text",placeholder:"Your answer (optional)","aria-labelledby":o,maxlength:"500","data-ckf-id":"answer",onInput:S=>n.cancel.answer(S.target.value)});v.value=(m=r.answer)!=null?m:"",x=v;}let E=(y=r.flow.reasons.find(v=>v.id===r.reason))==null?void 0:y.label;d.push(E?t("p",{className:"ckf-small"},`You said: ${E}`):false,t("h2",{className:"ckf-h",id:o},a.question),x,t("div",{className:"ckf-btns"},P("Continue","primary",n.cancel.next,{id:"go"}),P("Never mind, keep my plan","quiet",n.cancel.close)));}else if(r.screen==="offer"&&r.offer){let a=r.offer,x=(O=r.flow.reasons.find(S=>S.id===r.reason))==null?void 0:O.label,E=false;a.kind==="discount"&&a.percent&&(E=r.price?t("div",{className:"ckf-box"},t("span",{className:"ckf-big"},_(L(r.price.amount,a.percent),r.price.currency)),t("span",{className:"ckf-strike"},_(r.price.amount,r.price.currency)),t("span",{className:"ckf-small"},`/${(M=r.price.interval)!=null?M:"month"}${a.months?` for ${a.months} ${a.months===1?"month":"months"}`:""}`)):t("div",{className:"ckf-box"},t("span",{className:"ckf-big"},`${a.percent}% off`),a.months?t("span",{className:"ckf-small"},`for ${a.months} ${a.months===1?"month":"months"}`):false)),a.kind==="pause"&&a.months&&(E=t("div",{className:"ckf-box"},t("span",{},`Pause for up to ${a.months} ${a.months===1?"month":"months"}. Your data stays.`))),a.kind==="downgrade"&&a.planName&&(E=t("div",{className:"ckf-box"},t("b",{},a.planName)));let v=a.kind==="contact"?t("textarea",{className:"ckf-text",placeholder:"What would make you stay?","aria-label":"Your message",maxlength:"500","data-ckf-id":"comment",onInput:S=>u=S.target.value}):null;v&&(v.value=u),d.push(x?t("p",{className:"ckf-small"},`You said: ${x}`):false,t("h2",{className:"ckf-h",id:o},a.headline),E,v!=null?v:false,t("div",{className:"ckf-btns"},P(l?"One moment\u2026":A(a),"primary",()=>n.cancel.accept(u),{disabled:l,id:"go"}),P("No thanks, continue cancelling","quiet",n.cancel.decline,{disabled:l})));}else {let x=r.steps===1?null:t("textarea",{className:"ckf-text",placeholder:"Anything else we should know? (optional)","aria-label":"Anything else we should know? (optional)",maxlength:"500","data-ckf-id":"comment",onInput:E=>u=E.target.value});x&&(x.value=u),d.push(t("h2",{className:"ckf-h",id:o},r.flow.confirm.title),r.flow.confirm.body?t("p",{className:"ckf-p"},r.flow.confirm.body):false,x!=null?x:false,t("div",{className:"ckf-btns"},P(l?"One moment\u2026":r.flow.confirm.button,"danger",()=>n.cancel.confirm(u),{disabled:l,id:"go"}),P("Keep my plan","secondary",n.cancel.close,{disabled:l})));}return t("div",{className:"ckf-scrim",onClick:a=>a.target===a.currentTarget&&!l&&n.cancel.close()},t("div",{className:"ckf-card",role:"dialog","aria-modal":"true","aria-labelledby":o},t("div",{className:"ckf-top"},U(r.step,r.steps),$("Close",n.cancel.close)),...d,r.error?t("p",{className:"ckf-err",role:"alert"},r.error):false))},p=r=>{let o=r.step,l=t("div",{className:"ckf-card",role:"dialog","aria-modal":o.kind==="modal"?"true":void 0,"aria-labelledby":"ckf-title"},t("div",{className:"ckf-top"},r.total>1?t("p",{className:"ckf-small"},`Step ${r.index+1} of ${r.total}`):t("span"),$("Close the tour",n.onboarding.dismiss)),t("h2",{className:"ckf-h",id:"ckf-title"},o.title),o.body?t("p",{className:"ckf-p"},o.body):false,t("div",{className:"ckf-row"},P(o.button||(r.last?"Done":"Next"),"primary",n.onboarding.next,{id:"go"})));if(o.kind==="modal")return t("div",{className:"ckf-scrim"},l);let d=o.target?i(o.target):null,m=t("div",{className:"ckf-float"},l);if(d){let y=t("div",{className:"ckf-ring","aria-hidden":"true"});return b={float:m,ring:y,target:d},w(m,y,d),t("div",{},y,m)}return m},i=r=>{var l;let o=()=>document.querySelector(`[data-ck-target="${CSS.escape(r)}"]`);return /^[#.[]|[\s>:=]/.test(r)&&(l=f(r))!=null?l:o()},f=r=>{try{return document.querySelector(r)}catch(o){return null}},w=(r,o,l)=>{let d=l.getBoundingClientRect();Object.assign(o.style,{left:`${d.left-4}px`,top:`${d.top-4}px`,width:`${d.width+8}px`,height:`${d.height+8}px`});let m=Math.min(340,window.innerWidth-24),y=Math.max(12,Math.min(d.left,window.innerWidth-m-12)),O=d.bottom+12;Object.assign(r.style,{left:`${y}px`,right:"auto",width:`${m}px`}),O+200<window.innerHeight?Object.assign(r.style,{top:`${O}px`,bottom:"auto"}):Object.assign(r.style,{top:"auto",bottom:`${Math.max(12,window.innerHeight-d.top+12)}px`});},h=r=>{let o=r.items.filter(l=>l.done).length;return t("div",{className:"ckf-float"},t("div",{className:"ckf-card",role:"region","aria-labelledby":"ckf-title"},t("div",{className:"ckf-top"},t("h2",{className:"ckf-h",id:"ckf-title"},r.flow.name),$("Hide the checklist",n.onboarding.dismiss)),t("p",{className:"ckf-small"},`${o} of ${r.items.length} done`),t("div",{className:"ckf-bar",role:"progressbar","aria-valuemin":"0","aria-valuemax":String(r.items.length),"aria-valuenow":String(o)},t("i",{style:`width:${o/r.items.length*100}%`})),t("div",{className:"ckf-opts"},...r.items.map(({step:l,done:d})=>{let m=[t("span",{className:"ckf-tick","aria-hidden":"true"},d?"\u2713":""),t("span",{},t("b",{},l.title),l.body?t("span",{className:"ckf-small"},l.body):false)];return d||l.event?t("div",{className:"ckf-check","data-done":d,role:"listitem"},...m):t("button",{type:"button",className:"ckf-check","aria-label":`Mark \u201C${l.title}\u201D done`,"data-ckf-id":`i:${l.id}`,onClick:()=>n.onboarding.check(l.id)},...m)}))))},N=r=>{let o=r.message,l=o.button?P(o.button,"primary",n.message.click,{id:"go"}):false;return o.style==="banner"?t("div",{className:"ckf-banner",role:"status"},t("span",{},t("b",{},o.title),o.body?t("span",{className:"ckf-small"},o.body):false),l,$("Dismiss",n.message.dismiss)):t("div",{className:"ckf-scrim",onClick:d=>d.target===d.currentTarget&&n.message.dismiss()},t("div",{className:"ckf-card",role:"dialog","aria-modal":"true","aria-labelledby":"ckf-title"},t("div",{className:"ckf-top"},t("span"),$("Dismiss",n.message.dismiss)),t("h2",{className:"ckf-h",id:"ckf-title"},o.title),o.body?t("p",{className:"ckf-p"},o.body):false,l?t("div",{className:"ckf-row"},l):false))},F=()=>{var d,m,y,O,M;let r=n.view(),o=r?W(r):"",l=(m=(d=document.activeElement)==null?void 0:d.closest)!=null&&m.call(d,".ckf-root")?(y=document.activeElement)==null?void 0:y.getAttribute("data-ckf-id"):null;if(o&&!s&&document.activeElement instanceof HTMLElement&&!document.activeElement.closest(".ckf-root")&&(g=document.activeElement),o!==s&&!o.startsWith("c:")&&(u=""),o||(u=""),b=null,c.replaceChildren(...r?[r.type==="cancel"?C(r):r.type==="step"?p(r):r.type==="checklist"?h(r):N(r)]:[]),o!==s){let a=c.querySelector("[aria-modal=true]"),x=(O=a==null?void 0:a.querySelector("[role=radio], [data-ckf-id=go], textarea"))!=null?O:null;x==null||x.focus(),!o&&g&&(g.focus(),g=null);}else l&&((M=c.querySelector(`[data-ckf-id="${CSS.escape(l)}"]`))==null||M.focus());s=o;},k=r=>{if(r.key!=="Escape")return;let o=n.view();(o==null?void 0:o.type)==="cancel"?n.cancel.close():(o==null?void 0:o.type)==="message"?n.message.dismiss():(o==null?void 0:o.type)==="step"&&o.step.kind==="modal"&&n.onboarding.dismiss();},R=()=>{b&&w(b.float,b.ring,b.target);};document.addEventListener("keydown",k),window.addEventListener("resize",R),window.addEventListener("scroll",R,{passive:true,capture:true});let I=n.subscribe(F);return F(),()=>{I(),document.removeEventListener("keydown",k),window.removeEventListener("resize",R),window.removeEventListener("scroll",R,{capture:true}),c.remove();}},K=(n,e={})=>{if(typeof document=="undefined")return ()=>{};let c=H(e),s="",g=null,u=()=>{var h,N,F;let p=n.view(),i=p?`${p.paywall.id}:${p.index}`:"";if(i&&!s&&document.activeElement instanceof HTMLElement&&(g=document.activeElement),!p){c.replaceChildren(),c.removeAttribute("data-still"),s&&(g==null||g.focus()),s="";return}s&&c.setAttribute("data-still","");let f=p.page,w=f.kind==="plans"?t("div",{className:"ckf-opts",role:"radiogroup","aria-label":"Plans"},...p.plans.map(k=>t("button",{type:"button",role:"radio",className:"ckf-opt ckf-plan","aria-checked":String(p.plan===k.id),"data-ckf-id":`p:${k.id}`,onClick:()=>n.pick(k.id)},t("span",{className:"ckf-radio"}),t("span",{},k.name,k.note?t("span",{className:"ckf-small"},k.note):false),t("b",{},k.price)))):false;c.replaceChildren(t("div",{className:"ckf-scrim",onClick:k=>k.target===k.currentTarget&&n.close()},t("div",{className:"ckf-card ckf-pw",role:"dialog","aria-modal":"true","aria-labelledby":"ckf-title"},t("div",{className:"ckf-top"},U(p.index+1,p.total),$("Close",n.close)),t("h2",{className:"ckf-h",id:"ckf-title"},f.title),f.body?t("p",{className:"ckf-p"},f.body):false,f.kind==="benefits"&&((h=f.items)!=null&&h.length)?t("div",{className:"ckf-opts"},...f.items.map(k=>t("p",{className:"ckf-li"},k))):false,f.kind==="proof"&&((N=f.quotes)!=null&&N.length)?t("div",{className:"ckf-opts"},...f.quotes.map(k=>t("blockquote",{className:"ckf-quote"},k.text,k.by?t("cite",{},k.by):false))):false,w,p.error?t("p",{className:"ckf-err",role:"alert"},p.error):false,t("div",{className:"ckf-btns"},P(p.busy?"One moment\u2026":f.button||"Continue","primary",n.next,{disabled:p.busy||f.kind==="plans"&&!p.plan,id:"go"}),p.index?P("Back","quiet",n.back,{disabled:p.busy}):false)))),i!==s&&((F=c.querySelector("[data-ckf-id=go]"))==null||F.focus()),s=i;},b=p=>{n.view()&&p.key==="Escape"&&n.close();};document.addEventListener("keydown",b);let C=n.subscribe(u);return u(),()=>{C(),document.removeEventListener("keydown",b),c.remove();}};var Y=n=>{let e=2166136261;for(let c=0;c<n.length;c++)e^=n.charCodeAt(c),e=Math.imul(e,16777619);return (e>>>0)/4294967296};var q=(n,e)=>{var u,b,C,p;let c=(b=(u=n.variants[0])==null?void 0:u.id)!=null?b:"control";if(n.winner)return n.winner;if(n.status!=="running")return c;let s=Y(`${n.key}:${e}`),g=0;for(let i of n.variants)if(g+=i.weight,s<g)return i.id;return (p=(C=n.variants[n.variants.length-1])==null?void 0:C.id)!=null?p:c};var T="$paywall",B=(n,e,c)=>{var b,C;let s=e.experiment?n==null?void 0:n.experiments.find(p=>p.key===e.experiment):void 0,g=s?q(s,c):(b=e.variants[0])==null?void 0:b.id,u=(C=e.variants.find(p=>p.id===g))!=null?C:e.variants[0];return u?{variant:u.id,pages:u.pages,test:(s==null?void 0:s.status)==="running"&&!s.winner?s.key:void 0}:null},z=n=>{let e=null,c=new Set,s=()=>{for(let i of [...c])try{i();}catch(f){}},g=()=>e?{paywall:e.w.id,placement:e.w.placement,variant:e.variant}:{},u=()=>{e&&!e.seen.has(e.index)&&(e.seen.add(e.index),n.track(T,{...g(),page:e.pages[e.index].id,index:e.index,action:"page"})),s();},b=i=>{var w,h;let f=e;f&&(e=null,i.outcome==="closed"&&((h=(w=f.options).onClose)==null||h.call(w,i)),f.resolve(i),s());},C=i=>{var f;return {outcome:i,index:(f=e==null?void 0:e.index)!=null?f:0,...e?{paywall:e.w.id,variant:e.variant}:{},...i!=="closed"&&e?{plan:e.plans.find(w=>w.id===e.plan)}:{}}},p=()=>{let i=e,f=i==null?void 0:i.plans.find(w=>w.id===i.plan);if(!(!i||!f||i.busy)){if(!i.options.onPurchase)return b(C("chosen"));i.busy=true,i.error=void 0,s(),Promise.resolve().then(()=>{var w,h;return (h=(w=i.options).onPurchase)==null?void 0:h.call(w,f)}).then(w=>{if(i.busy=false,e===i){if(w===false)return s();n.track(T,{...g(),page:i.pages[i.index].id,index:i.index,plan:f.id,action:"purchased"}),b(C("purchased"));}},()=>{i.busy=false,i.error="The purchase did not go through. Please try again.",s();});}};return {open:(i,f={})=>{var o,l,d;if(e)return e.promise;let w=n.config(),h=(o=w==null?void 0:w.paywalls)==null?void 0:o.find(m=>m.placement===i),N=h?B(w,h,n.distinctId()):null;if(!h||!(N!=null&&N.pages.length))return Promise.resolve({outcome:"closed",index:0});let F=new Map(((l=f.plans)!=null?l:[]).map(m=>[m.id,m])),k=h.plans.map(m=>{var y;return (y=F.get(m.id))!=null?y:m}),R=()=>{},I=new Promise(m=>R=m),r=k.some(m=>m.id===h.highlight)?h.highlight:(d=k[0])==null?void 0:d.id;return e={w:h,variant:N.variant,pages:N.pages,plans:k,index:0,plan:r,busy:false,seen:new Set,options:f,resolve:R,promise:I},N.test&&n.track("$exposure",{experiment:N.test,variant:N.variant}),u(),I},view:()=>e?{paywall:e.w,variant:e.variant,page:e.pages[e.index],index:e.index,total:e.pages.length,last:e.index===e.pages.length-1,plans:e.plans,plan:e.plan,busy:e.busy,error:e.error}:null,subscribe:i=>(c.add(i),()=>c.delete(i)),next:()=>{if(!(!e||e.busy)){if(e.pages[e.index].kind==="plans"||e.index===e.pages.length-1)return p();e.index++,e.error=void 0,u();}},back:()=>{!e||e.busy||!e.index||(e.index--,e.error=void 0,s());},pick:i=>{!e||e.busy||!e.plans.some(f=>f.id===i)||(e.plan=i,e.error=void 0,s());},close:()=>{!e||e.busy||(n.track(T,{...g(),index:e.index,action:"closed"}),b(C("closed")));}}};var J=(n,e)=>{let c=z(n);return K(c,e),c};window.__mrrocketFlows={mountFlows:j,paywallHost:J};})();
@@ -1 +1 @@
1
- (function(){'use strict';var k=/pass(word|wd|code)?|pwd|secret|token|csrf|xsrf|nonce|captcha|honeypot|card|^cc([-_ ]|num|exp|$)|cvv|cvc|csc|expir|iban|(^|[^a-z])bic([^a-z]|$)|swift|routing|account[-_ ]?(no|num)|sort[-_ ]?code|(^|[^a-z])ssn([^a-z]|$)|social[-_ ]?sec|tax[-_ ]?id|vat[-_ ]?id|steuer|passport|(^|[^a-z])(pin|otp|pw)([^a-z]|$)|one[-_ ]?time|auth(?!or)|api[-_ ]?key|session|signature/i,h=new Set(["password","hidden","file","submit","button","reset","image"]),w=/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/,_=30,F=1e3,v=e=>e.toLowerCase().replace(/[\s_-]+/g,""),x=e=>{var n;if(e.ignored||h.has(e.type))return false;let t=((n=e.autocomplete)!=null?n:"").toLowerCase();return /^cc-|password|one-time-code/.test(t)?false:!k.test(e.name)},E=e=>e.type==="email"||/e-?mail/i.test(e.name)||e.autocomplete==="email",L=e=>{var u;let t=null,n=null,a=null,c=null,o,r={};for(let i of e){if(i.consent){o=(o!=null?o:false)||!i.unchecked;continue}if(!i.name||!x(i)||i.unchecked)continue;let s=i.value.trim().slice(0,F);if(!s)continue;let d=v(i.name),f=((u=i.autocomplete)!=null?u:"").toLowerCase();if(!t&&E(i)&&w.test(s)){t=s.toLowerCase();continue}if(!n&&(f==="name"||["name","fullname","yourname","contactname"].includes(d))){n=s.slice(0,100);continue}if(!a&&(f==="given-name"||["firstname","first","vorname","givenname","fname"].includes(d))){a=s.slice(0,60);continue}if(!c&&(f==="family-name"||["lastname","last","nachname","surname","familyname","lname"].includes(d))){c=s.slice(0,60);continue}let l=i.name.trim().slice(0,60);l in r?r[l]=`${r[l]}, ${s}`.slice(0,F):Object.keys(r).length<_&&(r[l]=s);}let m=n!=null?n:[a,c].filter(Boolean).join(" ")||null;return {email:t,name:m,fields:r,...o===void 0?{}:{consent:o}}},I=e=>(e.getAttribute("data-mrrocket-form")||e.getAttribute("name")||e.id||e.getAttribute("aria-label")||`Form on ${window.location.pathname}`).trim().slice(0,100),O=(e,t)=>e.closest("[data-mrrocket-ignore]")?false:e.hasAttribute("data-mrrocket-form")?true:t&&!!e.querySelector('input[type="email"], input[name*="email" i]')&&!e.querySelector('input[type="password"]'),M=e=>Array.from(e.elements).flatMap(t=>{var o;if(!(t instanceof HTMLInputElement||t instanceof HTMLSelectElement||t instanceof HTMLTextAreaElement))return [];if(t.disabled)return [];let n=t instanceof HTMLInputElement?t.type.toLowerCase():t instanceof HTMLSelectElement?"select":"textarea",a=n==="checkbox"||n==="radio",c=t instanceof HTMLSelectElement&&t.multiple?Array.from(t.selectedOptions,r=>r.value).join(", "):a&&t.value==="on"?"yes":t.value;return [{name:t.name||t.id||"",type:n,value:c,autocomplete:(o=t.getAttribute("autocomplete"))!=null?o:void 0,unchecked:a&&!t.checked,ignored:!!t.closest("[data-mrrocket-ignore]"),consent:t.hasAttribute("data-mrrocket-consent")}]}),b=e=>{var c;let t=window;(c=t.__mrrocketFormsOff)==null||c.call(t);let n=o=>{var l,p,g;let r=o.target;if(!(r instanceof HTMLFormElement)||!O(r,!!e.auto))return;let m=L(M(r));if(!m.email)return;let u=Object.fromEntries(Object.entries((p=(l=e.utm)==null?void 0:l.call(e))!=null?p:{}).filter(y=>typeof y[1]=="string"&&!!y[1])),i={key:e.apiKey,form:I(r),url:window.location.href.slice(0,500),title:document.title.slice(0,200)||void 0,referrer:((g=e.referrer)==null?void 0:g.call(e))||document.referrer||void 0,...Object.keys(u).length?{utm:u}:{},distinctId:e.distinctId(),...m},s=JSON.stringify(i),d=`${e.host}/api/sdk/forms`;"sendBeacon"in navigator&&navigator.sendBeacon(d,new Blob([s],{type:"text/plain"}))||fetch(d,{method:"POST",headers:{"content-type":"text/plain"},body:s,keepalive:true}).catch(()=>{}),e.tracking()&&!e.identified()&&e.identify(m.email,{email:m.email,...m.name?{name:m.name}:{}});};document.addEventListener("submit",n,true);let a=()=>{document.removeEventListener("submit",n,true),t.__mrrocketFormsOff===a&&delete t.__mrrocketFormsOff;};return t.__mrrocketFormsOff=a,a};window.__mrrocketForms={watchForms:b};})();
1
+ (function(){'use strict';var w=/pass(word|wd|code)?|pwd|secret|token|csrf|xsrf|nonce|captcha|honeypot|card|^cc([-_ ]|num|exp|$)|cvv|cvc|csc|expir|iban|(^|[^a-z])bic([^a-z]|$)|swift|routing|account[-_ ]?(no|num)|sort[-_ ]?code|(^|[^a-z])ssn([^a-z]|$)|social[-_ ]?sec|tax[-_ ]?id|vat[-_ ]?id|steuer|passport|(^|[^a-z])(pin|otp|pw)([^a-z]|$)|one[-_ ]?time|auth(?!or)|api[-_ ]?key|session|signature/i,_=new Set(["password","hidden","file","submit","button","reset","image"]),v=/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/,x=30,b=1e3,E=e=>e.toLowerCase().replace(/[\s_-]+/g,""),L=e=>{var n;if(e.ignored||_.has(e.type))return false;let t=((n=e.autocomplete)!=null?n:"").toLowerCase();return /^cc-|password|one-time-code/.test(t)?false:!w.test(e.name)},I=e=>e.type==="email"||/e-?mail/i.test(e.name)||e.autocomplete==="email",O=e=>{var u;let t=null,n=null,a=null,c=null,o,i={};for(let r of e){if(r.consent){o=(o!=null?o:false)||!r.unchecked;continue}if(!r.name||!L(r)||r.unchecked)continue;let s=r.value.trim().slice(0,b);if(!s)continue;let f=E(r.name),d=((u=r.autocomplete)!=null?u:"").toLowerCase();if(!t&&I(r)&&v.test(s)){t=s.toLowerCase();continue}if(!n&&(d==="name"||["name","fullname","yourname","contactname"].includes(f))){n=s.slice(0,100);continue}if(!a&&(d==="given-name"||["firstname","first","vorname","givenname","fname"].includes(f))){a=s.slice(0,60);continue}if(!c&&(d==="family-name"||["lastname","last","nachname","surname","familyname","lname"].includes(f))){c=s.slice(0,60);continue}let l=r.name.trim().slice(0,60);l in i?i[l]=`${i[l]}, ${s}`.slice(0,b):Object.keys(i).length<x&&(i[l]=s);}let m=n!=null?n:[a,c].filter(Boolean).join(" ")||null;return {email:t,name:m,fields:i,...o===void 0?{}:{consent:o}}},H=e=>(e.getAttribute("data-mrrocket-form")||e.getAttribute("name")||e.id||e.getAttribute("aria-label")||`Form on ${window.location.pathname}`).trim().slice(0,100),M=(e,t)=>e.closest("[data-mrrocket-ignore]")?false:e.hasAttribute("data-mrrocket-form")?true:t&&!!e.querySelector('input[type="email"], input[name*="email" i]')&&!e.querySelector('input[type="password"]'),T=e=>Array.from(e.elements).flatMap(t=>{var o;if(!(t instanceof HTMLInputElement||t instanceof HTMLSelectElement||t instanceof HTMLTextAreaElement))return [];if(t.disabled)return [];let n=t instanceof HTMLInputElement?t.type.toLowerCase():t instanceof HTMLSelectElement?"select":"textarea",a=n==="checkbox"||n==="radio",c=t instanceof HTMLSelectElement&&t.multiple?Array.from(t.selectedOptions,i=>i.value).join(", "):a&&t.value==="on"?"yes":t.value;return [{name:t.name||t.id||"",type:n,value:c,autocomplete:(o=t.getAttribute("autocomplete"))!=null?o:void 0,unchecked:a&&!t.checked,ignored:!!t.closest("[data-mrrocket-ignore]"),consent:t.hasAttribute("data-mrrocket-consent")}]}),k=e=>{var c;let t=window;(c=t.__mrrocketFormsOff)==null||c.call(t);let n=o=>{var p,g,h,y;let i=o.target;if(!(i instanceof HTMLFormElement)||!M(i,!!e.auto))return;let m=O(T(i));if(!m.email)return;let u=Object.fromEntries(Object.entries((g=(p=e.utm)==null?void 0:p.call(e))!=null?g:{}).filter(F=>typeof F[1]=="string"&&!!F[1])),r=e.distinctId(),s=r?(h=e.userHash)==null?void 0:h.call(e,r):void 0,f={key:e.apiKey,form:H(i),url:window.location.href.slice(0,500),title:document.title.slice(0,200)||void 0,referrer:((y=e.referrer)==null?void 0:y.call(e))||document.referrer||void 0,...Object.keys(u).length?{utm:u}:{},distinctId:r,...s?{userHash:s}:{},...m},d=JSON.stringify(f),l=`${e.host}/api/sdk/forms`;"sendBeacon"in navigator&&navigator.sendBeacon(l,new Blob([d],{type:"text/plain"}))||fetch(l,{method:"POST",headers:{"content-type":"text/plain"},body:d,keepalive:true}).catch(()=>{}),e.tracking()&&!e.identified()&&e.identify(m.email,{email:m.email,...m.name?{name:m.name}:{}});};document.addEventListener("submit",n,true);let a=()=>{document.removeEventListener("submit",n,true),t.__mrrocketFormsOff===a&&delete t.__mrrocketFormsOff;};return t.__mrrocketFormsOff=a,a};window.__mrrocketForms={watchForms:k};})();