@mrrocket/sdk 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +11 -2
- package/dist/api-client.d.ts +7 -1
- package/dist/api-client.js.map +1 -1
- package/dist/browser/mrrocket-flows.js +9 -2
- package/dist/browser/mrrocket-messenger.js +42 -43
- package/dist/browser/mrrocket.js +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/flows-ui.d.ts +1 -1
- package/dist/flows-ui.js +64 -14
- package/dist/flows-ui.js.map +1 -1
- package/dist/flows.d.ts +49 -3
- package/dist/flows.js +86 -7
- package/dist/flows.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/messenger-ui.d.ts +10 -2
- package/dist/messenger-ui.js +57 -46
- package/dist/messenger-ui.js.map +1 -1
- package/dist/react-native/index.d.cts +46 -7
- package/dist/react-native/index.d.ts +46 -7
- package/dist/react-native/index.js +135 -31
- package/dist/react-native/index.js.map +1 -1
- package/dist/styles-css.d.ts +1 -1
- package/dist/styles-css.js +40 -41
- package/dist/styles-css.js.map +1 -1
- package/package.json +1 -1
package/dist/flows.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/flows.ts"],"sourcesContent":["/**\n * The cancel flow, onboarding and in-app messages (SDK 0.7.0): what MRRocket's config and `GET /api/sdk/inapp` serve,\n * and one controller that decides what shows when and records what the person did. No DOM and no React: the web\n * renderer (flows-ui.ts, loaded only when something shows) and the React Native one (native/flows-view.ts) draw\n * `view()` and call the actions.\n *\n * What it records, as normal events under the SDK's analytics consent (nothing with consent \"off\"):\n * - `$cancel_flow` `{action: \"shown\" | \"answered\" | \"offer\" | \"finished\", run, version, …}`. MRRocket turns a\n * `finished` with `outcome: \"saved\"` into a save and with `outcome: \"cancelled\"` into a cancellation on the Churn page.\n * MRRocket never changes a subscription: an accepted offer goes to the app's `onSave`, a confirmed cancel to its\n * `onCancel`, and only after they succeed is the outcome recorded.\n * - `$onboarding` `{flow, step?, action: \"started\" | \"shown\" | \"completed\" | \"dismissed\"}`; since 0.11.0 a wait records\n * \"shown\" when the person reaches it and \"completed\" when it is over, a condition `{action: \"answered\", answer: \"yes\"\n * | \"no\"}` once it is decided.\n * - `$inapp` `{message, journey, action: \"shown\" | \"clicked\" | \"dismissed\"}`; the journey moves the person on.\n */\nimport type { CockpitConfig } from \"./core\";\n\nexport const CANCEL_EVENT = \"$cancel_flow\";\nexport const ONBOARDING_EVENT = \"$onboarding\";\nexport const INAPP_EVENT = \"$inapp\";\n/** 0.16.0: the app asked the store's rating dialog for a review request (or found none on the device). */\nexport const REVIEW_EVENT = \"$review_prompt\";\n\nexport type CancelOfferKind = \"discount\" | \"pause\" | \"downgrade\" | \"contact\";\n\n/** What one answer of the survey offers before the confirmation. An answer without one goes straight to it. */\nexport interface CancelOffer {\n kind: CancelOfferKind;\n headline: string;\n /** Discount: percent off. */\n percent?: number;\n /** Discount: for how many months; pause: up to how many months. */\n months?: number;\n /** Downgrade: the plan to switch to, as MRRocket knows it from the payment provider. */\n plan?: string;\n planName?: string;\n}\n\n/** 0.15.0: a second question after an answer (\"What would be a fair price?\"); no answers means a free text. */\nexport interface CancelFollowUp {\n question: string;\n /** The choices, in order; empty for a free text. */\n answers: string[];\n}\n\n/**\n * 0.15.0: a different offer for some customers of one answer. Every condition that is set must hold; the first rule\n * that matches wins, else the answer's own offer. `offer: null` means no offer (straight to the confirmation).\n */\nexport interface CancelRule {\n when: {\n /** The follow-up's answer, as written. */\n answer?: string;\n /** The customer's plan: its id or name as MRRocket knows it from the payment provider (any of a list). */\n plan?: string | string[];\n /** A customer for fewer than this many months. */\n tenureUnder?: number;\n /** A customer for at least this many months. */\n tenureOver?: number;\n };\n offer: CancelOffer | null;\n}\n\n/** The product's live cancel flow (MRRocket › Churn › Cancel flow). */\nexport interface CancelFlowConfig {\n /** Grows with every publish. */\n version: number;\n question: string;\n /** The survey's answers, in order. */\n reasons: { id: string; label: string }[];\n /** The offer per answer id. */\n offers: Record<string, CancelOffer>;\n /** 0.15.0: the follow-up question per answer id. */\n followUps?: Record<string, CancelFollowUp>;\n /** 0.15.0: the rules per answer id, checked in order before its offer. */\n rules?: Record<string, CancelRule[]>;\n /** 0.15.0: what one answer offers after its first offer was declined. */\n second?: Record<string, CancelOffer>;\n confirm: { title: string; body: string; button: string };\n /**\n * Who gets the survey and the offers: business customers only. Anyone else (a consumer, or a customer the app does\n * not say) gets the plain confirmation, with nothing in the way of cancelling (§ 312k BGB).\n */\n offersFor: \"business\";\n}\n\nexport type OnboardingStepKind = \"tour\" | \"checklist\" | \"modal\";\n\nexport interface OnboardingStep {\n id: string;\n kind: OnboardingStepKind;\n title: string;\n body?: string;\n button?: string;\n /** Tour steps: the element they point at, marked `data-ck-target=\"<target>\"` in the app. Without it: a card. */\n target?: string;\n /** Checklist items: the event that ticks them (`track(\"<event>\")`). Without it the person ticks it themselves. */\n event?: string;\n}\n\n/** A pause in a flow (0.11.0): the next step shows this long after the one before was done. */\nexport interface OnboardingWait {\n id: string;\n kind: \"wait\";\n amount: number;\n unit: \"hours\" | \"days\";\n}\n\n/**\n * A fork in a flow (0.11.0): \"Did <event> happen?\" (the person's events, here and on MRRocket) or \"Is on <page>?\" (the\n * page they are on when they reach it; `*` at the end for everything below). Decided once, when the person reaches it;\n * like a journey's branch it ends its line, the rest of the flow lives in its two paths.\n */\nexport interface OnboardingCondition {\n id: string;\n kind: \"condition\";\n event?: string;\n page?: string;\n yes: OnboardingNode[];\n no: OnboardingNode[];\n}\n\nexport type OnboardingNode = OnboardingStep | OnboardingWait | OnboardingCondition;\n\n/** A live onboarding flow's in-app steps (MRRocket › Conversion › Onboarding); its emails go out from MRRocket. */\nexport interface OnboardingFlow {\n id: string;\n name: string;\n /** When it starts: an event's words (\"Signed up\", \"First export\"), or \"First launch\" / \"First visit\". */\n trigger: string;\n /** What every version shows: with `nodes`, only the in-app steps before the first wait or condition. */\n steps: OnboardingStep[];\n /** 0.11.0+: the whole flow with its waits and conditions. Read instead of `steps` when it is there. */\n nodes?: OnboardingNode[];\n}\n\n/** An in-app message a Lifecycle journey has for this person. */\nexport interface InAppMessage {\n id: string;\n journey?: string;\n title: string;\n body?: string;\n button?: string;\n /** Where the button goes (http(s) or a path of the app). */\n url?: string;\n style: \"banner\" | \"modal\";\n}\n\n/** One flow's progress: the steps done, whether the person closed it, and (0.11.0) its decided conditions and times. */\nexport interface FlowProgress {\n done: string[];\n dismissed?: boolean;\n /** Each decided condition's path. */\n answers?: Record<string, \"yes\" | \"no\">;\n /** When each step was done and each wait was reached (ms since 1970). */\n at?: Record<string, number>;\n reached?: Record<string, number>;\n}\n\n/** What `GET /api/sdk/inapp` answers for one signed-in person. */\nexport interface InAppState {\n /** Progress per onboarding flow: the steps done and whether the person closed it. */\n onboarding: Record<string, FlowProgress>;\n messages: InAppMessage[];\n /** 0.11.0: the events the flows' conditions ask about that this person did (as `eventSlug`s). */\n events?: string[];\n /**\n * 0.16.0: a live review request may ask this person now. The React Native client's `requestReview()` opens the\n * store's own rating dialog (expo-store-review) and reports `$review_prompt`; websites have no store dialog.\n */\n review?: { campaign: string; store: string };\n}\n\nexport interface CancelFlowResult {\n /** \"cancelled\": confirmed; \"saved\": took the offer; \"kept\": closed the dialog without cancelling. */\n outcome: \"cancelled\" | \"saved\" | \"kept\";\n reason?: string;\n reasonLabel?: string;\n /** 0.15.0: the answer to the follow-up question. */\n answer?: string;\n offer?: CancelOffer;\n /** What the customer wrote on the confirmation, if anything. */\n comment?: string;\n /** This run's id, the same as in the events. */\n run: string;\n}\n\nexport interface CancelFlowOptions {\n /**\n * \"business\" shows the survey and the offers; anything else (the default) only the plain confirmation, so a\n * consumer can always cancel in one step (§ 312k BGB).\n */\n customer?: \"business\" | \"consumer\";\n /** The customer's current price, to show what a discount costs (`{ amount: 29, currency: \"EUR\" }`). */\n price?: { amount: number; currency: string; interval?: \"month\" | \"year\" };\n /** 0.15.0: the customer's plan (its id or name in your billing), for offers that depend on it. */\n plan?: string;\n /** 0.15.0: since when they pay (a date, ISO string or ms), for offers that depend on how long. */\n customerSince?: Date | string | number;\n /** The customer confirmed: cancel the subscription in your code. Throwing keeps the dialog open with an error. */\n onCancel?: (result: CancelFlowResult) => unknown;\n /** The customer took an offer: apply it in your code (coupon, pause, plan change). Throwing keeps the dialog open. */\n onSave?: (result: CancelFlowResult) => unknown;\n /** Closed without cancelling or taking an offer. */\n onClose?: (result: CancelFlowResult) => void;\n}\n\n/** What a renderer draws. */\nexport type FlowView =\n | {\n type: \"cancel\";\n screen: \"survey\" | \"followup\" | \"offer\" | \"confirm\";\n flow: CancelFlowConfig;\n /** The answer picked on the survey. */\n reason?: string;\n /** 0.15.0: the follow-up question on its screen, and its answer so far. */\n followUp?: CancelFollowUp;\n answer?: string;\n offer?: CancelOffer;\n /** 0.15.0: 2 for the offer after a declined one. */\n round?: 1 | 2;\n price?: CancelFlowOptions[\"price\"];\n busy: boolean;\n error?: string;\n /** The step for the dots, 1 to `steps`. */\n step: number;\n steps: number;\n }\n | { type: \"step\"; flow: OnboardingFlow; step: OnboardingStep; index: number; total: number; last: boolean }\n | { type: \"checklist\"; flow: OnboardingFlow; items: { step: OnboardingStep; done: boolean }[] }\n | { type: \"message\"; message: InAppMessage };\n\nexport interface FlowsEnv {\n /** Records an event under the SDK's consent (the normal queue). */\n track(event: string, props?: Record<string, unknown>): void;\n /** Whose messages and progress these are: the identify() id; undefined while nobody is signed in. */\n personId(): string | undefined;\n /** `GET /api/sdk/inapp` for that person; null when it fails. */\n fetchInApp(personId: string): Promise<InAppState | null>;\n /** Keeps onboarding progress on the device; only while the consent lets the SDK store something. */\n storage?: () => { get(key: string): string | null | Promise<string | null>; set(key: string, value: string): void } | null;\n /** Opens a message's link. */\n openUrl?: (url: string) => void;\n /** Start onboarding flows by their trigger on its own (default true). */\n auto?: boolean;\n /** The page the person is on (a path), for \"Is on <page>?\" conditions; without it they answer no. */\n path?: () => string | undefined;\n /** The clock, for tests. */\n now?: () => number;\n}\n\nexport interface FlowsController {\n setConfig(config: CockpitConfig | null): void;\n /** The person changed (identify, reset, consent): loads their messages and onboarding progress. */\n refresh(): Promise<void>;\n /** An event the app recorded: starts flows whose trigger it is and ticks checklist items. */\n onEvent(name: string): void;\n openCancelFlow(options?: CancelFlowOptions): Promise<CancelFlowResult>;\n /** Starts an onboarding flow now (the first live one without an id). False when there is none to start. */\n startOnboarding(id?: string): boolean;\n view(): FlowView | null;\n subscribe(listener: () => void): () => void;\n /** `accept(comment)`: a \"Talk to us\" offer sends what the customer wrote with it. */\n /** `answer(text)`: the follow-up's choice or free text (0.15.0); `next()` moves on from the survey and the follow-up. */\n cancel: {\n pick(reason: string): void;\n answer(text: string): void;\n next(): void;\n accept(comment?: string): void;\n decline(): void;\n confirm(comment?: string): void;\n close(): void;\n };\n onboarding: { next(): void; check(stepId: string): void; dismiss(): void };\n message: { click(): void; dismiss(): void };\n}\n\n/** The plain confirmation, when the product has no live cancel flow (or MRRocket was not reachable). */\nexport const PLAIN_CANCEL: CancelFlowConfig = {\n version: 0,\n question: \"\",\n reasons: [],\n offers: {},\n confirm: { title: \"Cancel your subscription?\", body: \"You keep access until the end of the billing period.\", button: \"Cancel subscription\" },\n offersFor: \"business\",\n};\n\nconst FAILED = \"Something went wrong. Please try again.\";\nconst PROGRESS_KEY = \"mrrocket:onboarding\";\n\n/** `\"Signed up\"` → `signed_up`, the way MRRocket names events from words. */\nexport const eventSlug = (s: string) =>\n s\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n\nconst SIGNUP = new Set([\"signed_up\", \"signup\", \"sign_up\", \"registered\", \"account_created\", \"user_signed_up\"]);\n\n/** Whether recording `event` is the trigger's moment (\"Signed up\" is any of the usual signup event names). */\nexport const matchesTrigger = (trigger: string, event: string) => {\n const t = eventSlug(trigger);\n const e = eventSlug(event);\n if (!t || !e) return false;\n return t === e || (SIGNUP.has(t) && SIGNUP.has(e));\n};\n\n/** Triggers that mean \"the first time the person opens the app\": the flow starts on load until it is done. */\nexport const startsOnLoad = (trigger: string) =>\n /^(first (launch|visit|open|session|login|sign ?in|app open)|app (opened|launched)|on load|every visit|always)?$/i.test(trigger.trim());\n\n/** A random id for one cancel flow run. */\nexport const runId = () => `cf_${Math.random().toString(36).slice(2, 10)}${Date.now().toString(36)}`;\n\n/** The money a discount leaves: `29 EUR` at 30 % → `20.30`. */\nexport const discounted = (amount: number, percent: number) => Math.round(amount * (1 - percent / 100) * 100) / 100;\n\n/** \"€20.30\" in the customer's own currency, without a library. */\nexport const money = (amount: number, currency: string) => {\n try {\n return new Intl.NumberFormat(undefined, { style: \"currency\", currency }).format(amount);\n } catch {\n return `${amount.toFixed(2)} ${currency}`;\n }\n};\n\n/** The button of an offer. */\nexport const offerButton = (o: CancelOffer) =>\n o.kind === \"contact\" ? \"Send to the team\" : o.kind === \"pause\" ? \"Pause instead\" : o.kind === \"downgrade\" ? \"Switch plan\" : \"Accept offer\";\n\n/** A link a message may open: http(s), or a path of the app. Nothing else (no `javascript:`). */\nexport const safeUrl = (url: string | undefined) => {\n const u = (url ?? \"\").trim();\n return /^https?:\\/\\//i.test(u) || /^\\/(?!\\/)/.test(u) ? u : undefined;\n};\n\ninterface Progress {\n done: Set<string>;\n dismissed: boolean;\n answers: Map<string, boolean>;\n at: Map<string, number>;\n reached: Map<string, number>;\n}\n\n/** The flow's nodes: the whole tree (0.11.0 config), or the plain steps of an older one. */\nexport const nodesOf = (f: OnboardingFlow): OnboardingNode[] => f.nodes ?? f.steps;\n\n/** How long a wait lasts. */\nexport const waitMs = (w: Pick<OnboardingWait, \"amount\" | \"unit\">) => Math.max(0, w.amount) * (w.unit === \"hours\" ? 3_600_000 : 86_400_000);\n\n/** Whether a path matches a condition's page: the same path, or below it for a pattern ending in `*`. */\nexport const pageMatches = (pattern: string, path: string) => {\n const p = pattern.trim();\n const at = (path.split(/[?#]/)[0] || \"/\").replace(/\\/+$/, \"\") || \"/\";\n if (!p) return false;\n if (p.endsWith(\"*\")) {\n const base = p.slice(0, -1).replace(/\\/+$/, \"\");\n return !base || at === base || at.startsWith(`${base}/`);\n }\n return at === (p.replace(/\\/+$/, \"\") || \"/\");\n};\n\n/** Every in-app step of a tree, in reading order (for ticking checklist items by their event). */\nconst stepsIn = (nodes: OnboardingNode[]): OnboardingStep[] =>\n nodes.flatMap((n) => (n.kind === \"condition\" ? [...stepsIn(n.yes), ...stepsIn(n.no)] : n.kind === \"wait\" ? [] : [n]));\n\ntype Walked =\n | { at: \"step\"; step: OnboardingStep; line: OnboardingNode[]; index: number; before: number }\n | { at: \"wait\"; until: number }\n | { at: \"pending\" }\n | { at: \"end\" };\n\n/** Whole months since a date the app passed (NaN without one, which no tenure condition holds for). */\nexport const monthsSince = (since: CancelFlowOptions[\"customerSince\"], now = Date.now()) =>\n since == null || since === \"\" ? NaN : Math.floor((now - new Date(since).getTime()) / 2_630_016_000);\n\nconst low = (x?: string) => x?.trim().toLowerCase();\n\n/** Whether a rule holds for this customer; a condition about something the app did not pass does not hold. */\nexport const ruleMatches = ({ when: w = {} }: CancelRule, c: { answer?: string; plan?: string; months: number }) =>\n (w.answer == null || low(w.answer) === low(c.answer)) &&\n (w.plan == null || [w.plan].flat().some((x) => low(x) === low(c.plan))) &&\n !(c.months >= w.tenureUnder!) &&\n !(c.months < w.tenureOver!) &&\n (w.tenureUnder == null || c.months >= 0) &&\n (w.tenureOver == null || c.months >= 0);\n\ninterface CancelRun {\n flow: CancelFlowConfig;\n options: CancelFlowOptions;\n plain: boolean;\n screen: \"survey\" | \"followup\" | \"offer\" | \"confirm\";\n reason?: string;\n answer?: string;\n /** The offer on the offer screen, which rule picked it, and which round it is. */\n offer?: CancelOffer;\n rule?: number;\n round: 1 | 2;\n run: string;\n busy: boolean;\n error?: string;\n resolve: (r: CancelFlowResult) => void;\n promise: Promise<CancelFlowResult>;\n}\n\nexport const createFlows = (env: FlowsEnv): FlowsController => {\n let config: CockpitConfig | null = null;\n let cancel: CancelRun | null = null;\n let active: string | null = null;\n let messages: InAppMessage[] = [];\n let known = false;\n let loading: Promise<void> | null = null;\n let loadedFor: string | undefined;\n let progressFor: string | undefined | null = null;\n const progress = new Map<string, Progress>();\n const handled = new Set<string>();\n const shown = new Set<string>();\n const listeners = new Set<() => void>();\n const auto = env.auto !== false;\n /** Events the person did (as slugs), for \"Did <event> happen?\": from MRRocket and from this device. */\n const seenEvents = new Set<string>();\n const now = () => env.now?.() ?? Date.now();\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const flows = () => config?.onboarding ?? [];\n const flowById = (id: string | null) => (id ? flows().find((f) => f.id === id) : undefined);\n const prog = (id: string) => {\n let p = progress.get(id);\n if (!p) progress.set(id, (p = { done: new Set(), dismissed: false, answers: new Map(), at: new Map(), reached: new Map() }));\n return p;\n };\n\n let dirty = false;\n const record = (props: Record<string, unknown>) => {\n env.track(ONBOARDING_EVENT, props);\n dirty = true;\n };\n const markDone = (p: Progress, id: string) => {\n p.done.add(id);\n p.at.set(id, now());\n };\n\n /** A condition's answer for this person, or undefined while what it asks is not known yet. */\n const decide = (c: OnboardingCondition): boolean | undefined => {\n if (c.page?.trim()) {\n const path = env.path?.();\n return path === undefined ? false : pageMatches(c.page, path);\n }\n if (c.event?.trim()) {\n // The person's events from MRRocket arrive with their progress: before that, wait.\n if (!known) return undefined;\n for (const e of seenEvents) if (matchesTrigger(c.event, e)) return true;\n return false;\n }\n return false;\n };\n\n /**\n * Where the person is in a line of the flow. `live`: it is their flow now, so reaching a wait starts it, a decided\n * condition is kept and both are recorded; otherwise nothing changes (to ask whether a flow is finished).\n */\n const walkLine = (flow: OnboardingFlow, line: OnboardingNode[], p: Progress, live: boolean, before = 0, last?: number): Walked => {\n for (let i = 0; i < line.length; i++) {\n const n = line[i];\n if (n.kind === \"wait\") {\n if (p.done.has(n.id)) {\n last = p.at.get(n.id) ?? last;\n continue;\n }\n let since = p.reached.get(n.id);\n if (since === undefined) {\n if (!live) return { at: \"pending\" };\n // Counted from when the step before was done; a flow that opens with a wait counts from now.\n since = last ?? now();\n p.reached.set(n.id, since);\n record({ flow: flow.id, step: n.id, kind: \"wait\", action: \"shown\" });\n }\n const until = since + waitMs(n);\n if (now() < until) return { at: \"wait\", until };\n if (!live) return { at: \"pending\" };\n markDone(p, n.id);\n record({ flow: flow.id, step: n.id, kind: \"wait\", action: \"completed\" });\n last = until;\n continue;\n }\n if (n.kind === \"condition\") {\n let answer = p.answers.get(n.id);\n if (answer === undefined) {\n if (!live) return { at: \"pending\" };\n answer = decide(n);\n if (answer === undefined) return { at: \"pending\" };\n p.answers.set(n.id, answer);\n record({ flow: flow.id, step: n.id, kind: \"condition\", action: \"answered\", answer: answer ? \"yes\" : \"no\" });\n }\n return walkLine(flow, answer ? n.yes : n.no, p, live, before, last);\n }\n if (!p.done.has(n.id)) return { at: \"step\", step: n, line, index: i, before };\n last = p.at.get(n.id) ?? last;\n before++;\n }\n return { at: \"end\" };\n };\n\n const finished = (f: OnboardingFlow) => {\n const p = progress.get(f.id);\n return !!p && (p.dismissed || walkLine(f, nodesOf(f), p, false).at === \"end\");\n };\n\n const keyOf = (pid: string | undefined) => `${PROGRESS_KEY}:${pid ?? \"visitor\"}`;\n const eventsKeyOf = (pid: string | undefined) => `${PROGRESS_KEY}:events:${pid ?? \"visitor\"}`;\n\n const save = () => {\n dirty = false;\n const store = env.storage?.();\n if (!store) return;\n const out: Record<string, FlowProgress> = {};\n for (const [id, p] of progress)\n out[id] = {\n done: [...p.done],\n ...(p.dismissed ? { dismissed: true } : {}),\n ...(p.answers.size ? { answers: Object.fromEntries([...p.answers].map(([k, v]) => [k, v ? \"yes\" : \"no\"])) } : {}),\n ...(p.at.size ? { at: Object.fromEntries(p.at) } : {}),\n ...(p.reached.size ? { reached: Object.fromEntries(p.reached) } : {}),\n };\n try {\n store.set(keyOf(env.personId()), JSON.stringify(out));\n if (seenEvents.size) store.set(eventsKeyOf(env.personId()), JSON.stringify([...seenEvents].slice(-200)));\n } catch {\n // Storage full or gone: progress stays in memory.\n }\n };\n\n const merge = (state: InAppState[\"onboarding\"] | undefined) => {\n for (const [id, s] of Object.entries(state ?? {})) {\n const p = prog(id);\n for (const d of s.done ?? []) p.done.add(d);\n if (s.dismissed) p.dismissed = true;\n // A condition decided elsewhere keeps its path; the earliest time wins for waits.\n for (const [k, v] of Object.entries(s.answers ?? {})) if (!p.answers.has(k)) p.answers.set(k, v === \"yes\");\n for (const [k, v] of Object.entries(s.at ?? {})) if (typeof v === \"number\" && !p.at.has(k)) p.at.set(k, v);\n for (const [k, v] of Object.entries(s.reached ?? {})) if (typeof v === \"number\" && (!p.reached.has(k) || v < p.reached.get(k)!)) p.reached.set(k, v);\n }\n };\n\n // ---------- What shows ----------\n\n /** Comes back when a short wait is over, so the next step shows without a reload (long waits wait for the next visit). */\n const wakeAt = (until: number) => {\n if (timer) clearTimeout(timer);\n timer = null;\n const ms = until - now();\n if (ms > 0 && ms <= 86_400_000) {\n timer = setTimeout(() => ((timer = null), emit()), ms + 50);\n // Never what keeps a process alive (tests, server rendering).\n (timer as { unref?: () => void }).unref?.();\n }\n };\n\n /** The active flow waits (a wait step, or a condition whose answer is not known yet): it stays the active one. */\n let holding = false;\n\n const onboardingView = (): FlowView | null => {\n holding = false;\n const flow = flowById(active);\n if (!flow) return null;\n const p = prog(flow.id);\n if (p.dismissed) return null;\n const w = walkLine(flow, nodesOf(flow), p, true);\n if (dirty) save();\n if (w.at === \"wait\") wakeAt(w.until);\n holding = w.at === \"wait\" || w.at === \"pending\";\n if (w.at !== \"step\") return null;\n const { step, line, index } = w;\n const isStep = (n: OnboardingNode | undefined): n is OnboardingStep => !!n && n.kind !== \"wait\" && n.kind !== \"condition\";\n if (step.kind === \"checklist\") {\n let from = index;\n while (from > 0 && isStep(line[from - 1]) && line[from - 1].kind === \"checklist\") from--;\n let to = index;\n while (to + 1 < line.length && isStep(line[to + 1]) && line[to + 1].kind === \"checklist\") to++;\n const items = line.slice(from, to + 1).filter(isStep);\n for (const i of items)\n if (!p.done.has(i.id) && !shown.has(`s:${i.id}`)) {\n shown.add(`s:${i.id}`);\n env.track(ONBOARDING_EVENT, { flow: flow.id, step: i.id, kind: \"checklist\", action: \"shown\" });\n }\n return { type: \"checklist\", flow, items: items.map((s) => ({ step: s, done: p.done.has(s.id) })) };\n }\n if (!shown.has(`s:${step.id}`)) {\n shown.add(`s:${step.id}`);\n env.track(ONBOARDING_EVENT, { flow: flow.id, step: step.id, kind: step.kind, action: \"shown\" });\n }\n // The dots: the steps behind on the person's path, this one, and the ones left on its line before any fork.\n const rest = line.slice(index + 1);\n const fork = rest.findIndex((n) => n.kind === \"condition\");\n const ahead = (fork < 0 ? rest : rest.slice(0, fork)).filter(isStep).length;\n return { type: \"step\", flow, step, index: w.before, total: w.before + 1 + ahead, last: !ahead && fork < 0 };\n };\n\n const view = (): FlowView | null => {\n if (cancel) {\n const c = cancel;\n const followUp = c.flow.followUps?.[c.reason!];\n // The dots: the survey, a follow-up, one or two offers, the confirmation.\n const f = followUp ? 1 : 0;\n const steps = c.plain ? 1 : 3 + f + (c.flow.second?.[c.reason!] ? 1 : 0);\n return {\n type: \"cancel\",\n screen: c.screen,\n flow: c.flow,\n reason: c.reason,\n ...(c.screen === \"followup\" && followUp ? { followUp, answer: c.answer } : {}),\n offer: c.screen === \"offer\" ? c.offer : undefined,\n ...(c.screen === \"offer\" ? { round: c.round } : {}),\n price: c.options.price,\n busy: c.busy,\n error: c.error,\n step: c.screen === \"survey\" ? 1 : c.screen === \"followup\" ? 2 : c.screen === \"offer\" ? 1 + f + c.round : steps,\n steps,\n };\n }\n const ob = onboardingView();\n if (ob) return ob;\n // A flow that just ended lets the next thing show; one that waits keeps its place.\n if (active && !holding) active = null;\n const m = messages.find((x) => !handled.has(x.id));\n return m ? { type: \"message\", message: m } : null;\n };\n\n /** Tells the renderers, and records what became visible (once per step or message per page load). */\n const emit = () => {\n // Onboarding steps record their \"shown\" where they are worked out (onboardingView), so a step a wait let through\n // counts also when nothing else changed.\n const v = view();\n if (v?.type === \"message\" && !shown.has(`m:${v.message.id}`)) {\n shown.add(`m:${v.message.id}`);\n env.track(INAPP_EVENT, { message: v.message.id, journey: v.message.journey, action: \"shown\" });\n }\n for (const l of [...listeners]) {\n try {\n l();\n } catch {\n // One renderer must not stop another.\n }\n }\n };\n\n const start = (flow: OnboardingFlow) => {\n if (active === flow.id) return true;\n if (active && flowById(active) && !finished(flowById(active)!)) return false;\n active = flow.id;\n env.track(ONBOARDING_EVENT, { flow: flow.id, action: \"started\" });\n emit();\n return true;\n };\n\n /** Flows whose trigger is \"the first launch\" start once their progress is known (never twice for a done flow). */\n const autoStart = () => {\n if (!auto || active || !known) return;\n // Without an id or storage a \"first launch\" could not be told from any other: no automatic start then.\n if (!env.personId() && !env.storage?.()) return;\n const next = flows().find((f) => startsOnLoad(f.trigger) && nodesOf(f).length && !finished(f));\n if (next) start(next);\n };\n\n const refresh = () => {\n const pid = env.personId();\n // Another person on this device: nothing of the one before carries over.\n if (progressFor !== null && progressFor !== pid) {\n progress.clear();\n seenEvents.clear();\n handled.clear();\n active = null;\n loadedFor = undefined;\n }\n progressFor = pid;\n const run = (async () => {\n const store = env.storage?.();\n if (store) {\n try {\n const raw = await store.get(keyOf(pid));\n if (raw) merge(JSON.parse(raw) as InAppState[\"onboarding\"]);\n const seen = await store.get(eventsKeyOf(pid));\n if (seen) for (const e of JSON.parse(seen) as string[]) if (typeof e === \"string\") seenEvents.add(e);\n } catch {\n // A damaged entry is ignored.\n }\n }\n const wants = flows().length > 0 || !!config?.messages;\n if (pid && wants && loadedFor !== pid) {\n loadedFor = pid;\n const state = await env.fetchInApp(pid).catch(() => null);\n if (state && env.personId() === pid) {\n merge(state.onboarding);\n for (const e of state.events ?? []) if (typeof e === \"string\") seenEvents.add(eventSlug(e));\n messages = (state.messages ?? []).filter((m) => m && m.id && m.title);\n }\n }\n if (!pid) messages = [];\n known = true;\n autoStart();\n emit();\n })();\n loading = run.finally(() => {\n if (loading === run) loading = null;\n });\n return loading;\n };\n\n // ---------- Cancel flow ----------\n\n /** Which offer of the answer was on screen, for MRRocket to know what was saved: a rule's or the second. */\n const offerAt = (c: CancelRun) => (c.round > 1 ? { round: 2 } : c.rule! >= 0 ? { rule: c.rule } : {});\n\n /** Into the offer screen with the answer's first offer (the first rule that holds, else its own), or the confirmation. */\n const toOffer = (c: CancelRun) => {\n const who = { answer: c.answer, plan: c.options.plan, months: monthsSince(c.options.customerSince, env.now?.()) };\n const rules = c.flow.rules?.[c.reason!] ?? [];\n c.rule = rules.findIndex((r) => ruleMatches(r, who));\n c.offer = c.rule < 0 ? c.flow.offers[c.reason!] : (rules[c.rule].offer ?? undefined);\n c.round = 1;\n c.screen = c.offer ? \"offer\" : \"confirm\";\n };\n\n const result = (c: CancelRun, outcome: CancelFlowResult[\"outcome\"], comment?: string): CancelFlowResult => {\n const offer = c.screen === \"offer\" ? c.offer : undefined;\n return {\n outcome,\n run: c.run,\n ...(c.reason ? { reason: c.reason, reasonLabel: c.flow.reasons.find((r) => r.id === c.reason)?.label } : {}),\n ...(c.answer ? { answer: c.answer } : {}),\n ...(outcome === \"saved\" && offer ? { offer } : {}),\n ...(comment?.trim() ? { comment: comment.trim().slice(0, 500) } : {}),\n };\n };\n\n const finish = (c: CancelRun, r: CancelFlowResult) => {\n env.track(CANCEL_EVENT, {\n action: \"finished\",\n run: c.run,\n version: c.flow.version,\n outcome: r.outcome,\n customer: c.options.customer ?? \"consumer\",\n ...(r.reason ? { reason: r.reason } : {}),\n ...(r.answer ? { answer: r.answer } : {}),\n ...(r.offer ? { offer: r.offer.kind, ...offerAt(c) } : {}),\n ...(r.comment ? { comment: r.comment } : {}),\n });\n if (cancel === c) cancel = null;\n c.resolve(r);\n emit();\n };\n\n /** Runs the app's callback; the outcome is recorded only once it went through. */\n const settle = (c: CancelRun, outcome: \"saved\" | \"cancelled\", comment?: string) => {\n if (c.busy) return;\n const r = result(c, outcome, comment);\n const fn = outcome === \"saved\" ? c.options.onSave : c.options.onCancel;\n c.busy = true;\n c.error = undefined;\n emit();\n Promise.resolve()\n .then(() => fn?.(r))\n .then(\n () => finish(c, r),\n () => {\n c.busy = false;\n c.error = FAILED;\n emit();\n },\n );\n };\n\n const openCancelFlow = (options: CancelFlowOptions = {}) => {\n if (cancel) return cancel.promise;\n const flow = config?.cancelFlow ?? PLAIN_CANCEL;\n const plain = options.customer !== \"business\" || flow.reasons.length < 2;\n let resolve: (r: CancelFlowResult) => void = () => {};\n const promise = new Promise<CancelFlowResult>((r) => (resolve = r));\n const c: CancelRun = { flow, options, plain, screen: plain ? \"confirm\" : \"survey\", round: 1, run: runId(), busy: false, resolve, promise };\n cancel = c;\n env.track(CANCEL_EVENT, { action: \"shown\", run: c.run, version: flow.version, customer: options.customer ?? \"consumer\", plain });\n emit();\n return promise;\n };\n\n const controller: FlowsController = {\n setConfig: (c) => {\n config = c;\n if (active && !flowById(active)) active = null;\n emit();\n },\n refresh,\n onEvent: (name) => {\n if (!name || name.startsWith(\"$\")) return;\n // Remembered for \"Did <event> happen?\" conditions the person reaches later.\n const slug = eventSlug(name);\n const fresh = !!slug && !seenEvents.has(slug);\n if (fresh) seenEvents.add(slug);\n const flow = flowById(active);\n let ticked = false;\n if (flow) {\n const p = prog(flow.id);\n for (const s of stepsIn(nodesOf(flow)))\n if (s.kind === \"checklist\" && s.event && !p.done.has(s.id) && matchesTrigger(s.event, name)) {\n markDone(p, s.id);\n ticked = true;\n env.track(ONBOARDING_EVENT, { flow: flow.id, step: s.id, kind: s.kind, action: \"completed\" });\n }\n }\n if (ticked || (fresh && flow)) {\n save();\n emit();\n } else if (fresh) save();\n // Like the first-launch start: only for someone whose progress is kept, so nobody gets a flow twice.\n if (!auto || (!env.personId() && !env.storage?.())) return;\n const next = flows().find((f) => nodesOf(f).length && matchesTrigger(f.trigger, name) && !finished(f));\n if (next) start(next);\n },\n openCancelFlow,\n startOnboarding: (id) => {\n const f = id ? flowById(id) : flows().find((x) => nodesOf(x).length && !finished(x));\n if (!f || !nodesOf(f).length) return false;\n const p = prog(f.id);\n // Started by hand: a flow the person closed before shows again.\n p.dismissed = false;\n return start(f);\n },\n view,\n subscribe: (l) => {\n listeners.add(l);\n return () => listeners.delete(l);\n },\n cancel: {\n pick: (reason) => {\n if (!cancel || cancel.busy || cancel.screen !== \"survey\" || !cancel.flow.reasons.some((r) => r.id === reason)) return;\n if (cancel.reason !== reason) cancel.answer = undefined;\n cancel.reason = reason;\n cancel.error = undefined;\n emit();\n },\n answer: (text) => {\n const c = cancel;\n if (!c || c.busy || c.screen !== \"followup\") return;\n const t = text.slice(0, 500);\n c.answer = t.trim() ? t : undefined;\n emit();\n },\n next: () => {\n const c = cancel;\n if (!c || !c.reason) return;\n // The follow-up's answer rides along on the finished event.\n if (c.screen === \"survey\") {\n env.track(CANCEL_EVENT, { action: \"answered\", run: c.run, version: c.flow.version, reason: c.reason });\n if (c.flow.followUps?.[c.reason]) c.screen = \"followup\";\n else toOffer(c);\n } else if (c.screen === \"followup\") {\n c.answer = c.answer?.trim();\n toOffer(c);\n } else return;\n emit();\n },\n accept: (comment) => {\n const c = cancel;\n if (!c || c.screen !== \"offer\" || !c.reason) return;\n if (!c.busy)\n env.track(CANCEL_EVENT, {\n action: \"offer\",\n run: c.run,\n version: c.flow.version,\n reason: c.reason,\n offer: c.offer?.kind,\n ...offerAt(c),\n outcome: \"accepted\",\n });\n settle(c, \"saved\", comment);\n },\n decline: () => {\n const c = cancel;\n if (!c || c.screen !== \"offer\" || c.busy) return;\n env.track(CANCEL_EVENT, {\n action: \"offer\",\n run: c.run,\n version: c.flow.version,\n reason: c.reason,\n offer: c.offer?.kind,\n ...offerAt(c),\n outcome: \"declined\",\n });\n // A second offer once, then the confirmation.\n const second = c.round === 1 && c.reason ? c.flow.second?.[c.reason] : undefined;\n if (second) {\n c.offer = second;\n c.rule = undefined;\n c.round = 2;\n } else c.screen = \"confirm\";\n c.error = undefined;\n emit();\n },\n confirm: (comment) => {\n const c = cancel;\n if (!c || c.screen !== \"confirm\") return;\n settle(c, \"cancelled\", comment);\n },\n close: () => {\n const c = cancel;\n if (!c || c.busy) return;\n const r = result(c, \"kept\");\n c.options.onClose?.(r);\n finish(c, r);\n },\n },\n onboarding: {\n next: () => {\n const v = onboardingView();\n if (v?.type !== \"step\") return;\n markDone(prog(v.flow.id), v.step.id);\n env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: v.step.id, kind: v.step.kind, action: \"completed\" });\n save();\n emit();\n },\n check: (stepId) => {\n const v = onboardingView();\n if (v?.type !== \"checklist\") return;\n const item = v.items.find((i) => i.step.id === stepId);\n if (!item || item.done) return;\n markDone(prog(v.flow.id), stepId);\n env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: stepId, kind: \"checklist\", action: \"completed\" });\n save();\n emit();\n },\n dismiss: () => {\n const v = onboardingView();\n if (!v || (v.type !== \"step\" && v.type !== \"checklist\")) return;\n prog(v.flow.id).dismissed = true;\n env.track(ONBOARDING_EVENT, { flow: v.flow.id, ...(v.type === \"step\" ? { step: v.step.id } : {}), action: \"dismissed\" });\n active = null;\n save();\n emit();\n },\n },\n message: {\n click: () => {\n const v = view();\n if (v?.type !== \"message\") return;\n handled.add(v.message.id);\n env.track(INAPP_EVENT, { message: v.message.id, journey: v.message.journey, action: \"clicked\" });\n const url = safeUrl(v.message.url);\n emit();\n if (url) env.openUrl?.(url);\n },\n dismiss: () => {\n const v = view();\n if (v?.type !== \"message\") return;\n handled.add(v.message.id);\n env.track(INAPP_EVENT, { message: v.message.id, journey: v.message.journey, action: \"dismissed\" });\n emit();\n },\n },\n };\n return controller;\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/flows.ts"],"sourcesContent":["/**\n * The cancel flow, onboarding and in-app messages (SDK 0.7.0): what MRRocket's config and `GET /api/sdk/inapp` serve,\n * and one controller that decides what shows when and records what the person did. No DOM and no React: the web\n * renderer (flows-ui.ts, loaded only when something shows) and the React Native one (native/flows-view.ts) draw\n * `view()` and call the actions.\n *\n * What it records, as normal events under the SDK's analytics consent (nothing with consent \"off\"):\n * - `$cancel_flow` `{action: \"shown\" | \"answered\" | \"offer\" | \"finished\", run, version, …}`. MRRocket turns a\n * `finished` with `outcome: \"saved\"` into a save and with `outcome: \"cancelled\"` into a cancellation on the Churn page.\n * MRRocket never changes a subscription: an accepted offer goes to the app's `onSave`, a confirmed cancel to its\n * `onCancel`, and only after they succeed is the outcome recorded.\n * - `$onboarding` `{flow, step?, action: \"started\" | \"shown\" | \"completed\" | \"dismissed\"}`; since 0.11.0 a wait records\n * \"shown\" when the person reaches it and \"completed\" when it is over, a condition `{action: \"answered\", answer: \"yes\"\n * | \"no\"}` once it is decided; since 0.17.0 a tour a checklist task started records `{action: \"started\", from}` with\n * the checklist's id.\n * - `$inapp` `{message, journey, action: \"shown\" | \"clicked\" | \"dismissed\"}`; the journey moves the person on.\n */\nimport type { CockpitConfig } from \"./core\";\n\nexport const CANCEL_EVENT = \"$cancel_flow\";\nexport const ONBOARDING_EVENT = \"$onboarding\";\nexport const INAPP_EVENT = \"$inapp\";\n/** 0.16.0: the app asked the store's rating dialog for a review request (or found none on the device). */\nexport const REVIEW_EVENT = \"$review_prompt\";\n\nexport type CancelOfferKind = \"discount\" | \"pause\" | \"downgrade\" | \"contact\";\n\n/** What one answer of the survey offers before the confirmation. An answer without one goes straight to it. */\nexport interface CancelOffer {\n kind: CancelOfferKind;\n headline: string;\n /** Discount: percent off. */\n percent?: number;\n /** Discount: for how many months; pause: up to how many months. */\n months?: number;\n /** Downgrade: the plan to switch to, as MRRocket knows it from the payment provider. */\n plan?: string;\n planName?: string;\n}\n\n/** 0.15.0: a second question after an answer (\"What would be a fair price?\"); no answers means a free text. */\nexport interface CancelFollowUp {\n question: string;\n /** The choices, in order; empty for a free text. */\n answers: string[];\n}\n\n/**\n * 0.15.0: a different offer for some customers of one answer. Every condition that is set must hold; the first rule\n * that matches wins, else the answer's own offer. `offer: null` means no offer (straight to the confirmation).\n */\nexport interface CancelRule {\n when: {\n /** The follow-up's answer, as written. */\n answer?: string;\n /** The customer's plan: its id or name as MRRocket knows it from the payment provider (any of a list). */\n plan?: string | string[];\n /** A customer for fewer than this many months. */\n tenureUnder?: number;\n /** A customer for at least this many months. */\n tenureOver?: number;\n };\n offer: CancelOffer | null;\n}\n\n/** The product's live cancel flow (MRRocket › Churn › Cancel flow). */\nexport interface CancelFlowConfig {\n /** Grows with every publish. */\n version: number;\n question: string;\n /** The survey's answers, in order. */\n reasons: { id: string; label: string }[];\n /** The offer per answer id. */\n offers: Record<string, CancelOffer>;\n /** 0.15.0: the follow-up question per answer id. */\n followUps?: Record<string, CancelFollowUp>;\n /** 0.15.0: the rules per answer id, checked in order before its offer. */\n rules?: Record<string, CancelRule[]>;\n /** 0.15.0: what one answer offers after its first offer was declined. */\n second?: Record<string, CancelOffer>;\n confirm: { title: string; body: string; button: string };\n /**\n * Who gets the survey and the offers: business customers only. Anyone else (a consumer, or a customer the app does\n * not say) gets the plain confirmation, with nothing in the way of cancelling (§ 312k BGB).\n */\n offersFor: \"business\";\n}\n\nexport type OnboardingStepKind = \"tour\" | \"checklist\" | \"modal\";\n\n/** 0.17.0: where a tour bubble sits next to its element; \"auto\" (and no value) puts it below, else above, as before. */\nexport type TourPlacement = \"auto\" | \"top\" | \"bottom\" | \"left\" | \"right\";\n\n/**\n * 0.17.0: a picture or a video above a tour step's or a modal's text. A video is a YouTube, Vimeo or Loom link (shown\n * through their privacy-friendly players, see `videoEmbed`) or a video file (mp4, webm).\n */\nexport interface FlowMedia {\n kind: \"image\" | \"video\";\n url: string;\n alt?: string;\n}\n\nexport interface OnboardingStep {\n id: string;\n kind: OnboardingStepKind;\n title: string;\n body?: string;\n button?: string;\n /** Tour steps: the element they point at, marked `data-ck-target=\"<target>\"` in the app. Without it: a card. */\n target?: string;\n /** Checklist items: the event that ticks them (`track(\"<event>\")`). Without it the person ticks it themselves. */\n event?: string;\n /** 0.17.0, tour steps: which side of the element the bubble takes (flips when there is no room). */\n placement?: TourPlacement;\n /** 0.17.0, tour steps and modals: a picture or a video above the text. */\n media?: FlowMedia;\n /** 0.17.0, modals: where the button goes (http(s) or a path of the app). */\n url?: string;\n /** 0.17.0, checklist items: the id of a tour (another flow) the item starts; finishing the tour ticks the item. */\n startsTour?: string;\n}\n\n/** A pause in a flow (0.11.0): the next step shows this long after the one before was done. */\nexport interface OnboardingWait {\n id: string;\n kind: \"wait\";\n amount: number;\n unit: \"hours\" | \"days\";\n}\n\n/**\n * A fork in a flow (0.11.0): \"Did <event> happen?\" (the person's events, here and on MRRocket) or \"Is on <page>?\" (the\n * page they are on when they reach it; `*` at the end for everything below). Decided once, when the person reaches it;\n * like a journey's branch it ends its line, the rest of the flow lives in its two paths.\n */\nexport interface OnboardingCondition {\n id: string;\n kind: \"condition\";\n event?: string;\n page?: string;\n yes: OnboardingNode[];\n no: OnboardingNode[];\n}\n\nexport type OnboardingNode = OnboardingStep | OnboardingWait | OnboardingCondition;\n\n/** A live onboarding flow's in-app steps (MRRocket › Conversion › Onboarding); its emails go out from MRRocket. */\nexport interface OnboardingFlow {\n id: string;\n /** A checklist's heading; for tours and modals the name MRRocket knows them by. */\n name: string;\n /**\n * When it starts: an event's words (\"Signed up\", \"First export\"), \"First launch\" / \"First visit\", since 0.17.0 a page\n * of the app (\"/settings\", \"/projects/*\"), or \"From a checklist\" (only a checklist task starts it).\n */\n trigger: string;\n /** 0.17.0: \"signed_in\" starts it on its own only for someone the app identified. */\n audience?: \"signed_in\";\n /** What every version shows: with `nodes`, only the in-app steps before the first wait or condition. */\n steps: OnboardingStep[];\n /** 0.11.0+: the whole flow with its waits and conditions. Read instead of `steps` when it is there. */\n nodes?: OnboardingNode[];\n}\n\n/** An in-app message a Lifecycle journey has for this person. */\nexport interface InAppMessage {\n id: string;\n journey?: string;\n title: string;\n body?: string;\n button?: string;\n /** Where the button goes (http(s) or a path of the app). */\n url?: string;\n style: \"banner\" | \"modal\";\n}\n\n/** One flow's progress: the steps done, whether the person closed it, and (0.11.0) its decided conditions and times. */\nexport interface FlowProgress {\n done: string[];\n dismissed?: boolean;\n /** Each decided condition's path. */\n answers?: Record<string, \"yes\" | \"no\">;\n /** When each step was done and each wait was reached (ms since 1970). */\n at?: Record<string, number>;\n reached?: Record<string, number>;\n}\n\n/** What `GET /api/sdk/inapp` answers for one signed-in person. */\nexport interface InAppState {\n /** Progress per onboarding flow: the steps done and whether the person closed it. */\n onboarding: Record<string, FlowProgress>;\n messages: InAppMessage[];\n /** 0.11.0: the events the flows' conditions ask about that this person did (as `eventSlug`s). */\n events?: string[];\n /**\n * 0.16.0: a live review request may ask this person now. The React Native client's `requestReview()` opens the\n * store's own rating dialog (expo-store-review) and reports `$review_prompt`; websites have no store dialog.\n */\n review?: { campaign: string; store: string };\n}\n\nexport interface CancelFlowResult {\n /** \"cancelled\": confirmed; \"saved\": took the offer; \"kept\": closed the dialog without cancelling. */\n outcome: \"cancelled\" | \"saved\" | \"kept\";\n reason?: string;\n reasonLabel?: string;\n /** 0.15.0: the answer to the follow-up question. */\n answer?: string;\n offer?: CancelOffer;\n /** What the customer wrote on the confirmation, if anything. */\n comment?: string;\n /** This run's id, the same as in the events. */\n run: string;\n}\n\nexport interface CancelFlowOptions {\n /**\n * \"business\" shows the survey and the offers; anything else (the default) only the plain confirmation, so a\n * consumer can always cancel in one step (§ 312k BGB).\n */\n customer?: \"business\" | \"consumer\";\n /** The customer's current price, to show what a discount costs (`{ amount: 29, currency: \"EUR\" }`). */\n price?: { amount: number; currency: string; interval?: \"month\" | \"year\" };\n /** 0.15.0: the customer's plan (its id or name in your billing), for offers that depend on it. */\n plan?: string;\n /** 0.15.0: since when they pay (a date, ISO string or ms), for offers that depend on how long. */\n customerSince?: Date | string | number;\n /** The customer confirmed: cancel the subscription in your code. Throwing keeps the dialog open with an error. */\n onCancel?: (result: CancelFlowResult) => unknown;\n /** The customer took an offer: apply it in your code (coupon, pause, plan change). Throwing keeps the dialog open. */\n onSave?: (result: CancelFlowResult) => unknown;\n /** Closed without cancelling or taking an offer. */\n onClose?: (result: CancelFlowResult) => void;\n}\n\n/** What a renderer draws. */\nexport type FlowView =\n | {\n type: \"cancel\";\n screen: \"survey\" | \"followup\" | \"offer\" | \"confirm\";\n flow: CancelFlowConfig;\n /** The answer picked on the survey. */\n reason?: string;\n /** 0.15.0: the follow-up question on its screen, and its answer so far. */\n followUp?: CancelFollowUp;\n answer?: string;\n offer?: CancelOffer;\n /** 0.15.0: 2 for the offer after a declined one. */\n round?: 1 | 2;\n price?: CancelFlowOptions[\"price\"];\n busy: boolean;\n error?: string;\n /** The step for the dots, 1 to `steps`. */\n step: number;\n steps: number;\n }\n | { type: \"step\"; flow: OnboardingFlow; step: OnboardingStep; index: number; total: number; last: boolean }\n /** `tour`: the tour the item starts (0.17.0), when the config has it. */\n | { type: \"checklist\"; flow: OnboardingFlow; items: { step: OnboardingStep; done: boolean; tour?: OnboardingFlow }[] }\n | { type: \"message\"; message: InAppMessage };\n\nexport interface FlowsEnv {\n /** Records an event under the SDK's consent (the normal queue). */\n track(event: string, props?: Record<string, unknown>): void;\n /** Whose messages and progress these are: the identify() id; undefined while nobody is signed in. */\n personId(): string | undefined;\n /** `GET /api/sdk/inapp` for that person; null when it fails. */\n fetchInApp(personId: string): Promise<InAppState | null>;\n /** Keeps onboarding progress on the device; only while the consent lets the SDK store something. */\n storage?: () => { get(key: string): string | null | Promise<string | null>; set(key: string, value: string): void } | null;\n /** Opens a message's link. */\n openUrl?: (url: string) => void;\n /** Start onboarding flows by their trigger on its own (default true). */\n auto?: boolean;\n /** The page the person is on (a path), for \"Is on <page>?\" conditions; without it they answer no. */\n path?: () => string | undefined;\n /** The clock, for tests. */\n now?: () => number;\n}\n\nexport interface FlowsController {\n setConfig(config: CockpitConfig | null): void;\n /** The person changed (identify, reset, consent): loads their messages and onboarding progress. */\n refresh(): Promise<void>;\n /** An event the app recorded: starts flows whose trigger it is and ticks checklist items. */\n onEvent(name: string): void;\n openCancelFlow(options?: CancelFlowOptions): Promise<CancelFlowResult>;\n /** Starts an onboarding flow now (the first live one without an id). False when there is none to start. */\n startOnboarding(id?: string): boolean;\n view(): FlowView | null;\n subscribe(listener: () => void): () => void;\n /** `accept(comment)`: a \"Talk to us\" offer sends what the customer wrote with it. */\n /** `answer(text)`: the follow-up's choice or free text (0.15.0); `next()` moves on from the survey and the follow-up. */\n cancel: {\n pick(reason: string): void;\n answer(text: string): void;\n next(): void;\n accept(comment?: string): void;\n decline(): void;\n confirm(comment?: string): void;\n close(): void;\n };\n /** `startTour(stepId)` (0.17.0): the tour a checklist item starts; the checklist comes back when it ends. */\n onboarding: { next(): void; check(stepId: string): void; dismiss(): void; startTour(stepId: string): boolean };\n message: { click(): void; dismiss(): void };\n}\n\n/** The plain confirmation, when the product has no live cancel flow (or MRRocket was not reachable). */\nexport const PLAIN_CANCEL: CancelFlowConfig = {\n version: 0,\n question: \"\",\n reasons: [],\n offers: {},\n confirm: { title: \"Cancel your subscription?\", body: \"You keep access until the end of the billing period.\", button: \"Cancel subscription\" },\n offersFor: \"business\",\n};\n\nconst FAILED = \"Something went wrong. Please try again.\";\nconst PROGRESS_KEY = \"mrrocket:onboarding\";\n\n/** `\"Signed up\"` → `signed_up`, the way MRRocket names events from words. */\nexport const eventSlug = (s: string) =>\n s\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n\nconst SIGNUP = new Set([\"signed_up\", \"signup\", \"sign_up\", \"registered\", \"account_created\", \"user_signed_up\"]);\n\n/** Whether recording `event` is the trigger's moment (\"Signed up\" is any of the usual signup event names). */\nexport const matchesTrigger = (trigger: string, event: string) => {\n const t = eventSlug(trigger);\n const e = eventSlug(event);\n if (!t || !e) return false;\n return t === e || (SIGNUP.has(t) && SIGNUP.has(e));\n};\n\n/** Triggers that mean \"the first time the person opens the app\": the flow starts on load until it is done. */\nexport const startsOnLoad = (trigger: string) =>\n /^(first (launch|visit|open|session|login|sign ?in|app open)|app (opened|launched)|on load|every visit|always)?$/i.test(trigger.trim());\n\n/** 0.17.0: the trigger of a tour only a checklist task starts. */\nexport const FROM_CHECKLIST = \"From a checklist\";\n\n/** 0.17.0: a trigger that is a page of the app (\"/settings\", \"/projects/*\"): the flow starts when the person opens it. */\nexport const isPageTrigger = (trigger: string) => trigger.trim().startsWith(\"/\");\n\n/** A \"From a checklist\" tour never starts on its own. */\nconst manual = (trigger: string) => eventSlug(trigger) === \"from_a_checklist\";\n\n/**\n * 0.17.0: how a video link plays. YouTube, Vimeo and Loom through their players without tracking cookies\n * (youtube-nocookie.com, Vimeo's `dnt=1`), a file (mp4, webm, mov, or an upload in MRRocket) in a plain video element;\n * anything else is not shown. Regular expressions only, so React Native can use it too.\n */\nexport const videoEmbed = (url: string | undefined): { kind: \"iframe\" | \"file\"; src: string } | null => {\n const u = (url ?? \"\").trim();\n if (!/^https?:\\/\\//i.test(u)) return null;\n let m = u.match(/^https?:\\/\\/(?:www\\.|m\\.)?(?:youtube\\.com\\/(?:watch\\?(?:.*&)?v=|embed\\/|shorts\\/|live\\/)|youtu\\.be\\/|youtube-nocookie\\.com\\/embed\\/)([\\w-]{6,20})/i);\n if (m) return { kind: \"iframe\", src: `https://www.youtube-nocookie.com/embed/${m[1]}?rel=0` };\n m = u.match(/^https?:\\/\\/(?:www\\.|player\\.)?vimeo\\.com\\/(?:video\\/)?(\\d{4,12})/i);\n if (m) return { kind: \"iframe\", src: `https://player.vimeo.com/video/${m[1]}?dnt=1` };\n m = u.match(/^https?:\\/\\/(?:www\\.)?loom\\.com\\/(?:share|embed)\\/([\\w-]{8,64})/i);\n if (m) return { kind: \"iframe\", src: `https://www.loom.com/embed/${m[1]}` };\n if (/\\.(mp4|webm|mov|m4v)(\\?|#|$)/i.test(u) || /\\/media\\/uploads\\//.test(u)) return { kind: \"file\", src: u };\n return null;\n};\n\n/** 0.17.0: a step's picture or video when the dialogs may load it (http(s), a video they can play), else undefined. */\nexport const mediaOf = (step: Pick<OnboardingStep, \"media\">): FlowMedia | undefined => {\n const m = step.media;\n if (!m || (m.kind !== \"image\" && m.kind !== \"video\") || !/^https?:\\/\\//i.test(m.url ?? \"\")) return undefined;\n return m.kind === \"video\" && !videoEmbed(m.url) ? undefined : m;\n};\n\n/** A random id for one cancel flow run. */\nexport const runId = () => `cf_${Math.random().toString(36).slice(2, 10)}${Date.now().toString(36)}`;\n\n/** The money a discount leaves: `29 EUR` at 30 % → `20.30`. */\nexport const discounted = (amount: number, percent: number) => Math.round(amount * (1 - percent / 100) * 100) / 100;\n\n/** \"€20.30\" in the customer's own currency, without a library. */\nexport const money = (amount: number, currency: string) => {\n try {\n return new Intl.NumberFormat(undefined, { style: \"currency\", currency }).format(amount);\n } catch {\n return `${amount.toFixed(2)} ${currency}`;\n }\n};\n\n/** The button of an offer. */\nexport const offerButton = (o: CancelOffer) =>\n o.kind === \"contact\" ? \"Send to the team\" : o.kind === \"pause\" ? \"Pause instead\" : o.kind === \"downgrade\" ? \"Switch plan\" : \"Accept offer\";\n\n/** A link a message may open: http(s), or a path of the app. Nothing else (no `javascript:`). */\nexport const safeUrl = (url: string | undefined) => {\n const u = (url ?? \"\").trim();\n return /^https?:\\/\\//i.test(u) || /^\\/(?!\\/)/.test(u) ? u : undefined;\n};\n\ninterface Progress {\n done: Set<string>;\n dismissed: boolean;\n answers: Map<string, boolean>;\n at: Map<string, number>;\n reached: Map<string, number>;\n}\n\n/** The flow's nodes: the whole tree (0.11.0 config), or the plain steps of an older one. */\nexport const nodesOf = (f: OnboardingFlow): OnboardingNode[] => f.nodes ?? f.steps;\n\n/** How long a wait lasts. */\nexport const waitMs = (w: Pick<OnboardingWait, \"amount\" | \"unit\">) => Math.max(0, w.amount) * (w.unit === \"hours\" ? 3_600_000 : 86_400_000);\n\n/** Whether a path matches a condition's page: the same path, or below it for a pattern ending in `*`. */\nexport const pageMatches = (pattern: string, path: string) => {\n const p = pattern.trim();\n const at = (path.split(/[?#]/)[0] || \"/\").replace(/\\/+$/, \"\") || \"/\";\n if (!p) return false;\n if (p.endsWith(\"*\")) {\n const base = p.slice(0, -1).replace(/\\/+$/, \"\");\n return !base || at === base || at.startsWith(`${base}/`);\n }\n return at === (p.replace(/\\/+$/, \"\") || \"/\");\n};\n\n/** Every in-app step of a tree, in reading order (for ticking checklist items by their event). */\nconst stepsIn = (nodes: OnboardingNode[]): OnboardingStep[] =>\n nodes.flatMap((n) => (n.kind === \"condition\" ? [...stepsIn(n.yes), ...stepsIn(n.no)] : n.kind === \"wait\" ? [] : [n]));\n\ntype Walked =\n | { at: \"step\"; step: OnboardingStep; line: OnboardingNode[]; index: number; before: number }\n | { at: \"wait\"; until: number }\n | { at: \"pending\" }\n | { at: \"end\" };\n\n/** Whole months since a date the app passed (NaN without one, which no tenure condition holds for). */\nexport const monthsSince = (since: CancelFlowOptions[\"customerSince\"], now = Date.now()) =>\n since == null || since === \"\" ? NaN : Math.floor((now - new Date(since).getTime()) / 2_630_016_000);\n\nconst low = (x?: string) => x?.trim().toLowerCase();\n\n/** Whether a rule holds for this customer; a condition about something the app did not pass does not hold. */\nexport const ruleMatches = ({ when: w = {} }: CancelRule, c: { answer?: string; plan?: string; months: number }) =>\n (w.answer == null || low(w.answer) === low(c.answer)) &&\n (w.plan == null || [w.plan].flat().some((x) => low(x) === low(c.plan))) &&\n !(c.months >= w.tenureUnder!) &&\n !(c.months < w.tenureOver!) &&\n (w.tenureUnder == null || c.months >= 0) &&\n (w.tenureOver == null || c.months >= 0);\n\ninterface CancelRun {\n flow: CancelFlowConfig;\n options: CancelFlowOptions;\n plain: boolean;\n screen: \"survey\" | \"followup\" | \"offer\" | \"confirm\";\n reason?: string;\n answer?: string;\n /** The offer on the offer screen, which rule picked it, and which round it is. */\n offer?: CancelOffer;\n rule?: number;\n round: 1 | 2;\n run: string;\n busy: boolean;\n error?: string;\n resolve: (r: CancelFlowResult) => void;\n promise: Promise<CancelFlowResult>;\n}\n\nexport const createFlows = (env: FlowsEnv): FlowsController => {\n let config: CockpitConfig | null = null;\n let cancel: CancelRun | null = null;\n let active: string | null = null;\n let messages: InAppMessage[] = [];\n let known = false;\n let loading: Promise<void> | null = null;\n let loadedFor: string | undefined;\n let progressFor: string | undefined | null = null;\n const progress = new Map<string, Progress>();\n const handled = new Set<string>();\n const shown = new Set<string>();\n const listeners = new Set<() => void>();\n const auto = env.auto !== false;\n /** Events the person did (as slugs), for \"Did <event> happen?\": from MRRocket and from this device. */\n const seenEvents = new Set<string>();\n const now = () => env.now?.() ?? Date.now();\n let timer: ReturnType<typeof setTimeout> | null = null;\n /** 0.17.0: the checklist (and its item) a running tour was started from; it comes back when the tour ends. */\n let returnTo: { flow: string; step: string } | null = null;\n\n const flows = () => config?.onboarding ?? [];\n const flowById = (id: string | null) => (id ? flows().find((f) => f.id === id) : undefined);\n const prog = (id: string) => {\n let p = progress.get(id);\n if (!p) progress.set(id, (p = { done: new Set(), dismissed: false, answers: new Map(), at: new Map(), reached: new Map() }));\n return p;\n };\n\n let dirty = false;\n const record = (props: Record<string, unknown>) => {\n env.track(ONBOARDING_EVENT, props);\n dirty = true;\n };\n const markDone = (p: Progress, id: string) => {\n p.done.add(id);\n p.at.set(id, now());\n };\n\n /** A condition's answer for this person, or undefined while what it asks is not known yet. */\n const decide = (c: OnboardingCondition): boolean | undefined => {\n if (c.page?.trim()) {\n const path = env.path?.();\n return path === undefined ? false : pageMatches(c.page, path);\n }\n if (c.event?.trim()) {\n // The person's events from MRRocket arrive with their progress: before that, wait.\n if (!known) return undefined;\n for (const e of seenEvents) if (matchesTrigger(c.event, e)) return true;\n return false;\n }\n return false;\n };\n\n /**\n * Where the person is in a line of the flow. `live`: it is their flow now, so reaching a wait starts it, a decided\n * condition is kept and both are recorded; otherwise nothing changes (to ask whether a flow is finished).\n */\n const walkLine = (flow: OnboardingFlow, line: OnboardingNode[], p: Progress, live: boolean, before = 0, last?: number): Walked => {\n for (let i = 0; i < line.length; i++) {\n const n = line[i];\n if (n.kind === \"wait\") {\n if (p.done.has(n.id)) {\n last = p.at.get(n.id) ?? last;\n continue;\n }\n let since = p.reached.get(n.id);\n if (since === undefined) {\n if (!live) return { at: \"pending\" };\n // Counted from when the step before was done; a flow that opens with a wait counts from now.\n since = last ?? now();\n p.reached.set(n.id, since);\n record({ flow: flow.id, step: n.id, kind: \"wait\", action: \"shown\" });\n }\n const until = since + waitMs(n);\n if (now() < until) return { at: \"wait\", until };\n if (!live) return { at: \"pending\" };\n markDone(p, n.id);\n record({ flow: flow.id, step: n.id, kind: \"wait\", action: \"completed\" });\n last = until;\n continue;\n }\n if (n.kind === \"condition\") {\n let answer = p.answers.get(n.id);\n if (answer === undefined) {\n if (!live) return { at: \"pending\" };\n answer = decide(n);\n if (answer === undefined) return { at: \"pending\" };\n p.answers.set(n.id, answer);\n record({ flow: flow.id, step: n.id, kind: \"condition\", action: \"answered\", answer: answer ? \"yes\" : \"no\" });\n }\n return walkLine(flow, answer ? n.yes : n.no, p, live, before, last);\n }\n if (!p.done.has(n.id)) return { at: \"step\", step: n, line, index: i, before };\n last = p.at.get(n.id) ?? last;\n before++;\n }\n return { at: \"end\" };\n };\n\n const finished = (f: OnboardingFlow) => {\n const p = progress.get(f.id);\n return !!p && (p.dismissed || walkLine(f, nodesOf(f), p, false).at === \"end\");\n };\n\n const keyOf = (pid: string | undefined) => `${PROGRESS_KEY}:${pid ?? \"visitor\"}`;\n const eventsKeyOf = (pid: string | undefined) => `${PROGRESS_KEY}:events:${pid ?? \"visitor\"}`;\n\n const save = () => {\n dirty = false;\n const store = env.storage?.();\n if (!store) return;\n const out: Record<string, FlowProgress> = {};\n for (const [id, p] of progress)\n out[id] = {\n done: [...p.done],\n ...(p.dismissed ? { dismissed: true } : {}),\n ...(p.answers.size ? { answers: Object.fromEntries([...p.answers].map(([k, v]) => [k, v ? \"yes\" : \"no\"])) } : {}),\n ...(p.at.size ? { at: Object.fromEntries(p.at) } : {}),\n ...(p.reached.size ? { reached: Object.fromEntries(p.reached) } : {}),\n };\n try {\n store.set(keyOf(env.personId()), JSON.stringify(out));\n if (seenEvents.size) store.set(eventsKeyOf(env.personId()), JSON.stringify([...seenEvents].slice(-200)));\n } catch {\n // Storage full or gone: progress stays in memory.\n }\n };\n\n const merge = (state: InAppState[\"onboarding\"] | undefined) => {\n for (const [id, s] of Object.entries(state ?? {})) {\n const p = prog(id);\n for (const d of s.done ?? []) p.done.add(d);\n if (s.dismissed) p.dismissed = true;\n // A condition decided elsewhere keeps its path; the earliest time wins for waits.\n for (const [k, v] of Object.entries(s.answers ?? {})) if (!p.answers.has(k)) p.answers.set(k, v === \"yes\");\n for (const [k, v] of Object.entries(s.at ?? {})) if (typeof v === \"number\" && !p.at.has(k)) p.at.set(k, v);\n for (const [k, v] of Object.entries(s.reached ?? {})) if (typeof v === \"number\" && (!p.reached.has(k) || v < p.reached.get(k)!)) p.reached.set(k, v);\n }\n };\n\n // ---------- What shows ----------\n\n /** Comes back when a short wait is over, so the next step shows without a reload (long waits wait for the next visit). */\n const wakeAt = (until: number) => {\n if (timer) clearTimeout(timer);\n timer = null;\n const ms = until - now();\n if (ms > 0 && ms <= 86_400_000) {\n timer = setTimeout(() => ((timer = null), emit()), ms + 50);\n // Never what keeps a process alive (tests, server rendering).\n (timer as { unref?: () => void }).unref?.();\n }\n };\n\n /** The active flow waits (a wait step, or a condition whose answer is not known yet): it stays the active one. */\n let holding = false;\n\n const onboardingView = (): FlowView | null => {\n holding = false;\n const flow = flowById(active);\n if (!flow) return null;\n const p = prog(flow.id);\n if (p.dismissed) return null;\n const w = walkLine(flow, nodesOf(flow), p, true);\n if (dirty) save();\n if (w.at === \"wait\") wakeAt(w.until);\n holding = w.at === \"wait\" || w.at === \"pending\";\n if (w.at !== \"step\") return null;\n const { step, line, index } = w;\n const isStep = (n: OnboardingNode | undefined): n is OnboardingStep => !!n && n.kind !== \"wait\" && n.kind !== \"condition\";\n if (step.kind === \"checklist\") {\n let from = index;\n while (from > 0 && isStep(line[from - 1]) && line[from - 1].kind === \"checklist\") from--;\n let to = index;\n while (to + 1 < line.length && isStep(line[to + 1]) && line[to + 1].kind === \"checklist\") to++;\n const items = line.slice(from, to + 1).filter(isStep);\n for (const i of items)\n if (!p.done.has(i.id) && !shown.has(`s:${i.id}`)) {\n shown.add(`s:${i.id}`);\n env.track(ONBOARDING_EVENT, { flow: flow.id, step: i.id, kind: \"checklist\", action: \"shown\" });\n }\n return {\n type: \"checklist\",\n flow,\n items: items.map((s) => {\n const tour = s.startsTour && s.startsTour !== flow.id ? flowById(s.startsTour) : undefined;\n return { step: s, done: p.done.has(s.id), ...(tour && nodesOf(tour).length ? { tour } : {}) };\n }),\n };\n }\n if (!shown.has(`s:${step.id}`)) {\n shown.add(`s:${step.id}`);\n env.track(ONBOARDING_EVENT, { flow: flow.id, step: step.id, kind: step.kind, action: \"shown\" });\n }\n // The dots: the steps behind on the person's path, this one, and the ones left on its line before any fork.\n const rest = line.slice(index + 1);\n const fork = rest.findIndex((n) => n.kind === \"condition\");\n const ahead = (fork < 0 ? rest : rest.slice(0, fork)).filter(isStep).length;\n return { type: \"step\", flow, step, index: w.before, total: w.before + 1 + ahead, last: !ahead && fork < 0 };\n };\n\n const view = (): FlowView | null => {\n if (cancel) {\n const c = cancel;\n const followUp = c.flow.followUps?.[c.reason!];\n // The dots: the survey, a follow-up, one or two offers, the confirmation.\n const f = followUp ? 1 : 0;\n const steps = c.plain ? 1 : 3 + f + (c.flow.second?.[c.reason!] ? 1 : 0);\n return {\n type: \"cancel\",\n screen: c.screen,\n flow: c.flow,\n reason: c.reason,\n ...(c.screen === \"followup\" && followUp ? { followUp, answer: c.answer } : {}),\n offer: c.screen === \"offer\" ? c.offer : undefined,\n ...(c.screen === \"offer\" ? { round: c.round } : {}),\n price: c.options.price,\n busy: c.busy,\n error: c.error,\n step: c.screen === \"survey\" ? 1 : c.screen === \"followup\" ? 2 : c.screen === \"offer\" ? 1 + f + c.round : steps,\n steps,\n };\n }\n const ob = onboardingView();\n if (ob) return ob;\n // A flow that just ended lets the next thing show; one that waits keeps its place.\n if (active && !holding) {\n const back = returnTo;\n const ended = active;\n returnTo = null;\n active = null;\n const list = back ? flowById(back.flow) : undefined;\n if (back && list) {\n // The tour a checklist item started is over: the checklist comes back, and a finished tour ticks an item\n // that has no event of its own (a closed one does not).\n const item = stepsIn(nodesOf(list)).find((s) => s.id === back.step);\n const p = prog(list.id);\n if (item && !item.event && !progress.get(ended)?.dismissed && !p.done.has(item.id)) {\n markDone(p, item.id);\n record({ flow: list.id, step: item.id, kind: \"checklist\", action: \"completed\" });\n }\n active = list.id;\n const again = onboardingView();\n if (again) return again;\n if (!holding) active = null;\n } else if (known) {\n // 0.17.0: the next flow whose moment has come (another \"First launch\", this page's, or one of the same\n // trigger as the one that ended) follows right away.\n const after = flowById(ended)?.trigger;\n Promise.resolve().then(() => autoStart(after));\n }\n }\n const m = messages.find((x) => !handled.has(x.id));\n return m ? { type: \"message\", message: m } : null;\n };\n\n /** Tells the renderers, and records what became visible (once per step or message per page load). */\n const emit = () => {\n // Onboarding steps record their \"shown\" where they are worked out (onboardingView), so a step a wait let through\n // counts also when nothing else changed.\n const v = view();\n if (v?.type === \"message\" && !shown.has(`m:${v.message.id}`)) {\n shown.add(`m:${v.message.id}`);\n env.track(INAPP_EVENT, { message: v.message.id, journey: v.message.journey, action: \"shown\" });\n }\n for (const l of [...listeners]) {\n try {\n l();\n } catch {\n // One renderer must not stop another.\n }\n }\n };\n\n const start = (flow: OnboardingFlow) => {\n if (active === flow.id) return true;\n if (active && flowById(active) && !finished(flowById(active)!)) return false;\n active = flow.id;\n env.track(ONBOARDING_EVENT, { flow: flow.id, action: \"started\" });\n emit();\n return true;\n };\n\n /** 0.17.0: a flow for signed-in people starts on its own only for someone the app identified. */\n const allowed = (f: OnboardingFlow) => f.audience !== \"signed_in\" || !!env.personId();\n\n /**\n * Flows whose trigger is \"the first launch\" (or, 0.17.0, the page the person is on) start once their progress is\n * known (never twice for a done flow). `after`: the trigger of a flow that just ended, whose next unfinished flow\n * follows it (onboarding items made from one older flow share its trigger).\n */\n const autoStart = (after?: string) => {\n if (!auto || active || !known) return;\n // Without an id or storage a \"first launch\" could not be told from any other: no automatic start then.\n if (!env.personId() && !env.storage?.()) return;\n const path = env.path?.();\n const same = after && !manual(after) ? eventSlug(after) : \"\";\n const next = flows().find(\n (f) =>\n (isPageTrigger(f.trigger) ? path !== undefined && pageMatches(f.trigger, path) : startsOnLoad(f.trigger) || (!!same && eventSlug(f.trigger) === same)) &&\n allowed(f) &&\n nodesOf(f).length &&\n !finished(f),\n );\n if (next) start(next);\n };\n\n const refresh = () => {\n const pid = env.personId();\n // Another person on this device: nothing of the one before carries over.\n if (progressFor !== null && progressFor !== pid) {\n progress.clear();\n seenEvents.clear();\n handled.clear();\n active = null;\n returnTo = null;\n loadedFor = undefined;\n }\n progressFor = pid;\n const run = (async () => {\n const store = env.storage?.();\n if (store) {\n try {\n const raw = await store.get(keyOf(pid));\n if (raw) merge(JSON.parse(raw) as InAppState[\"onboarding\"]);\n const seen = await store.get(eventsKeyOf(pid));\n if (seen) for (const e of JSON.parse(seen) as string[]) if (typeof e === \"string\") seenEvents.add(e);\n } catch {\n // A damaged entry is ignored.\n }\n }\n const wants = flows().length > 0 || !!config?.messages;\n if (pid && wants && loadedFor !== pid) {\n loadedFor = pid;\n const state = await env.fetchInApp(pid).catch(() => null);\n if (state && env.personId() === pid) {\n merge(state.onboarding);\n for (const e of state.events ?? []) if (typeof e === \"string\") seenEvents.add(eventSlug(e));\n messages = (state.messages ?? []).filter((m) => m && m.id && m.title);\n }\n }\n if (!pid) messages = [];\n known = true;\n autoStart();\n emit();\n })();\n loading = run.finally(() => {\n if (loading === run) loading = null;\n });\n return loading;\n };\n\n // ---------- Cancel flow ----------\n\n /** Which offer of the answer was on screen, for MRRocket to know what was saved: a rule's or the second. */\n const offerAt = (c: CancelRun) => (c.round > 1 ? { round: 2 } : c.rule! >= 0 ? { rule: c.rule } : {});\n\n /** Into the offer screen with the answer's first offer (the first rule that holds, else its own), or the confirmation. */\n const toOffer = (c: CancelRun) => {\n const who = { answer: c.answer, plan: c.options.plan, months: monthsSince(c.options.customerSince, env.now?.()) };\n const rules = c.flow.rules?.[c.reason!] ?? [];\n c.rule = rules.findIndex((r) => ruleMatches(r, who));\n c.offer = c.rule < 0 ? c.flow.offers[c.reason!] : (rules[c.rule].offer ?? undefined);\n c.round = 1;\n c.screen = c.offer ? \"offer\" : \"confirm\";\n };\n\n const result = (c: CancelRun, outcome: CancelFlowResult[\"outcome\"], comment?: string): CancelFlowResult => {\n const offer = c.screen === \"offer\" ? c.offer : undefined;\n return {\n outcome,\n run: c.run,\n ...(c.reason ? { reason: c.reason, reasonLabel: c.flow.reasons.find((r) => r.id === c.reason)?.label } : {}),\n ...(c.answer ? { answer: c.answer } : {}),\n ...(outcome === \"saved\" && offer ? { offer } : {}),\n ...(comment?.trim() ? { comment: comment.trim().slice(0, 500) } : {}),\n };\n };\n\n const finish = (c: CancelRun, r: CancelFlowResult) => {\n env.track(CANCEL_EVENT, {\n action: \"finished\",\n run: c.run,\n version: c.flow.version,\n outcome: r.outcome,\n customer: c.options.customer ?? \"consumer\",\n ...(r.reason ? { reason: r.reason } : {}),\n ...(r.answer ? { answer: r.answer } : {}),\n ...(r.offer ? { offer: r.offer.kind, ...offerAt(c) } : {}),\n ...(r.comment ? { comment: r.comment } : {}),\n });\n if (cancel === c) cancel = null;\n c.resolve(r);\n emit();\n };\n\n /** Runs the app's callback; the outcome is recorded only once it went through. */\n const settle = (c: CancelRun, outcome: \"saved\" | \"cancelled\", comment?: string) => {\n if (c.busy) return;\n const r = result(c, outcome, comment);\n const fn = outcome === \"saved\" ? c.options.onSave : c.options.onCancel;\n c.busy = true;\n c.error = undefined;\n emit();\n Promise.resolve()\n .then(() => fn?.(r))\n .then(\n () => finish(c, r),\n () => {\n c.busy = false;\n c.error = FAILED;\n emit();\n },\n );\n };\n\n const openCancelFlow = (options: CancelFlowOptions = {}) => {\n if (cancel) return cancel.promise;\n const flow = config?.cancelFlow ?? PLAIN_CANCEL;\n const plain = options.customer !== \"business\" || flow.reasons.length < 2;\n let resolve: (r: CancelFlowResult) => void = () => {};\n const promise = new Promise<CancelFlowResult>((r) => (resolve = r));\n const c: CancelRun = { flow, options, plain, screen: plain ? \"confirm\" : \"survey\", round: 1, run: runId(), busy: false, resolve, promise };\n cancel = c;\n env.track(CANCEL_EVENT, { action: \"shown\", run: c.run, version: flow.version, customer: options.customer ?? \"consumer\", plain });\n emit();\n return promise;\n };\n\n const controller: FlowsController = {\n setConfig: (c) => {\n config = c;\n if (active && !flowById(active)) active = null;\n emit();\n },\n refresh,\n onEvent: (name) => {\n // 0.17.0: a page view may be the moment of a flow with a page trigger.\n if (name === \"$pageview\") autoStart();\n if (!name || name.startsWith(\"$\")) return;\n // Remembered for \"Did <event> happen?\" conditions the person reaches later.\n const slug = eventSlug(name);\n const fresh = !!slug && !seenEvents.has(slug);\n if (fresh) seenEvents.add(slug);\n const flow = flowById(active);\n let ticked = false;\n if (flow) {\n const p = prog(flow.id);\n for (const s of stepsIn(nodesOf(flow)))\n if (s.kind === \"checklist\" && s.event && !p.done.has(s.id) && matchesTrigger(s.event, name)) {\n markDone(p, s.id);\n ticked = true;\n env.track(ONBOARDING_EVENT, { flow: flow.id, step: s.id, kind: s.kind, action: \"completed\" });\n }\n }\n if (ticked || (fresh && flow)) {\n save();\n emit();\n } else if (fresh) save();\n // Like the first-launch start: only for someone whose progress is kept, so nobody gets a flow twice.\n if (!auto || (!env.personId() && !env.storage?.())) return;\n const next = flows().find((f) => nodesOf(f).length && !manual(f.trigger) && matchesTrigger(f.trigger, name) && allowed(f) && !finished(f));\n if (next) start(next);\n },\n openCancelFlow,\n startOnboarding: (id) => {\n const f = id ? flowById(id) : flows().find((x) => nodesOf(x).length && !manual(x.trigger) && !finished(x));\n if (!f || !nodesOf(f).length) return false;\n const p = prog(f.id);\n // Started by hand: a flow the person closed before shows again.\n p.dismissed = false;\n return start(f);\n },\n view,\n subscribe: (l) => {\n listeners.add(l);\n return () => listeners.delete(l);\n },\n cancel: {\n pick: (reason) => {\n if (!cancel || cancel.busy || cancel.screen !== \"survey\" || !cancel.flow.reasons.some((r) => r.id === reason)) return;\n if (cancel.reason !== reason) cancel.answer = undefined;\n cancel.reason = reason;\n cancel.error = undefined;\n emit();\n },\n answer: (text) => {\n const c = cancel;\n if (!c || c.busy || c.screen !== \"followup\") return;\n const t = text.slice(0, 500);\n c.answer = t.trim() ? t : undefined;\n emit();\n },\n next: () => {\n const c = cancel;\n if (!c || !c.reason) return;\n // The follow-up's answer rides along on the finished event.\n if (c.screen === \"survey\") {\n env.track(CANCEL_EVENT, { action: \"answered\", run: c.run, version: c.flow.version, reason: c.reason });\n if (c.flow.followUps?.[c.reason]) c.screen = \"followup\";\n else toOffer(c);\n } else if (c.screen === \"followup\") {\n c.answer = c.answer?.trim();\n toOffer(c);\n } else return;\n emit();\n },\n accept: (comment) => {\n const c = cancel;\n if (!c || c.screen !== \"offer\" || !c.reason) return;\n if (!c.busy)\n env.track(CANCEL_EVENT, {\n action: \"offer\",\n run: c.run,\n version: c.flow.version,\n reason: c.reason,\n offer: c.offer?.kind,\n ...offerAt(c),\n outcome: \"accepted\",\n });\n settle(c, \"saved\", comment);\n },\n decline: () => {\n const c = cancel;\n if (!c || c.screen !== \"offer\" || c.busy) return;\n env.track(CANCEL_EVENT, {\n action: \"offer\",\n run: c.run,\n version: c.flow.version,\n reason: c.reason,\n offer: c.offer?.kind,\n ...offerAt(c),\n outcome: \"declined\",\n });\n // A second offer once, then the confirmation.\n const second = c.round === 1 && c.reason ? c.flow.second?.[c.reason] : undefined;\n if (second) {\n c.offer = second;\n c.rule = undefined;\n c.round = 2;\n } else c.screen = \"confirm\";\n c.error = undefined;\n emit();\n },\n confirm: (comment) => {\n const c = cancel;\n if (!c || c.screen !== \"confirm\") return;\n settle(c, \"cancelled\", comment);\n },\n close: () => {\n const c = cancel;\n if (!c || c.busy) return;\n const r = result(c, \"kept\");\n c.options.onClose?.(r);\n finish(c, r);\n },\n },\n onboarding: {\n next: () => {\n const v = onboardingView();\n if (v?.type !== \"step\") return;\n markDone(prog(v.flow.id), v.step.id);\n env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: v.step.id, kind: v.step.kind, action: \"completed\" });\n save();\n emit();\n // 0.17.0: a modal's button may lead somewhere.\n const url = v.step.kind === \"modal\" ? safeUrl(v.step.url) : undefined;\n if (url) env.openUrl?.(url);\n },\n check: (stepId) => {\n const v = onboardingView();\n if (v?.type !== \"checklist\") return;\n const item = v.items.find((i) => i.step.id === stepId);\n if (!item || item.done) return;\n markDone(prog(v.flow.id), stepId);\n env.track(ONBOARDING_EVENT, { flow: v.flow.id, step: stepId, kind: \"checklist\", action: \"completed\" });\n save();\n emit();\n },\n dismiss: () => {\n const v = onboardingView();\n if (!v || (v.type !== \"step\" && v.type !== \"checklist\")) return;\n prog(v.flow.id).dismissed = true;\n env.track(ONBOARDING_EVENT, { flow: v.flow.id, ...(v.type === \"step\" ? { step: v.step.id } : {}), action: \"dismissed\" });\n // A tour started from a checklist was closed: the checklist comes back, its item stays open.\n active = returnTo && returnTo.flow !== v.flow.id && flowById(returnTo.flow) ? returnTo.flow : null;\n returnTo = null;\n save();\n emit();\n },\n startTour: (stepId) => {\n const v = onboardingView();\n if (v?.type !== \"checklist\") return false;\n const tour = v.items.find((i) => i.step.id === stepId)?.tour;\n if (!tour) return false;\n // Started by hand: from its first step, also when the person saw or closed it before.\n const p = prog(tour.id);\n p.done.clear();\n p.at.clear();\n p.dismissed = false;\n returnTo = { flow: v.flow.id, step: stepId };\n active = tour.id;\n env.track(ONBOARDING_EVENT, { flow: tour.id, action: \"started\", from: v.flow.id });\n save();\n emit();\n return true;\n },\n },\n message: {\n click: () => {\n const v = view();\n if (v?.type !== \"message\") return;\n handled.add(v.message.id);\n env.track(INAPP_EVENT, { message: v.message.id, journey: v.message.journey, action: \"clicked\" });\n const url = safeUrl(v.message.url);\n emit();\n if (url) env.openUrl?.(url);\n },\n dismiss: () => {\n const v = view();\n if (v?.type !== \"message\") return;\n handled.add(v.message.id);\n env.track(INAPP_EVENT, { message: v.message.id, journey: v.message.journey, action: \"dismissed\" });\n emit();\n },\n },\n };\n return controller;\n};\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export type { PushResult, PushStatus, WebPushOptions } from "./push-web.js";
|
|
|
3
3
|
/** `identify(id, props, { hash })` (0.16.0); the hash comes from `userHash()` in `@mrrocket/sdk/next/server`. */
|
|
4
4
|
export type { IdentifyOptions } from "./identity.js";
|
|
5
5
|
export { type CockpitConfig, type CockpitEvent, type ConsentChoice, type ConsentMode, type ReplayConfig, type ErrorReport, type ExperimentConfig, type FlagCondition, type FlagConditionSet, type FlagConfig, type FlagPerson, type RedirectConfig, type RedirectDecision, type VariantConfig, type VariantId, EMBED_PARAM, EXPOSURE_PARAM, SDK_VERSION, ID_COOKIE, PREVIEW_PARAM, SESSION_COOKIE, assign, evaluateFlag, parsePreview, previewHref, redirectFor, } from "./core.js";
|
|
6
|
-
export { type CancelFlowConfig, type CancelFlowOptions, type CancelFlowResult, type CancelOffer, type InAppMessage, type OnboardingFlow, type OnboardingStep, } from "./flows.js";
|
|
6
|
+
export { type CancelFlowConfig, type CancelFlowOptions, type CancelFlowResult, type CancelOffer, type InAppMessage, type OnboardingFlow, type OnboardingStep, type FlowMedia, type TourPlacement, videoEmbed, } from "./flows.js";
|
|
7
7
|
export type { FlowsTheme } from "./flows-ui.js";
|
|
8
8
|
export { PAYWALL_EVENT, type PaywallConfig, type PaywallOptions, type PaywallPage, type PaywallPlan, type PaywallResult } from "./paywall.js";
|
|
9
9
|
export { type AttributionMode, type CheckoutReferral, type Referral, REFERRAL_COOKIE, getReferral, readReferralCookie, referralForCheckout, referralLink, } from "./referral.js";
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,9 @@ import {
|
|
|
33
33
|
previewHref,
|
|
34
34
|
redirectFor
|
|
35
35
|
} from "./core.js";
|
|
36
|
+
import {
|
|
37
|
+
videoEmbed
|
|
38
|
+
} from "./flows.js";
|
|
36
39
|
import { PAYWALL_EVENT } from "./paywall.js";
|
|
37
40
|
import {
|
|
38
41
|
REFERRAL_COOKIE,
|
|
@@ -77,6 +80,7 @@ export {
|
|
|
77
80
|
usePaywall,
|
|
78
81
|
usePush,
|
|
79
82
|
useStyles,
|
|
80
|
-
useVariant
|
|
83
|
+
useVariant,
|
|
84
|
+
videoEmbed
|
|
81
85
|
};
|
|
82
86
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export {\n type ChangelogEntry,\n type ChatMessage,\n type ChatThread,\n type CockpitProviderProps,\n type CockpitUser,\n type FeedbackBoardInfo,\n type FeedbackBoardProps,\n type FeedbackComment,\n type FeedbackList,\n type FeedbackPost,\n type FeedbackRoadmap,\n type FeedbackWidgetProps,\n type MessengerProps,\n type PostStatus,\n type SurveyAnswer,\n type SurveyDefinition,\n type SurveyQuestion,\n type SurveyQuestionKind,\n CockpitProvider,\n Experiment,\n FeedbackBoard,\n FeedbackWidget,\n Messenger,\n Survey,\n Text,\n WIDGET_CSS,\n createApi,\n useCancelFlow,\n useCockpit,\n useFeedback,\n useFlag,\n useFlagVariant,\n useMessenger,\n usePaywall,\n usePush,\n useStyles,\n useVariant,\n} from \"./browser\";\nexport type { PushResult, PushStatus, WebPushOptions } from \"./push-web\";\n/** `identify(id, props, { hash })` (0.16.0); the hash comes from `userHash()` in `@mrrocket/sdk/next/server`. */\nexport type { IdentifyOptions } from \"./identity\";\nexport {\n type CockpitConfig,\n type CockpitEvent,\n type ConsentChoice,\n type ConsentMode,\n type ReplayConfig,\n type ErrorReport,\n type ExperimentConfig,\n type FlagCondition,\n type FlagConditionSet,\n type FlagConfig,\n type FlagPerson,\n type RedirectConfig,\n type RedirectDecision,\n type VariantConfig,\n type VariantId,\n EMBED_PARAM,\n EXPOSURE_PARAM,\n SDK_VERSION,\n ID_COOKIE,\n PREVIEW_PARAM,\n SESSION_COOKIE,\n assign,\n evaluateFlag,\n parsePreview,\n previewHref,\n redirectFor,\n} from \"./core\";\nexport {\n type CancelFlowConfig,\n type CancelFlowOptions,\n type CancelFlowResult,\n type CancelOffer,\n type InAppMessage,\n type OnboardingFlow,\n type OnboardingStep,\n} from \"./flows\";\nexport type { FlowsTheme } from \"./flows-ui\";\nexport { PAYWALL_EVENT, type PaywallConfig, type PaywallOptions, type PaywallPage, type PaywallPlan, type PaywallResult } from \"./paywall\";\nexport {\n type AttributionMode,\n type CheckoutReferral,\n type Referral,\n REFERRAL_COOKIE,\n getReferral,\n readReferralCookie,\n referralForCheckout,\n referralLink,\n} from \"./referral\";\n"],"mappings":";AAAA;AAAA,EAmBI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AAIP;AAAA,EAgBI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export {\n type ChangelogEntry,\n type ChatMessage,\n type ChatThread,\n type CockpitProviderProps,\n type CockpitUser,\n type FeedbackBoardInfo,\n type FeedbackBoardProps,\n type FeedbackComment,\n type FeedbackList,\n type FeedbackPost,\n type FeedbackRoadmap,\n type FeedbackWidgetProps,\n type MessengerProps,\n type PostStatus,\n type SurveyAnswer,\n type SurveyDefinition,\n type SurveyQuestion,\n type SurveyQuestionKind,\n CockpitProvider,\n Experiment,\n FeedbackBoard,\n FeedbackWidget,\n Messenger,\n Survey,\n Text,\n WIDGET_CSS,\n createApi,\n useCancelFlow,\n useCockpit,\n useFeedback,\n useFlag,\n useFlagVariant,\n useMessenger,\n usePaywall,\n usePush,\n useStyles,\n useVariant,\n} from \"./browser\";\nexport type { PushResult, PushStatus, WebPushOptions } from \"./push-web\";\n/** `identify(id, props, { hash })` (0.16.0); the hash comes from `userHash()` in `@mrrocket/sdk/next/server`. */\nexport type { IdentifyOptions } from \"./identity\";\nexport {\n type CockpitConfig,\n type CockpitEvent,\n type ConsentChoice,\n type ConsentMode,\n type ReplayConfig,\n type ErrorReport,\n type ExperimentConfig,\n type FlagCondition,\n type FlagConditionSet,\n type FlagConfig,\n type FlagPerson,\n type RedirectConfig,\n type RedirectDecision,\n type VariantConfig,\n type VariantId,\n EMBED_PARAM,\n EXPOSURE_PARAM,\n SDK_VERSION,\n ID_COOKIE,\n PREVIEW_PARAM,\n SESSION_COOKIE,\n assign,\n evaluateFlag,\n parsePreview,\n previewHref,\n redirectFor,\n} from \"./core\";\nexport {\n type CancelFlowConfig,\n type CancelFlowOptions,\n type CancelFlowResult,\n type CancelOffer,\n type InAppMessage,\n type OnboardingFlow,\n type OnboardingStep,\n type FlowMedia,\n type TourPlacement,\n videoEmbed,\n} from \"./flows\";\nexport type { FlowsTheme } from \"./flows-ui\";\nexport { PAYWALL_EVENT, type PaywallConfig, type PaywallOptions, type PaywallPage, type PaywallPlan, type PaywallResult } from \"./paywall\";\nexport {\n type AttributionMode,\n type CheckoutReferral,\n type Referral,\n REFERRAL_COOKIE,\n getReferral,\n readReferralCookie,\n referralForCheckout,\n referralLink,\n} from \"./referral\";\n"],"mappings":";AAAA;AAAA,EAmBI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AAIP;AAAA,EAgBI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AACP;AAAA,EAUI;AAAA,OACG;AAEP,SAAS,qBAAsH;AAC/H;AAAA,EAII;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;","names":[]}
|
package/dist/messenger-ui.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The web messenger, after Crisp's: a round launcher (
|
|
2
|
+
* The web messenger, after Crisp's: a round launcher (a click opens it, no card on hover) and a panel
|
|
3
3
|
* with a bar on top (Home, a "Chat · Help" switch while there are both, a ⋮ menu to minimize or widen it). Home shows the greeting,
|
|
4
4
|
* the latest chat and the help search; Chat is the chat with the team, answered in MRRocket › Support by the owner or
|
|
5
5
|
* the Customer experience agent; Help lists the product's published help articles by collection, searchable, read in
|
|
@@ -57,8 +57,14 @@ export interface MessengerUiOptions {
|
|
|
57
57
|
header?: "plain" | "color";
|
|
58
58
|
/** "Powered by MRRocket" at the bottom; MRRocket's server says whether the plan may hide it. */
|
|
59
59
|
branding?: boolean;
|
|
60
|
-
/** The
|
|
60
|
+
/** The logo at the top left of Home (MRRocket sends the uploaded logo, else the favicon); without it the title's first letter. */
|
|
61
61
|
logo?: string;
|
|
62
|
+
/** The round mark in the chat's head (MRRocket sends the favicon); without it the title's first letter. */
|
|
63
|
+
icon?: string;
|
|
64
|
+
/** The AI agent's name on its answers (default "AI agent"). */
|
|
65
|
+
agentName?: string;
|
|
66
|
+
/** The AI agent's picture beside its answers. */
|
|
67
|
+
agentPhoto?: string;
|
|
62
68
|
/** MRRocket's own preview: mount inside this element (positioned) instead of the page. */
|
|
63
69
|
container?: HTMLElement;
|
|
64
70
|
/** MRRocket's own preview: answers from here instead of the server. */
|
|
@@ -82,5 +88,7 @@ export interface MessengerHandle {
|
|
|
82
88
|
/**
|
|
83
89
|
* Mounts the messenger with its look from MRRocket (Support › Messenger) underneath what the code passes: an option the
|
|
84
90
|
* code sets wins, one it leaves out comes from MRRocket, then the default. Calls made before the look arrived wait.
|
|
91
|
+
* A repeat visit starts with the look this browser saw last; a look that arrives later than that (changed, or slower
|
|
92
|
+
* than LOOK_WAIT_MS) replaces the launcher while the panel is closed, or once it closes.
|
|
85
93
|
*/
|
|
86
94
|
export declare const mountMessenger: (o: MessengerUiOptions) => MessengerHandle;
|