react-native-revnix 1.4.2 → 1.6.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/dist/esm/index.js CHANGED
@@ -3,6 +3,7 @@ import { setLocale } from "./blocks/locale.js";
3
3
  export { RevnixClient } from "./client.js";
4
4
  export { PurchaseBlockedError, RevnixAuthError, RevnixError, RevnixNetworkError, RevnixRateLimitError, RevnixTimeoutError, } from "./errors.js";
5
5
  export { purchaseInputFromIap } from "./iap.js";
6
+ export { setStoreAdapter, } from "./store.js";
6
7
  export { memoryStorage, generateAnonymousId } from "./storage.js";
7
8
  export { SDK_VERSION } from "./version.js";
8
9
  export { DEVICE_HEADER, encodeDeviceHeader } from "./device.js";
@@ -0,0 +1,22 @@
1
+ import { NativeEventEmitter, NativeModules } from "react-native";
2
+ import { setStoreAdapter, } from "../store.js";
3
+ const native = NativeModules.RevnixStore;
4
+ const missing = () => Promise.reject(new Error("Revnix: RevnixStore native module not found. Run pod install / rebuild the app (Expo: use a development build)."));
5
+ export const RevnixStore = native
6
+ ? {
7
+ purchase: (productId, { customerId }) => native.purchase(productId, customerId),
8
+ restore: () => native.restore(),
9
+ finish: (transactionId) => native.finish(transactionId),
10
+ observe(onTransaction) {
11
+ const sub = new NativeEventEmitter(NativeModules.RevnixStore).addListener("revnixStoreTransaction", (tx) => onTransaction(tx));
12
+ return () => sub.remove();
13
+ },
14
+ }
15
+ : {
16
+ purchase: missing,
17
+ restore: missing,
18
+ finish: missing,
19
+ observe: () => () => { },
20
+ };
21
+ if (native)
22
+ setStoreAdapter(RevnixStore);
@@ -0,0 +1,9 @@
1
+ let adapter = null;
2
+ /** Installs the store that `RevnixClient.purchase()` / `restore()` drive.
3
+ * `import "react-native-revnix/store"` calls this with the native bridge. */
4
+ export function setStoreAdapter(next) {
5
+ adapter = next;
6
+ }
7
+ export function getStoreAdapter() {
8
+ return adapter;
9
+ }
@@ -17,6 +17,8 @@
17
17
  import * as React from "react";
18
18
  import { ActivityIndicator, Alert, Image, Linking, Pressable, ScrollView, StyleSheet, Text, View, } from "react-native";
19
19
  import { PREVIEW_PLACEMENT_KEY } from "../implicit.js";
20
+ import { getStoreAdapter } from "../store.js";
21
+ import { paywallImageUrls } from "../client.js";
20
22
  import { withAlpha } from "../blocks/color.js";
21
23
  import { RevnixBlockPaywall, } from "./RevnixBlockPaywall.js";
22
24
  /** `config.blocks` is opaque on the core config (the non-UI entrypoint must
@@ -65,6 +67,12 @@ const CURRENCY_SYMBOL = /[$€£¥₹₩₽₺₫₪฿₴₦₱]/;
65
67
  // part of the public theme — derived from the config's mode, in lockstep
66
68
  // with the dashboard preview's SCREEN_PALETTES.card.
67
69
  const CARD_BG = { dark: "#181b22", light: "#f4f5f7" };
70
+ /** Warms React Native's image cache with every image `resolution`'s paywall
71
+ * loads, so it paints without waiting on the network. Pair it with
72
+ * `client.preload` / `resolvePlacement`. Never throws. */
73
+ export async function prefetchPaywallImages(resolution) {
74
+ await Promise.all(paywallImageUrls(resolution?.paywall).map((url) => Image.prefetch(url).catch(() => false)));
75
+ }
68
76
  export function RevnixPaywall(props) {
69
77
  // One view per mount: remounting (a re-shown paywall) is a genuine new
70
78
  // display; re-renders are not.
@@ -141,7 +149,7 @@ export function RevnixPaywall(props) {
141
149
  });
142
150
  onSelectPackage?.(packageId);
143
151
  }, [onSelectPackage, report, productOf]);
