@mrrocket/sdk 0.12.0 → 0.14.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 +13 -0
- package/README.md +60 -0
- package/dist/browser/mrrocket-flows.js +11 -2
- package/dist/browser/mrrocket-messenger.js +143 -65
- package/dist/browser/mrrocket.js +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +2 -1
- package/dist/browser.js.map +1 -1
- package/dist/client.d.ts +12 -0
- package/dist/client.js +22 -0
- package/dist/client.js.map +1 -1
- package/dist/context.d.ts +3 -0
- package/dist/context.js.map +1 -1
- package/dist/core.d.ts +4 -1
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/flows-ui.d.ts +6 -4
- package/dist/flows-ui.js +99 -4
- package/dist/flows-ui.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/messenger-ui.d.ts +22 -0
- package/dist/messenger-ui.js +313 -79
- package/dist/messenger-ui.js.map +1 -1
- package/dist/paywall.d.ts +123 -0
- package/dist/paywall.js +131 -0
- package/dist/paywall.js.map +1 -0
- package/dist/proxy.d.ts +1 -0
- package/dist/proxy.js +3 -0
- package/dist/proxy.js.map +1 -1
- package/dist/react-native/index.d.cts +134 -3
- package/dist/react-native/index.d.ts +134 -3
- package/dist/react-native/index.js +204 -4
- package/dist/react-native/index.js.map +1 -1
- package/dist/security.d.ts +71 -0
- package/dist/security.js +73 -0
- package/dist/security.js.map +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +8 -1
- package/dist/server.js.map +1 -1
- package/dist/styles-css.d.ts +1 -1
- package/dist/styles-css.js +141 -63
- package/dist/styles-css.js.map +1 -1
- package/package.json +1 -1
package/dist/proxy.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type CockpitConfig } from "./core.js";
|
|
2
2
|
export { getCockpitConfig } from "./server.js";
|
|
3
|
+
export { type SecurityOptions, reportSuspiciousRequest, watchRequest } from "./security.js";
|
|
3
4
|
/** What `request.cookies` (Next's `RequestCookies`) offers: read, and set so that the page of this request sees the id. */
|
|
4
5
|
interface IncomingCookies {
|
|
5
6
|
get(name: string): {
|
package/dist/proxy.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/proxy.ts
|
|
2
2
|
import { ID_COOKIE, PREVIEW_PARAM, newDistinctId, parsePreview, redirectFor } from "./core.js";
|
|
3
3
|
import { getCockpitConfig } from "./server.js";
|
|
4
|
+
import { reportSuspiciousRequest, watchRequest } from "./security.js";
|
|
4
5
|
var withCockpitId = (incoming, outgoing) => {
|
|
5
6
|
if (incoming.get(ID_COOKIE)) return;
|
|
6
7
|
const id = newDistinctId();
|
|
@@ -20,6 +21,8 @@ var experimentRedirect = (request, config, options = {}) => {
|
|
|
20
21
|
export {
|
|
21
22
|
experimentRedirect,
|
|
22
23
|
getCockpitConfig,
|
|
24
|
+
reportSuspiciousRequest,
|
|
25
|
+
watchRequest,
|
|
23
26
|
withCockpitId
|
|
24
27
|
};
|
|
25
28
|
//# sourceMappingURL=proxy.js.map
|
package/dist/proxy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/proxy.ts"],"sourcesContent":["import { type CockpitConfig, ID_COOKIE, PREVIEW_PARAM, newDistinctId, parsePreview, redirectFor } from \"./core\";\n\nexport { getCockpitConfig } from \"./server\";\n\n/** What `request.cookies` (Next's `RequestCookies`) offers: read, and set so that the page of this request sees the id. */\ninterface IncomingCookies {\n get(name: string): { value: string } | undefined;\n set(name: string, value: string): unknown;\n}\n\n/** What `response.cookies` (Next's `ResponseCookies`) offers: set with options. */\ninterface OutgoingCookies {\n set(name: string, value: string, options?: { path?: string; maxAge?: number; sameSite?: \"lax\" | \"strict\" | \"none\" }): unknown;\n}\n\n/**\n * For `proxy.ts`: gives a first-time visitor their `ck_id` before the page renders, so the server already assigns\n * the variant they will keep. It writes a cookie, so only use it with `consent=\"cookies\"` and only once the visitor\n * agreed; in the default cookieless mode leave it out.\n *\n * export function proxy(request: NextRequest) {\n * const response = NextResponse.next();\n * withCockpitId(request.cookies, response.cookies);\n * return response;\n * }\n */\nexport const withCockpitId = (incoming: IncomingCookies, outgoing: OutgoingCookies) => {\n if (incoming.get(ID_COOKIE)) return;\n const id = newDistinctId();\n incoming.set(ID_COOKIE, id);\n outgoing.set(ID_COOKIE, id, { path: \"/\", maxAge: 60 * 60 * 24 * 365, sameSite: \"lax\" });\n};\n\n/**\n * For `proxy.ts`: where to send this request, or null to render it. Page redirects set in MRRocket (Landing pages ›\n * Redirect) and redirect tests (Experiments) answer before the page renders, so nobody sees the old page flash. The\n * page the visitor lands on records the test's exposure (`<CockpitProvider>` reads `ck_x` there); control renders as\n * usual and the provider records it.\n *\n * A running test needs a stable visitor id to keep each person in one variant: the `ck_id` cookie (consent \"cookies\",\n * with `withCockpitId` before this), or your signed-in user id as `distinctId`. Without either, the test is left to the\n * browser (the provider redirects after the first paint); page redirects and shipped winners still answer here.\n *\n * import { NextResponse, type NextRequest } from \"next/server\";\n * import { experimentRedirect, getCockpitConfig } from \"@mrrocket/sdk/next/proxy\";\n *\n * export async function proxy(request: NextRequest) {\n * const to = experimentRedirect(request, await getCockpitConfig({ timeoutMs: 800 }));\n * return to ? NextResponse.redirect(to, 307) : NextResponse.next();\n * }\n * export const config = { matcher: [\"/pricing\", \"/old-pricing\"] };\n *\n * Keep the matcher to the pages with a redirect or a test: each matched request waits for the config (at most\n * `timeoutMs`; MRRocket unreachable means no redirect).\n */\nexport const experimentRedirect = (\n request: { url: string; cookies?: { get(name: string): { value: string } | undefined } },\n config: CockpitConfig | null,\n options: { distinctId?: string } = {},\n): string | null => {\n const id = options.distinctId ?? request.cookies?.get(ID_COOKIE)?.value ?? null;\n let preview = {};\n try {\n preview = parsePreview(new URL(request.url).searchParams.get(PREVIEW_PARAM));\n } catch {\n return null;\n }\n return redirectFor(config, request.url, id, preview)?.to ?? null;\n};\n"],"mappings":";AAAA,SAA6B,WAAW,eAAe,eAAe,cAAc,mBAAmB;AAEvG,SAAS,wBAAwB;
|
|
1
|
+
{"version":3,"sources":["../src/proxy.ts"],"sourcesContent":["import { type CockpitConfig, ID_COOKIE, PREVIEW_PARAM, newDistinctId, parsePreview, redirectFor } from \"./core\";\n\nexport { getCockpitConfig } from \"./server\";\nexport { type SecurityOptions, reportSuspiciousRequest, watchRequest } from \"./security\";\n\n/** What `request.cookies` (Next's `RequestCookies`) offers: read, and set so that the page of this request sees the id. */\ninterface IncomingCookies {\n get(name: string): { value: string } | undefined;\n set(name: string, value: string): unknown;\n}\n\n/** What `response.cookies` (Next's `ResponseCookies`) offers: set with options. */\ninterface OutgoingCookies {\n set(name: string, value: string, options?: { path?: string; maxAge?: number; sameSite?: \"lax\" | \"strict\" | \"none\" }): unknown;\n}\n\n/**\n * For `proxy.ts`: gives a first-time visitor their `ck_id` before the page renders, so the server already assigns\n * the variant they will keep. It writes a cookie, so only use it with `consent=\"cookies\"` and only once the visitor\n * agreed; in the default cookieless mode leave it out.\n *\n * export function proxy(request: NextRequest) {\n * const response = NextResponse.next();\n * withCockpitId(request.cookies, response.cookies);\n * return response;\n * }\n */\nexport const withCockpitId = (incoming: IncomingCookies, outgoing: OutgoingCookies) => {\n if (incoming.get(ID_COOKIE)) return;\n const id = newDistinctId();\n incoming.set(ID_COOKIE, id);\n outgoing.set(ID_COOKIE, id, { path: \"/\", maxAge: 60 * 60 * 24 * 365, sameSite: \"lax\" });\n};\n\n/**\n * For `proxy.ts`: where to send this request, or null to render it. Page redirects set in MRRocket (Landing pages ›\n * Redirect) and redirect tests (Experiments) answer before the page renders, so nobody sees the old page flash. The\n * page the visitor lands on records the test's exposure (`<CockpitProvider>` reads `ck_x` there); control renders as\n * usual and the provider records it.\n *\n * A running test needs a stable visitor id to keep each person in one variant: the `ck_id` cookie (consent \"cookies\",\n * with `withCockpitId` before this), or your signed-in user id as `distinctId`. Without either, the test is left to the\n * browser (the provider redirects after the first paint); page redirects and shipped winners still answer here.\n *\n * import { NextResponse, type NextRequest } from \"next/server\";\n * import { experimentRedirect, getCockpitConfig } from \"@mrrocket/sdk/next/proxy\";\n *\n * export async function proxy(request: NextRequest) {\n * const to = experimentRedirect(request, await getCockpitConfig({ timeoutMs: 800 }));\n * return to ? NextResponse.redirect(to, 307) : NextResponse.next();\n * }\n * export const config = { matcher: [\"/pricing\", \"/old-pricing\"] };\n *\n * Keep the matcher to the pages with a redirect or a test: each matched request waits for the config (at most\n * `timeoutMs`; MRRocket unreachable means no redirect).\n */\nexport const experimentRedirect = (\n request: { url: string; cookies?: { get(name: string): { value: string } | undefined } },\n config: CockpitConfig | null,\n options: { distinctId?: string } = {},\n): string | null => {\n const id = options.distinctId ?? request.cookies?.get(ID_COOKIE)?.value ?? null;\n let preview = {};\n try {\n preview = parsePreview(new URL(request.url).searchParams.get(PREVIEW_PARAM));\n } catch {\n return null;\n }\n return redirectFor(config, request.url, id, preview)?.to ?? null;\n};\n"],"mappings":";AAAA,SAA6B,WAAW,eAAe,eAAe,cAAc,mBAAmB;AAEvG,SAAS,wBAAwB;AACjC,SAA+B,yBAAyB,oBAAoB;AAwBrE,IAAM,gBAAgB,CAAC,UAA2B,aAA8B;AACnF,MAAI,SAAS,IAAI,SAAS,EAAG;AAC7B,QAAM,KAAK,cAAc;AACzB,WAAS,IAAI,WAAW,EAAE;AAC1B,WAAS,IAAI,WAAW,IAAI,EAAE,MAAM,KAAK,QAAQ,KAAK,KAAK,KAAK,KAAK,UAAU,MAAM,CAAC;AAC1F;AAwBO,IAAM,qBAAqB,CAC9B,SACA,QACA,UAAmC,CAAC,MACpB;AAChB,QAAM,KAAK,QAAQ,cAAc,QAAQ,SAAS,IAAI,SAAS,GAAG,SAAS;AAC3E,MAAI,UAAU,CAAC;AACf,MAAI;AACA,cAAU,aAAa,IAAI,IAAI,QAAQ,GAAG,EAAE,aAAa,IAAI,aAAa,CAAC;AAAA,EAC/E,QAAQ;AACJ,WAAO;AAAA,EACX;AACA,SAAO,YAAY,QAAQ,QAAQ,KAAK,IAAI,OAAO,GAAG,MAAM;AAChE;","names":[]}
|
|
@@ -414,6 +414,116 @@ interface FlowsController {
|
|
|
414
414
|
};
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
+
/**
|
|
418
|
+
* Paywalls with several pages (SDK 0.14.0): what MRRocket's config serves under `paywalls` (one per placement, built
|
|
419
|
+
* in MRRocket › Conversion › Paywalls) and a small controller that walks the person through its pages, page after page,
|
|
420
|
+
* until the last one hands the chosen plan to the app. No DOM and no React: the web renderer (`mountPaywall` in
|
|
421
|
+
* flows-ui.ts, loaded only when a paywall opens) and the React Native one (native/flows-view.ts) draw `view()` and
|
|
422
|
+
* call the actions.
|
|
423
|
+
*
|
|
424
|
+
* MRRocket never takes a payment. The last page's button calls the app's `onPurchase(plan)`, which buys through the
|
|
425
|
+
* app's own store code (StoreKit or Play through RevenueCat or Adapty, or Stripe Checkout on the web); only when it
|
|
426
|
+
* resolves is the purchase recorded. It throwing keeps the paywall open with an error, resolving `false` (the person
|
|
427
|
+
* closed the store sheet) keeps it open without one.
|
|
428
|
+
*
|
|
429
|
+
* A running test on the paywall (`experiment`) gives each person their variant's pages, by the same stable hash as
|
|
430
|
+
* every other test (`assign`), and records `$exposure` when the paywall opens. What it records, as normal events under
|
|
431
|
+
* the SDK's analytics consent:
|
|
432
|
+
* - `$paywall` `{paywall, placement, variant, page, index, action: "page"}` the first time each page shows in a run,
|
|
433
|
+
* so MRRocket shows how many reach page 2 and 3;
|
|
434
|
+
* - `{action: "purchased", plan}` after `onPurchase` went through (a test's purchase goal counts it), and
|
|
435
|
+
* `{action: "closed", index}` when the person closed it.
|
|
436
|
+
*/
|
|
437
|
+
|
|
438
|
+
/** One page of a paywall. Plans pages list the plans and buy; the others say why and go on. */
|
|
439
|
+
interface PaywallPage {
|
|
440
|
+
id: string;
|
|
441
|
+
/** "benefits": a list of what the person gets; "proof": quotes of happy customers; "plans": the plans and the buy button. */
|
|
442
|
+
kind: "benefits" | "proof" | "plans";
|
|
443
|
+
title: string;
|
|
444
|
+
body?: string;
|
|
445
|
+
/** Benefits: one line each. */
|
|
446
|
+
items?: string[];
|
|
447
|
+
/** Proof: what customers said, and who. */
|
|
448
|
+
quotes?: {
|
|
449
|
+
text: string;
|
|
450
|
+
by?: string;
|
|
451
|
+
}[];
|
|
452
|
+
/** The button: "Continue" on the way, the buy button on the plans page ("Start free trial"). */
|
|
453
|
+
button?: string;
|
|
454
|
+
}
|
|
455
|
+
/** A plan the paywall offers. `id` is what the app buys: the store product id or the Stripe price, as set in MRRocket. */
|
|
456
|
+
interface PaywallPlan {
|
|
457
|
+
id: string;
|
|
458
|
+
name: string;
|
|
459
|
+
/** As shown ("€59.99 / year"). The app's own store prices win when it passes `plans`. */
|
|
460
|
+
price: string;
|
|
461
|
+
/** A line under it ("€5 a month", "7 days free"). */
|
|
462
|
+
note?: string;
|
|
463
|
+
}
|
|
464
|
+
/** A published paywall as the config serves it. */
|
|
465
|
+
interface PaywallConfig {
|
|
466
|
+
id: string;
|
|
467
|
+
/** The placement's key (`onboarding_end`), what the app opens: `paywall.open("onboarding_end")`. */
|
|
468
|
+
placement: string;
|
|
469
|
+
/** The running test on it, a key of `experiments`; each of its variants has pages under `variants`. */
|
|
470
|
+
experiment?: string;
|
|
471
|
+
/** Control (the live paywall) first. */
|
|
472
|
+
variants: {
|
|
473
|
+
id: VariantId;
|
|
474
|
+
pages: PaywallPage[];
|
|
475
|
+
}[];
|
|
476
|
+
plans: PaywallPlan[];
|
|
477
|
+
/** The plan picked before the person picks one. */
|
|
478
|
+
highlight?: string;
|
|
479
|
+
}
|
|
480
|
+
interface PaywallResult {
|
|
481
|
+
/**
|
|
482
|
+
* "purchased": `onPurchase` went through; "chosen": the person picked a plan and there is no `onPurchase`, so the app
|
|
483
|
+
* buys it now (the store's purchase still counts for a test through RevenueCat or Adapty); "closed": the person
|
|
484
|
+
* closed it (or there was no paywall).
|
|
485
|
+
*/
|
|
486
|
+
outcome: "purchased" | "chosen" | "closed";
|
|
487
|
+
plan?: PaywallPlan;
|
|
488
|
+
paywall?: string;
|
|
489
|
+
variant?: VariantId;
|
|
490
|
+
/** The page the person was on, from 0. */
|
|
491
|
+
index: number;
|
|
492
|
+
}
|
|
493
|
+
interface PaywallOptions {
|
|
494
|
+
/**
|
|
495
|
+
* Buy the plan in your code (RevenueCat `purchasePackage`, Adapty `makePurchase`, Stripe Checkout). Throwing keeps
|
|
496
|
+
* the paywall open with an error; `false` keeps it open without one (the person cancelled the store sheet).
|
|
497
|
+
*/
|
|
498
|
+
onPurchase?: (plan: PaywallPlan) => unknown;
|
|
499
|
+
/** Your store's plans with their local prices, by the same ids; they replace what MRRocket has for them. */
|
|
500
|
+
plans?: PaywallPlan[];
|
|
501
|
+
onClose?: (result: PaywallResult) => void;
|
|
502
|
+
}
|
|
503
|
+
interface PaywallView {
|
|
504
|
+
paywall: PaywallConfig;
|
|
505
|
+
variant: VariantId;
|
|
506
|
+
page: PaywallPage;
|
|
507
|
+
index: number;
|
|
508
|
+
total: number;
|
|
509
|
+
last: boolean;
|
|
510
|
+
plans: PaywallPlan[];
|
|
511
|
+
/** The picked plan's id. */
|
|
512
|
+
plan?: string;
|
|
513
|
+
busy: boolean;
|
|
514
|
+
error?: string;
|
|
515
|
+
}
|
|
516
|
+
interface PaywallHost {
|
|
517
|
+
/** Opens the paywall of a placement; resolves when it is bought or closed (at once with "closed" when there is none). */
|
|
518
|
+
open(placement: string, options?: PaywallOptions): Promise<PaywallResult>;
|
|
519
|
+
view(): PaywallView | null;
|
|
520
|
+
subscribe(listener: () => void): () => void;
|
|
521
|
+
next(): void;
|
|
522
|
+
back(): void;
|
|
523
|
+
pick(planId: string): void;
|
|
524
|
+
close(): void;
|
|
525
|
+
}
|
|
526
|
+
|
|
417
527
|
/**
|
|
418
528
|
* The part of the SDK that runs anywhere: experiment config, variant assignment, preview links and event shapes.
|
|
419
529
|
* No React, no browser APIs, so the server helper, the proxy helper and the client share it.
|
|
@@ -421,7 +531,7 @@ interface FlowsController {
|
|
|
421
531
|
|
|
422
532
|
type VariantId = string;
|
|
423
533
|
/** This build of the SDK. Sent with the config request so MRRocket knows what the site runs (error tracking, flags). */
|
|
424
|
-
declare const SDK_VERSION = "0.
|
|
534
|
+
declare const SDK_VERSION = "0.14.0";
|
|
425
535
|
/** One variant of an experiment. `changes` replaces texts that the page marks with `<Text id>`. */
|
|
426
536
|
interface VariantConfig {
|
|
427
537
|
id: VariantId;
|
|
@@ -478,6 +588,8 @@ interface CockpitConfig {
|
|
|
478
588
|
messages?: boolean;
|
|
479
589
|
/** Page redirects set in MRRocket; the SDK sends visitors on before the page shows (`redirectFor`). SDK 0.7.0+. */
|
|
480
590
|
redirects?: RedirectConfig[];
|
|
591
|
+
/** Published paywalls with their pages, per placement (MRRocket › Conversion › Paywalls), for `paywall.open()`. SDK 0.14.0+. */
|
|
592
|
+
paywalls?: PaywallConfig[];
|
|
481
593
|
}
|
|
482
594
|
/** One condition of a flag's audience, on a property of the person (`identify(id, props)`), the id or the page. */
|
|
483
595
|
interface FlagCondition {
|
|
@@ -668,6 +780,14 @@ interface MRRocketClient {
|
|
|
668
780
|
openCancelFlow(options?: CancelFlowOptions): Promise<CancelFlowResult>;
|
|
669
781
|
/** Starts an onboarding flow now (the first live one without an id). False when there is none. */
|
|
670
782
|
startOnboarding(id?: string): boolean;
|
|
783
|
+
/**
|
|
784
|
+
* Shows the paywall of a placement built in MRRocket › Paywalls page by page (drawn by `<MRRocketProvider>`). Its
|
|
785
|
+
* last page hands the chosen plan to `onPurchase`, where your code buys it (RevenueCat, Adapty, StoreKit, Play);
|
|
786
|
+
* MRRocket never takes a payment. Pass your store's `plans` to show their local prices.
|
|
787
|
+
*/
|
|
788
|
+
openPaywall(placement: string, options?: PaywallOptions): Promise<PaywallResult>;
|
|
789
|
+
/** The open paywall for a renderer (the provider's, or your own). */
|
|
790
|
+
paywall: PaywallHost;
|
|
671
791
|
/**
|
|
672
792
|
* Push notifications from your Lifecycle journeys for the signed-in person (call `identify` first). Asks the
|
|
673
793
|
* permission (unless `ask: false`), gets the Expo push token and sends it to MRRocket. Needs `expo-notifications`.
|
|
@@ -720,8 +840,9 @@ interface NativeAppearance {
|
|
|
720
840
|
theme?: "light" | "dark" | "auto";
|
|
721
841
|
accent?: string;
|
|
722
842
|
}
|
|
723
|
-
declare const MRRocketFlows: ({ flows, appearance }: {
|
|
843
|
+
declare const MRRocketFlows: ({ flows, paywall, appearance }: {
|
|
724
844
|
flows: FlowsController;
|
|
845
|
+
paywall?: PaywallHost;
|
|
725
846
|
appearance?: NativeAppearance;
|
|
726
847
|
}) => react.FunctionComponentElement<Record<string, unknown>> | null;
|
|
727
848
|
|
|
@@ -891,6 +1012,16 @@ declare const usePush: () => {
|
|
|
891
1012
|
register: MRRocketClient["registerForPush"];
|
|
892
1013
|
unregister: MRRocketClient["unregisterForPush"];
|
|
893
1014
|
};
|
|
1015
|
+
/**
|
|
1016
|
+
* The paywalls built in MRRocket › Paywalls, page by page; the provider draws them. The last page hands the chosen plan
|
|
1017
|
+
* to `onPurchase`, where your code buys it. MRRocket never takes a payment.
|
|
1018
|
+
*
|
|
1019
|
+
* const paywall = usePaywall();
|
|
1020
|
+
* paywall.open("onboarding_end", { plans: storePlans, onPurchase: (plan) => Purchases.purchasePackage(packages[plan.id]) });
|
|
1021
|
+
*/
|
|
1022
|
+
declare const usePaywall: () => {
|
|
1023
|
+
open: MRRocketClient["openPaywall"];
|
|
1024
|
+
};
|
|
894
1025
|
/** Starts an onboarding flow now (the first live one without an id), whatever its trigger. */
|
|
895
1026
|
declare const useOnboarding: () => {
|
|
896
1027
|
start: (id?: string) => boolean;
|
|
@@ -929,4 +1060,4 @@ declare const useExpoRouterTracking: () => void;
|
|
|
929
1060
|
*/
|
|
930
1061
|
declare const useNavigationTracking: () => (state: unknown) => void;
|
|
931
1062
|
|
|
932
|
-
export { type AppContext, type CancelFlowConfig, type CancelFlowOptions, type CancelFlowResult, type CancelOffer, type ChatMessage, type ChatThread, type CockpitConfig, type FeedbackPost, type FlagConfig, type FlowView, type InAppMessage, type KeyValueStorage, type MRRocketClient, MRRocketFeedback, MRRocketFlows, MRRocketMask, MRRocketMessenger, type MRRocketOptions, MRRocketProvider, type MRRocketProviderProps, MRRocketSurveys, type NativeAppearance, type NativeConsent, type NativeFeedbackOptions, type NativeMessengerOptions, type NativePushResult, type NativePushStatus, type OnboardingFlow, type OnboardingStep, type RegisterForPushOptions, SDK_VERSION, type SurveyDefinition, type VariantId, activeRouteName, createMRRocket, getMRRocket, useCancelFlow, useExpoRouterTracking, useFeedback, useFlag, useFlagVariant, useMRRocket, useMessenger, useNavigationTracking, useOnboarding, usePush, useText, useVariant };
|
|
1063
|
+
export { type AppContext, type CancelFlowConfig, type CancelFlowOptions, type CancelFlowResult, type CancelOffer, type ChatMessage, type ChatThread, type CockpitConfig, type FeedbackPost, type FlagConfig, type FlowView, type InAppMessage, type KeyValueStorage, type MRRocketClient, MRRocketFeedback, MRRocketFlows, MRRocketMask, MRRocketMessenger, type MRRocketOptions, MRRocketProvider, type MRRocketProviderProps, MRRocketSurveys, type NativeAppearance, type NativeConsent, type NativeFeedbackOptions, type NativeMessengerOptions, type NativePushResult, type NativePushStatus, type OnboardingFlow, type OnboardingStep, type PaywallConfig, type PaywallOptions, type PaywallPage, type PaywallPlan, type PaywallResult, type RegisterForPushOptions, SDK_VERSION, type SurveyDefinition, type VariantId, activeRouteName, createMRRocket, getMRRocket, useCancelFlow, useExpoRouterTracking, useFeedback, useFlag, useFlagVariant, useMRRocket, useMessenger, useNavigationTracking, useOnboarding, usePaywall, usePush, useText, useVariant };
|
|
@@ -414,6 +414,116 @@ interface FlowsController {
|
|
|
414
414
|
};
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
+
/**
|
|
418
|
+
* Paywalls with several pages (SDK 0.14.0): what MRRocket's config serves under `paywalls` (one per placement, built
|
|
419
|
+
* in MRRocket › Conversion › Paywalls) and a small controller that walks the person through its pages, page after page,
|
|
420
|
+
* until the last one hands the chosen plan to the app. No DOM and no React: the web renderer (`mountPaywall` in
|
|
421
|
+
* flows-ui.ts, loaded only when a paywall opens) and the React Native one (native/flows-view.ts) draw `view()` and
|
|
422
|
+
* call the actions.
|
|
423
|
+
*
|
|
424
|
+
* MRRocket never takes a payment. The last page's button calls the app's `onPurchase(plan)`, which buys through the
|
|
425
|
+
* app's own store code (StoreKit or Play through RevenueCat or Adapty, or Stripe Checkout on the web); only when it
|
|
426
|
+
* resolves is the purchase recorded. It throwing keeps the paywall open with an error, resolving `false` (the person
|
|
427
|
+
* closed the store sheet) keeps it open without one.
|
|
428
|
+
*
|
|
429
|
+
* A running test on the paywall (`experiment`) gives each person their variant's pages, by the same stable hash as
|
|
430
|
+
* every other test (`assign`), and records `$exposure` when the paywall opens. What it records, as normal events under
|
|
431
|
+
* the SDK's analytics consent:
|
|
432
|
+
* - `$paywall` `{paywall, placement, variant, page, index, action: "page"}` the first time each page shows in a run,
|
|
433
|
+
* so MRRocket shows how many reach page 2 and 3;
|
|
434
|
+
* - `{action: "purchased", plan}` after `onPurchase` went through (a test's purchase goal counts it), and
|
|
435
|
+
* `{action: "closed", index}` when the person closed it.
|
|
436
|
+
*/
|
|
437
|
+
|
|
438
|
+
/** One page of a paywall. Plans pages list the plans and buy; the others say why and go on. */
|
|
439
|
+
interface PaywallPage {
|
|
440
|
+
id: string;
|
|
441
|
+
/** "benefits": a list of what the person gets; "proof": quotes of happy customers; "plans": the plans and the buy button. */
|
|
442
|
+
kind: "benefits" | "proof" | "plans";
|
|
443
|
+
title: string;
|
|
444
|
+
body?: string;
|
|
445
|
+
/** Benefits: one line each. */
|
|
446
|
+
items?: string[];
|
|
447
|
+
/** Proof: what customers said, and who. */
|
|
448
|
+
quotes?: {
|
|
449
|
+
text: string;
|
|
450
|
+
by?: string;
|
|
451
|
+
}[];
|
|
452
|
+
/** The button: "Continue" on the way, the buy button on the plans page ("Start free trial"). */
|
|
453
|
+
button?: string;
|
|
454
|
+
}
|
|
455
|
+
/** A plan the paywall offers. `id` is what the app buys: the store product id or the Stripe price, as set in MRRocket. */
|
|
456
|
+
interface PaywallPlan {
|
|
457
|
+
id: string;
|
|
458
|
+
name: string;
|
|
459
|
+
/** As shown ("€59.99 / year"). The app's own store prices win when it passes `plans`. */
|
|
460
|
+
price: string;
|
|
461
|
+
/** A line under it ("€5 a month", "7 days free"). */
|
|
462
|
+
note?: string;
|
|
463
|
+
}
|
|
464
|
+
/** A published paywall as the config serves it. */
|
|
465
|
+
interface PaywallConfig {
|
|
466
|
+
id: string;
|
|
467
|
+
/** The placement's key (`onboarding_end`), what the app opens: `paywall.open("onboarding_end")`. */
|
|
468
|
+
placement: string;
|
|
469
|
+
/** The running test on it, a key of `experiments`; each of its variants has pages under `variants`. */
|
|
470
|
+
experiment?: string;
|
|
471
|
+
/** Control (the live paywall) first. */
|
|
472
|
+
variants: {
|
|
473
|
+
id: VariantId;
|
|
474
|
+
pages: PaywallPage[];
|
|
475
|
+
}[];
|
|
476
|
+
plans: PaywallPlan[];
|
|
477
|
+
/** The plan picked before the person picks one. */
|
|
478
|
+
highlight?: string;
|
|
479
|
+
}
|
|
480
|
+
interface PaywallResult {
|
|
481
|
+
/**
|
|
482
|
+
* "purchased": `onPurchase` went through; "chosen": the person picked a plan and there is no `onPurchase`, so the app
|
|
483
|
+
* buys it now (the store's purchase still counts for a test through RevenueCat or Adapty); "closed": the person
|
|
484
|
+
* closed it (or there was no paywall).
|
|
485
|
+
*/
|
|
486
|
+
outcome: "purchased" | "chosen" | "closed";
|
|
487
|
+
plan?: PaywallPlan;
|
|
488
|
+
paywall?: string;
|
|
489
|
+
variant?: VariantId;
|
|
490
|
+
/** The page the person was on, from 0. */
|
|
491
|
+
index: number;
|
|
492
|
+
}
|
|
493
|
+
interface PaywallOptions {
|
|
494
|
+
/**
|
|
495
|
+
* Buy the plan in your code (RevenueCat `purchasePackage`, Adapty `makePurchase`, Stripe Checkout). Throwing keeps
|
|
496
|
+
* the paywall open with an error; `false` keeps it open without one (the person cancelled the store sheet).
|
|
497
|
+
*/
|
|
498
|
+
onPurchase?: (plan: PaywallPlan) => unknown;
|
|
499
|
+
/** Your store's plans with their local prices, by the same ids; they replace what MRRocket has for them. */
|
|
500
|
+
plans?: PaywallPlan[];
|
|
501
|
+
onClose?: (result: PaywallResult) => void;
|
|
502
|
+
}
|
|
503
|
+
interface PaywallView {
|
|
504
|
+
paywall: PaywallConfig;
|
|
505
|
+
variant: VariantId;
|
|
506
|
+
page: PaywallPage;
|
|
507
|
+
index: number;
|
|
508
|
+
total: number;
|
|
509
|
+
last: boolean;
|
|
510
|
+
plans: PaywallPlan[];
|
|
511
|
+
/** The picked plan's id. */
|
|
512
|
+
plan?: string;
|
|
513
|
+
busy: boolean;
|
|
514
|
+
error?: string;
|
|
515
|
+
}
|
|
516
|
+
interface PaywallHost {
|
|
517
|
+
/** Opens the paywall of a placement; resolves when it is bought or closed (at once with "closed" when there is none). */
|
|
518
|
+
open(placement: string, options?: PaywallOptions): Promise<PaywallResult>;
|
|
519
|
+
view(): PaywallView | null;
|
|
520
|
+
subscribe(listener: () => void): () => void;
|
|
521
|
+
next(): void;
|
|
522
|
+
back(): void;
|
|
523
|
+
pick(planId: string): void;
|
|
524
|
+
close(): void;
|
|
525
|
+
}
|
|
526
|
+
|
|
417
527
|
/**
|
|
418
528
|
* The part of the SDK that runs anywhere: experiment config, variant assignment, preview links and event shapes.
|
|
419
529
|
* No React, no browser APIs, so the server helper, the proxy helper and the client share it.
|
|
@@ -421,7 +531,7 @@ interface FlowsController {
|
|
|
421
531
|
|
|
422
532
|
type VariantId = string;
|
|
423
533
|
/** This build of the SDK. Sent with the config request so MRRocket knows what the site runs (error tracking, flags). */
|
|
424
|
-
declare const SDK_VERSION = "0.
|
|
534
|
+
declare const SDK_VERSION = "0.14.0";
|
|
425
535
|
/** One variant of an experiment. `changes` replaces texts that the page marks with `<Text id>`. */
|
|
426
536
|
interface VariantConfig {
|
|
427
537
|
id: VariantId;
|
|
@@ -478,6 +588,8 @@ interface CockpitConfig {
|
|
|
478
588
|
messages?: boolean;
|
|
479
589
|
/** Page redirects set in MRRocket; the SDK sends visitors on before the page shows (`redirectFor`). SDK 0.7.0+. */
|
|
480
590
|
redirects?: RedirectConfig[];
|
|
591
|
+
/** Published paywalls with their pages, per placement (MRRocket › Conversion › Paywalls), for `paywall.open()`. SDK 0.14.0+. */
|
|
592
|
+
paywalls?: PaywallConfig[];
|
|
481
593
|
}
|
|
482
594
|
/** One condition of a flag's audience, on a property of the person (`identify(id, props)`), the id or the page. */
|
|
483
595
|
interface FlagCondition {
|
|
@@ -668,6 +780,14 @@ interface MRRocketClient {
|
|
|
668
780
|
openCancelFlow(options?: CancelFlowOptions): Promise<CancelFlowResult>;
|
|
669
781
|
/** Starts an onboarding flow now (the first live one without an id). False when there is none. */
|
|
670
782
|
startOnboarding(id?: string): boolean;
|
|
783
|
+
/**
|
|
784
|
+
* Shows the paywall of a placement built in MRRocket › Paywalls page by page (drawn by `<MRRocketProvider>`). Its
|
|
785
|
+
* last page hands the chosen plan to `onPurchase`, where your code buys it (RevenueCat, Adapty, StoreKit, Play);
|
|
786
|
+
* MRRocket never takes a payment. Pass your store's `plans` to show their local prices.
|
|
787
|
+
*/
|
|
788
|
+
openPaywall(placement: string, options?: PaywallOptions): Promise<PaywallResult>;
|
|
789
|
+
/** The open paywall for a renderer (the provider's, or your own). */
|
|
790
|
+
paywall: PaywallHost;
|
|
671
791
|
/**
|
|
672
792
|
* Push notifications from your Lifecycle journeys for the signed-in person (call `identify` first). Asks the
|
|
673
793
|
* permission (unless `ask: false`), gets the Expo push token and sends it to MRRocket. Needs `expo-notifications`.
|
|
@@ -720,8 +840,9 @@ interface NativeAppearance {
|
|
|
720
840
|
theme?: "light" | "dark" | "auto";
|
|
721
841
|
accent?: string;
|
|
722
842
|
}
|
|
723
|
-
declare const MRRocketFlows: ({ flows, appearance }: {
|
|
843
|
+
declare const MRRocketFlows: ({ flows, paywall, appearance }: {
|
|
724
844
|
flows: FlowsController;
|
|
845
|
+
paywall?: PaywallHost;
|
|
725
846
|
appearance?: NativeAppearance;
|
|
726
847
|
}) => react.FunctionComponentElement<Record<string, unknown>> | null;
|
|
727
848
|
|
|
@@ -891,6 +1012,16 @@ declare const usePush: () => {
|
|
|
891
1012
|
register: MRRocketClient["registerForPush"];
|
|
892
1013
|
unregister: MRRocketClient["unregisterForPush"];
|
|
893
1014
|
};
|
|
1015
|
+
/**
|
|
1016
|
+
* The paywalls built in MRRocket › Paywalls, page by page; the provider draws them. The last page hands the chosen plan
|
|
1017
|
+
* to `onPurchase`, where your code buys it. MRRocket never takes a payment.
|
|
1018
|
+
*
|
|
1019
|
+
* const paywall = usePaywall();
|
|
1020
|
+
* paywall.open("onboarding_end", { plans: storePlans, onPurchase: (plan) => Purchases.purchasePackage(packages[plan.id]) });
|
|
1021
|
+
*/
|
|
1022
|
+
declare const usePaywall: () => {
|
|
1023
|
+
open: MRRocketClient["openPaywall"];
|
|
1024
|
+
};
|
|
894
1025
|
/** Starts an onboarding flow now (the first live one without an id), whatever its trigger. */
|
|
895
1026
|
declare const useOnboarding: () => {
|
|
896
1027
|
start: (id?: string) => boolean;
|
|
@@ -929,4 +1060,4 @@ declare const useExpoRouterTracking: () => void;
|
|
|
929
1060
|
*/
|
|
930
1061
|
declare const useNavigationTracking: () => (state: unknown) => void;
|
|
931
1062
|
|
|
932
|
-
export { type AppContext, type CancelFlowConfig, type CancelFlowOptions, type CancelFlowResult, type CancelOffer, type ChatMessage, type ChatThread, type CockpitConfig, type FeedbackPost, type FlagConfig, type FlowView, type InAppMessage, type KeyValueStorage, type MRRocketClient, MRRocketFeedback, MRRocketFlows, MRRocketMask, MRRocketMessenger, type MRRocketOptions, MRRocketProvider, type MRRocketProviderProps, MRRocketSurveys, type NativeAppearance, type NativeConsent, type NativeFeedbackOptions, type NativeMessengerOptions, type NativePushResult, type NativePushStatus, type OnboardingFlow, type OnboardingStep, type RegisterForPushOptions, SDK_VERSION, type SurveyDefinition, type VariantId, activeRouteName, createMRRocket, getMRRocket, useCancelFlow, useExpoRouterTracking, useFeedback, useFlag, useFlagVariant, useMRRocket, useMessenger, useNavigationTracking, useOnboarding, usePush, useText, useVariant };
|
|
1063
|
+
export { type AppContext, type CancelFlowConfig, type CancelFlowOptions, type CancelFlowResult, type CancelOffer, type ChatMessage, type ChatThread, type CockpitConfig, type FeedbackPost, type FlagConfig, type FlowView, type InAppMessage, type KeyValueStorage, type MRRocketClient, MRRocketFeedback, MRRocketFlows, MRRocketMask, MRRocketMessenger, type MRRocketOptions, MRRocketProvider, type MRRocketProviderProps, MRRocketSurveys, type NativeAppearance, type NativeConsent, type NativeFeedbackOptions, type NativeMessengerOptions, type NativePushResult, type NativePushStatus, type OnboardingFlow, type OnboardingStep, type PaywallConfig, type PaywallOptions, type PaywallPage, type PaywallPlan, type PaywallResult, type RegisterForPushOptions, SDK_VERSION, type SurveyDefinition, type VariantId, activeRouteName, createMRRocket, getMRRocket, useCancelFlow, useExpoRouterTracking, useFeedback, useFlag, useFlagVariant, useMRRocket, useMessenger, useNavigationTracking, useOnboarding, usePaywall, usePush, useText, useVariant };
|