react-native-revnix 1.4.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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +766 -0
  3. package/dist/blocks/background.d.ts +101 -0
  4. package/dist/blocks/background.js +165 -0
  5. package/dist/blocks/color.d.ts +88 -0
  6. package/dist/blocks/color.js +329 -0
  7. package/dist/blocks/locale.d.ts +42 -0
  8. package/dist/blocks/locale.js +263 -0
  9. package/dist/blocks/selection.d.ts +49 -0
  10. package/dist/blocks/selection.js +109 -0
  11. package/dist/blocks/style.d.ts +93 -0
  12. package/dist/blocks/style.js +365 -0
  13. package/dist/blocks/tags.d.ts +12 -0
  14. package/dist/blocks/tags.js +121 -0
  15. package/dist/blocks/types.d.ts +301 -0
  16. package/dist/blocks/types.js +44 -0
  17. package/dist/client.d.ts +402 -0
  18. package/dist/client.js +1653 -0
  19. package/dist/device.d.ts +29 -0
  20. package/dist/device.js +137 -0
  21. package/dist/errors.d.ts +39 -0
  22. package/dist/errors.js +68 -0
  23. package/dist/esm/blocks/background.js +160 -0
  24. package/dist/esm/blocks/color.js +318 -0
  25. package/dist/esm/blocks/locale.js +255 -0
  26. package/dist/esm/blocks/selection.js +100 -0
  27. package/dist/esm/blocks/style.js +356 -0
  28. package/dist/esm/blocks/tags.js +117 -0
  29. package/dist/esm/blocks/types.js +40 -0
  30. package/dist/esm/client.js +1649 -0
  31. package/dist/esm/device.js +130 -0
  32. package/dist/esm/errors.js +59 -0
  33. package/dist/esm/iap.js +101 -0
  34. package/dist/esm/implicit.js +63 -0
  35. package/dist/esm/index.js +29 -0
  36. package/dist/esm/package.json +1 -0
  37. package/dist/esm/storage.js +78 -0
  38. package/dist/esm/types.js +4 -0
  39. package/dist/esm/ui/RevnixBlockPaywall.js +706 -0
  40. package/dist/esm/ui/RevnixPaywall.js +890 -0
  41. package/dist/esm/ui/index.js +6 -0
  42. package/dist/esm/version.js +4 -0
  43. package/dist/iap.d.ts +20 -0
  44. package/dist/iap.js +104 -0
  45. package/dist/implicit.d.ts +69 -0
  46. package/dist/implicit.js +67 -0
  47. package/dist/index.d.ts +18 -0
  48. package/dist/index.js +50 -0
  49. package/dist/storage.d.ts +14 -0
  50. package/dist/storage.js +85 -0
  51. package/dist/types.d.ts +463 -0
  52. package/dist/types.js +5 -0
  53. package/dist/ui/RevnixBlockPaywall.d.ts +96 -0
  54. package/dist/ui/RevnixBlockPaywall.js +742 -0
  55. package/dist/ui/RevnixPaywall.d.ts +148 -0
  56. package/dist/ui/RevnixPaywall.js +927 -0
  57. package/dist/ui/index.d.ts +7 -0
  58. package/dist/ui/index.js +25 -0
  59. package/dist/version.d.ts +1 -0
  60. package/dist/version.js +7 -0
  61. package/package.json +79 -0
  62. package/src/blocks/background.ts +232 -0
  63. package/src/blocks/color.ts +352 -0
  64. package/src/blocks/locale.ts +271 -0
  65. package/src/blocks/paywall-background-wire.json +12 -0
  66. package/src/blocks/paywall-selection-wire.json +149 -0
  67. package/src/blocks/selection.ts +161 -0
  68. package/src/blocks/style.ts +411 -0
  69. package/src/blocks/tags.ts +125 -0
  70. package/src/blocks/types.ts +352 -0
  71. package/src/client.ts +1993 -0
  72. package/src/device.ts +146 -0
  73. package/src/errors.ts +69 -0
  74. package/src/iap.ts +137 -0
  75. package/src/implicit.ts +129 -0
  76. package/src/index.ts +82 -0
  77. package/src/storage.ts +92 -0
  78. package/src/types.ts +528 -0
  79. package/src/ui/RevnixBlockPaywall.tsx +1423 -0
  80. package/src/ui/RevnixPaywall.tsx +1542 -0
  81. package/src/ui/index.ts +43 -0
  82. package/src/version.ts +4 -0
  83. package/ui.d.ts +1 -0
  84. package/ui.js +5 -0