144
- const { onPurchase } = props;
152
+ const { onPurchase, onPurchaseCompleted } = props;
145
153
  // Rises per attempt so a retry after a failure is its own occurrence rather
146
154
  // than being absorbed as a duplicate of the first try.
147
155
  const attemptRef = React.useRef(0);
@@ -168,9 +176,26 @@ export function RevnixPaywall(props) {
168
176
  eventId: occurrence,
169
177
  });
170
178
  };
179
+ const purchase = onPurchase ??
180
+ (async () => {
181
+ if (!productId) {
182
+ return { status: "failed", message: "package has no productId" };
183
+ }
184
+ if (!client?.purchase) {
185
+ return { status: "failed", message: "pass client or onPurchase" };
186
+ }
187
+ const result = await client.purchase(productId, {
188
+ viewId: viewIdRef.current,
189
+ placementKey,
190
+ paywallId,
191
+ });
192
+ if (result)
193
+ onPurchaseCompleted?.(result);
194
+ return { status: result ? "completed" : "cancelled" };
195
+ });
171
196
  let returned;
172
197
  try {
173
- returned = onPurchase(packageId);
198
+ returned = purchase(packageId);
174
199
  }
175
200
  catch (err) {
176
201
  // A host that throws synchronously has failed the purchase as surely
@@ -183,14 +208,23 @@ export function RevnixPaywall(props) {
183
208
  return;
184
209
  }
185
210
  settle(returned);
186
- }, [onPurchase, report, productOf, placementKey]);
211
+ }, [onPurchase, onPurchaseCompleted, report, productOf, placementKey, paywallId, client]);
187
212
  const { onRestore } = props;
