@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.
Files changed (45) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +60 -0
  3. package/dist/browser/mrrocket-flows.js +11 -2
  4. package/dist/browser/mrrocket-messenger.js +143 -65
  5. package/dist/browser/mrrocket.js +1 -1
  6. package/dist/browser.d.ts +1 -1
  7. package/dist/browser.js +2 -1
  8. package/dist/browser.js.map +1 -1
  9. package/dist/client.d.ts +12 -0
  10. package/dist/client.js +22 -0
  11. package/dist/client.js.map +1 -1
  12. package/dist/context.d.ts +3 -0
  13. package/dist/context.js.map +1 -1
  14. package/dist/core.d.ts +4 -1
  15. package/dist/core.js +1 -1
  16. package/dist/core.js.map +1 -1
  17. package/dist/flows-ui.d.ts +6 -4
  18. package/dist/flows-ui.js +99 -4
  19. package/dist/flows-ui.js.map +1 -1
  20. package/dist/index.d.ts +2 -1
  21. package/dist/index.js +4 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/messenger-ui.d.ts +22 -0
  24. package/dist/messenger-ui.js +313 -79
  25. package/dist/messenger-ui.js.map +1 -1
  26. package/dist/paywall.d.ts +123 -0
  27. package/dist/paywall.js +131 -0
  28. package/dist/paywall.js.map +1 -0
  29. package/dist/proxy.d.ts +1 -0
  30. package/dist/proxy.js +3 -0
  31. package/dist/proxy.js.map +1 -1
  32. package/dist/react-native/index.d.cts +134 -3
  33. package/dist/react-native/index.d.ts +134 -3
  34. package/dist/react-native/index.js +204 -4
  35. package/dist/react-native/index.js.map +1 -1
  36. package/dist/security.d.ts +71 -0
  37. package/dist/security.js +73 -0
  38. package/dist/security.js.map +1 -0
  39. package/dist/server.d.ts +1 -0
  40. package/dist/server.js +8 -1
  41. package/dist/server.js.map +1 -1
  42. package/dist/styles-css.d.ts +1 -1
  43. package/dist/styles-css.js +141 -63
  44. package/dist/styles-css.js.map +1 -1
  45. package/package.json +1 -1
@@ -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.12.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 };