@mrrocket/sdk 0.18.0 → 0.21.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.
- package/CHANGELOG.md +23 -0
- package/README.md +24 -1
- package/dist/api-client.d.ts +28 -4
- package/dist/api-client.js +30 -13
- package/dist/api-client.js.map +1 -1
- package/dist/browser/mrrocket-flows.js +8 -3
- package/dist/browser/mrrocket-messenger.js +38 -5
- package/dist/browser/mrrocket.js +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js.map +1 -1
- package/dist/client.d.ts +6 -1
- package/dist/client.js +22 -4
- package/dist/client.js.map +1 -1
- package/dist/core.d.ts +5 -1
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/feedback.js +161 -119
- package/dist/feedback.js.map +1 -1
- package/dist/flows-ui.d.ts +1 -1
- package/dist/flows-ui.js +100 -72
- package/dist/flows-ui.js.map +1 -1
- package/dist/flows-web.js +2 -1
- package/dist/flows-web.js.map +1 -1
- package/dist/flows.d.ts +38 -6
- package/dist/flows.js +51 -17
- package/dist/flows.js.map +1 -1
- package/dist/i18n.d.ts +22 -0
- package/dist/i18n.js +74 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/messenger-ui.d.ts +9 -3
- package/dist/messenger-ui.js +324 -152
- package/dist/messenger-ui.js.map +1 -1
- package/dist/messenger.d.ts +3 -1
- package/dist/messenger.js +3 -2
- package/dist/messenger.js.map +1 -1
- package/dist/paywall.js +2 -1
- package/dist/paywall.js.map +1 -1
- package/dist/react-native/index.d.cts +66 -8
- package/dist/react-native/index.d.ts +66 -8
- package/dist/react-native/index.js +615 -334
- package/dist/react-native/index.js.map +1 -1
- package/dist/server.d.ts +4 -2
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/dist/styles-css.d.ts +1 -1
- package/dist/styles-css.js +35 -2
- package/dist/styles-css.js.map +1 -1
- package/dist/survey.js +18 -14
- package/dist/survey.js.map +1 -1
- package/dist/ui.d.ts +2 -0
- package/dist/ui.js +15 -12
- package/dist/ui.js.map +1 -1
- package/package.json +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
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.21.0 (not published yet, together with 0.20.0)
|
|
6
|
+
|
|
7
|
+
- **Onboarding questions (web, script tag and React Native):** a number scale (`type: "scale"` with `min`, `max`, `lowLabel`, `highLabel`), an "Other" option where people type their own words (`other`, `otherLabel`; stored as `other:<words>`), and a Skip button on an optional question (`optional`, `skipLabel`; `onboarding.skip()` records `skipped: true`). An older app shows a scale as a text field, leaves "Other" out and offers no Skip.
|
|
8
|
+
- **Dialogs animate only when they open (web and script tag):** picking an answer no longer replays the fade, so it stops looking like the page reloading. Questions, a modal and the cancel flow also stay still from slide to slide, as a paywall does; closing and opening again animates. A checklist and a banner stay still when they redraw; a tour's card still comes in at each step, since it moves to another element.
|
|
9
|
+
- **Feedback board:** a full page (`fullPage`) has its width and background again, so it no longer sits against the left edge or shows dark words on white. On a wide page the types and the roadmap's counts stand in a side column beside the requests, and "Powered by MRRocket" stands at the foot of the board.
|
|
10
|
+
- **Languages (web, script tag and React Native):** every word a visitor reads or a screen reader hears (messenger, feedback board, surveys, cancel flow, onboarding, paywall, errors, dates) can now come in the visitor's language. The SDK sends `lang=<locale>` to `/api/sdk/*`, and a config with `words` replaces its English words; without them nothing changes. `locale` on the React provider and the native client, `data-locale` on the script tag and `setLocale()` / `currentLocale()` are for an app with its own language switch; `setLocale` reads the config again. The SDK's own dialogs and the messenger get `lang` from the config's `lang`. A cancel flow's `followUps[…].answerLabels` and a survey question's `labels` show a translated choice while the original answer is what gets recorded and compared, so numbers do not split by language. `getCockpitConfig({ locale })` reads a server-side config in that language. `sdk.js` budget 18.5 KB.
|
|
11
|
+
- Version reported in event batches: `0.21.0`.
|
|
12
|
+
|
|
13
|
+
## 0.20.0
|
|
14
|
+
|
|
15
|
+
- **A message from the team reaches an open page (web, script tag and React Native):** with the messenger closed and no running chat, the SDK now reads once a minute, so a message the team starts (Inbox › Support › New message, such as "we are currently experiencing issues") shows on the launcher and as a preview card (a banner in apps) within a minute, with the chime on the web. Before, it showed on the next page load or when the tab came back.
|
|
16
|
+
- **Who reads, and when:** only someone the team can write to: a signed-in person, or on the web a visitor who already has a conversation. A passing visitor still reads once per page load, and whoever signs in later is read from the next minute on. A hidden tab or an app in the background reads nothing and catches up when it returns. A read the server refuses (401 or 403, such as a signed-in user without a valid `userHash`) ends the slow reads for that page or app session, so a setup problem does not repeat every minute.
|
|
17
|
+
- A conversation that first appears while the page is open now counts as news: it plays the chime and brings back a preview the visitor dismissed earlier (web).
|
|
18
|
+
- Version reported in event batches: `0.20.0`.
|
|
19
|
+
|
|
20
|
+
## 0.19.0
|
|
21
|
+
|
|
22
|
+
- **A new question opens a new conversation (web, script tag and React Native):** what a visitor types into Home's "Ask a question" starts its own conversation instead of landing in a chat that is still running, and so do "Send us a message" after a help article and files dropped on Home. The field inside a chat keeps answering in that chat. Each conversation is its own in MRRocket's inbox, with its acknowledgement and Support rules.
|
|
23
|
+
- **A list of conversations:** Home shows the two open ones with "All conversations" below them, and Messages lists them all with "New conversation" once there are two or more. A new answer shows on its conversation's row and opens that conversation from the launcher and the preview. New answers are remembered per conversation (`ckw_msgr_seen_chats` in localStorage); the older `ckw_msgr_seen` still counts for the chat it belongs to.
|
|
24
|
+
- **API client:** `sendChat({ conversation })` takes an id or `"new"`, `chat(signal, conversation?)` reads one conversation, and `ChatThread.conversations` (type `ChatSummary`) lists them. A request that names none works as before, so older versions keep their one chat.
|
|
25
|
+
- **React Native:** Home's field has its own words and starts a new conversation, the field inside a chat answers in that chat by name, and Home and Messages list the conversations as on the web. New answers count on their conversation, and the launcher and the banner open the one the answer is in. An app remembers what was read only while it runs, as before: after a restart nothing counts as new until the next answer.
|
|
26
|
+
- Version reported in event batches: `0.19.0`.
|
|
27
|
+
|
|
5
28
|
## 0.18.0
|
|
6
29
|
|
|
7
30
|
- **No grey box while the chat loads (web):** the Messages view shows the greeting and the field at once; the visitor's earlier messages take the greeting's place when the read comes back. Opening the panel again no longer cuts off a read on its way (a slow first read started over on every open), and a send drops an older read so it cannot bring the chat without the new message.
|
package/README.md
CHANGED
|
@@ -492,7 +492,7 @@ A running test needs a stable id on the server (the `ck_id` cookie with `consent
|
|
|
492
492
|
|
|
493
493
|
## 18. Messenger (0.9.0)
|
|
494
494
|
|
|
495
|
-
A chat bubble like Featurebase's or Intercom's messenger, with three tabs: **Home** (greeting,
|
|
495
|
+
A chat bubble like Featurebase's or Intercom's messenger, with three tabs: **Home** (greeting, "Ask a question", the open conversations, top articles), **Messages** (the chats with your team) and **Help** (your published help articles from Support › Help center, searchable, read in the panel). Every chat is a Support conversation (channel "Chat") in MRRocket's inbox: you answer there, or the Customer experience agent does (Support › Automation › AI answers). A new answer while the panel is closed plays a short sound, shows a count on the bubble and a preview card above it.
|
|
496
496
|
|
|
497
497
|
```tsx
|
|
498
498
|
import { Messenger, useMessenger } from "@mrrocket/sdk/next";
|
|
@@ -512,6 +512,8 @@ The look and texts (color, light or dark, corners, Home's top, font, place, name
|
|
|
512
512
|
|
|
513
513
|
Props: `title`, `greeting`, `launcher` (default true), `help` (the Help tab, default true; hidden while there is no published article), `sound` (default true), `preview` (default true), `askEmail` (default true), `position` ("bottom-right" default, "bottom-left", "bottom-center", "top-left", "top-center", "top-right", "middle-left", "middle-right"), `corners` ("round", "soft", "square"), `header` ("plain", "color"), `font` ("site", "system"), `label`, `theme`, `accent`. The panel is plain DOM, loaded with a dynamic import after the page rendered.
|
|
514
514
|
|
|
515
|
+
Several conversations (0.19, web, script tag and React Native): what a visitor types into Home's "Ask a question" starts a new conversation, and the field inside a chat answers in that chat. Home shows the open conversations and Messages lists them all with "New conversation" once there are two or more; each is its own conversation in the inbox. A message to a closed chat starts a new one. If you build your own UI on `useApi()`, `sendChat({ body, conversation })` takes a conversation id or `"new"`, `chat(signal, id)` reads one, and `conversations` on the answer lists them; without `conversation` a message goes into the running chat as in earlier versions.
|
|
516
|
+
|
|
515
517
|
Files (0.15): a paperclip in the message field attaches pictures (PNG, JPEG, WebP, GIF) and PDFs, pasting an image (Cmd/Ctrl+V) or dropping files onto the panel does too, and where the browser has `getDisplayMedia` (not on phones) the paperclip offers "Screenshot of this page": the tab is shared once (`preferCurrentTab`), the messenger hides for one frame, the stream stops. Each file uploads when picked (`POST /api/sdk/messages/attachments`, multipart `key`, `distinctId`, `file`; the server checks the bytes, never SVG, at most 10 MB) and answers a signed id; `sendChat({ body, attachments: [id, …] })` sends up to 4, and the body may then be empty. Sent files come back as `message.attachments` and are read with `api.attachmentUrl(id)` (the visitor's own chat only). Each message also sends `context: { viewport }`; the server adds browser and system from the user agent. In React Native the paperclip shows when `expo-image-picker` is installed (optional peer) and picks from the photo library; the app sends its system and screen size.
|
|
516
518
|
|
|
517
519
|
Call `identify(user.id, { name, email })` and chats carry the person's name and email (they are not asked). While the panel is open it reads `GET /api/sdk/messages` every 4 seconds; while a visitor waits for an answer every 8 seconds for ten minutes, then every 30. Articles come from `GET /api/sdk/articles` as blocks, written with textContent, never HTML.
|
|
@@ -629,6 +631,27 @@ const hash = user ? await userHash(user.id) : undefined;
|
|
|
629
631
|
- **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.
|
|
630
632
|
- `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.
|
|
631
633
|
|
|
634
|
+
## 23. Your own health signals
|
|
635
|
+
|
|
636
|
+
A customer's health score (Retention › Churn) adds any part you report, with an event named `health.<part>`. Send it from your server or the SDK. Only customers who pay count.
|
|
637
|
+
|
|
638
|
+
```ts
|
|
639
|
+
// did it work: the share that worked over the last 14 days becomes the part
|
|
640
|
+
await fetch("https://app.mrrocket.dev/api/v1/events", {
|
|
641
|
+
method: "POST",
|
|
642
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${process.env.MRROCKET_API_KEY}` },
|
|
643
|
+
body: JSON.stringify({ email, event: "health.exports", properties: { ok: false, label: "Exports" } }),
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
// or a score of your own, 0 to 100: the average over the last 14 days
|
|
647
|
+
track("health.nps", { score: 80, weight: 0.3 });
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
- `ok` (true or false) or `score` (0 to 100) says how it went. An event with neither is ignored.
|
|
651
|
+
- `label` names the part in MRRocket; without one the name after `health.` is used.
|
|
652
|
+
- `weight` (0 to 1, default 0.2) sets how much it counts next to usage, logins, billing and support (0.4, 0.2, 0.2, 0.2).
|
|
653
|
+
- Two failed `ok` events at under 70 % put a signal on the customer. At most 8 parts per customer count, in the order of their names.
|
|
654
|
+
|
|
632
655
|
## Privacy and consent
|
|
633
656
|
|
|
634
657
|
```tsx
|
package/dist/api-client.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type PostStatus = "review" | "planned" | "progress" | "shipped";
|
|
2
|
+
/** Read at the moment it is shown, so it follows the visitor's language. */
|
|
2
3
|
export declare const STATUS_LABEL: Record<PostStatus, string>;
|
|
3
4
|
export interface FeedbackBoardInfo {
|
|
4
5
|
id: string;
|
|
@@ -59,6 +60,11 @@ export interface SurveyQuestion {
|
|
|
59
60
|
description?: string;
|
|
60
61
|
required: boolean;
|
|
61
62
|
options?: string[];
|
|
63
|
+
/**
|
|
64
|
+
* 0.21.0: what each option says in the visitor's language, parallel to `options`. An answer is still recorded as the
|
|
65
|
+
* `options` value, so rules and numbers do not split by language.
|
|
66
|
+
*/
|
|
67
|
+
labels?: string[];
|
|
62
68
|
scale?: number;
|
|
63
69
|
low?: string;
|
|
64
70
|
high?: string;
|
|
@@ -175,6 +181,8 @@ export interface MessengerLook {
|
|
|
175
181
|
/** A round chat button in the app (React Native) too. */
|
|
176
182
|
appLauncher?: boolean;
|
|
177
183
|
help: boolean;
|
|
184
|
+
/** The Updates tab with the published changelog (MRRocket from 0.22; older servers leave it out). */
|
|
185
|
+
updates?: boolean;
|
|
178
186
|
sound: boolean;
|
|
179
187
|
preview: boolean;
|
|
180
188
|
askEmail: boolean;
|
|
@@ -198,16 +206,27 @@ export interface MessengerLook {
|
|
|
198
206
|
/** The link behind "Powered by MRRocket". */
|
|
199
207
|
export declare const BRAND_URL = "https://mrrocket.dev/?utm_source=messenger&utm_medium=powered-by";
|
|
200
208
|
/** The line under "Send us a message" and in the chat's head. */
|
|
201
|
-
export declare const replyLine: (
|
|
209
|
+
export declare const replyLine: (time: MessengerLook["replyTime"]) => string;
|
|
202
210
|
/** Black or white, whichever reads better on the accent (a light custom color gets dark text). */
|
|
203
211
|
export declare const inkOn: (hex: string) => "#ffffff" | "#111214";
|
|
204
|
-
/**
|
|
212
|
+
/** One of the visitor's conversations in their list (0.19). */
|
|
213
|
+
export interface ChatSummary {
|
|
214
|
+
id: string;
|
|
215
|
+
status: "open" | "solved";
|
|
216
|
+
/** The visitor's first words. */
|
|
217
|
+
subject: string;
|
|
218
|
+
/** The newest message, its body cut to a preview. */
|
|
219
|
+
last: ChatMessage | null;
|
|
220
|
+
}
|
|
221
|
+
/** One chat (the one asked for, else the visitor's newest, open or closed) and its messages. */
|
|
205
222
|
export interface ChatThread {
|
|
206
223
|
conversation: {
|
|
207
224
|
id: string;
|
|
208
225
|
status: "open" | "solved";
|
|
209
226
|
} | null;
|
|
210
227
|
messages: ChatMessage[];
|
|
228
|
+
/** All the visitor's conversations, the latest activity first (0.19). */
|
|
229
|
+
conversations?: ChatSummary[];
|
|
211
230
|
/** The visitor gave an email already: the messenger stops asking. */
|
|
212
231
|
hasEmail: boolean;
|
|
213
232
|
}
|
|
@@ -263,10 +282,15 @@ export declare const createApi: (c: Conn) => {
|
|
|
263
282
|
completed: boolean;
|
|
264
283
|
seconds: number;
|
|
265
284
|
}) => Promise<unknown>;
|
|
266
|
-
chat
|
|
267
|
-
|
|
285
|
+
/** One chat by id, else the visitor's newest; `conversations` lists them all. */
|
|
286
|
+
chat: (signal?: AbortSignal, conversation?: string) => Promise<ChatThread>;
|
|
287
|
+
/**
|
|
288
|
+
* A message; `attachments` are upload ids from `uploadChatFile` (then the body may be empty). `conversation` is
|
|
289
|
+
* the chat it answers, or "new" to start one; without it, the running chat or else a new one.
|
|
290
|
+
*/
|
|
268
291
|
sendChat: (m: {
|
|
269
292
|
body: string;
|
|
293
|
+
conversation?: string;
|
|
270
294
|
email?: string;
|
|
271
295
|
url?: string;
|
|
272
296
|
attachments?: string[];
|
package/dist/api-client.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
// src/api-client.ts
|
|
2
2
|
import { warnUnsigned } from "./identity.js";
|
|
3
|
+
import { currentLocale, t } from "./i18n.js";
|
|
3
4
|
var STATUS_LABEL = {
|
|
4
|
-
review
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
get review() {
|
|
6
|
+
return t("Under review");
|
|
7
|
+
},
|
|
8
|
+
get planned() {
|
|
9
|
+
return t("Planned");
|
|
10
|
+
},
|
|
11
|
+
get progress() {
|
|
12
|
+
return t("In progress");
|
|
13
|
+
},
|
|
14
|
+
get shipped() {
|
|
15
|
+
return t("Shipped");
|
|
16
|
+
}
|
|
8
17
|
};
|
|
9
18
|
var CHAT_FILE_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif", "application/pdf"];
|
|
10
19
|
var CHAT_FILE_MAX_BYTES = 10 * 1024 * 1024;
|
|
@@ -15,7 +24,7 @@ var CORNERS = {
|
|
|
15
24
|
square: { panel: 6, bubble: 6, tail: 2, card: 4, field: 4 }
|
|
16
25
|
};
|
|
17
26
|
var BRAND_URL = "https://mrrocket.dev/?utm_source=messenger&utm_medium=powered-by";
|
|
18
|
-
var replyLine = (
|
|
27
|
+
var replyLine = (time) => time === "minutes" ? t("We usually reply in a few minutes") : time === "day" ? t("We usually reply within a day") : t("We usually reply in a few hours");
|
|
19
28
|
var inkOn = (hex) => {
|
|
20
29
|
const m = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
|
|
21
30
|
if (!m) return "#ffffff";
|
|
@@ -32,7 +41,11 @@ var ApiError = class extends Error {
|
|
|
32
41
|
this.status = status;
|
|
33
42
|
}
|
|
34
43
|
};
|
|
35
|
-
var qs = (params) => Object.entries(params).filter((e) => !!e[1]).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
|
|
44
|
+
var qs = (params, lang = currentLocale()) => Object.entries({ ...params, lang }).filter((e) => !!e[1]).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
|
|
45
|
+
var withLang = (url) => {
|
|
46
|
+
const q = qs({});
|
|
47
|
+
return q ? `${url}?${q}` : url;
|
|
48
|
+
};
|
|
36
49
|
var call = async (url, init, signal) => {
|
|
37
50
|
let res;
|
|
38
51
|
try {
|
|
@@ -44,7 +57,7 @@ var call = async (url, init, signal) => {
|
|
|
44
57
|
});
|
|
45
58
|
} catch (e) {
|
|
46
59
|
if (e.name === "AbortError") throw e;
|
|
47
|
-
throw new ApiError("Could not reach the server. Check your connection and try again.", 0);
|
|
60
|
+
throw new ApiError(t("Could not reach the server. Check your connection and try again."), 0);
|
|
48
61
|
}
|
|
49
62
|
if (!res.ok) {
|
|
50
63
|
warnUnsigned(res.status);
|
|
@@ -53,7 +66,7 @@ var call = async (url, init, signal) => {
|
|
|
53
66
|
detail = (await res.json()).error ?? "";
|
|
54
67
|
} catch {
|
|
55
68
|
}
|
|
56
|
-
throw new ApiError(detail || (res.status === 429 ? "Too many requests. Try again in a minute." : "Something went wrong. Please try again."), res.status);
|
|
69
|
+
throw new ApiError(detail || (res.status === 429 ? t("Too many requests. Try again in a minute.") : t("Something went wrong. Please try again.")), res.status);
|
|
57
70
|
}
|
|
58
71
|
return await res.json();
|
|
59
72
|
};
|
|
@@ -61,7 +74,7 @@ var createApi = (c) => {
|
|
|
61
74
|
const base = `${c.host}/api/sdk`;
|
|
62
75
|
const who = { key: c.apiKey, distinctId: c.distinctId, user: c.user, userHash: c.userHash };
|
|
63
76
|
const me = { key: c.apiKey, distinctId: c.distinctId, userHash: c.userHash };
|
|
64
|
-
const post = (path, body, signal) => call(`${base}${path}
|
|
77
|
+
const post = (path, body, signal) => call(withLang(`${base}${path}`), { method: "POST", body: JSON.stringify({ ...who, ...body }) }, signal);
|
|
65
78
|
return {
|
|
66
79
|
list: (o, signal) => call(`${base}/feedback?${qs({ ...me, board: o.board, sort: o.sort, q: o.q })}`, void 0, signal),
|
|
67
80
|
post: (id, signal) => call(`${base}/feedback/${encodeURIComponent(id)}?${qs(me)}`, void 0, signal),
|
|
@@ -74,8 +87,12 @@ var createApi = (c) => {
|
|
|
74
87
|
survey: (url, signal) => call(`${base}/surveys?${qs({ ...me, url })}`, void 0, signal),
|
|
75
88
|
seen: (id) => post(`/surveys/${encodeURIComponent(id)}/seen`, { user: void 0 }),
|
|
76
89
|
respond: (id, r) => post(`/surveys/${encodeURIComponent(id)}/respond`, r),
|
|
77
|
-
chat
|
|
78
|
-
|
|
90
|
+
/** One chat by id, else the visitor's newest; `conversations` lists them all. */
|
|
91
|
+
chat: (signal, conversation) => call(`${base}/messages?${qs({ ...me, conversation })}`, void 0, signal),
|
|
92
|
+
/**
|
|
93
|
+
* A message; `attachments` are upload ids from `uploadChatFile` (then the body may be empty). `conversation` is
|
|
94
|
+
* the chat it answers, or "new" to start one; without it, the running chat or else a new one.
|
|
95
|
+
*/
|
|
79
96
|
sendChat: (m) => post("/messages", m),
|
|
80
97
|
/** Uploads one picked file for the next message (pictures and PDFs, at most 10 MB); answers its upload id. */
|
|
81
98
|
uploadChatFile: (file, signal) => {
|
|
@@ -85,10 +102,10 @@ var createApi = (c) => {
|
|
|
85
102
|
if (c.userHash) form.append("userHash", c.userHash);
|
|
86
103
|
if ("uri" in file) form.append("file", file);
|
|
87
104
|
else form.append("file", file, file.name || "file");
|
|
88
|
-
return call(`${base}/messages/attachments
|
|
105
|
+
return call(withLang(`${base}/messages/attachments`), { method: "POST", body: form }, signal);
|
|
89
106
|
},
|
|
90
107
|
/** Where a sent file of this visitor's chat is read (an image's src, a PDF's link). */
|
|
91
|
-
attachmentUrl: (id) => `${base}/messages/attachments/${encodeURIComponent(id)}?${qs(me)}`,
|
|
108
|
+
attachmentUrl: (id) => `${base}/messages/attachments/${encodeURIComponent(id)}?${qs(me, "")}`,
|
|
92
109
|
articles: (q, signal) => call(`${base}/articles?${qs({ key: c.apiKey, q })}`, void 0, signal),
|
|
93
110
|
article: (id, signal) => call(`${base}/articles/${encodeURIComponent(id)}?${qs(me)}`, void 0, signal),
|
|
94
111
|
messengerLook: (signal) => call(`${base}/messenger?${qs({ key: c.apiKey })}`, void 0, signal),
|
package/dist/api-client.js.map
CHANGED
|
@@ -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 */\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 logo at the top left of Home: the one uploaded in MRRocket, else the product's favicon; without it the title's first letter. */\n logo?: string;\n /** The product's favicon, the round mark in the chat's head (from 0.17; without it the title's first letter). */\n icon?: string;\n /** The AI agent's name on its answers (from 0.17; empty or left out: \"AI agent\"). */\n agentName?: string;\n /** The AI agent's picture beside its answers (from 0.17). */\n agentPhoto?: 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":[]}
|
|
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\";\nimport { currentLocale, t } from \"./i18n\";\n\nexport type PostStatus = \"review\" | \"planned\" | \"progress\" | \"shipped\";\n\n/** Read at the moment it is shown, so it follows the visitor's language. */\nexport const STATUS_LABEL: Record<PostStatus, string> = {\n get review() {\n return t(\"Under review\");\n },\n get planned() {\n return t(\"Planned\");\n },\n get progress() {\n return t(\"In progress\");\n },\n get shipped() {\n return t(\"Shipped\");\n },\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 /**\n * 0.21.0: what each option says in the visitor's language, parallel to `options`. An answer is still recorded as the\n * `options` value, so rules and numbers do not split by language.\n */\n labels?: 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 /** The Updates tab with the published changelog (MRRocket from 0.22; older servers leave it out). */\n updates?: 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 logo at the top left of Home: the one uploaded in MRRocket, else the product's favicon; without it the title's first letter. */\n logo?: string;\n /** The product's favicon, the round mark in the chat's head (from 0.17; without it the title's first letter). */\n icon?: string;\n /** The AI agent's name on its answers (from 0.17; empty or left out: \"AI agent\"). */\n agentName?: string;\n /** The AI agent's picture beside its answers (from 0.17). */\n agentPhoto?: 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 = (time: MessengerLook[\"replyTime\"]) =>\n time === \"minutes\" ? t(\"We usually reply in a few minutes\") : time === \"day\" ? t(\"We usually reply within a day\") : t(\"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/** One of the visitor's conversations in their list (0.19). */\nexport interface ChatSummary {\n id: string;\n status: \"open\" | \"solved\";\n /** The visitor's first words. */\n subject: string;\n /** The newest message, its body cut to a preview. */\n last: ChatMessage | null;\n}\n\n/** One chat (the one asked for, else the visitor's newest, open or closed) and its messages. */\nexport interface ChatThread {\n conversation: { id: string; status: \"open\" | \"solved\" } | null;\n messages: ChatMessage[];\n /** All the visitor's conversations, the latest activity first (0.19). */\n conversations?: ChatSummary[];\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.\n// `lang` goes along so the answer's words can be in the visitor's language; \"\" leaves it out.\nconst qs = (params: Record<string, string | undefined>, lang = currentLocale()) =>\n Object.entries({ ...params, lang })\n .filter((e): e is [string, string] => !!e[1])\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)\n .join(\"&\");\n\n/** A POST's address: its body names the visitor, the query only the language of the words in its answer. */\nconst withLang = (url: string) => {\n const q = qs({});\n return q ? `${url}?${q}` : url;\n};\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(t(\"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 ? t(\"Too many requests. Try again in a minute.\") : t(\"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>(withLang(`${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 /** One chat by id, else the visitor's newest; `conversations` lists them all. */\n chat: (signal?: AbortSignal, conversation?: string) => call<ChatThread>(`${base}/messages?${qs({ ...me, conversation })}`, undefined, signal),\n /**\n * A message; `attachments` are upload ids from `uploadChatFile` (then the body may be empty). `conversation` is\n * the chat it answers, or \"new\" to start one; without it, the running chat or else a new one.\n */\n sendChat: (m: { body: string; conversation?: 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>(withLang(`${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":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(){'use strict';var
|
|
1
|
+
(function(){'use strict';var Fe=()=>{try{return typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches}catch(e){return false}},ae=["#f59e0b","#10b981","#ef4444","#8b5cf6","#06b6d4","#ec4899"],Pe=(e,t)=>{e.beginPath();for(let o=0;o<10;o++){let s=Math.PI/5*o-Math.PI/2,c=o%2?t*.45:t;e[o?"lineTo":"moveTo"](Math.cos(s)*c,Math.sin(s)*c);}e.closePath(),e.fill();},Ee=(e,t,o,s)=>{let c=(d,w)=>d+Math.random()*(w-d),m=()=>s[Math.floor(Math.random()*s.length)],h=[];if(e==="confetti")for(let d=0;d<110;d++){let w=d%2===0;h.push({x:w?t*.15:t*.85,y:o*.95,vx:(w?1:-1)*c(.12,.42)*(t/900+.4),vy:-c(.75,1.25)*(o/900+.35),r:c(4,7),spin:c(-0.02,.02),angle:c(0,Math.PI),color:m(),born:c(0,180),life:c(1500,2100),shape:"rect"});}else if(e==="fireworks")for(let d=0;d<4;d++){let w=c(t*.2,t*.8),l=c(o*.18,o*.5),x=m();for(let k=0;k<46;k++){let M=Math.PI*2*k/46+c(-0.05,.05),E=c(.12,.3)*(Math.min(t,o)/600+.5);h.push({x:w,y:l,vx:Math.cos(M)*E,vy:Math.sin(M)*E,r:c(1.6,2.6),spin:0,angle:0,color:x,born:d*320,life:c(900,1300),shape:"dot"});}}else for(let d=0;d<34;d++)h.push({x:c(t*.08,t*.92),y:c(o*.2,o*.85),vx:0,vy:-c(.02,.06),r:c(6,13),spin:c(-4e-3,.004),angle:c(0,Math.PI),color:m(),born:c(0,700),life:c(900,1300),shape:"star"});return h},ce=(e,t={})=>{if(!e||e==="none"||typeof document=="undefined"||!t.force&&Fe())return ()=>{};let o=document.createElement("canvas");o.setAttribute("aria-hidden","true");let s=t.host;o.style.cssText=s?"position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:5":"position:fixed;inset:0;width:100vw;height:100vh;pointer-events:none;z-index:2147483003",(s!=null?s:document.body).appendChild(o);let c=s?s.clientWidth:window.innerWidth,m=s?s.clientHeight:window.innerHeight,h=Math.min(2,window.devicePixelRatio||1);o.width=Math.max(1,Math.round(c*h)),o.height=Math.max(1,Math.round(m*h));let d=o.getContext("2d");if(!d)return o.remove(),()=>{};let w=t.accent?[t.accent,t.accent,...ae]:ae,l=Ee(e,c,m,w),x=Math.max(...l.map(z=>z.born+z.life)),k=e==="confetti"?.0016:e==="fireworks"?35e-5:0,M=0,E=0,H=false,T=()=>{H||(H=true,cancelAnimationFrame(M),o.remove());},N=z=>{if(H)return;E||(E=z);let V=z-E;d.setTransform(h,0,0,h,0,0),d.clearRect(0,0,c,m);for(let y of l){let P=V-y.born;if(P<0||P>y.life)continue;let q=y.x+y.vx*P,R=y.y+y.vy*P+.5*k*P*P,r=Math.min(1,(y.life-P)/350);if(d.save(),d.globalAlpha=Math.max(0,r),d.translate(q,R),d.rotate(y.angle+y.spin*P),d.fillStyle=y.color,y.shape==="rect")d.fillRect(-y.r,-y.r/2.2,y.r*2,y.r/1.1);else if(y.shape==="dot")d.beginPath(),d.arc(0,0,y.r,0,Math.PI*2),d.fill();else {let i=Math.min(1,P/220);Pe(d,y.r*(i<1?i:1-Math.max(0,(P-y.life*.6)/(y.life*.4))*.6));}d.restore();}V<x?M=requestAnimationFrame(N):T();};return M=requestAnimationFrame(N),T};var Y=globalThis,le,K=(le=Y.__mrrocketI18n)!=null?le:Y.__mrrocketI18n={version:0,onWords:new Set,onLocale:new Set};var f=e=>{var t;return ((t=K.words)==null?void 0:t[e])||e},I=(e,t)=>f(e).replace(/\{(\w+)\}/g,(o,s)=>s in t?String(t[s]):o);var de=()=>{var e,t;return (t=(e=K.locale)!=null?e:K.device)!=null?t:typeof navigator!="undefined"&&navigator.language||void 0};var fe=()=>K.lang;var J=e=>(K.onWords.add(e),()=>{K.onWords.delete(e);});var U="other:",Q=e=>{var s,c;let t=(s=e==null?void 0:e.min)!=null?s:0,o=(c=e==null?void 0:e.max)!=null?c:10;return Array.from({length:Math.max(0,Math.min(o-t,20)+1)},(m,h)=>t+h)},pe=(e,t)=>{var o;return !e||t==null?false:e.type==="choice"?e.other&&t.startsWith(U)?t.slice(U.length).trim().length>0:((o=e.options)!=null?o:[]).some(s=>s.id===t):e.type==="scale"?Q(e).some(s=>String(s)===t):t.trim().length>0&&t.length<=500};var X=e=>{let t=(e!=null?e:"").trim();if(!/^https?:\/\//i.test(t))return null;let o=t.match(/^https?:\/\/(?:www\.|m\.)?(?:youtube\.com\/(?:watch\?(?:.*&)?v=|embed\/|shorts\/|live\/)|youtu\.be\/|youtube-nocookie\.com\/embed\/)([\w-]{6,20})/i);return o?{kind:"iframe",src:`https://www.youtube-nocookie.com/embed/${o[1]}?rel=0`}:(o=t.match(/^https?:\/\/(?:www\.|player\.)?vimeo\.com\/(?:video\/)?(\d{4,12})/i),o?{kind:"iframe",src:`https://player.vimeo.com/video/${o[1]}?dnt=1`}:(o=t.match(/^https?:\/\/(?:www\.)?loom\.com\/(?:share|embed)\/([\w-]{8,64})/i),o?{kind:"iframe",src:`https://www.loom.com/embed/${o[1]}`}:/\.(mp4|webm|mov|m4v)(\?|#|$)/i.test(t)||/\/media\/uploads\//.test(t)?{kind:"file",src:t}:null))},ue=e=>{var o;let t=e.media;if(!(!t||t.kind!=="image"&&t.kind!=="video"||!/^https?:\/\//i.test((o=t.url)!=null?o:"")))return t.kind==="video"&&!X(t.url)?void 0:t};var ge=(e,t)=>Math.round(e*(1-t/100)*100)/100,G=(e,t)=>{try{return new Intl.NumberFormat(de(),{style:"currency",currency:t}).format(e)}catch(o){return `${e.toFixed(2)} ${t}`}},me=e=>e.kind==="contact"?f("Send to the team"):e.kind==="pause"?f("Pause instead"):e.kind==="downgrade"?f("Switch plan"):f("Accept offer"),Z=e=>e===1?f("1 month"):I("{n} months",{n:e}),ee=e=>I("for {months}",{months:Z(e)});var B={check:{label:"Check",paths:["M5 12.5l4.5 4.5L19 7.5"],emoji:"\u2705"},star:{label:"Star",paths:["M12 3.5l2.6 5.3 5.9.9-4.3 4.1 1 5.8L12 16.9l-5.2 2.7 1-5.8-4.3-4.1 5.9-.9z"],emoji:"\u2B50"},rocket:{label:"Rocket",paths:["M12 3c3 2 4.5 5.5 4 10l-2 3h-4l-2-3c-.5-4.5 1-8 4-10z","M8 13.5l-3 3 3.5.5","M16 13.5l3 3-3.5.5","M13.5 9.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z","M11 19.5v1.5M13 19.5v1.5"],emoji:"\u{1F680}"},user:{label:"Person",paths:["M15.5 8a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0z","M5 20c.8-3.5 3.6-5.5 7-5.5s6.2 2 7 5.5"],emoji:"\u{1F464}"},users:{label:"Team",paths:["M13 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0z","M3.5 19c.6-3 3-4.8 6.5-4.8s5.9 1.8 6.5 4.8","M16 5.2a3 3 0 0 1 0 5.6","M18 14.6c1.5.6 2.4 2 2.7 4.4"],emoji:"\u{1F465}"},settings:{label:"Settings",paths:["M12 2.8v2.4M12 18.8v2.4M2.8 12h2.4M18.8 12h2.4M5.5 5.5l1.7 1.7M16.8 16.8l1.7 1.7M5.5 18.5l1.7-1.7M16.8 7.2l1.7-1.7","M17 12a5 5 0 1 1-10 0 5 5 0 0 1 10 0z","M14 12a2 2 0 1 1-4 0 2 2 0 0 1 4 0z"],emoji:"\u2699\uFE0F"},bell:{label:"Bell",paths:["M6 16v-5a6 6 0 0 1 12 0v5l1.5 2h-15z","M10 20.5a2 2 0 0 0 4 0"],emoji:"\u{1F514}"},mail:{label:"Mail",paths:["M4 6h16v12H4z","M4 7l8 6 8-6"],emoji:"\u2709\uFE0F"},chat:{label:"Chat",paths:["M4 5h16v11H9l-5 4z"],emoji:"\u{1F4AC}"},heart:{label:"Heart",paths:["M12 20s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 10c0 5.6-7 10-7 10z"],emoji:"\u2764\uFE0F"},flag:{label:"Flag",paths:["M5 21V4","M5 4h11l-2 4 2 4H5"],emoji:"\u{1F6A9}"},bolt:{label:"Bolt",paths:["M13 3L5 13.5h6L10 21l8-10.5h-6z"],emoji:"\u26A1"},book:{label:"Book",paths:["M4 5.5C6.5 4 9.5 4 12 6c2.5-2 5.5-2 8-.5V19c-2.5-1.5-5.5-1.5-8 .5-2.5-2-5.5-2-8-.5z","M12 6v13.5"],emoji:"\u{1F4D6}"},calendar:{label:"Calendar",paths:["M4 6h16v14H4z","M4 10h16","M8 3.5v4M16 3.5v4"],emoji:"\u{1F4C5}"},camera:{label:"Camera",paths:["M4 8h3.5L9 5.5h6L16.5 8H20v11H4z","M15.5 13a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0z"],emoji:"\u{1F4F7}"},cart:{label:"Cart",paths:["M3.5 4.5H6l2 10h10l2-7H7","M9.5 19.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z","M18.5 19.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"],emoji:"\u{1F6D2}"},chart:{label:"Chart",paths:["M4 20h16","M7 16v-4M12 16V8M17 16V9"],emoji:"\u{1F4CA}"},gift:{label:"Gift",paths:["M4 10h16v10H4z","M3 7h18v3H3z","M12 7v13","M12 7c-1.5-3-5-3-5-1s3.5 1 5 1z","M12 7c1.5-3 5-3 5-1s-3.5 1-5 1z"],emoji:"\u{1F381}"},home:{label:"Home",paths:["M4 11l8-7 8 7","M6 9.5V20h12V9.5","M10 20v-6h4v6"],emoji:"\u{1F3E0}"},link:{label:"Link",paths:["M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1","M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1"],emoji:"\u{1F517}"},lock:{label:"Lock",paths:["M6 11h12v9H6z","M8.5 11V8a3.5 3.5 0 0 1 7 0v3"],emoji:"\u{1F512}"},pencil:{label:"Pencil",paths:["M4 20l1-4L16 5l3 3L8 19z","M14 7l3 3"],emoji:"\u270F\uFE0F"},plus:{label:"Plus",paths:["M12 5v14M5 12h14"],emoji:"\u2795"},search:{label:"Search",paths:["M16 10.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z","M14.5 14.5L20 20"],emoji:"\u{1F50D}"},upload:{label:"Upload",paths:["M12 15V4","M7.5 8.5L12 4l4.5 4.5","M4 15v5h16v-5"],emoji:"\u2B06\uFE0F"},play:{label:"Play",paths:["M8 5l11 7-11 7z"],emoji:"\u25B6\uFE0F"},sparkle:{label:"Sparkle",paths:["M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8z","M19 16l.7 2 2 .7-2 .7-.7 2-.7-2-2-.7 2-.7z"],emoji:"\u2728"},globe:{label:"Globe",paths:["M20 12a8 8 0 1 1-16 0 8 8 0 0 1 16 0z","M4 12h16","M12 4c2.2 2.2 3.3 4.9 3.3 8s-1.1 5.8-3.3 8c-2.2-2.2-3.3-4.9-3.3-8S9.8 6.2 12 4z"],emoji:"\u{1F310}"}},ke=e=>{if(e){if(e.kind==="builtin")return e.name&&B[e.name]?e:void 0;if(e.kind==="emoji")return e.value&&e.value.trim()&&[...e.value.trim()].length<=8?e:void 0;if(e.kind==="image")return e.url&&/^https?:\/\//i.test(e.url)?e:void 0}};var he="ckf-styles",Se=`
|
|
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)}}
|
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
.ckf-radio{width:16px;height:16px;flex:none;border-radius:50%;border:1.5px solid var(--ckf-line);display:inline-flex;align-items:center;justify-content:center}
|
|
26
26
|
.ckf-opt[aria-checked=true] .ckf-radio{border-color:var(--ckf-accent)}
|
|
27
27
|
.ckf-opt[aria-checked=true] .ckf-radio::after{content:"";width:8px;height:8px;border-radius:50%;background:var(--ckf-accent)}
|
|
28
|
+
.ckf-scale{display:flex;gap:4px}
|
|
29
|
+
.ckf-num{flex:1;min-width:0;height:38px;padding:0;border:1px solid var(--ckf-line);border-radius:8px;background:var(--ckf-bg);cursor:pointer;font-weight:500}
|
|
30
|
+
.ckf-num:hover{background:var(--ckf-soft)}
|
|
31
|
+
.ckf-num[aria-checked=true]{border-color:var(--ckf-accent);background:var(--ckf-accent);color:var(--ckf-accent-ink)}
|
|
32
|
+
.ckf-ends{display:flex;justify-content:space-between;gap:12px;margin-top:6px}
|
|
28
33
|
.ckf-box{border-radius:10px;background:var(--ckf-soft);padding:12px;display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}
|
|
29
34
|
.ckf-big{font-size:22px;font-weight:650;font-variant-numeric:tabular-nums}
|
|
30
35
|
.ckf-strike{color:var(--ckf-muted);text-decoration:line-through;font-variant-numeric:tabular-nums}
|
|
@@ -70,7 +75,7 @@
|
|
|
70
75
|
.ckf-banner>span{flex:1;min-width:0;display:flex;flex-direction:column}
|
|
71
76
|
.ckf-banner .ckf-btn{height:32px}
|
|
72
77
|
.ckf-pw{max-width:420px;gap:14px}
|
|
73
|
-
.ckf-root[data-still] :is(.ckf-scrim,.ckf-card){animation:none}
|
|
78
|
+
.ckf-root[data-still] :is(.ckf-scrim,.ckf-card,.ckf-banner){animation:none}
|
|
74
79
|
.ckf-li{display:flex;gap:10px;align-items:flex-start;margin:0}
|
|
75
80
|
.ckf-li::before{content:"\u2713";flex:none;width:20px;height:20px;border-radius:50%;background:color-mix(in srgb,var(--ckf-accent) 14%,var(--ckf-bg));color:var(--ckf-accent);font-size:12px;font-weight:700;display:inline-flex;align-items:center;justify-content:center}
|
|
76
81
|
.ckf-quote{margin:0;padding:12px 14px;border-radius:12px;background:var(--ckf-soft)}
|
|
@@ -83,5 +88,5 @@
|
|
|
83
88
|
.ckf-banner{animation-name:ckf-fade}
|
|
84
89
|
@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}}
|
|
85
90
|
@media (prefers-reduced-motion:reduce){.ckf-root *{animation:none!important;transition:none!important}}
|
|
86
|
-
`,n=(e,t={},...i)=>{let s=document.createElement(e);for(let[a,g]of Object.entries(t))g===void 0||g===false||(typeof g=="function"?s.addEventListener(a.slice(2).toLowerCase(),g):a==="className"?s.className=String(g):s.setAttribute(a,g===true?"":g));for(let a of i)(a||a==="")&&s.append(typeof a=="string"?document.createTextNode(a):a);return s},E=(e,t,i,s={})=>n("button",{type:"button",className:`ckf-btn${t==="secondary"?"":` ckf-${t}`}`,disabled:!!s.disabled,"data-ckf-id":s.id,onClick:()=>i()},e),j=(e,t)=>n("button",{type:"button",className:"ckf-x","aria-label":e,"data-ckf-id":"x",onClick:()=>t()},"\xD7"),B=(e,t)=>t>1?n("span",{className:"ckf-dots",role:"img","aria-label":`Step ${e} of ${t}`},...Array.from({length:t},(i,s)=>n("span",{className:"ckf-dot","data-on":s<e}))):n("span"),ge=e=>{var t;return e.type==="cancel"?`c:${e.screen}:${(t=e.round)!=null?t:1}`:e.type==="step"?`s:${e.step.id}`:e.type==="checklist"?`l:${e.flow.id}`:`m:${e.message.id}`},oe=e=>{var i;if(!document.getElementById(re)){let s=document.createElement("style");s.id=re,s.textContent=me,document.head.appendChild(s);}let t=n("div",{className:`ckf-root${e.className?` ${e.className}`:""}`,"data-theme":(i=e.theme)!=null?i:"auto"});return e.accent&&t.style.setProperty("--ckf-accent",e.accent),document.body.appendChild(t),t},K=(e,t)=>{let i=ne(e);if(!i)return false;let s=n("span",{className:t,"aria-hidden":"true"});if(i.kind==="emoji")s.textContent=i.value;else if(i.kind==="image")s.append(n("img",{src:i.url,alt:""}));else {let a="http://www.w3.org/2000/svg",g=document.createElementNS(a,"svg");for(let[k,l]of Object.entries({viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"}))g.setAttribute(k,l);for(let k of z[i.name].paths){let l=document.createElementNS(a,"path");l.setAttribute("d",k),g.append(l);}s.append(g);}return s},ie=(e,t={})=>{if(typeof document=="undefined")return ()=>{};let i=oe(t),s="",a="",g=null,k="",l=null,m=r=>{var p,C,S,O;let o="ckf-title",c=r.busy,u=[];if(r.screen==="survey")u.push(n("h2",{className:"ckf-h",id:o},r.flow.question||"Why are you leaving?"),n("div",{className:"ckf-opts",role:"radiogroup","aria-labelledby":o},...r.flow.reasons.map(d=>n("button",{type:"button",role:"radio",className:"ckf-opt","aria-checked":String(r.reason===d.id),"data-ckf-id":`r:${d.id}`,onClick:()=>e.cancel.pick(d.id)},n("span",{className:"ckf-radio"}),d.label))),n("div",{className:"ckf-btns"},E("Continue","primary",e.cancel.next,{disabled:!r.reason,id:"go"}),E("Never mind, keep my plan","quiet",e.cancel.close)));else if(r.screen==="followup"&&r.followUp){let d=r.followUp,w;if(d.answers.length)w=n("div",{className:"ckf-opts",role:"radiogroup","aria-labelledby":o},...d.answers.map((M,I)=>n("button",{type:"button",role:"radio",className:"ckf-opt","aria-checked":String(r.answer===M),"data-ckf-id":`a:${I}`,onClick:()=>e.cancel.answer(M)},n("span",{className:"ckf-radio"}),M)));else {let M=n("textarea",{className:"ckf-text",placeholder:"Your answer (optional)","aria-labelledby":o,maxlength:"500","data-ckf-id":"answer",onInput:I=>e.cancel.answer(I.target.value)});M.value=(p=r.answer)!=null?p:"",w=M;}let P=(C=r.flow.reasons.find(M=>M.id===r.reason))==null?void 0:C.label;u.push(P?n("p",{className:"ckf-small"},`You said: ${P}`):false,n("h2",{className:"ckf-h",id:o},d.question),w,n("div",{className:"ckf-btns"},E("Continue","primary",e.cancel.next,{id:"go"}),E("Never mind, keep my plan","quiet",e.cancel.close)));}else if(r.screen==="offer"&&r.offer){let d=r.offer,w=(S=r.flow.reasons.find(I=>I.id===r.reason))==null?void 0:S.label,P=false;d.kind==="discount"&&d.percent&&(P=r.price?n("div",{className:"ckf-box"},n("span",{className:"ckf-big"},U(ee(r.price.amount,d.percent),r.price.currency)),n("span",{className:"ckf-strike"},U(r.price.amount,r.price.currency)),n("span",{className:"ckf-small"},`/${(O=r.price.interval)!=null?O:"month"}${d.months?` for ${d.months} ${d.months===1?"month":"months"}`:""}`)):n("div",{className:"ckf-box"},n("span",{className:"ckf-big"},`${d.percent}% off`),d.months?n("span",{className:"ckf-small"},`for ${d.months} ${d.months===1?"month":"months"}`):false)),d.kind==="pause"&&d.months&&(P=n("div",{className:"ckf-box"},n("span",{},`Pause for up to ${d.months} ${d.months===1?"month":"months"}. Your data stays.`))),d.kind==="downgrade"&&d.planName&&(P=n("div",{className:"ckf-box"},n("b",{},d.planName)));let M=d.kind==="contact"?n("textarea",{className:"ckf-text",placeholder:"What would make you stay?","aria-label":"Your message",maxlength:"500","data-ckf-id":"comment",onInput:I=>k=I.target.value}):null;M&&(M.value=k),u.push(w?n("p",{className:"ckf-small"},`You said: ${w}`):false,n("h2",{className:"ckf-h",id:o},d.headline),P,M!=null?M:false,n("div",{className:"ckf-btns"},E(c?"One moment\u2026":te(d),"primary",()=>e.cancel.accept(k),{disabled:c,id:"go"}),E("No thanks, continue cancelling","quiet",e.cancel.decline,{disabled:c})));}else {let w=r.steps===1?null:n("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:P=>k=P.target.value});w&&(w.value=k),u.push(n("h2",{className:"ckf-h",id:o},r.flow.confirm.title),r.flow.confirm.body?n("p",{className:"ckf-p"},r.flow.confirm.body):false,w!=null?w:false,n("div",{className:"ckf-btns"},E(c?"One moment\u2026":r.flow.confirm.button,"danger",()=>e.cancel.confirm(k),{disabled:c,id:"go"}),E("Keep my plan","secondary",e.cancel.close,{disabled:c})));}return n("div",{className:"ckf-scrim",onClick:d=>d.target===d.currentTarget&&!c&&e.cancel.close()},n("div",{className:"ckf-card",role:"dialog","aria-modal":"true","aria-labelledby":o},n("div",{className:"ckf-top"},B(r.step,r.steps),j("Close",e.cancel.close)),...u,r.error?n("p",{className:"ckf-err",role:"alert"},r.error):false))},f=r=>{var u;let o=Z(r);if(!o)return false;if(o.kind==="image")return n("img",{className:"ckf-media",src:o.url,alt:(u=o.alt)!=null?u:"",onLoad:()=>b()});let c=q(o.url);return c.kind==="iframe"?n("div",{className:"ckf-video"},n("iframe",{src:c.src,title:o.alt||"Video",allow:"fullscreen; picture-in-picture",allowfullscreen:true,loading:"lazy",referrerpolicy:"strict-origin-when-cross-origin"})):n("video",{className:"ckf-media",src:c.src,controls:true,playsinline:true,preload:"metadata","aria-label":o.alt||void 0,onLoadedmetadata:()=>b()})},h=r=>{var S,O;let o=r.step,c=o.question,u="ckf-title",p=X(c,r.answer),C=false;if((c==null?void 0:c.type)==="choice")C=n("div",{className:"ckf-opts",role:"radiogroup","aria-labelledby":u},...((S=c.options)!=null?S:[]).map(d=>n("button",{type:"button",role:"radio",className:"ckf-opt","aria-checked":String(r.answer===d.id),"data-ckf-id":`q:${d.id}`,onClick:()=>e.onboarding.answer(d.id)},n("span",{className:"ckf-radio"}),d.label)));else if(c){let d=n("input",{type:"text",className:"ckf-input",placeholder:c.placeholder||"Your answer","aria-labelledby":u,maxlength:"500","data-ckf-id":"answer",onInput:w=>e.onboarding.answer(w.target.value),onKeydown:w=>w.key==="Enter"&&e.onboarding.next()});d.value=(O=r.answer)!=null?O:"",C=d;}return n("div",{className:"ckf-scrim"},n("div",{className:"ckf-card",role:"dialog","aria-modal":"true","aria-labelledby":u},n("div",{className:"ckf-top"},B(r.index+1,r.total),j("Close",e.onboarding.dismiss)),r.index===0?K(r.flow.icon,"ckf-hic"):false,n("h2",{className:"ckf-h",id:u},o.title),o.body?n("p",{className:"ckf-p"},o.body):false,C,n("div",{className:"ckf-row"},r.back?E("Back","quiet",e.onboarding.back):false,E(o.button||(r.last?"Done":"Continue"),"primary",e.onboarding.next,{disabled:!p,id:"go"}))))},x=r=>{let o=r.step;if(o.kind==="question")return h(r);let c=n("div",{className:"ckf-card",role:"dialog","aria-modal":o.kind==="modal"?"true":void 0,"aria-labelledby":"ckf-title"},n("div",{className:"ckf-top"},r.total>1?n("p",{className:"ckf-small"},`Step ${r.index+1} of ${r.total}`):n("span"),j("Close the tour",e.onboarding.dismiss)),o.kind==="modal"?K(r.flow.icon,"ckf-hic"):false,f(o),n("h2",{className:"ckf-h",id:"ckf-title"},o.title),o.body?n("p",{className:"ckf-p"},o.body):false,n("div",{className:"ckf-row"},E(o.button||(r.last?"Done":"Next"),"primary",e.onboarding.next,{id:"go"})));if(o.kind==="modal")return n("div",{className:"ckf-scrim"},c);let u=o.target?y(o.target):null,p=n("div",{className:"ckf-float"},c);if(u){let C=n("div",{className:"ckf-ring","aria-hidden":"true"});return p.append(n("span",{className:"ckf-arrow","aria-hidden":"true"})),l={float:p,ring:C,target:u,side:o.placement},R(p,C,u,o.placement),n("div",{},C,p)}return p},y=r=>{var c;let o=()=>document.querySelector(`[data-ck-target="${CSS.escape(r)}"]`);return /^[#.[]|[\s>:=]/.test(r)&&(c=N(r))!=null?c:o()},N=r=>{try{return document.querySelector(r)}catch(o){return null}},R=(r,o,c,u="auto")=>{var J;let p=c.getBoundingClientRect();Object.assign(o.style,{left:`${p.left-4}px`,top:`${p.top-4}px`,width:`${p.width+8}px`,height:`${p.height+8}px`});let C=window.innerWidth,S=window.innerHeight,O=Math.min(340,C-24),d=r.offsetHeight||200,w=14,P={bottom:S-p.bottom-w-12>=d,top:p.top-w-12>=d,right:C-p.right-w-12>=O,left:p.left-w-12>=O},M={top:"bottom",bottom:"top",left:"right",right:"left"},T=(J=(u==="auto"||!M[u]?["bottom","top"]:[u,M[u],"bottom","top","right","left"]).find(V=>P[V]))!=null?J:u!=="auto"&&M[u]?u:p.top>S-p.bottom?"top":"bottom",A=(V,de)=>Math.max(12,Math.min(V,de-12)),H=T==="top"||T==="bottom",D=H?A(u==="auto"?p.left:p.left+p.width/2-O/2,C-O):T==="right"?p.right+w:p.left-w-O,Y=H?T==="bottom"?p.bottom+w:p.top-w-d:A(p.top+p.height/2-d/2,S-d);Object.assign(r.style,{left:`${D}px`,top:`${Y}px`,right:"auto",bottom:"auto",width:`${O}px`}),r.setAttribute("data-side",T);let le=H?p.left+p.width/2-D:p.top+p.height/2-Y;r.style.setProperty("--ckf-ap",`${Math.max(18,Math.min(le,(H?O:d)-18))}px`);},v=r=>{let o=r.items.filter(c=>c.done).length;return n("div",{className:"ckf-float"},n("div",{className:"ckf-card",role:"region","aria-labelledby":"ckf-title"},n("div",{className:"ckf-top"},n("h2",{className:"ckf-h",id:"ckf-title"},r.flow.name),j("Hide the checklist",e.onboarding.dismiss)),n("p",{className:"ckf-small"},`${o} of ${r.items.length} done`),n("div",{className:"ckf-bar",role:"progressbar","aria-valuemin":"0","aria-valuemax":String(r.items.length),"aria-valuenow":String(o)},n("i",{style:`width:${o/r.items.length*100}%`})),n("div",{className:"ckf-opts"},...r.items.map(({step:c,done:u,opens:p})=>{let C=[n("span",{className:"ckf-tick","aria-hidden":"true"},u?"\u2713":""),K(c.icon,"ckf-ic"),n("span",{},n("b",{},c.title),c.body?n("span",{className:"ckf-small"},c.body):false)];return !u&&p?n("button",{type:"button",className:"ckf-check","aria-label":`Start \u201C${c.title}\u201D`,"data-ckf-id":`t:${c.id}`,onClick:()=>e.onboarding.open(c.id)},...C,n("span",{className:"ckf-go","aria-hidden":"true"},"\u203A")):u||c.event?n("div",{className:"ckf-check","data-done":u,role:"listitem"},...C):n("button",{type:"button",className:"ckf-check","aria-label":`Mark \u201C${c.title}\u201D done`,"data-ckf-id":`i:${c.id}`,onClick:()=>e.onboarding.check(c.id)},...C)}))))},L=r=>{let o=r.message,c=o.button?E(o.button,"primary",e.message.click,{id:"go"}):false;return o.style==="banner"?n("div",{className:"ckf-banner",role:"status"},n("span",{},n("b",{},o.title),o.body?n("span",{className:"ckf-small"},o.body):false),c,j("Dismiss",e.message.dismiss)):n("div",{className:"ckf-scrim",onClick:u=>u.target===u.currentTarget&&e.message.dismiss()},n("div",{className:"ckf-card",role:"dialog","aria-modal":"true","aria-labelledby":"ckf-title"},n("div",{className:"ckf-top"},n("span"),j("Dismiss",e.message.dismiss)),n("h2",{className:"ckf-h",id:"ckf-title"},o.title),o.body?n("p",{className:"ckf-p"},o.body):false,c?n("div",{className:"ckf-row"},c):false))},$=()=>{var C,S,O,d,w,P,M,I;let r=e.view(),o=(S=t.accent)!=null?S:(C=e.accent)==null?void 0:C.call(e);o&&i.style.setProperty("--ckf-accent",o);let c=(O=e.finale)==null?void 0:O.call(e);c&&c.key!==s&&(s=c.key,G(c.kind,{accent:o}));let u=r?ge(r):"",p=(w=(d=document.activeElement)==null?void 0:d.closest)!=null&&w.call(d,".ckf-root")?(P=document.activeElement)==null?void 0:P.getAttribute("data-ckf-id"):null;if(u&&!a&&document.activeElement instanceof HTMLElement&&!document.activeElement.closest(".ckf-root")&&(g=document.activeElement),u!==a&&!u.startsWith("c:")&&(k=""),u||(k=""),l=null,i.replaceChildren(...r?[r.type==="cancel"?m(r):r.type==="step"?x(r):r.type==="checklist"?v(r):L(r)]:[]),b(),u!==a){let T=i.querySelector("[aria-modal=true]"),A=(M=T==null?void 0:T.querySelector("[role=radio], input, [data-ckf-id=go], textarea"))!=null?M:null;A==null||A.focus(),!u&&g&&(g.focus(),g=null);}else p&&((I=i.querySelector(`[data-ckf-id="${CSS.escape(p)}"]`))==null||I.focus());a=u;},_=r=>{if(r.key!=="Escape")return;let o=e.view();(o==null?void 0:o.type)==="cancel"?e.cancel.close():(o==null?void 0:o.type)==="message"?e.message.dismiss():(o==null?void 0:o.type)==="step"&&(o.step.kind==="modal"||o.step.kind==="question")&&e.onboarding.dismiss();};function b(){l&&R(l.float,l.ring,l.target,l.side);}document.addEventListener("keydown",_),window.addEventListener("resize",b),window.addEventListener("scroll",b,{passive:true,capture:true});let F=e.subscribe($);return $(),()=>{F(),document.removeEventListener("keydown",_),window.removeEventListener("resize",b),window.removeEventListener("scroll",b,{capture:true}),i.remove();}},se=(e,t={})=>{if(typeof document=="undefined")return ()=>{};let i=oe(t),s="",a=null,g=()=>{var y,N,R;let m=e.view(),f=m?`${m.paywall.id}:${m.index}`:"";if(f&&!s&&document.activeElement instanceof HTMLElement&&(a=document.activeElement),!m){i.replaceChildren(),i.removeAttribute("data-still"),s&&(a==null||a.focus()),s="";return}s&&i.setAttribute("data-still","");let h=m.page,x=h.kind==="plans"?n("div",{className:"ckf-opts",role:"radiogroup","aria-label":"Plans"},...m.plans.map(v=>n("button",{type:"button",role:"radio",className:"ckf-opt ckf-plan","aria-checked":String(m.plan===v.id),"data-ckf-id":`p:${v.id}`,onClick:()=>e.pick(v.id)},n("span",{className:"ckf-radio"}),n("span",{},v.name,v.note?n("span",{className:"ckf-small"},v.note):false),n("b",{},v.price)))):false;i.replaceChildren(n("div",{className:"ckf-scrim",onClick:v=>v.target===v.currentTarget&&e.close()},n("div",{className:"ckf-card ckf-pw",role:"dialog","aria-modal":"true","aria-labelledby":"ckf-title"},n("div",{className:"ckf-top"},B(m.index+1,m.total),j("Close",e.close)),n("h2",{className:"ckf-h",id:"ckf-title"},h.title),h.body?n("p",{className:"ckf-p"},h.body):false,h.kind==="benefits"&&((y=h.items)!=null&&y.length)?n("div",{className:"ckf-opts"},...h.items.map(v=>n("p",{className:"ckf-li"},v))):false,h.kind==="proof"&&((N=h.quotes)!=null&&N.length)?n("div",{className:"ckf-opts"},...h.quotes.map(v=>n("blockquote",{className:"ckf-quote"},v.text,v.by?n("cite",{},v.by):false))):false,x,m.error?n("p",{className:"ckf-err",role:"alert"},m.error):false,n("div",{className:"ckf-btns"},E(m.busy?"One moment\u2026":h.button||"Continue","primary",e.next,{disabled:m.busy||h.kind==="plans"&&!m.plan,id:"go"}),m.index?E("Back","quiet",e.back,{disabled:m.busy}):false)))),f!==s&&((R=i.querySelector("[data-ckf-id=go]"))==null||R.focus()),s=f;},k=m=>{e.view()&&m.key==="Escape"&&e.close();};document.addEventListener("keydown",k);let l=e.subscribe(g);return g(),()=>{l(),document.removeEventListener("keydown",k),i.remove();}};var ke=e=>{let t=2166136261;for(let i=0;i<e.length;i++)t^=e.charCodeAt(i),t=Math.imul(t,16777619);return (t>>>0)/4294967296};var ae=(e,t)=>{var g,k,l,m;let i=(k=(g=e.variants[0])==null?void 0:g.id)!=null?k:"control";if(e.winner)return e.winner;if(e.status!=="running")return i;let s=ke(`${e.key}:${t}`),a=0;for(let f of e.variants)if(a+=f.weight,s<a)return f.id;return (m=(l=e.variants[e.variants.length-1])==null?void 0:l.id)!=null?m:i};var W="$paywall",he=(e,t,i)=>{var k,l;let s=t.experiment?e==null?void 0:e.experiments.find(m=>m.key===t.experiment):void 0,a=s?ae(s,i):(k=t.variants[0])==null?void 0:k.id,g=(l=t.variants.find(m=>m.id===a))!=null?l:t.variants[0];return g?{variant:g.id,pages:g.pages,test:(s==null?void 0:s.status)==="running"&&!s.winner?s.key:void 0}:null},ce=e=>{let t=null,i=new Set,s=()=>{for(let f of [...i])try{f();}catch(h){}},a=()=>t?{paywall:t.w.id,placement:t.w.placement,variant:t.variant}:{},g=()=>{t&&!t.seen.has(t.index)&&(t.seen.add(t.index),e.track(W,{...a(),page:t.pages[t.index].id,index:t.index,action:"page"})),s();},k=f=>{var x,y;let h=t;h&&(t=null,f.outcome==="closed"&&((y=(x=h.options).onClose)==null||y.call(x,f)),h.resolve(f),s());},l=f=>{var h;return {outcome:f,index:(h=t==null?void 0:t.index)!=null?h:0,...t?{paywall:t.w.id,variant:t.variant}:{},...f!=="closed"&&t?{plan:t.plans.find(x=>x.id===t.plan)}:{}}},m=()=>{let f=t,h=f==null?void 0:f.plans.find(x=>x.id===f.plan);if(!(!f||!h||f.busy)){if(!f.options.onPurchase)return k(l("chosen"));f.busy=true,f.error=void 0,s(),Promise.resolve().then(()=>{var x,y;return (y=(x=f.options).onPurchase)==null?void 0:y.call(x,h)}).then(x=>{if(f.busy=false,t===f){if(x===false)return s();e.track(W,{...a(),page:f.pages[f.index].id,index:f.index,plan:h.id,action:"purchased"}),k(l("purchased"));}},()=>{f.busy=false,f.error="The purchase did not go through. Please try again.",s();});}};return {open:(f,h={})=>{var b,F,r;if(t)return t.promise;let x=e.config(),y=(b=x==null?void 0:x.paywalls)==null?void 0:b.find(o=>o.placement===f),N=y?he(x,y,e.distinctId()):null;if(!y||!(N!=null&&N.pages.length))return Promise.resolve({outcome:"closed",index:0});let R=new Map(((F=h.plans)!=null?F:[]).map(o=>[o.id,o])),v=y.plans.map(o=>{var c;return (c=R.get(o.id))!=null?c:o}),L=()=>{},$=new Promise(o=>L=o),_=v.some(o=>o.id===y.highlight)?y.highlight:(r=v[0])==null?void 0:r.id;return t={w:y,variant:N.variant,pages:N.pages,plans:v,index:0,plan:_,busy:false,seen:new Set,options:h,resolve:L,promise:$},N.test&&e.track("$exposure",{experiment:N.test,variant:N.variant}),g(),$},view:()=>t?{paywall:t.w,variant:t.variant,page:t.pages[t.index],index:t.index,total:t.pages.length,last:t.index===t.pages.length-1,plans:t.plans,plan:t.plan,busy:t.busy,error:t.error}:null,subscribe:f=>(i.add(f),()=>i.delete(f)),next:()=>{if(!(!t||t.busy)){if(t.pages[t.index].kind==="plans"||t.index===t.pages.length-1)return m();t.index++,t.error=void 0,g();}},back:()=>{!t||t.busy||!t.index||(t.index--,t.error=void 0,s());},pick:f=>{!t||t.busy||!t.plans.some(h=>h.id===f)||(t.plan=f,t.error=void 0,s());},close:()=>{!t||t.busy||(e.track(W,{...a(),index:t.index,action:"closed"}),k(l("closed")));}}};var be=(e,t)=>{let i=ce(e);return se(i,t),i};window.__mrrocketFlows={mountFlows:ie,paywallHost:be};
|
|
91
|
+
`,n=(e,t={},...o)=>{let s=document.createElement(e);for(let[c,m]of Object.entries(t))m===void 0||m===false||(typeof m=="function"?s.addEventListener(c.slice(2).toLowerCase(),m):c==="className"?s.className=String(m):s.setAttribute(c,m===true?"":m));for(let c of o)(c||c==="")&&s.append(typeof c=="string"?document.createTextNode(c):c);return s},L=(e,t,o,s={})=>n("button",{type:"button",className:`ckf-btn${t==="secondary"?"":` ckf-${t}`}`,disabled:!!s.disabled,"data-ckf-id":s.id,onClick:()=>o()},e),W=(e,t)=>n("button",{type:"button",className:"ckf-x","aria-label":e,"data-ckf-id":"x",onClick:()=>t()},"\xD7"),ne=(e,t)=>t>1?n("span",{className:"ckf-dots",role:"img","aria-label":I("Step {n} of {total}",{n:e,total:t})},...Array.from({length:t},(o,s)=>n("span",{className:"ckf-dot","data-on":s<e}))):n("span"),be=e=>{var t;return e.type==="cancel"?`c:${e.screen}:${(t=e.round)!=null?t:1}`:e.type==="step"?`s:${e.step.id}`:e.type==="checklist"?`l:${e.flow.id}`:`m:${e.message.id}`},Re=e=>e.type==="cancel"?"c":e.type==="step"&&(e.step.kind==="question"||e.step.kind==="modal")?`d:${e.flow.id}`:be(e),we=e=>{var o;if(!document.getElementById(he)){let s=document.createElement("style");s.id=he,s.textContent=Se,document.head.appendChild(s);}let t=n("div",{className:`ckf-root${e.className?` ${e.className}`:""}`,"data-theme":(o=e.theme)!=null?o:"auto"});return e.accent&&t.style.setProperty("--ckf-accent",e.accent),document.body.appendChild(t),t},xe=e=>{let t=fe();t?e.setAttribute("lang",t):e.removeAttribute("lang");},te=(e,t)=>{let o=ke(e);if(!o)return false;let s=n("span",{className:t,"aria-hidden":"true"});if(o.kind==="emoji")s.textContent=o.value;else if(o.kind==="image")s.append(n("img",{src:o.url,alt:""}));else {let c="http://www.w3.org/2000/svg",m=document.createElementNS(c,"svg");for(let[h,d]of Object.entries({viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"}))m.setAttribute(h,d);for(let h of B[o.name].paths){let d=document.createElementNS(c,"path");d.setAttribute("d",h),m.append(d);}s.append(m);}return s},ye=(e,t={})=>{if(typeof document=="undefined")return ()=>{};let o=we(t),s="",c="",m="",h=null,d="",w=null,l=r=>{var g,O,$;let i="ckf-title",a=r.busy,u=[];if(r.screen==="survey")u.push(n("h2",{className:"ckf-h",id:i},r.flow.question||f("Why are you leaving?")),n("div",{className:"ckf-opts",role:"radiogroup","aria-labelledby":i},...r.flow.reasons.map(p=>n("button",{type:"button",role:"radio",className:"ckf-opt","aria-checked":String(r.reason===p.id),"data-ckf-id":`r:${p.id}`,onClick:()=>e.cancel.pick(p.id)},n("span",{className:"ckf-radio"}),p.label))),n("div",{className:"ckf-btns"},L(f("Continue"),"primary",e.cancel.next,{disabled:!r.reason,id:"go"}),L(f("Never mind, keep my plan"),"quiet",e.cancel.close)));else if(r.screen==="followup"&&r.followUp){let p=r.followUp,C;if(p.answers.length)C=n("div",{className:"ckf-opts",role:"radiogroup","aria-labelledby":i},...p.answers.map((F,b)=>{var S;return n("button",{type:"button",role:"radio",className:"ckf-opt","aria-checked":String(r.answer===F),"data-ckf-id":`a:${b}`,onClick:()=>e.cancel.answer(F)},n("span",{className:"ckf-radio"}),((S=p.answerLabels)==null?void 0:S[b])||F)}));else {let F=n("textarea",{className:"ckf-text",placeholder:f("Your answer (optional)"),"aria-labelledby":i,maxlength:"500","data-ckf-id":"answer",onInput:b=>e.cancel.answer(b.target.value)});F.value=(g=r.answer)!=null?g:"",C=F;}let v=(O=r.flow.reasons.find(F=>F.id===r.reason))==null?void 0:O.label;u.push(v?n("p",{className:"ckf-small"},I("You said: {label}",{label:v})):false,n("h2",{className:"ckf-h",id:i},p.question),C,n("div",{className:"ckf-btns"},L(f("Continue"),"primary",e.cancel.next,{id:"go"}),L(f("Never mind, keep my plan"),"quiet",e.cancel.close)));}else if(r.screen==="offer"&&r.offer){let p=r.offer,C=($=r.flow.reasons.find(b=>b.id===r.reason))==null?void 0:$.label,v=false;p.kind==="discount"&&p.percent&&(v=r.price?n("div",{className:"ckf-box"},n("span",{className:"ckf-big"},G(ge(r.price.amount,p.percent),r.price.currency)),n("span",{className:"ckf-strike"},G(r.price.amount,r.price.currency)),n("span",{className:"ckf-small"},`/${r.price.interval==="year"?f("year"):f("month")}${p.months?` ${ee(p.months)}`:""}`)):n("div",{className:"ckf-box"},n("span",{className:"ckf-big"},I("{percent}% off",{percent:p.percent})),p.months?n("span",{className:"ckf-small"},ee(p.months)):false)),p.kind==="pause"&&p.months&&(v=n("div",{className:"ckf-box"},n("span",{},I("Pause for up to {months}. Your data stays.",{months:Z(p.months)})))),p.kind==="downgrade"&&p.planName&&(v=n("div",{className:"ckf-box"},n("b",{},p.planName)));let F=p.kind==="contact"?n("textarea",{className:"ckf-text",placeholder:f("What would make you stay?"),"aria-label":f("Your message"),maxlength:"500","data-ckf-id":"comment",onInput:b=>d=b.target.value}):null;F&&(F.value=d),u.push(C?n("p",{className:"ckf-small"},I("You said: {label}",{label:C})):false,n("h2",{className:"ckf-h",id:i},p.headline),v,F!=null?F:false,n("div",{className:"ckf-btns"},L(a?f("One moment\u2026"):me(p),"primary",()=>e.cancel.accept(d),{disabled:a,id:"go"}),L(f("No thanks, continue cancelling"),"quiet",e.cancel.decline,{disabled:a})));}else {let C=r.steps===1?null:n("textarea",{className:"ckf-text",placeholder:f("Anything else we should know? (optional)"),"aria-label":f("Anything else we should know? (optional)"),maxlength:"500","data-ckf-id":"comment",onInput:v=>d=v.target.value});C&&(C.value=d),u.push(n("h2",{className:"ckf-h",id:i},r.flow.confirm.title),r.flow.confirm.body?n("p",{className:"ckf-p"},r.flow.confirm.body):false,C!=null?C:false,n("div",{className:"ckf-btns"},L(a?f("One moment\u2026"):r.flow.confirm.button,"danger",()=>e.cancel.confirm(d),{disabled:a,id:"go"}),L(f("Keep my plan"),"secondary",e.cancel.close,{disabled:a})));}return n("div",{className:"ckf-scrim",onClick:p=>p.target===p.currentTarget&&!a&&e.cancel.close()},n("div",{className:"ckf-card",role:"dialog","aria-modal":"true","aria-labelledby":i},n("div",{className:"ckf-top"},ne(r.step,r.steps),W(f("Close"),e.cancel.close)),...u,r.error?n("p",{className:"ckf-err",role:"alert"},r.error):false))},x=r=>{var u;let i=ue(r);if(!i)return false;if(i.kind==="image")return n("img",{className:"ckf-media",src:i.url,alt:(u=i.alt)!=null?u:"",onLoad:()=>P()});let a=X(i.url);return a.kind==="iframe"?n("div",{className:"ckf-video"},n("iframe",{src:a.src,title:i.alt||f("Video"),allow:"fullscreen; picture-in-picture",allowfullscreen:true,loading:"lazy",referrerpolicy:"strict-origin-when-cross-origin"})):n("video",{className:"ckf-media",src:a.src,controls:true,playsinline:true,preload:"metadata","aria-label":i.alt||void 0,onLoadedmetadata:()=>P()})},k=r=>{var C,v,F;let i=r.step,a=i.question,u="ckf-title",g=pe(a,r.answer),O=false,$=(b,S,_)=>{let A=n("input",{type:"text",className:"ckf-input",placeholder:b,"aria-labelledby":u,maxlength:"500","data-ckf-id":"answer",onInput:j=>_(j.target.value),onKeydown:j=>j.key==="Enter"&&e.onboarding.next()});return A.value=S,A},p=(b,S,_,A)=>n("button",{type:"button",role:"radio",className:"ckf-opt","aria-checked":String(b),"data-ckf-id":`q:${S}`,onClick:A},n("span",{className:"ckf-radio"}),_);if((a==null?void 0:a.type)==="choice"){let b=!!a.other&&!!((C=r.answer)!=null&&C.startsWith(U));O=n("div",{className:"ckf-opts",role:"radiogroup","aria-labelledby":u},...((v=a.options)!=null?v:[]).map(S=>p(r.answer===S.id,S.id,S.label,()=>e.onboarding.answer(S.id))),a.other?p(b,"other",a.otherLabel||f("Other"),()=>b||e.onboarding.answer(U)):false,b?$(f("Your answer"),r.answer.slice(U.length),S=>e.onboarding.answer(U+S)):false);}else (a==null?void 0:a.type)==="scale"?O=n("div",{},n("div",{className:"ckf-scale",role:"radiogroup","aria-labelledby":u},...Q(a).map(b=>n("button",{type:"button",role:"radio",className:"ckf-num","aria-checked":String(r.answer===String(b)),"data-ckf-id":`q:${b}`,onClick:()=>e.onboarding.answer(String(b))},String(b)))),a.lowLabel||a.highLabel?n("div",{className:"ckf-ends ckf-small"},n("span",{},a.lowLabel||""),n("span",{},a.highLabel||"")):false):a&&(O=$(a.placeholder||f("Your answer"),(F=r.answer)!=null?F:"",b=>e.onboarding.answer(b)));return n("div",{className:"ckf-scrim"},n("div",{className:"ckf-card",role:"dialog","aria-modal":"true","aria-labelledby":u},n("div",{className:"ckf-top"},ne(r.index+1,r.total),W(f("Close"),e.onboarding.dismiss)),r.index===0?te(r.flow.icon,"ckf-hic"):false,n("h2",{className:"ckf-h",id:u},i.title),i.body?n("p",{className:"ckf-p"},i.body):false,O,n("div",{className:"ckf-row"},r.back?L(f("Back"),"quiet",e.onboarding.back):false,a!=null&&a.optional?L(a.skipLabel||f("Skip"),"quiet",e.onboarding.skip):false,L(i.button||(r.last?f("Done"):f("Continue")),"primary",e.onboarding.next,{disabled:!g,id:"go"}))))},M=r=>{let i=r.step;if(i.kind==="question")return k(r);let a=n("div",{className:"ckf-card",role:"dialog","aria-modal":i.kind==="modal"?"true":void 0,"aria-labelledby":"ckf-title"},n("div",{className:"ckf-top"},r.total>1?n("p",{className:"ckf-small"},I("Step {n} of {total}",{n:r.index+1,total:r.total})):n("span"),W(f("Close the tour"),e.onboarding.dismiss)),i.kind==="modal"?te(r.flow.icon,"ckf-hic"):false,x(i),n("h2",{className:"ckf-h",id:"ckf-title"},i.title),i.body?n("p",{className:"ckf-p"},i.body):false,n("div",{className:"ckf-row"},L(i.button||(r.last?f("Done"):f("Next")),"primary",e.onboarding.next,{id:"go"})));if(i.kind==="modal")return n("div",{className:"ckf-scrim"},a);let u=i.target?E(i.target):null,g=n("div",{className:"ckf-float"},a);if(u){let O=n("div",{className:"ckf-ring","aria-hidden":"true"});return g.append(n("span",{className:"ckf-arrow","aria-hidden":"true"})),w={float:g,ring:O,target:u,side:i.placement},T(g,O,u,i.placement),n("div",{},O,g)}return g},E=r=>{var a;let i=()=>document.querySelector(`[data-ck-target="${CSS.escape(r)}"]`);return /^[#.[]|[\s>:=]/.test(r)&&(a=H(r))!=null?a:i()},H=r=>{try{return document.querySelector(r)}catch(i){return null}},T=(r,i,a,u="auto")=>{var se;let g=a.getBoundingClientRect();Object.assign(i.style,{left:`${g.left-4}px`,top:`${g.top-4}px`,width:`${g.width+8}px`,height:`${g.height+8}px`});let O=window.innerWidth,$=window.innerHeight,p=Math.min(340,O-24),C=r.offsetHeight||200,v=14,F={bottom:$-g.bottom-v-12>=C,top:g.top-v-12>=C,right:O-g.right-v-12>=p,left:g.left-v-12>=p},b={top:"bottom",bottom:"top",left:"right",right:"left"},_=(se=(u==="auto"||!b[u]?["bottom","top"]:[u,b[u],"bottom","top","right","left"]).find(D=>F[D]))!=null?se:u!=="auto"&&b[u]?u:g.top>$-g.bottom?"top":"bottom",A=(D,Oe)=>Math.max(12,Math.min(D,Oe-12)),j=_==="top"||_==="bottom",oe=j?A(u==="auto"?g.left:g.left+g.width/2-p/2,O-p):_==="right"?g.right+v:g.left-v-p,ie=j?_==="bottom"?g.bottom+v:g.top-v-C:A(g.top+g.height/2-C/2,$-C);Object.assign(r.style,{left:`${oe}px`,top:`${ie}px`,right:"auto",bottom:"auto",width:`${p}px`}),r.setAttribute("data-side",_);let Ne=j?g.left+g.width/2-oe:g.top+g.height/2-ie;r.style.setProperty("--ckf-ap",`${Math.max(18,Math.min(Ne,(j?p:C)-18))}px`);},N=r=>{let i=r.items.filter(a=>a.done).length;return n("div",{className:"ckf-float"},n("div",{className:"ckf-card",role:"region","aria-labelledby":"ckf-title"},n("div",{className:"ckf-top"},n("h2",{className:"ckf-h",id:"ckf-title"},r.flow.name),W(f("Hide the checklist"),e.onboarding.dismiss)),n("p",{className:"ckf-small"},I("{done} of {total} done",{done:i,total:r.items.length})),n("div",{className:"ckf-bar",role:"progressbar","aria-valuemin":"0","aria-valuemax":String(r.items.length),"aria-valuenow":String(i)},n("i",{style:`width:${i/r.items.length*100}%`})),n("div",{className:"ckf-opts"},...r.items.map(({step:a,done:u,opens:g})=>{let O=[n("span",{className:"ckf-tick","aria-hidden":"true"},u?"\u2713":""),te(a.icon,"ckf-ic"),n("span",{},n("b",{},a.title),a.body?n("span",{className:"ckf-small"},a.body):false)];return !u&&g?n("button",{type:"button",className:"ckf-check","aria-label":I("Start \u201C{title}\u201D",{title:a.title}),"data-ckf-id":`t:${a.id}`,onClick:()=>e.onboarding.open(a.id)},...O,n("span",{className:"ckf-go","aria-hidden":"true"},"\u203A")):u||a.event?n("div",{className:"ckf-check","data-done":u,role:"listitem"},...O):n("button",{type:"button",className:"ckf-check","aria-label":I("Mark \u201C{title}\u201D done",{title:a.title}),"data-ckf-id":`i:${a.id}`,onClick:()=>e.onboarding.check(a.id)},...O)}))))},z=r=>{let i=r.message,a=i.button?L(i.button,"primary",e.message.click,{id:"go"}):false;return i.style==="banner"?n("div",{className:"ckf-banner",role:"status"},n("span",{},n("b",{},i.title),i.body?n("span",{className:"ckf-small"},i.body):false),a,W(f("Dismiss"),e.message.dismiss)):n("div",{className:"ckf-scrim",onClick:u=>u.target===u.currentTarget&&e.message.dismiss()},n("div",{className:"ckf-card",role:"dialog","aria-modal":"true","aria-labelledby":"ckf-title"},n("div",{className:"ckf-top"},n("span"),W(f("Dismiss"),e.message.dismiss)),n("h2",{className:"ckf-h",id:"ckf-title"},i.title),i.body?n("p",{className:"ckf-p"},i.body):false,a?n("div",{className:"ckf-row"},a):false))},V=()=>{var $,p,C,v,F,b,S,_;let r=e.view();xe(o);let i=(p=t.accent)!=null?p:($=e.accent)==null?void 0:$.call(e);i&&o.style.setProperty("--ckf-accent",i);let a=(C=e.finale)==null?void 0:C.call(e);a&&a.key!==s&&(s=a.key,ce(a.kind,{accent:i}));let u=r?be(r):"",g=(F=(v=document.activeElement)==null?void 0:v.closest)!=null&&F.call(v,".ckf-root")?(b=document.activeElement)==null?void 0:b.getAttribute("data-ckf-id"):null;u&&!c&&document.activeElement instanceof HTMLElement&&!document.activeElement.closest(".ckf-root")&&(h=document.activeElement),u!==c&&!u.startsWith("c:")&&(d=""),u||(d=""),w=null;let O=r?Re(r):"";if(o.toggleAttribute("data-still",!!O&&O===m),m=O,o.replaceChildren(...r?[r.type==="cancel"?l(r):r.type==="step"?M(r):r.type==="checklist"?N(r):z(r)]:[]),P(),u!==c){let A=o.querySelector("[aria-modal=true]"),j=(S=A==null?void 0:A.querySelector("[role=radio], input, [data-ckf-id=go], textarea"))!=null?S:null;j==null||j.focus(),!u&&h&&(h.focus(),h=null);}else g&&((_=o.querySelector(`[data-ckf-id="${CSS.escape(g)}"]`))==null||_.focus());c=u;},y=r=>{if(r.key!=="Escape")return;let i=e.view();(i==null?void 0:i.type)==="cancel"?e.cancel.close():(i==null?void 0:i.type)==="message"?e.message.dismiss():(i==null?void 0:i.type)==="step"&&(i.step.kind==="modal"||i.step.kind==="question")&&e.onboarding.dismiss();};function P(){w&&T(w.float,w.ring,w.target,w.side);}document.addEventListener("keydown",y),window.addEventListener("resize",P),window.addEventListener("scroll",P,{passive:true,capture:true});let q=e.subscribe(V),R=J(V);return V(),()=>{q(),R(),document.removeEventListener("keydown",y),window.removeEventListener("resize",P),window.removeEventListener("scroll",P,{capture:true}),o.remove();}},ve=(e,t={})=>{if(typeof document=="undefined")return ()=>{};let o=we(t),s="",c=null,m=()=>{var E,H,T;let l=e.view();xe(o);let x=l?`${l.paywall.id}:${l.index}`:"";if(x&&!s&&document.activeElement instanceof HTMLElement&&(c=document.activeElement),!l){o.replaceChildren(),o.removeAttribute("data-still"),s&&(c==null||c.focus()),s="";return}s&&o.setAttribute("data-still","");let k=l.page,M=k.kind==="plans"?n("div",{className:"ckf-opts",role:"radiogroup","aria-label":f("Plans")},...l.plans.map(N=>n("button",{type:"button",role:"radio",className:"ckf-opt ckf-plan","aria-checked":String(l.plan===N.id),"data-ckf-id":`p:${N.id}`,onClick:()=>e.pick(N.id)},n("span",{className:"ckf-radio"}),n("span",{},N.name,N.note?n("span",{className:"ckf-small"},N.note):false),n("b",{},N.price)))):false;o.replaceChildren(n("div",{className:"ckf-scrim",onClick:N=>N.target===N.currentTarget&&e.close()},n("div",{className:"ckf-card ckf-pw",role:"dialog","aria-modal":"true","aria-labelledby":"ckf-title"},n("div",{className:"ckf-top"},ne(l.index+1,l.total),W(f("Close"),e.close)),n("h2",{className:"ckf-h",id:"ckf-title"},k.title),k.body?n("p",{className:"ckf-p"},k.body):false,k.kind==="benefits"&&((E=k.items)!=null&&E.length)?n("div",{className:"ckf-opts"},...k.items.map(N=>n("p",{className:"ckf-li"},N))):false,k.kind==="proof"&&((H=k.quotes)!=null&&H.length)?n("div",{className:"ckf-opts"},...k.quotes.map(N=>n("blockquote",{className:"ckf-quote"},N.text,N.by?n("cite",{},N.by):false))):false,M,l.error?n("p",{className:"ckf-err",role:"alert"},l.error):false,n("div",{className:"ckf-btns"},L(l.busy?f("One moment\u2026"):k.button||f("Continue"),"primary",e.next,{disabled:l.busy||k.kind==="plans"&&!l.plan,id:"go"}),l.index?L(f("Back"),"quiet",e.back,{disabled:l.busy}):false)))),x!==s&&((T=o.querySelector("[data-ckf-id=go]"))==null||T.focus()),s=x;},h=l=>{e.view()&&l.key==="Escape"&&e.close();};document.addEventListener("keydown",h);let d=e.subscribe(m),w=J(m);return m(),()=>{d(),w(),document.removeEventListener("keydown",h),o.remove();}};var Le=e=>{let t=2166136261;for(let o=0;o<e.length;o++)t^=e.charCodeAt(o),t=Math.imul(t,16777619);return (t>>>0)/4294967296};var Ce=(e,t)=>{var m,h,d,w;let o=(h=(m=e.variants[0])==null?void 0:m.id)!=null?h:"control";if(e.winner)return e.winner;if(e.status!=="running")return o;let s=Le(`${e.key}:${t}`),c=0;for(let l of e.variants)if(c+=l.weight,s<c)return l.id;return (w=(d=e.variants[e.variants.length-1])==null?void 0:d.id)!=null?w:o};var re="$paywall",Ie=(e,t,o)=>{var h,d;let s=t.experiment?e==null?void 0:e.experiments.find(w=>w.key===t.experiment):void 0,c=s?Ce(s,o):(h=t.variants[0])==null?void 0:h.id,m=(d=t.variants.find(w=>w.id===c))!=null?d:t.variants[0];return m?{variant:m.id,pages:m.pages,test:(s==null?void 0:s.status)==="running"&&!s.winner?s.key:void 0}:null},Me=e=>{let t=null,o=new Set,s=()=>{for(let l of [...o])try{l();}catch(x){}},c=()=>t?{paywall:t.w.id,placement:t.w.placement,variant:t.variant}:{},m=()=>{t&&!t.seen.has(t.index)&&(t.seen.add(t.index),e.track(re,{...c(),page:t.pages[t.index].id,index:t.index,action:"page"})),s();},h=l=>{var k,M;let x=t;x&&(t=null,l.outcome==="closed"&&((M=(k=x.options).onClose)==null||M.call(k,l)),x.resolve(l),s());},d=l=>{var x;return {outcome:l,index:(x=t==null?void 0:t.index)!=null?x:0,...t?{paywall:t.w.id,variant:t.variant}:{},...l!=="closed"&&t?{plan:t.plans.find(k=>k.id===t.plan)}:{}}},w=()=>{let l=t,x=l==null?void 0:l.plans.find(k=>k.id===l.plan);if(!(!l||!x||l.busy)){if(!l.options.onPurchase)return h(d("chosen"));l.busy=true,l.error=void 0,s(),Promise.resolve().then(()=>{var k,M;return (M=(k=l.options).onPurchase)==null?void 0:M.call(k,x)}).then(k=>{if(l.busy=false,t===l){if(k===false)return s();e.track(re,{...c(),page:l.pages[l.index].id,index:l.index,plan:x.id,action:"purchased"}),h(d("purchased"));}},()=>{l.busy=false,l.error=f("The purchase did not go through. Please try again."),s();});}};return {open:(l,x={})=>{var y,P,q;if(t)return t.promise;let k=e.config(),M=(y=k==null?void 0:k.paywalls)==null?void 0:y.find(R=>R.placement===l),E=M?Ie(k,M,e.distinctId()):null;if(!M||!(E!=null&&E.pages.length))return Promise.resolve({outcome:"closed",index:0});let H=new Map(((P=x.plans)!=null?P:[]).map(R=>[R.id,R])),T=M.plans.map(R=>{var r;return (r=H.get(R.id))!=null?r:R}),N=()=>{},z=new Promise(R=>N=R),V=T.some(R=>R.id===M.highlight)?M.highlight:(q=T[0])==null?void 0:q.id;return t={w:M,variant:E.variant,pages:E.pages,plans:T,index:0,plan:V,busy:false,seen:new Set,options:x,resolve:N,promise:z},E.test&&e.track("$exposure",{experiment:E.test,variant:E.variant}),m(),z},view:()=>t?{paywall:t.w,variant:t.variant,page:t.pages[t.index],index:t.index,total:t.pages.length,last:t.index===t.pages.length-1,plans:t.plans,plan:t.plan,busy:t.busy,error:t.error}:null,subscribe:l=>(o.add(l),()=>o.delete(l)),next:()=>{if(!(!t||t.busy)){if(t.pages[t.index].kind==="plans"||t.index===t.pages.length-1)return w();t.index++,t.error=void 0,m();}},back:()=>{!t||t.busy||!t.index||(t.index--,t.error=void 0,s());},pick:l=>{!t||t.busy||!t.plans.some(x=>x.id===l)||(t.plan=l,t.error=void 0,s());},close:()=>{!t||t.busy||(e.track(re,{...c(),index:t.index,action:"closed"}),h(d("closed")));}}};var Te=(e,t)=>{let o=Me(e);return ve(o,t),o};window.__mrrocketFlows={mountFlows:ye,paywallHost:Te};
|
|
87
92
|
})();
|