188
- const handleRestore = React.useMemo(() => onRestore
189
- ? () => {
190
- report("restore");
191
- onRestore();
213
+ const handleRestore = React.useMemo(() => {
214
+ if (onRestore) {
215
+ return () => {
216
+ report("restore");
217
+ onRestore();
218
+ };
192
219
  }
193
- : undefined, [onRestore, report]);
220
+ const restore = client?.restore?.bind(client);
221
+ if (!restore || !getStoreAdapter())
222
+ return undefined;
223
+ return () => {
224
+ report("restore");
225
+ void restore().catch((err) => client?.reportRenderDiagnostic?.(String(err?.message ?? err)));
226
+ };
227
+ }, [onRestore, report, client]);
194
228
  // The host's dismissal runs whatever happens to the beacon — an analytics
195
229
  // failure must never trap the customer on the screen (REV-252).
196
230
  const { onClose } = props;
@@ -1,4 +1,4 @@
1
- export { RevnixPaywall, DEFAULT_PAYWALL_THEME, LIGHT_PAYWALL_THEME, } from "./RevnixPaywall.js";
1
+ export { RevnixPaywall, DEFAULT_PAYWALL_THEME, LIGHT_PAYWALL_THEME, prefetchPaywallImages, } from "./RevnixPaywall.js";
2
2
  export { RevnixBlockPaywall, } from "./RevnixBlockPaywall.js";
3
3
  export { DESIGN_CANVAS, } from "../blocks/types.js";
4
4
  export { backgroundBaseColor, backgroundLayers, coverPlacement, } from "../blocks/background.js";
@@ -1,4 +1,4 @@
1
1
  // L3: the single source of the SDK version inside src/. Must match
2
2
  // package.json "version" — version.test.ts fails the build when they drift
3
3
  // (they had: client.ts said 0.7.0 while the package shipped 0.9.0).
4
- export const SDK_VERSION = "1.4.2";
4
+ export const SDK_VERSION = "1.6.0";
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { RevnixConfig } from "./types.js";
4
4
  export { RevnixClient } from "./client.js";
5
5
  export { PurchaseBlockedError, RevnixAuthError, RevnixError, RevnixNetworkError, RevnixRateLimitError, RevnixTimeoutError, } from "./errors.js";
6
6
  export { purchaseInputFromIap, type IapPurchase } from "./iap.js";
7
+ export { setStoreAdapter, type StoreAdapter, type StoreTransaction, } from "./store.js";
7
8
  export { memoryStorage, generateAnonymousId } from "./storage.js";
8
9
  export { SDK_VERSION } from "./version.js";
9
10
  export { DEVICE_HEADER, encodeDeviceHeader } from "./device.js";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Revnix = exports.PREVIEW_PLACEMENT_KEY = exports.IMPLICIT_PLACEMENTS = exports.HOST_CALL_PLACEMENT = exports.DEFAULT_SESSION_TIMEOUT_MS = exports.browserLifecycle = exports.encodeDeviceHeader = exports.DEVICE_HEADER = exports.SDK_VERSION = exports.generateAnonymousId = exports.memoryStorage = exports.purchaseInputFromIap = exports.RevnixTimeoutError = exports.RevnixRateLimitError = exports.RevnixNetworkError = exports.RevnixError = exports.RevnixAuthError = exports.PurchaseBlockedError = exports.RevnixClient = void 0;
3
+ exports.Revnix = exports.PREVIEW_PLACEMENT_KEY = exports.IMPLICIT_PLACEMENTS = exports.HOST_CALL_PLACEMENT = exports.DEFAULT_SESSION_TIMEOUT_MS = exports.browserLifecycle = exports.encodeDeviceHeader = exports.DEVICE_HEADER = exports.SDK_VERSION = exports.generateAnonymousId = exports.memoryStorage = exports.setStoreAdapter = exports.purchaseInputFromIap = exports.RevnixTimeoutError = exports.RevnixRateLimitError = exports.RevnixNetworkError = exports.RevnixError = exports.RevnixAuthError = exports.PurchaseBlockedError = exports.RevnixClient = void 0;
4
4
  const client_js_1 = require("./client.js");
5
5
  const locale_js_1 = require("./blocks/locale.js");
6
6
  var client_js_2 = require("./client.js");
@@ -14,6 +14,8 @@ Object.defineProperty(exports, "RevnixRateLimitError", { enumerable: true, get:
14
14
  Object.defineProperty(exports, "RevnixTimeoutError", { enumerable: true, get: function () { return errors_js_1.RevnixTimeoutError; } });
15
15
  var iap_js_1 = require("./iap.js");
16
16
  Object.defineProperty(exports, "purchaseInputFromIap", { enumerable: true, get: function () { return iap_js_1.purchaseInputFromIap; } });
17
+ var store_js_1 = require("./store.js");
18
+ Object.defineProperty(exports, "setStoreAdapter", { enumerable: true, get: function () { return store_js_1.setStoreAdapter; } });
17
19
  var storage_js_1 = require("./storage.js");
18
20
  Object.defineProperty(exports, "memoryStorage", { enumerable: true, get: function () { return storage_js_1.memoryStorage; } });
19
21
  Object.defineProperty(exports, "generateAnonymousId", { enumerable: true, get: function () { return storage_js_1.generateAnonymousId; } });
@@ -0,0 +1,2 @@
1
+ import { type StoreAdapter } from "../store.js";
2
+ export declare const RevnixStore: StoreAdapter;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RevnixStore = void 0;
4
+ const react_native_1 = require("react-native");
5
+ const store_js_1 = require("../store.js");
6
+ const native = react_native_1.NativeModules.RevnixStore;
7
+ const missing = () => Promise.reject(new Error("Revnix: RevnixStore native module not found. Run pod install / rebuild the app (Expo: use a development build)."));
8
+ exports.RevnixStore = native
9
+ ? {
10
+ purchase: (productId, { customerId }) => native.purchase(productId, customerId),
11
+ restore: () => native.restore(),
12
+ finish: (transactionId) => native.finish(transactionId),
13
+ observe(onTransaction) {
14
+ const sub = new react_native_1.NativeEventEmitter(react_native_1.NativeModules.RevnixStore).addListener("revnixStoreTransaction", (tx) => onTransaction(tx));
15
+ return () => sub.remove();
16
+ },
17
+ }
18
+ : {
19
+ purchase: missing,
20
+ restore: missing,
21
+ finish: missing,
22
+ observe: () => () => { },
23
+ };
24
+ if (native)
25
+ (0, store_js_1.setStoreAdapter)(exports.RevnixStore);
@@ -0,0 +1,30 @@
1
+ import type { Store } from "./types.js";
2
+ export interface StoreTransaction {
3
+ store: Store;
4
+ productId: string;
5
+ /** Apple: transaction id · Google: purchaseToken. */
6
+ transactionId: string;
7
+ /** Apple: originalTransactionId · Google: purchaseToken. */
8
+ token: string;
9
+ /** Unix ms. */
10
+ occurredAt?: number;
11
+ /** Unix ms. */
12
+ expiresAt?: number;
13
+ /** Apple only: the StoreKit 2 JWS. */
14
+ signedTransactionInfo?: string;
15
+ }
16
+ export interface StoreAdapter {
17
+ /** Resolves null when the customer cancelled or the purchase is pending. */
18
+ purchase(productId: string, opts: {
19
+ customerId: string;
20
+ }): Promise<StoreTransaction | null>;
21
+ restore(): Promise<StoreTransaction[]>;
22
+ finish(transactionId: string): Promise<void>;
23
+ /** Transactions arriving outside purchase(): renewals, other devices, Ask
24
+ * to Buy, refunds. Returns an unsubscribe. */
25
+ observe(onTransaction: (tx: StoreTransaction) => void): () => void;
26
+ }
27
+ /** Installs the store that `RevnixClient.purchase()` / `restore()` drive.
28
+ * `import "react-native-revnix/store"` calls this with the native bridge. */
29
+ export declare function setStoreAdapter(next: StoreAdapter | null): void;
30
+ export declare function getStoreAdapter(): StoreAdapter | null;
package/dist/store.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setStoreAdapter = setStoreAdapter;
4
+ exports.getStoreAdapter = getStoreAdapter;
5
+ let adapter = null;
6
+ /** Installs the store that `RevnixClient.purchase()` / `restore()` drive.
7
+ * `import "react-native-revnix/store"` calls this with the native bridge. */
8
+ function setStoreAdapter(next) {
9
+ adapter = next;
10
+ }
11
+ function getStoreAdapter() {
12
+ return adapter;
13
+ }
@@ -1,6 +1,7 @@
1
1
  import * as React from "react";
2
2
  import { type StyleProp, type ViewStyle } from "react-native";
3
- import type { PaywallConfig, PaywallInteraction, PaywallPurchaseOutcome } from "../types.js";
3
+ import type { PaywallConfig, PaywallInteraction, PaywallPurchaseOutcome, PlacementResolution, RegisterPurchaseResult } from "../types.js";
4
+ import { type RevnixClient } from "../client.js";
4
5
  import type { FontFamilyMap } from "../blocks/style.js";
5
6
  import { type RevnixBlurComponent, type RevnixGradientComponent } from "./RevnixBlockPaywall.js";
6
7
  /** One purchasable row. `priceLabel` must come from the store (localized). */
@@ -51,15 +52,21 @@ export interface RevnixPaywallProps {
51
52
  * `paywall.purchase_failed`, and a promise that REJECTS is reported as a
52
53
  * failure carrying the error's message. Returning nothing keeps today's
53
54
  * behavior exactly — the start is reported, the outcome is not, and you
54
- * can still report it yourself with `client.logPaywallEvent`. */
55
- onPurchase: (packageId: string) => void | PaywallPurchaseOutcome | Promise<PaywallPurchaseOutcome | void>;
55
+ * can still report it yourself with `client.logPaywallEvent`.
56
+ *
57
+ * Omit it to let the paywall buy through `client.purchase()`, which needs
58
+ * `import "react-native-revnix/store"`. */
59
+ onPurchase?: (packageId: string) => void | PaywallPurchaseOutcome | Promise<PaywallPurchaseOutcome | void>;
56
60
  /** Controlled selection; omit to let the paywall manage it (initial
57
61
  * selection is the config's highlight package, else the first package). */
58
62
  selectedPackageId?: string;
59
63
  onSelectPackage?: (packageId: string) => void;
60
64
  /** Renders a spinner in the CTA and disables purchasing. */
61
65
  loading?: boolean;
66
+ /** Omit it to restore through `client.restore()`. */
62
67
  onRestore?: () => void;
68
+ /** Fires when the paywall's own purchase (no `onPurchase`) succeeds. */
69
+ onPurchaseCompleted?: (result: RegisterPurchaseResult) => void;
63
70
  onTerms?: () => void;
64
71
  onPrivacy?: () => void;
65
72
  /** Called when the customer dismisses the paywall (REV-252) — either by
@@ -104,6 +111,9 @@ export interface RevnixPaywallProps {
104
111
  message?: string;
105
112
  eventId?: string;
106
113
  }) => void;
114
+ /** Used when `onPurchase` / `onRestore` are omitted. */
115
+ purchase?: RevnixClient["purchase"];
116
+ restore?: RevnixClient["restore"];
107
117
  };
108
118
  /** Placement/paywall attribution attached to the view report. */
109
119
  placementKey?: string;
@@ -145,4 +155,8 @@ export interface RevnixPaywallProps {
145
155
  * layouts below render `config`'s own copy whatever this says. */
146
156
  locale?: string;
147
157
  }
158
+ /** Warms React Native's image cache with every image `resolution`'s paywall
159
+ * loads, so it paints without waiting on the network. Pair it with
160
+ * `client.preload` / `resolvePlacement`. Never throws. */
161
+ export declare function prefetchPaywallImages(resolution: PlacementResolution | null): Promise<void>;
148
162
  export declare function RevnixPaywall(props: RevnixPaywallProps): React.JSX.Element;
@@ -50,10 +50,13 @@ var __importStar = (this && this.__importStar) || (function () {
50
50
  })();
51
51
  Object.defineProperty(exports, "__esModule", { value: true });
52
52
  exports.LIGHT_PAYWALL_THEME = exports.DEFAULT_PAYWALL_THEME = void 0;
53
+ exports.prefetchPaywallImages = prefetchPaywallImages;
53
54
  exports.RevnixPaywall = RevnixPaywall;
54
55
  const React = __importStar(require("react"));
55
56
  const react_native_1 = require("react-native");
56
57
  const implicit_js_1 = require("../implicit.js");
58
+ const store_js_1 = require("../store.js");
59
+ const client_js_1 = require("../client.js");
57
60
  const color_js_1 = require("../blocks/color.js");
58
61
  const RevnixBlockPaywall_js_1 = require("./RevnixBlockPaywall.js");
59
62
  /** `config.blocks` is opaque on the core config (the non-UI entrypoint must
@@ -102,6 +105,12 @@ const CURRENCY_SYMBOL = /[$€£¥₹₩₽₺₫₪฿₴₦₱]/;
102
105
  // part of the public theme — derived from the config's mode, in lockstep
103
106
  // with the dashboard preview's SCREEN_PALETTES.card.
104
107
  const CARD_BG = { dark: "#181b22", light: "#f4f5f7" };
108
+ /** Warms React Native's image cache with every image `resolution`'s paywall
109
+ * loads, so it paints without waiting on the network. Pair it with
110
+ * `client.preload` / `resolvePlacement`. Never throws. */
111
+ async function prefetchPaywallImages(resolution) {
112
+ await Promise.all((0, client_js_1.paywallImageUrls)(resolution?.paywall).map((url) => react_native_1.Image.prefetch(url).catch(() => false)));
113
+ }
105
114
  function RevnixPaywall(props) {
106
115
  // One view per mount: remounting (a re-shown paywall) is a genuine new
107
116
  // display; re-renders are not.
@@ -178,7 +187,7 @@ function RevnixPaywall(props) {
178
187
  });
179
188
  onSelectPackage?.(packageId);
180
189
  }, [onSelectPackage, report, productOf]);
181
- const { onPurchase } = props;
190
+ const { onPurchase, onPurchaseCompleted } = props;
182
191
  // Rises per attempt so a retry after a failure is its own occurrence rather
183
192
  // than being absorbed as a duplicate of the first try.
184
193
  const attemptRef = React.useRef(0);
@@ -205,9 +214,26 @@ function RevnixPaywall(props) {
205
214
  eventId: occurrence,
206
215
  });
