@mrrocket/sdk 0.13.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 +8 -1
- package/README.md +34 -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/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/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
|
@@ -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 };
|
|
@@ -41,6 +41,7 @@ __export(native_exports, {
|
|
|
41
41
|
useMessenger: () => useMessenger,
|
|
42
42
|
useNavigationTracking: () => useNavigationTracking,
|
|
43
43
|
useOnboarding: () => useOnboarding,
|
|
44
|
+
usePaywall: () => usePaywall,
|
|
44
45
|
usePush: () => usePush,
|
|
45
46
|
useText: () => useText,
|
|
46
47
|
useVariant: () => useVariant
|
|
@@ -49,7 +50,7 @@ module.exports = __toCommonJS(native_exports);
|
|
|
49
50
|
var import_react3 = require("react");
|
|
50
51
|
|
|
51
52
|
// src/core.ts
|
|
52
|
-
var SDK_VERSION = "0.
|
|
53
|
+
var SDK_VERSION = "0.14.0";
|
|
53
54
|
var propOf = (person, name) => {
|
|
54
55
|
const want = name.trim().toLowerCase();
|
|
55
56
|
for (const [k, v] of Object.entries(person)) if (k.toLowerCase() === want) return v;
|
|
@@ -805,6 +806,131 @@ var createFlows = (env) => {
|
|
|
805
806
|
return controller;
|
|
806
807
|
};
|
|
807
808
|
|
|
809
|
+
// src/paywall.ts
|
|
810
|
+
var PAYWALL_EVENT = "$paywall";
|
|
811
|
+
var paywallPages = (config, w, distinctId) => {
|
|
812
|
+
const e = w.experiment ? config?.experiments.find((x) => x.key === w.experiment) : void 0;
|
|
813
|
+
const id = e ? assign(e, distinctId) : w.variants[0]?.id;
|
|
814
|
+
const v = w.variants.find((x) => x.id === id) ?? w.variants[0];
|
|
815
|
+
return v ? { variant: v.id, pages: v.pages, test: e?.status === "running" && !e.winner ? e.key : void 0 } : null;
|
|
816
|
+
};
|
|
817
|
+
var createPaywallHost = (env) => {
|
|
818
|
+
let run = null;
|
|
819
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
820
|
+
const emit = () => {
|
|
821
|
+
for (const l of [...listeners]) {
|
|
822
|
+
try {
|
|
823
|
+
l();
|
|
824
|
+
} catch {
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
const base = () => run ? { paywall: run.w.id, placement: run.w.placement, variant: run.variant } : {};
|
|
829
|
+
const show = () => {
|
|
830
|
+
if (run && !run.seen.has(run.index)) {
|
|
831
|
+
run.seen.add(run.index);
|
|
832
|
+
env.track(PAYWALL_EVENT, { ...base(), page: run.pages[run.index].id, index: run.index, action: "page" });
|
|
833
|
+
}
|
|
834
|
+
emit();
|
|
835
|
+
};
|
|
836
|
+
const end = (r) => {
|
|
837
|
+
const c = run;
|
|
838
|
+
if (!c) return;
|
|
839
|
+
run = null;
|
|
840
|
+
if (r.outcome === "closed") c.options.onClose?.(r);
|
|
841
|
+
c.resolve(r);
|
|
842
|
+
emit();
|
|
843
|
+
};
|
|
844
|
+
const result = (outcome) => ({
|
|
845
|
+
outcome,
|
|
846
|
+
index: run?.index ?? 0,
|
|
847
|
+
...run ? { paywall: run.w.id, variant: run.variant } : {},
|
|
848
|
+
...outcome !== "closed" && run ? { plan: run.plans.find((p) => p.id === run.plan) } : {}
|
|
849
|
+
});
|
|
850
|
+
const buy = () => {
|
|
851
|
+
const c = run;
|
|
852
|
+
const plan = c?.plans.find((p) => p.id === c.plan);
|
|
853
|
+
if (!c || !plan || c.busy) return;
|
|
854
|
+
if (!c.options.onPurchase) return end(result("chosen"));
|
|
855
|
+
c.busy = true;
|
|
856
|
+
c.error = void 0;
|
|
857
|
+
emit();
|
|
858
|
+
Promise.resolve().then(() => c.options.onPurchase?.(plan)).then(
|
|
859
|
+
(ok) => {
|
|
860
|
+
c.busy = false;
|
|
861
|
+
if (run !== c) return;
|
|
862
|
+
if (ok === false) return emit();
|
|
863
|
+
env.track(PAYWALL_EVENT, { ...base(), page: c.pages[c.index].id, index: c.index, plan: plan.id, action: "purchased" });
|
|
864
|
+
end(result("purchased"));
|
|
865
|
+
},
|
|
866
|
+
() => {
|
|
867
|
+
c.busy = false;
|
|
868
|
+
c.error = "The purchase did not go through. Please try again.";
|
|
869
|
+
emit();
|
|
870
|
+
}
|
|
871
|
+
);
|
|
872
|
+
};
|
|
873
|
+
return {
|
|
874
|
+
open: (placement, options = {}) => {
|
|
875
|
+
if (run) return run.promise;
|
|
876
|
+
const config = env.config();
|
|
877
|
+
const w = config?.paywalls?.find((x) => x.placement === placement);
|
|
878
|
+
const picked = w ? paywallPages(config, w, env.distinctId()) : null;
|
|
879
|
+
if (!w || !picked?.pages.length) return Promise.resolve({ outcome: "closed", index: 0 });
|
|
880
|
+
const own = new Map((options.plans ?? []).map((p) => [p.id, p]));
|
|
881
|
+
const plans = w.plans.map((p) => own.get(p.id) ?? p);
|
|
882
|
+
let resolve = () => {
|
|
883
|
+
};
|
|
884
|
+
const promise = new Promise((r) => resolve = r);
|
|
885
|
+
const plan = plans.some((p) => p.id === w.highlight) ? w.highlight : plans[0]?.id;
|
|
886
|
+
run = { w, variant: picked.variant, pages: picked.pages, plans, index: 0, plan, busy: false, seen: /* @__PURE__ */ new Set(), options, resolve, promise };
|
|
887
|
+
if (picked.test) env.track("$exposure", { experiment: picked.test, variant: picked.variant });
|
|
888
|
+
show();
|
|
889
|
+
return promise;
|
|
890
|
+
},
|
|
891
|
+
view: () => run ? {
|
|
892
|
+
paywall: run.w,
|
|
893
|
+
variant: run.variant,
|
|
894
|
+
page: run.pages[run.index],
|
|
895
|
+
index: run.index,
|
|
896
|
+
total: run.pages.length,
|
|
897
|
+
last: run.index === run.pages.length - 1,
|
|
898
|
+
plans: run.plans,
|
|
899
|
+
plan: run.plan,
|
|
900
|
+
busy: run.busy,
|
|
901
|
+
error: run.error
|
|
902
|
+
} : null,
|
|
903
|
+
subscribe: (l) => {
|
|
904
|
+
listeners.add(l);
|
|
905
|
+
return () => listeners.delete(l);
|
|
906
|
+
},
|
|
907
|
+
next: () => {
|
|
908
|
+
if (!run || run.busy) return;
|
|
909
|
+
if (run.pages[run.index].kind === "plans" || run.index === run.pages.length - 1) return buy();
|
|
910
|
+
run.index++;
|
|
911
|
+
run.error = void 0;
|
|
912
|
+
show();
|
|
913
|
+
},
|
|
914
|
+
back: () => {
|
|
915
|
+
if (!run || run.busy || !run.index) return;
|
|
916
|
+
run.index--;
|
|
917
|
+
run.error = void 0;
|
|
918
|
+
emit();
|
|
919
|
+
},
|
|
920
|
+
pick: (id) => {
|
|
921
|
+
if (!run || run.busy || !run.plans.some((p) => p.id === id)) return;
|
|
922
|
+
run.plan = id;
|
|
923
|
+
run.error = void 0;
|
|
924
|
+
emit();
|
|
925
|
+
},
|
|
926
|
+
close: () => {
|
|
927
|
+
if (!run || run.busy) return;
|
|
928
|
+
env.track(PAYWALL_EVENT, { ...base(), index: run.index, action: "closed" });
|
|
929
|
+
end(result("closed"));
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
};
|
|
933
|
+
|
|
808
934
|
// src/rage.ts
|
|
809
935
|
var RageDetector = class {
|
|
810
936
|
constructor(windowMs = 1e3, radius = 30, count = 3) {
|
|
@@ -1072,6 +1198,7 @@ var createClient = (options, deps) => {
|
|
|
1072
1198
|
const errorQueue = [];
|
|
1073
1199
|
const errorsSeen = /* @__PURE__ */ new Set();
|
|
1074
1200
|
let errorsSent = 0;
|
|
1201
|
+
const paywall = createPaywallHost({ config: () => config, distinctId: () => distinctId(), track: (event, props) => record(event, props) });
|
|
1075
1202
|
const flows = createFlows({
|
|
1076
1203
|
track: (event, props) => record(event, props),
|
|
1077
1204
|
personId: () => consent !== "off" ? identified : void 0,
|
|
@@ -1632,6 +1759,8 @@ var createClient = (options, deps) => {
|
|
|
1632
1759
|
},
|
|
1633
1760
|
openCancelFlow: (o) => flows.openCancelFlow(o),
|
|
1634
1761
|
startOnboarding: (id) => flows.startOnboarding(id),
|
|
1762
|
+
openPaywall: (placement, o) => paywall.open(placement, o),
|
|
1763
|
+
paywall,
|
|
1635
1764
|
registerForPush,
|
|
1636
1765
|
unregisterForPush,
|
|
1637
1766
|
flows,
|
|
@@ -1697,19 +1826,21 @@ var RN = require("react-native");
|
|
|
1697
1826
|
var LIGHT = { bg: "#ffffff", fg: "#1c1d20", muted: "#5d6068", soft: "#f4f5f7", line: "#e4e5e8" };
|
|
1698
1827
|
var DARK = { bg: "#17181b", fg: "#f1f2f4", muted: "#a9acb3", soft: "#232428", line: "#2e2f35" };
|
|
1699
1828
|
var months = (n) => `${n} ${n === 1 ? "month" : "months"}`;
|
|
1700
|
-
var MRRocketFlows = ({ flows, appearance = {} }) => {
|
|
1829
|
+
var MRRocketFlows = ({ flows, paywall, appearance = {} }) => {
|
|
1701
1830
|
const scheme = RN.useColorScheme?.();
|
|
1702
1831
|
const dark = appearance.theme === "dark" || (appearance.theme ?? "auto") === "auto" && scheme === "dark";
|
|
1703
1832
|
const p = { ...dark ? DARK : LIGHT, accent: appearance.accent ?? "#3f76ff", danger: "#d92d20" };
|
|
1704
1833
|
const [, setTick] = (0, import_react.useState)(0);
|
|
1705
1834
|
(0, import_react.useEffect)(() => flows.subscribe(() => setTick((t) => t + 1)), [flows]);
|
|
1835
|
+
(0, import_react.useEffect)(() => paywall?.subscribe(() => setTick((t) => t + 1)), [paywall]);
|
|
1836
|
+
const pw = paywall?.view() ?? null;
|
|
1706
1837
|
const v = flows.view();
|
|
1707
1838
|
const [comment, setComment] = (0, import_react.useState)("");
|
|
1708
1839
|
const screen = v?.type === "cancel" ? v.screen : null;
|
|
1709
1840
|
(0, import_react.useEffect)(() => {
|
|
1710
1841
|
if (!screen) setComment("");
|
|
1711
1842
|
}, [screen]);
|
|
1712
|
-
if (!v) return null;
|
|
1843
|
+
if (!v && !pw) return null;
|
|
1713
1844
|
const text2 = (children, style = {}) => (0, import_react.createElement)(RN.Text, { style: { color: p.fg, fontSize: 15, ...style } }, children);
|
|
1714
1845
|
const muted = (children, style = {}) => text2(children, { color: p.muted, fontSize: 13, ...style });
|
|
1715
1846
|
const title = (children) => (0, import_react.createElement)(RN.Text, { accessibilityRole: "header", style: { color: p.fg, fontSize: 18, fontWeight: "700" } }, children);
|
|
@@ -1778,6 +1909,71 @@ var MRRocketFlows = ({ flows, appearance = {} }) => {
|
|
|
1778
1909
|
accessibilityLabel: placeholder,
|
|
1779
1910
|
style: { minHeight: 72, borderWidth: 1, borderColor: p.line, borderRadius: 10, padding: 10, color: p.fg, textAlignVertical: "top" }
|
|
1780
1911
|
});
|
|
1912
|
+
const paywallSheet = (w) => {
|
|
1913
|
+
const pg = w.page;
|
|
1914
|
+
const host = paywall;
|
|
1915
|
+
return sheet(
|
|
1916
|
+
[
|
|
1917
|
+
(0, import_react.createElement)(
|
|
1918
|
+
RN.View,
|
|
1919
|
+
{ key: "top", style: { flexDirection: "row", alignItems: "center", gap: 4 } },
|
|
1920
|
+
...w.total > 1 ? Array.from(
|
|
1921
|
+
{ length: w.total },
|
|
1922
|
+
(_, i) => (0, import_react.createElement)(RN.View, { key: i, style: { width: 16, height: 4, borderRadius: 4, backgroundColor: i <= w.index ? p.accent : p.line } })
|
|
1923
|
+
) : [],
|
|
1924
|
+
close("Close", host.close)
|
|
1925
|
+
),
|
|
1926
|
+
title(pg.title),
|
|
1927
|
+
pg.body ? muted(pg.body, { fontSize: 15 }) : null,
|
|
1928
|
+
...pg.kind === "benefits" ? (pg.items ?? []).map(
|
|
1929
|
+
(item, i) => (0, import_react.createElement)(
|
|
1930
|
+
RN.View,
|
|
1931
|
+
{ key: `b${i}`, style: { flexDirection: "row", gap: 10, alignItems: "flex-start" } },
|
|
1932
|
+
text2("\u2713", { color: p.accent, fontWeight: "700" }),
|
|
1933
|
+
text2(item, { flex: 1 })
|
|
1934
|
+
)
|
|
1935
|
+
) : [],
|
|
1936
|
+
...pg.kind === "proof" ? (pg.quotes ?? []).map(
|
|
1937
|
+
(q, i) => (0, import_react.createElement)(RN.View, { key: `q${i}`, style: { backgroundColor: p.soft, borderRadius: 12, padding: 12, gap: 6 } }, text2(q.text), q.by ? muted(q.by) : null)
|
|
1938
|
+
) : [],
|
|
1939
|
+
pg.kind === "plans" ? (0, import_react.createElement)(
|
|
1940
|
+
RN.View,
|
|
1941
|
+
{ key: "plans", accessibilityRole: "radiogroup", style: { gap: 6 } },
|
|
1942
|
+
...w.plans.map(
|
|
1943
|
+
(plan) => (0, import_react.createElement)(
|
|
1944
|
+
RN.Pressable,
|
|
1945
|
+
{
|
|
1946
|
+
key: plan.id,
|
|
1947
|
+
onPress: () => host.pick(plan.id),
|
|
1948
|
+
accessibilityRole: "radio",
|
|
1949
|
+
accessibilityLabel: `${plan.name}, ${plan.price}`,
|
|
1950
|
+
accessibilityState: { checked: w.plan === plan.id },
|
|
1951
|
+
style: {
|
|
1952
|
+
minHeight: 52,
|
|
1953
|
+
paddingHorizontal: 12,
|
|
1954
|
+
borderRadius: 10,
|
|
1955
|
+
borderWidth: 1,
|
|
1956
|
+
borderColor: w.plan === plan.id ? p.accent : p.line,
|
|
1957
|
+
backgroundColor: w.plan === plan.id ? p.soft : p.bg,
|
|
1958
|
+
flexDirection: "row",
|
|
1959
|
+
alignItems: "center",
|
|
1960
|
+
gap: 10
|
|
1961
|
+
}
|
|
1962
|
+
},
|
|
1963
|
+
(0, import_react.createElement)(RN.View, { style: { flex: 1 } }, text2(plan.name, { fontWeight: "600" }), plan.note ? muted(plan.note) : null),
|
|
1964
|
+
text2(plan.price, { fontWeight: "700" })
|
|
1965
|
+
)
|
|
1966
|
+
)
|
|
1967
|
+
) : null,
|
|
1968
|
+
w.error ? text2(w.error, { color: p.danger, fontSize: 13 }) : null,
|
|
1969
|
+
btn(w.busy ? "One moment\u2026" : pg.button || "Continue", "primary", host.next, w.busy || pg.kind === "plans" && !w.plan),
|
|
1970
|
+
w.index ? btn("Back", "quiet", host.back, w.busy) : null
|
|
1971
|
+
],
|
|
1972
|
+
host.close
|
|
1973
|
+
);
|
|
1974
|
+
};
|
|
1975
|
+
if (pw) return paywallSheet(pw);
|
|
1976
|
+
if (!v) return null;
|
|
1781
1977
|
if (v.type === "cancel") {
|
|
1782
1978
|
const top = (0, import_react.createElement)(
|
|
1783
1979
|
RN.View,
|
|
@@ -3067,7 +3263,7 @@ var MRRocketProvider = ({ client: given, children, consent, flows = true, appear
|
|
|
3067
3263
|
Ctx.Provider,
|
|
3068
3264
|
{ value: client },
|
|
3069
3265
|
heatmaps ? (0, import_react3.createElement)(RN4.View, { style: { flex: 1 }, onTouchStartCapture: (e) => touch(e, false), onTouchEndCapture: (e) => touch(e, true) }, children) : children,
|
|
3070
|
-
flows ? (0, import_react3.createElement)(MRRocketFlows, { flows: client.flows, appearance }) : null,
|
|
3266
|
+
flows ? (0, import_react3.createElement)(MRRocketFlows, { flows: client.flows, paywall: client.paywall, appearance }) : null,
|
|
3071
3267
|
surveys ? (0, import_react3.createElement)(MRRocketSurveys, { client, appearance }) : null,
|
|
3072
3268
|
feedback ? (0, import_react3.createElement)(MRRocketFeedback, { client, appearance, options: feedback }) : null,
|
|
3073
3269
|
messenger ? (0, import_react3.createElement)(MRRocketMessenger, { client, appearance, options: messenger }) : null
|
|
@@ -3104,6 +3300,10 @@ var usePush = () => {
|
|
|
3104
3300
|
const client = useClient();
|
|
3105
3301
|
return { register: client.registerForPush, unregister: client.unregisterForPush };
|
|
3106
3302
|
};
|
|
3303
|
+
var usePaywall = () => {
|
|
3304
|
+
const client = useClient();
|
|
3305
|
+
return { open: client.openPaywall };
|
|
3306
|
+
};
|
|
3107
3307
|
var useOnboarding = () => {
|
|
3108
3308
|
const client = useClient();
|
|
3109
3309
|
return { start: client.startOnboarding };
|