@@ -0,0 +1,7 @@
1
+ export { RevnixPaywall, DEFAULT_PAYWALL_THEME, LIGHT_PAYWALL_THEME, type RevnixPaywallPackage, type RevnixPaywallProps, type RevnixPaywallTheme, } from "./RevnixPaywall.js";
2
+ export { RevnixBlockPaywall, type RevnixBlockPaywallProps, type RevnixBlurComponent, type RevnixBlurProps, type RevnixGradientComponent, type RevnixGradientProps, } from "./RevnixBlockPaywall.js";
3
+ export { DESIGN_CANVAS, type BlockStyle, type PaywallBlock, type PaywallBlockDoc, } from "../blocks/types.js";
4
+ export { backgroundBaseColor, backgroundLayers, coverPlacement, type BackgroundFit, type BackgroundImage, type BackgroundLayers, type BackgroundOverlay, type PaywallBackground, type PaywallBackgroundSpec, } from "../blocks/background.js";
5
+ export { detectLocale, linkLabels, localeChain, localizeBlockDoc, normalizeLocale, setLocale, } from "../blocks/locale.js";
6
+ export { minorUnitsForCurrency, resolveTags } from "../blocks/tags.js";
7
+ export type { FontFamilyMap, FontMapping } from "../blocks/style.js";
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveTags = exports.minorUnitsForCurrency = exports.setLocale = exports.normalizeLocale = exports.localizeBlockDoc = exports.localeChain = exports.linkLabels = exports.detectLocale = exports.coverPlacement = exports.backgroundLayers = exports.backgroundBaseColor = exports.DESIGN_CANVAS = exports.RevnixBlockPaywall = exports.LIGHT_PAYWALL_THEME = exports.DEFAULT_PAYWALL_THEME = exports.RevnixPaywall = void 0;
4
+ var RevnixPaywall_js_1 = require("./RevnixPaywall.js");
5
+ Object.defineProperty(exports, "RevnixPaywall", { enumerable: true, get: function () { return RevnixPaywall_js_1.RevnixPaywall; } });
6
+ Object.defineProperty(exports, "DEFAULT_PAYWALL_THEME", { enumerable: true, get: function () { return RevnixPaywall_js_1.DEFAULT_PAYWALL_THEME; } });
7
+ Object.defineProperty(exports, "LIGHT_PAYWALL_THEME", { enumerable: true, get: function () { return RevnixPaywall_js_1.LIGHT_PAYWALL_THEME; } });
8
+ var RevnixBlockPaywall_js_1 = require("./RevnixBlockPaywall.js");
9
+ Object.defineProperty(exports, "RevnixBlockPaywall", { enumerable: true, get: function () { return RevnixBlockPaywall_js_1.RevnixBlockPaywall; } });
10
+ var types_js_1 = require("../blocks/types.js");
11
+ Object.defineProperty(exports, "DESIGN_CANVAS", { enumerable: true, get: function () { return types_js_1.DESIGN_CANVAS; } });
12
+ var background_js_1 = require("../blocks/background.js");
13
+ Object.defineProperty(exports, "backgroundBaseColor", { enumerable: true, get: function () { return background_js_1.backgroundBaseColor; } });
14
+ Object.defineProperty(exports, "backgroundLayers", { enumerable: true, get: function () { return background_js_1.backgroundLayers; } });
15
+ Object.defineProperty(exports, "coverPlacement", { enumerable: true, get: function () { return background_js_1.coverPlacement; } });
16
+ var locale_js_1 = require("../blocks/locale.js");
17
+ Object.defineProperty(exports, "detectLocale", { enumerable: true, get: function () { return locale_js_1.detectLocale; } });
18
+ Object.defineProperty(exports, "linkLabels", { enumerable: true, get: function () { return locale_js_1.linkLabels; } });
19
+ Object.defineProperty(exports, "localeChain", { enumerable: true, get: function () { return locale_js_1.localeChain; } });
20
+ Object.defineProperty(exports, "localizeBlockDoc", { enumerable: true, get: function () { return locale_js_1.localizeBlockDoc; } });
21
+ Object.defineProperty(exports, "normalizeLocale", { enumerable: true, get: function () { return locale_js_1.normalizeLocale; } });
22
+ Object.defineProperty(exports, "setLocale", { enumerable: true, get: function () { return locale_js_1.setLocale; } });
23
+ var tags_js_1 = require("../blocks/tags.js");
24
+ Object.defineProperty(exports, "minorUnitsForCurrency", { enumerable: true, get: function () { return tags_js_1.minorUnitsForCurrency; } });
25
+ Object.defineProperty(exports, "resolveTags", { enumerable: true, get: function () { return tags_js_1.resolveTags; } });
@@ -0,0 +1 @@
1
+ export declare const SDK_VERSION = "1.4.0";
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SDK_VERSION = void 0;
4
+ // L3: the single source of the SDK version inside src/. Must match
5
+ // package.json "version" — version.test.ts fails the build when they drift
6
+ // (they had: client.ts said 0.7.0 while the package shipped 0.9.0).
7
+ exports.SDK_VERSION = "1.4.0";
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "react-native-revnix",
3
+ "version": "1.4.0",
4
+ "description": "Revnix SDK for React, React Native & Next.js: purchase registration, entitlements, identity, and placements over the Revnix /v1 API.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Oth-tech/revnix-sdk.git"
9
+ },
10
+ "keywords": [
11
+ "revnix",
12
+ "react-native",
13
+ "react",
14
+ "nextjs",
15
+ "in-app-purchase",
16
+ "subscriptions",
17
+ "entitlements",
18
+ "storekit",
19
+ "play-billing"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "main": "dist/index.js",
25
+ "module": "dist/esm/index.js",
26
+ "types": "dist/index.d.ts",
27
+ "sideEffects": false,
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/esm/index.js",
32
+ "require": "./dist/index.js"
33
+ },
34
+ "./ui": {
35
+ "types": "./ui.d.ts",
36
+ "import": "./dist/esm/ui/index.js",
37
+ "require": "./ui.js"
38
+ },
39
+ "./package.json": "./package.json"
40
+ },
41
+ "files": [
42
+ "dist",
43
+ "src",
44
+ "ui.js",
45
+ "ui.d.ts",
46
+ "!src/**/*.test.ts"
47
+ ],
48
+ "scripts": {
49
+ "build": "npm run build:cjs && npm run build:esm",
50
+ "build:cjs": "tsc",
51
+ "build:esm": "tsc -p tsconfig.esm.json && node -e \"require('fs').writeFileSync('dist/esm/package.json', JSON.stringify({ type: 'module' }))\"",
52
+ "clean": "rm -rf dist",
53
+ "typecheck": "tsc --noEmit",
54
+ "test": "jest",
55
+ "prepack": "npm run clean && npm run build"
56
+ },
57
+ "devDependencies": {
58
+ "@jest/globals": "^29.7.0",
59
+ "@types/react": "^19.1.0",
60
+ "jest": "^29.7.0",
61
+ "react": "^19.1.0",
62
+ "react-native": "^0.81.0",
63
+ "ts-jest": "^29.4.11",
64
+ "typescript": "^5.8.3"
65
+ },
66
+ "peerDependencies": {
67
+ "react": ">=18",
68
+ "react-native": ">=0.72"
69
+ },
70
+ "peerDependenciesMeta": {
71
+ "react": {
72
+ "optional": true
73
+ },
74
+ "react-native": {
75
+ "optional": true
76
+ }
77
+ },
78
+ "dependencies": {}
79
+ }
@@ -0,0 +1,232 @@
1
+ // The paywall screen background — the SDK half of the dashboard's model.
2
+ //
3
+ // A background is either the original CSS string (color or gradient) or a
4
+ // layered spec: ground color/gradient, then a photo (fit, focal point,
5
+ // opacity, blur), then a scrim. `backgroundLayers()` resolves either form to
6
+ // one paint list, mirroring the dashboard renderer exactly — the same
7
+ // function name, the same defaults, the same clamping — so a background can
8
+ // never look different on device than in the builder for reasons of
9
+ // interpretation. (Type parity with revnix-app/src/lib/paywall-blocks/
10
+ // background.ts; keep the two in step.)
11
+
12
+ export type BackgroundFit = "cover" | "contain";
13
+
14
+ export interface BackgroundImage {
15
+ /** An https URL the device can load — Convex storage or any CDN. */
16
+ url: string;
17
+ /** Default "cover". */
18
+ fit?: BackgroundFit;
19
+ /** Focal point in percent (0–100, 50/50 = centered): the part of the
20
+ * photo that must survive a cover-crop. */
21
+ focalX?: number;
22
+ focalY?: number;
23
+ /** 0–100, default 100. */
24
+ opacity?: number;
25
+ /** Gaussian blur radius in px. */
26
+ blur?: number;
27
+ }
28
+
29
+ export interface BackgroundOverlay {
30
+ /** Any CSS color or gradient; palette tokens resolve as in block fills. */
31
+ fill: string;
32
+ /** 0–100, default 100. */
33
+ opacity?: number;
34
+ }
35
+
36
+ export interface PaywallBackgroundSpec {
37
+ /** Ground paint under the image — a solid color or a CSS gradient. */
38
+ color?: string;
39
+ image?: BackgroundImage;
40
+ overlay?: BackgroundOverlay;
41
+ /** Builder bookkeeping (library preset id); ignored on device. */
42
+ presetId?: string;
43
+ /** Builder bookkeeping ("reset to design default"); ignored on device. */
44
+ original?: PaywallBackground;
45
+ }
46
+
47
+ export type PaywallBackground = string | PaywallBackgroundSpec;
48
+
49
+ const DEFAULT_GROUND = "#000000";
50
+
51
+ /**
52
+ * The flat color a ground stands in for — what `@bg` resolves to, and what
53
+ * paints under a gradient this build cannot draw.
54
+ *
55
+ * It reads the LAST comma-separated layer, because in CSS the first-listed
56
+ * layer paints on TOP: taking the first color would answer with the
57
+ * translucent accent glow the library's Spotlight and Corner halo presets
58
+ * stack over their base, not with the base itself. Fully transparent stops are
59
+ * skipped for the same reason — a scrim that fades to `…00` says nothing about
60
+ * the ground under it.
61
+ *
62
+ * Returning the raw gradient here instead is what put this SDK and the
63
+ * dashboard out of step with the other five renderers, which have always
64
+ * collapsed it: `@bg` is fed to `color-mix()` (and, inside a gradient, to
65
+ * another gradient's stop list), and neither can take a gradient — so the raw
66
+ * form resolved to something invalid and painted NOTHING. Of the 250 shipped
67
+ * gallery presets only 7 have a gradient ground and none of those use `@bg`,
68
+ * so no design that ships today changes; what changes is a background a
69
+ * customer edits to a gradient, which used to silently drop every `@bg` tint
70
+ * on the screen.
71
+ */
72
+ export function backgroundBaseColor(
73
+ bg: PaywallBackground | undefined,
74
+ ): string {
75
+ const ground = typeof bg === "string" ? bg : bg?.color;
76
+ if (ground === undefined) return DEFAULT_GROUND;
77
+ const layers = splitLayers(ground);
78
+ const bottom = layers.length > 0 ? layers[layers.length - 1]! : ground;
79
+ const colors = bottom.match(/#[0-9a-fA-F]{3,8}\b|rgba?\([^)]*\)/gi);
80
+ if (!colors) return ground;
81
+ return colors.find((color) => !isFullyTransparent(color)) ?? colors[0]!;
82
+ }
83
+
84
+ /** Whether a color literal is fully transparent. Kept here rather than routed
85
+ * through `color.ts`, which imports THIS module — only the two forms the
86
+ * dashboard emits are recognized, and anything else counts as opaque, which
87
+ * is the safe answer for picking a ground. */
88
+ function isFullyTransparent(color: string): boolean {
89
+ const s = color.trim();
90
+ if (s.startsWith("#")) {
91
+ const hex = s.slice(1);
92
+ if (hex.length === 8) return parseInt(hex.slice(6), 16) === 0;
93
+ if (hex.length === 4) return parseInt(hex.slice(3).repeat(2), 16) === 0;
94
+ return false;
95
+ }
96
+ const open = s.indexOf("(");
97
+ const close = s.indexOf(")");
98
+ if (open < 0 || close < 0) return false;
99
+ const parts = s
100
+ .slice(open + 1, close)
101
+ .split(/[,/\s]+/)
102
+ .filter(Boolean);
103
+ return parts.length >= 4 && parseFloat(parts[3]!) === 0;
104
+ }
105
+
106
+ /** Splits on top-level commas only, so the commas inside `rgba(...)` and
107
+ * inside a gradient's argument list do not tear a layer in half. */
108
+ function splitLayers(input: string): string[] {
109
+ const out: string[] = [];
110
+ let depth = 0;
111
+ let current = "";
112
+ for (const ch of input) {
113
+ if (ch === "(") depth++;
114
+ else if (ch === ")" && depth > 0) depth--;
115
+ if (ch === "," && depth === 0) {
116
+ out.push(current.trim());
117
+ current = "";
118
+ continue;
119
+ }
120
+ current += ch;
121
+ }
122
+ const tail = current.trim();
123
+ if (tail) out.push(tail);
124
+ return out;
125
+ }
126
+
127
+ export interface ResolvedBackgroundImage {
128
+ url: string;
129
+ fit: BackgroundFit;
130
+ focalX: number;
131
+ focalY: number;
132
+ /** 0–1. */
133
+ opacity: number;
134
+ /** px; absent when no blur was asked for. */
135
+ blur?: number;
136
+ }
137
+
138
+ export interface ResolvedBackgroundOverlay {
139
+ fill: string;
140
+ /** 0–1. */
141
+ opacity: number;
142
+ }
143
+
144
+ export interface BackgroundLayers {
145
+ /** The ground fill (color or gradient string), or undefined for none. */
146
+ ground?: string;
147
+ image?: ResolvedBackgroundImage;
148
+ overlay?: ResolvedBackgroundOverlay;
149
+ }
150
+
151
+ function pct(value: number | undefined, fallback: number): number {
152
+ if (value === undefined || !Number.isFinite(value)) return fallback;
153
+ return Math.min(1, Math.max(0, value / 100));
154
+ }
155
+
156
+ function coord(value: number | undefined): number {
157
+ if (value === undefined || !Number.isFinite(value)) return 50;
158
+ return Math.min(100, Math.max(0, value));
159
+ }
160
+
161
+ /** The paint list, bottom layer first. Layers that would draw nothing (no
162
+ * url, zero opacity) are dropped, so a legacy string resolves to exactly
163
+ * one ground layer. */
164
+ export function backgroundLayers(
165
+ bg: PaywallBackground | undefined,
166
+ /** Resolves palette tokens in the ground and overlay fills. */
167
+ resolve: (value: string) => string = (v) => v,
168
+ ): BackgroundLayers {
169
+ const spec: PaywallBackgroundSpec =
170
+ bg === undefined ? {} : typeof bg === "string" ? { color: bg } : bg;
171
+ const layers: BackgroundLayers = {};
172
+
173
+ if (spec.color) layers.ground = resolve(spec.color);
174
+
175
+ if (spec.image?.url) {
176
+ const opacity = pct(spec.image.opacity, 1);
177
+ if (opacity > 0) {
178
+ layers.image = {
179
+ url: spec.image.url,
180
+ fit: spec.image.fit === "contain" ? "contain" : "cover",
181
+ focalX: coord(spec.image.focalX),
182
+ focalY: coord(spec.image.focalY),
183
+ opacity,
184
+ ...(spec.image.blur ? { blur: spec.image.blur } : {}),
185
+ };
186
+ }
187
+ }
188
+
189
+ if (spec.overlay?.fill) {
190
+ const opacity = pct(spec.overlay.opacity, 1);
191
+ if (opacity > 0) {
192
+ layers.overlay = { fill: resolve(spec.overlay.fill), opacity };
193
+ }
194
+ }
195
+
196
+ return layers;
197
+ }
198
+
199
+ /**
200
+ * Cover-fit geometry with a focal point — the SDK's stand-in for CSS
201
+ * `object-position`, which React Native's <Image> lacks. Given the box and
202
+ * the source dimensions it returns the drawn size and the offset that puts
203
+ * the focal point as close to the box's matching anchor as the image edges
204
+ * allow (exactly CSS's `object-position: X% Y%` rule: the X% point of the
205
+ * image aligns to the X% point of the box, clamped so no edge shows).
206
+ */
207
+ export function coverPlacement(
208
+ box: { width: number; height: number },
209
+ source: { width: number; height: number },
210
+ focalX: number,
211
+ focalY: number,
212
+ ): { width: number; height: number; left: number; top: number } {
213
+ if (box.width <= 0 || box.height <= 0 || source.width <= 0 || source.height <= 0) {
214
+ return { width: box.width, height: box.height, left: 0, top: 0 };
215
+ }
216
+ const scale = Math.max(box.width / source.width, box.height / source.height);
217
+ const width = source.width * scale;
218
+ const height = source.height * scale;
219
+ const place = (boxLen: number, drawnLen: number, pctPoint: number) => {
220
+ const offset = (boxLen - drawnLen) * (pctPoint / 100);
221
+ // Clamp: never pull an image edge inside the box. `|| 0` normalises the
222
+ // -0 that falls out of the arithmetic at the edges — harmless to RN, but
223
+ // it makes the returned geometry compare cleanly.
224
+ return Math.min(0, Math.max(boxLen - drawnLen, offset)) || 0;
225
+ };
226
+ return {
227
+ width,
228
+ height,
229
+ left: place(box.width, width, focalX),
230
+ top: place(box.height, height, focalY),
231
+ };
232
+ }