207
216
  };
217
+ const purchase = onPurchase ??
218
+ (async () => {
219
+ if (!productId) {
220
+ return { status: "failed", message: "package has no productId" };
221
+ }
222
+ if (!client?.purchase) {
223
+ return { status: "failed", message: "pass client or onPurchase" };
224
+ }
225
+ const result = await client.purchase(productId, {
226
+ viewId: viewIdRef.current,
227
+ placementKey,
228
+ paywallId,
229
+ });
230
+ if (result)
231
+ onPurchaseCompleted?.(result);
232
+ return { status: result ? "completed" : "cancelled" };
233
+ });
208
234
  let returned;
209
235
  try {
210
- returned = onPurchase(packageId);
236
+ returned = purchase(packageId);
211
237
  }
212
238
  catch (err) {
213
239
  // A host that throws synchronously has failed the purchase as surely
@@ -220,14 +246,23 @@ function RevnixPaywall(props) {
220
246
  return;
221
247
  }
222
248
  settle(returned);
223
- }, [onPurchase, report, productOf, placementKey]);
249
+ }, [onPurchase, onPurchaseCompleted, report, productOf, placementKey, paywallId, client]);
224
250
  const { onRestore } = props;
225
- const handleRestore = React.useMemo(() => onRestore
226
- ? () => {
227
- report("restore");
228
- onRestore();
251
+ const handleRestore = React.useMemo(() => {
252
+ if (onRestore) {
253
+ return () => {
254
+ report("restore");
255
+ onRestore();
256
+ };
229
257
  }
230
- : undefined, [onRestore, report]);
258
+ const restore = client?.restore?.bind(client);
259
+ if (!restore || !(0, store_js_1.getStoreAdapter)())
260
+ return undefined;
261
+ return () => {
262
+ report("restore");
263
+ void restore().catch((err) => client?.reportRenderDiagnostic?.(String(err?.message ?? err)));
264
+ };
265
+ }, [onRestore, report, client]);
231
266
  // The host's dismissal runs whatever happens to the beacon — an analytics
232
267
  // failure must never trap the customer on the screen (REV-252).
233
268
  const { onClose } = props;
@@ -1,4 +1,4 @@
1
- export { RevnixPaywall, DEFAULT_PAYWALL_THEME, LIGHT_PAYWALL_THEME, type RevnixPaywallPackage, type RevnixPaywallProps, type RevnixPaywallTheme, } from "./RevnixPaywall.js";
1
+ export { RevnixPaywall, DEFAULT_PAYWALL_THEME, LIGHT_PAYWALL_THEME, prefetchPaywallImages, type RevnixPaywallPackage, type RevnixPaywallProps, type RevnixPaywallTheme, } from "./RevnixPaywall.js";
2
2
  export { RevnixBlockPaywall, type RevnixBlockPaywallProps, type RevnixBlurComponent, type RevnixBlurProps, type RevnixGradientComponent, type RevnixGradientProps, } from "./RevnixBlockPaywall.js";
3
3
  export { DESIGN_CANVAS, type BlockStyle, type PaywallBlock, type PaywallBlockDoc, } from "../blocks/types.js";
4
4
  export { backgroundBaseColor, backgroundLayers, coverPlacement, type BackgroundFit, type BackgroundImage, type BackgroundLayers, type BackgroundOverlay, type PaywallBackground, type PaywallBackgroundSpec, } from "../blocks/background.js";
package/dist/ui/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
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;
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.prefetchPaywallImages = exports.LIGHT_PAYWALL_THEME = exports.DEFAULT_PAYWALL_THEME = exports.RevnixPaywall = void 0;
4
4
  var RevnixPaywall_js_1 = require("./RevnixPaywall.js");
5
5
  Object.defineProperty(exports, "RevnixPaywall", { enumerable: true, get: function () { return RevnixPaywall_js_1.RevnixPaywall; } });
6
6
  Object.defineProperty(exports, "DEFAULT_PAYWALL_THEME", { enumerable: true, get: function () { return RevnixPaywall_js_1.DEFAULT_PAYWALL_THEME; } });
7
7
  Object.defineProperty(exports, "LIGHT_PAYWALL_THEME", { enumerable: true, get: function () { return RevnixPaywall_js_1.LIGHT_PAYWALL_THEME; } });
8
+ Object.defineProperty(exports, "prefetchPaywallImages", { enumerable: true, get: function () { return RevnixPaywall_js_1.prefetchPaywallImages; } });
8
9
  var RevnixBlockPaywall_js_1 = require("./RevnixBlockPaywall.js");
9
10
  Object.defineProperty(exports, "RevnixBlockPaywall", { enumerable: true, get: function () { return RevnixBlockPaywall_js_1.RevnixBlockPaywall; } });
10
11
  var types_js_1 = require("../blocks/types.js");
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "1.4.2";
1
+ export declare const SDK_VERSION = "1.6.0";
package/dist/version.js CHANGED
@@ -4,4 +4,4 @@ exports.SDK_VERSION = void 0;
4
4
  // L3: the single source of the SDK version inside src/. Must match
5
5
  // package.json "version" — version.test.ts fails the build when they drift
6
6
  // (they had: client.ts said 0.7.0 while the package shipped 0.9.0).
7
- exports.SDK_VERSION = "1.4.2";
7
+ exports.SDK_VERSION = "1.6.0";
@@ -0,0 +1,18 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTEventEmitter.h>
3
+
4
+ @interface RCT_EXTERN_MODULE(RevnixStore, RCTEventEmitter)
5
+
6
+ RCT_EXTERN_METHOD(purchase:(NSString *)productId
7
+ customerId:(NSString *)customerId
8
+ resolver:(RCTPromiseResolveBlock)resolve
9
+ rejecter:(RCTPromiseRejectBlock)reject)
10
+
11
+ RCT_EXTERN_METHOD(restore:(RCTPromiseResolveBlock)resolve
12
+ rejecter:(RCTPromiseRejectBlock)reject)
13
+
14
+ RCT_EXTERN_METHOD(finish:(NSString *)transactionId
15
+ resolver:(RCTPromiseResolveBlock)resolve
16
+ rejecter:(RCTPromiseRejectBlock)reject)
17
+
18
+ @end
@@ -0,0 +1,106 @@
1
+ import Foundation
2
+ import React
3
+ import StoreKit
4
+
5
+ @available(iOS 15.0, *)
6
+ @objc(RevnixStore)
7
+ class RevnixStore: RCTEventEmitter {
8
+ private var updates: Task<Void, Never>?
9
+
10
+ @objc override static func requiresMainQueueSetup() -> Bool {
11
+ false
12
+ }
13
+
14
+ override func supportedEvents() -> [String]! {
15
+ ["revnixStoreTransaction"]
16
+ }
17
+
18
+ override func startObserving() {
19
+ updates?.cancel()
20
+ updates = Task { [weak self] in
21
+ for await update in Transaction.updates where update.unsafePayloadValue.revocationDate == nil {
22
+ self?.sendEvent(withName: "revnixStoreTransaction", body: RevnixStore.payload(update))
23
+ }
24
+ }
25
+ }
26
+
27
+ override func stopObserving() {
28
+ updates?.cancel()
29
+ updates = nil
30
+ }
31
+
32
+ @objc(purchase:customerId:resolver:rejecter:)
33
+ func purchase(
34
+ _ productId: String,
35
+ customerId: String,
36
+ resolver resolve: @escaping RCTPromiseResolveBlock,
37
+ rejecter reject: @escaping RCTPromiseRejectBlock
38
+ ) {
39
+ Task {
40
+ do {
41
+ guard let product = try await Product.products(for: [productId]).first else {
42
+ reject("product_not_found", "No App Store product with id \(productId)", nil)
43
+ return
44
+ }
45
+ switch try await product.purchase() {
46
+ case .success(let verification):
47
+ resolve(RevnixStore.payload(verification))
48
+ default:
49
+ resolve(nil)
50
+ }
51
+ } catch {
52
+ reject("store_error", error.localizedDescription, error)
53
+ }
54
+ }
55
+ }
56
+
57
+ @objc(restore:rejecter:)
58
+ func restore(
59
+ _ resolve: @escaping RCTPromiseResolveBlock,
60
+ rejecter reject: @escaping RCTPromiseRejectBlock
61
+ ) {
62
+ Task {
63
+ try? await AppStore.sync()
64
+ var transactions: [[String: Any]] = []
65
+ for await entitlement in Transaction.currentEntitlements
66
+ where entitlement.unsafePayloadValue.revocationDate == nil {
67
+ transactions.append(RevnixStore.payload(entitlement))
68
+ }
69
+ resolve(transactions)
70
+ }
71
+ }
72
+
73
+ @objc(finish:resolver:rejecter:)
74
+ func finish(
75
+ _ transactionId: String,
76
+ resolver resolve: @escaping RCTPromiseResolveBlock,
77
+ rejecter reject: @escaping RCTPromiseRejectBlock
78
+ ) {
79
+ Task {
80
+ for await unfinished in Transaction.unfinished {
81
+ let transaction = unfinished.unsafePayloadValue
82
+ if String(transaction.id) == transactionId {
83
+ await transaction.finish()
84
+ break
85
+ }
86
+ }
87
+ resolve(nil)
88
+ }
89
+ }
90
+
91
+ private static func payload(_ verification: VerificationResult<Transaction>) -> [String: Any] {
92
+ let transaction = verification.unsafePayloadValue
93
+ var payload: [String: Any] = [
94
+ "store": "apple",
95
+ "productId": transaction.productID,
96
+ "transactionId": String(transaction.id),
97
+ "token": String(transaction.originalID),
98
+ "occurredAt": Int(transaction.purchaseDate.timeIntervalSince1970 * 1000),
99
+ "signedTransactionInfo": verification.jwsRepresentation,
100
+ ]
101
+ if let expiresAt = transaction.expirationDate {
102
+ payload["expiresAt"] = Int(expiresAt.timeIntervalSince1970 * 1000)
103
+ }
104
+ return payload
105
+ }
106
+ }
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "react-native-revnix",
3
- "version": "1.4.2",
3
+ "version": "1.6.0",
4
4
  "description": "Revnix SDK for React, React Native & Next.js: purchase registration, entitlements, identity, and placements over the Revnix /v1 API.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/Oth-tech/revnix-sdk.git"
9
9
  },
