react-native-revnix 1.4.0 → 1.5.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/README.md +139 -714
- package/android/build.gradle +34 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/kotlin/com/revnix/reactnative/RevnixStoreModule.kt +247 -0
- package/android/src/main/kotlin/com/revnix/reactnative/RevnixStorePackage.kt +14 -0
- package/dist/client.d.ts +12 -0
- package/dist/client.js +69 -1
- package/dist/esm/client.js +69 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/store/index.js +22 -0
- package/dist/esm/store.js +9 -0
- package/dist/esm/ui/RevnixPaywall.js +36 -9
- package/dist/esm/version.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.js +25 -0
- package/dist/store.d.ts +30 -0
- package/dist/store.js +13 -0
- package/dist/types.d.ts +1 -1
- package/dist/ui/RevnixPaywall.d.ts +13 -3
- package/dist/ui/RevnixPaywall.js +36 -9
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/ios/RevnixStore.m +18 -0
- package/ios/RevnixStore.swift +106 -0
- package/package.json +20 -2
- package/react-native-revnix.podspec +22 -0
- package/src/client.ts +88 -1
- package/src/index.ts +5 -0
- package/src/store/index.ts +44 -0
- package/src/store.ts +41 -0
- package/src/types.ts +1 -1
- package/src/ui/RevnixPaywall.tsx +55 -18
- package/src/version.ts +1 -1
- package/store.d.ts +1 -0
- package/store.js +1 -0
- package/ui.js +1 -1
package/src/store.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Store } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export interface StoreTransaction {
|
|
4
|
+
store: Store;
|
|
5
|
+
productId: string;
|
|
6
|
+
/** Apple: transaction id · Google: purchaseToken. */
|
|
7
|
+
transactionId: string;
|
|
8
|
+
/** Apple: originalTransactionId · Google: purchaseToken. */
|
|
9
|
+
token: string;
|
|
10
|
+
/** Unix ms. */
|
|
11
|
+
occurredAt?: number;
|
|
12
|
+
/** Unix ms. */
|
|
13
|
+
expiresAt?: number;
|
|
14
|
+
/** Apple only: the StoreKit 2 JWS. */
|
|
15
|
+
signedTransactionInfo?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface StoreAdapter {
|
|
19
|
+
/** Resolves null when the customer cancelled or the purchase is pending. */
|
|
20
|
+
purchase(
|
|
21
|
+
productId: string,
|
|
22
|
+
opts: { customerId: string }
|
|
23
|
+
): Promise<StoreTransaction | null>;
|
|
24
|
+
restore(): Promise<StoreTransaction[]>;
|
|
25
|
+
finish(transactionId: string): Promise<void>;
|
|
26
|
+
/** Transactions arriving outside purchase(): renewals, other devices, Ask
|
|
27
|
+
* to Buy, refunds. Returns an unsubscribe. */
|
|
28
|
+
observe(onTransaction: (tx: StoreTransaction) => void): () => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let adapter: StoreAdapter | null = null;
|
|
32
|
+
|
|
33
|
+
/** Installs the store that `RevnixClient.purchase()` / `restore()` drive.
|
|
34
|
+
* `import "react-native-revnix/store"` calls this with the native bridge. */
|
|
35
|
+
export function setStoreAdapter(next: StoreAdapter | null): void {
|
|
36
|
+
adapter = next;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getStoreAdapter(): StoreAdapter | null {
|
|
40
|
+
return adapter;
|
|
41
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -427,7 +427,7 @@ export interface PaywallConfig {
|
|
|
427
427
|
* When present RevnixPaywall renders THIS and the fields above act as the
|
|
428
428
|
* fallback for clients that predate block rendering — so an older app
|
|
429
429
|
* keeps showing a sane classic screen instead of nothing. Typed as the
|
|
430
|
-
* block document in "revnix
|
|
430
|
+
* block document in "react-native-revnix/ui"; the core entrypoint keeps it opaque
|
|
431
431
|
* so non-UI consumers pull in no renderer types. */
|
|
432
432
|
blocks?: unknown;
|
|
433
433
|
}
|
package/src/ui/RevnixPaywall.tsx
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// An unrecognized template (config published by a newer dashboard) falls
|
|
13
13
|
// back to the classic structure instead of rendering nothing.
|
|
14
14
|
//
|
|
15
|
-
// Import from "revnix
|
|
15
|
+
// Import from "react-native-revnix/ui" — the core entrypoint stays free of
|
|
16
16
|
// react / react-native so non-RN consumers are unaffected.
|
|
17
17
|
|
|
18
18
|
import * as React from "react";
|
|
@@ -34,8 +34,11 @@ import type {
|
|
|
34
34
|
PaywallConfig,
|
|
35
35
|
PaywallInteraction,
|
|
36
36
|
PaywallPurchaseOutcome,
|
|
37
|
+
RegisterPurchaseResult,
|
|
37
38
|
} from "../types.js";
|
|
38
39
|
import { PREVIEW_PLACEMENT_KEY } from "../implicit.js";
|
|
40
|
+
import { getStoreAdapter } from "../store.js";
|
|
41
|
+
import type { RevnixClient } from "../client.js";
|
|
39
42
|
import type { PaywallBlockDoc } from "../blocks/types.js";
|
|
40
43
|
import type { FontFamilyMap } from "../blocks/style.js";
|
|
41
44
|
import { withAlpha } from "../blocks/color.js";
|
|
@@ -142,8 +145,11 @@ export interface RevnixPaywallProps {
|
|
|
142
145
|
* `paywall.purchase_failed`, and a promise that REJECTS is reported as a
|
|
143
146
|
* failure carrying the error's message. Returning nothing keeps today's
|
|
144
147
|
* behavior exactly — the start is reported, the outcome is not, and you
|
|
145
|
-
* can still report it yourself with `client.logPaywallEvent`.
|
|
146
|
-
|
|
148
|
+
* can still report it yourself with `client.logPaywallEvent`.
|
|
149
|
+
*
|
|
150
|
+
* Omit it to let the paywall buy through `client.purchase()`, which needs
|
|
151
|
+
* `import "react-native-revnix/store"`. */
|
|
152
|
+
onPurchase?: (
|
|
147
153
|
packageId: string
|
|
148
154
|
) => void | PaywallPurchaseOutcome | Promise<PaywallPurchaseOutcome | void>;
|
|
149
155
|
/** Controlled selection; omit to let the paywall manage it (initial
|
|
@@ -152,7 +158,10 @@ export interface RevnixPaywallProps {
|
|
|
152
158
|
onSelectPackage?: (packageId: string) => void;
|
|
153
159
|
/** Renders a spinner in the CTA and disables purchasing. */
|
|
154
160
|
loading?: boolean;
|
|
161
|
+
/** Omit it to restore through `client.restore()`. */
|
|
155
162
|
onRestore?: () => void;
|
|
163
|
+
/** Fires when the paywall's own purchase (no `onPurchase`) succeeds. */
|
|
164
|
+
onPurchaseCompleted?: (result: RegisterPurchaseResult) => void;
|
|
156
165
|
onTerms?: () => void;
|
|
157
166
|
onPrivacy?: () => void;
|
|
158
167
|
/** Called when the customer dismisses the paywall (REV-252) — either by
|
|
@@ -201,6 +210,9 @@ export interface RevnixPaywallProps {
|
|
|
201
210
|
eventId?: string;
|
|
202
211
|
}
|
|
203
212
|
) => void;
|
|
213
|
+
/** Used when `onPurchase` / `onRestore` are omitted. */
|
|
214
|
+
purchase?: RevnixClient["purchase"];
|
|
215
|
+
restore?: RevnixClient["restore"];
|
|
204
216
|
};
|
|
205
217
|
/** Placement/paywall attribution attached to the view report. */
|
|
206
218
|
placementKey?: string;
|
|
@@ -338,7 +350,7 @@ export function RevnixPaywall(props: RevnixPaywallProps) {
|
|
|
338
350
|
[onSelectPackage, report, productOf]
|
|
339
351
|
);
|
|
340
352
|
|
|
341
|
-
const { onPurchase } = props;
|
|
353
|
+
const { onPurchase, onPurchaseCompleted } = props;
|
|
342
354
|
// Rises per attempt so a retry after a failure is its own occurrence rather
|
|
343
355
|
// than being absorbed as a duplicate of the first try.
|
|
344
356
|
const attemptRef = React.useRef(0);
|
|
@@ -368,9 +380,26 @@ export function RevnixPaywall(props: RevnixPaywallProps) {
|
|
|
368
380
|
}
|
|
369
381
|
);
|
|
370
382
|
};
|
|
371
|
-
|
|
383
|
+
const purchase =
|
|
384
|
+
onPurchase ??
|
|
385
|
+
(async (): Promise<PaywallPurchaseOutcome> => {
|
|
386
|
+
if (!productId) {
|
|
387
|
+
return { status: "failed", message: "package has no productId" };
|
|
388
|
+
}
|
|
389
|
+
if (!client?.purchase) {
|
|
390
|
+
return { status: "failed", message: "pass client or onPurchase" };
|
|
391
|
+
}
|
|
392
|
+
const result = await client.purchase(productId, {
|
|
393
|
+
viewId: viewIdRef.current,
|
|
394
|
+
placementKey,
|
|
395
|
+
paywallId,
|
|
396
|
+
});
|
|
397
|
+
if (result) onPurchaseCompleted?.(result);
|
|
398
|
+
return { status: result ? "completed" : "cancelled" };
|
|
399
|
+
});
|
|
400
|
+
let returned: ReturnType<typeof purchase>;
|
|
372
401
|
try {
|
|
373
|
-
returned =
|
|
402
|
+
returned = purchase(packageId);
|
|
374
403
|
} catch (err) {
|
|
375
404
|
// A host that throws synchronously has failed the purchase as surely
|
|
376
405
|
// as one whose promise rejects.
|
|
@@ -386,20 +415,26 @@ export function RevnixPaywall(props: RevnixPaywallProps) {
|
|
|
386
415
|
}
|
|
387
416
|
settle(returned as PaywallPurchaseOutcome | void);
|
|
388
417
|
},
|
|
389
|
-
[onPurchase, report, productOf, placementKey]
|
|
418
|
+
[onPurchase, onPurchaseCompleted, report, productOf, placementKey, paywallId, client]
|
|
390
419
|
);
|
|
391
420
|
|
|
392
421
|
const { onRestore } = props;
|
|
393
|
-
const handleRestore = React.useMemo(
|
|
394
|
-
()
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
422
|
+
const handleRestore = React.useMemo(() => {
|
|
423
|
+
if (onRestore) {
|
|
424
|
+
return () => {
|
|
425
|
+
report("restore");
|
|
426
|
+
onRestore();
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
const restore = client?.restore?.bind(client);
|
|
430
|
+
if (!restore || !getStoreAdapter()) return undefined;
|
|
431
|
+
return () => {
|
|
432
|
+
report("restore");
|
|
433
|
+
void restore().catch((err) =>
|
|
434
|
+
client?.reportRenderDiagnostic?.(String(err?.message ?? err))
|
|
435
|
+
);
|
|
436
|
+
};
|
|
437
|
+
}, [onRestore, report, client]);
|
|
403
438
|
|
|
404
439
|
// The host's dismissal runs whatever happens to the beacon — an analytics
|
|
405
440
|
// failure must never trap the customer on the screen (REV-252).
|
|
@@ -532,7 +567,9 @@ function ClassicClose({
|
|
|
532
567
|
|
|
533
568
|
/** The nine `template` layouts — every paywall published before the block
|
|
534
569
|
* builder, and the fallback for any config that carries no block tree. */
|
|
535
|
-
function ClassicPaywall(
|
|
570
|
+
function ClassicPaywall(
|
|
571
|
+
props: RevnixPaywallProps & { onPurchase: (packageId: string) => void }
|
|
572
|
+
) {
|
|
536
573
|
const { config, packages, loading } = props;
|
|
537
574
|
// Base scheme comes from the dashboard config (mode: dark|light, absent =
|
|
538
575
|
// dark for legacy configs); the host app's explicit theme prop wins on top.
|
package/src/version.ts
CHANGED
|
@@ -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
|
+
export const SDK_VERSION = "1.5.0";
|
package/store.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./dist/store/index";
|
package/store.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require("./dist/store/index.js");
|
package/ui.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Subpath entry: require("revnix
|
|
1
|
+
// Subpath entry: require("react-native-revnix/ui"). Kept at the package root
|
|
2
2
|
// so every resolver (Metro, Node, TS "node" moduleResolution) finds it
|
|
3
3
|
// without an "exports" map. UI pulls in react/react-native peers — keep it
|
|
4
4
|
// out of the core index so pure-JS consumers never load them.
|