10
+ "homepage": "https://www.revnix.io/docs/react-native",
11
+ "bugs": {
12
+ "email": "support@revnix.io"
13
+ },
10
14
  "keywords": [
11
15
  "revnix",
12
16
  "react-native",
@@ -24,7 +28,11 @@
24
28
  "main": "dist/index.js",
25
29
  "module": "dist/esm/index.js",
26
30
  "types": "dist/index.d.ts",
27
- "sideEffects": false,
31
+ "sideEffects": [
32
+ "./store.js",
33
+ "./dist/store/index.js",
34
+ "./dist/esm/store/index.js"
35
+ ],
28
36
  "exports": {
29
37
  ".": {
30
38
  "types": "./dist/index.d.ts",
@@ -36,6 +44,11 @@
36
44
  "import": "./dist/esm/ui/index.js",
37
45
  "require": "./ui.js"
38
46
  },
47
+ "./store": {
48
+ "types": "./store.d.ts",
49
+ "import": "./dist/esm/store/index.js",
50
+ "require": "./store.js"
51
+ },
39
52
  "./package.json": "./package.json"
40
53
  },
41
54
  "files": [
@@ -43,6 +56,11 @@
43
56
  "src",
44
57
  "ui.js",
45
58
  "ui.d.ts",
59
+ "store.js",
60
+ "store.d.ts",
61
+ "ios",
62
+ "android",
63
+ "react-native-revnix.podspec",
46
64
  "!src/**/*.test.ts"
47
65
  ],
48
66
  "scripts": {
@@ -0,0 +1,22 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "react-native-revnix"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.author = "Revnix"
12
+ s.platforms = { :ios => "15.1" }
13
+ s.source = { :git => "https://github.com/Oth-tech/revnix-sdk.git", :tag => "v#{s.version}" }
14
+ s.source_files = "ios/**/*.{h,m,swift}"
15
+ s.swift_version = "5.0"
16
+
17
+ if respond_to?(:install_modules_dependencies, true)
18
+ install_modules_dependencies(s)
19
+ else
20
+ s.dependency "React-Core"
21
+ end
22
+ end