react-native-revnix 1.5.0 → 1.7.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 +16 -0
- package/android/build.gradle +3 -0
- package/android/consumer-rules.pro +1 -0
- package/android/src/main/kotlin/com/revnix/reactnative/RevnixStoreModule.kt +43 -0
- package/dist/blocks/locale.js +7 -0
- package/dist/blocks/selection.d.ts +7 -1
- package/dist/blocks/selection.js +9 -0
- package/dist/blocks/tags.d.ts +3 -0
- package/dist/blocks/tags.js +33 -0
- package/dist/blocks/types.d.ts +45 -3
- package/dist/blocks/types.js +1 -0
- package/dist/client.d.ts +50 -11
- package/dist/client.js +408 -119
- package/dist/esm/blocks/locale.js +7 -0
- package/dist/esm/blocks/selection.js +8 -0
- package/dist/esm/blocks/tags.js +31 -0
- package/dist/esm/blocks/types.js +1 -0
- package/dist/esm/client.js +407 -119
- package/dist/esm/store/index.js +7 -0
- package/dist/esm/ui/RevnixBlockPaywall.js +103 -22
- package/dist/esm/ui/RevnixPaywall.js +7 -0
- package/dist/esm/ui/index.js +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/store/index.d.ts +8 -0
- package/dist/store/index.js +10 -1
- package/dist/types.d.ts +32 -0
- package/dist/ui/RevnixBlockPaywall.js +100 -19
- package/dist/ui/RevnixPaywall.d.ts +6 -2
- package/dist/ui/RevnixPaywall.js +8 -0
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +2 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/ios/RevnixStore.m +4 -0
- package/ios/RevnixStore.swift +29 -0
- package/package.json +1 -1
- package/src/blocks/locale.ts +7 -0
- package/src/blocks/selection.ts +13 -0
- package/src/blocks/tags.ts +38 -0
- package/src/blocks/types.ts +51 -2
- package/src/client.ts +478 -154
- package/src/index.ts +1 -0
- package/src/store/index.ts +15 -0
- package/src/types.ts +30 -0
- package/src/ui/RevnixBlockPaywall.tsx +186 -20
- package/src/ui/RevnixPaywall.tsx +15 -1
- package/src/ui/index.ts +1 -0
- package/src/version.ts +1 -1
package/ios/RevnixStore.swift
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import CryptoKit
|
|
2
|
+
import DeviceCheck
|
|
1
3
|
import Foundation
|
|
2
4
|
import React
|
|
3
5
|
import StoreKit
|
|
@@ -88,6 +90,33 @@ class RevnixStore: RCTEventEmitter {
|
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
92
|
|
|
93
|
+
@objc(deviceIntegrity:resolver:rejecter:)
|
|
94
|
+
func deviceIntegrity(
|
|
95
|
+
_ customerId: String,
|
|
96
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
97
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
98
|
+
) {
|
|
99
|
+
let service = DCAppAttestService.shared
|
|
100
|
+
guard service.isSupported else {
|
|
101
|
+
resolve(nil)
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
Task {
|
|
105
|
+
do {
|
|
106
|
+
let keyId = try await service.generateKey()
|
|
107
|
+
let clientDataHash = Data(SHA256.hash(data: Data(customerId.utf8)))
|
|
108
|
+
let attestation = try await service.attestKey(keyId, clientDataHash: clientDataHash)
|
|
109
|
+
resolve([
|
|
110
|
+
"platform": "ios",
|
|
111
|
+
"keyId": keyId,
|
|
112
|
+
"attestation": attestation.base64EncodedString(),
|
|
113
|
+
])
|
|
114
|
+
} catch {
|
|
115
|
+
resolve(nil)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
91
120
|
private static func payload(_ verification: VerificationResult<Transaction>) -> [String: Any] {
|
|
92
121
|
let transaction = verification.unsafePayloadValue
|
|
93
122
|
var payload: [String: Any] = [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-revnix",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.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": {
|
package/src/blocks/locale.ts
CHANGED
|
@@ -125,7 +125,14 @@ function localizeBlock(
|
|
|
125
125
|
case "text":
|
|
126
126
|
return { ...block, text: at("text", block.text) };
|
|
127
127
|
case "button":
|
|
128
|
+
case "toggle":
|
|
128
129
|
return { ...block, label: at("label", block.label) };
|
|
130
|
+
case "timer":
|
|
131
|
+
return {
|
|
132
|
+
...block,
|
|
133
|
+
format: opt("format", block.format),
|
|
134
|
+
expiredText: opt("expiredText", block.expiredText),
|
|
135
|
+
};
|
|
129
136
|
case "list":
|
|
130
137
|
return {
|
|
131
138
|
...block,
|
package/src/blocks/selection.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
CardBlock,
|
|
12
12
|
PaywallBlock,
|
|
13
13
|
PaywallBlockDoc,
|
|
14
|
+
ToggleBlock,
|
|
14
15
|
} from "./types.js";
|
|
15
16
|
import { DESIGN_CANVAS } from "./types.js";
|
|
16
17
|
import { resolveTags } from "./tags.js";
|
|
@@ -33,6 +34,18 @@ export function selectedPackageId(
|
|
|
33
34
|
return packages[0]?.packageId;
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
export function toggleSelection(
|
|
38
|
+
block: ToggleBlock,
|
|
39
|
+
packages: ReadonlyArray<{ packageId: string }>,
|
|
40
|
+
selectedId: string | undefined
|
|
41
|
+
): { on: boolean; target: string } | null {
|
|
42
|
+
const on = packages[block.onPackageIndex];
|
|
43
|
+
const off = packages[block.offPackageIndex];
|
|
44
|
+
if (on === undefined || off === undefined) return null;
|
|
45
|
+
const isOn = on.packageId === selectedId;
|
|
46
|
+
return { on: isOn, target: (isOn ? off : on).packageId };
|
|
47
|
+
}
|
|
48
|
+
|
|
36
49
|
/** The context a package-bearing card opens for itself and its subtree. */
|
|
37
50
|
export function packageContext(
|
|
38
51
|
pkg: { packageId: string } | undefined,
|
package/src/blocks/tags.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// approximated — a wrong price must never reach a customer silently.
|
|
12
12
|
|
|
13
13
|
import type { RevnixPaywallPackage } from "../ui/RevnixPaywall.js";
|
|
14
|
+
import type { TimerBlock } from "./types.js";
|
|
14
15
|
|
|
15
16
|
/** Renewal cycles, in months. Lifetime and one-time products have no cycle. */
|
|
16
17
|
const MONTHS: Record<string, number | undefined> = {
|
|
@@ -123,3 +124,40 @@ export function resolveTags(
|
|
|
123
124
|
}
|
|
124
125
|
});
|
|
125
126
|
}
|
|
127
|
+
|
|
128
|
+
export function formatCountdown(format: string, remaining: number): string {
|
|
129
|
+
const pad = (n: number) => String(n).padStart(2, "0");
|
|
130
|
+
let span = Infinity;
|
|
131
|
+
const part = (token: string, unit: number) => {
|
|
132
|
+
const value = Math.floor((remaining % span) / unit);
|
|
133
|
+
if (format.includes(token)) span = unit;
|
|
134
|
+
return value;
|
|
135
|
+
};
|
|
136
|
+
const days = part("{d}", 86400);
|
|
137
|
+
const hours = part("{hh}", 3600);
|
|
138
|
+
const minutes = part("{mm}", 60);
|
|
139
|
+
const seconds = part("{ss}", 1);
|
|
140
|
+
return format
|
|
141
|
+
.replace(/\{d\}/g, String(days))
|
|
142
|
+
.replace(/\{hh\}/g, pad(hours))
|
|
143
|
+
.replace(/\{mm\}/g, pad(minutes))
|
|
144
|
+
.replace(/\{ss\}/g, pad(seconds));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function timerText(
|
|
148
|
+
block: TimerBlock,
|
|
149
|
+
shownAt: number,
|
|
150
|
+
now: number,
|
|
151
|
+
): string | null {
|
|
152
|
+
const deadline =
|
|
153
|
+
block.endsAt !== undefined
|
|
154
|
+
? Date.parse(block.endsAt)
|
|
155
|
+
: shownAt + (block.duration ?? 0) * 1000;
|
|
156
|
+
const remaining = Number.isNaN(deadline)
|
|
157
|
+
? 0
|
|
158
|
+
: Math.max(0, Math.floor((deadline - now) / 1000));
|
|
159
|
+
if (remaining > 0) {
|
|
160
|
+
return formatCountdown(block.format ?? "{hh}:{mm}:{ss}", remaining);
|
|
161
|
+
}
|
|
162
|
+
return block.expiredText ?? null;
|
|
163
|
+
}
|
package/src/blocks/types.ts
CHANGED
|
@@ -257,13 +257,58 @@ export interface SpacerBlock {
|
|
|
257
257
|
visibility?: BlockVisibility;
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
/** Countdown text. `endsAt` is one deadline for everyone; `duration` counts
|
|
261
|
+
* from the moment this paywall view opened. At zero it draws `expiredText`,
|
|
262
|
+
* or nothing when that is absent. Tokens in `format`: {d} {hh} {mm} {ss}. */
|
|
263
|
+
export interface TimerBlock {
|
|
264
|
+
id: string;
|
|
265
|
+
type: "timer";
|
|
266
|
+
endsAt?: string;
|
|
267
|
+
duration?: number;
|
|
268
|
+
format?: string;
|
|
269
|
+
expiredText?: string;
|
|
270
|
+
style?: BlockStyle;
|
|
271
|
+
selectedStyle?: BlockStyle;
|
|
272
|
+
visibility?: BlockVisibility;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** A muted looping clip. React Native has no built-in player, so this SDK
|
|
276
|
+
* draws `poster` in its place, sized like an image block. */
|
|
277
|
+
export interface VideoBlock {
|
|
278
|
+
id: string;
|
|
279
|
+
type: "video";
|
|
280
|
+
url: string;
|
|
281
|
+
poster?: string;
|
|
282
|
+
fit?: "cover" | "contain";
|
|
283
|
+
style?: BlockStyle;
|
|
284
|
+
selectedStyle?: BlockStyle;
|
|
285
|
+
visibility?: BlockVisibility;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** The free-trial switch: ON while package `onPackageIndex` is selected, and
|
|
289
|
+
* tapping it selects the other one. Hidden when the offering lacks either
|
|
290
|
+
* index. */
|
|
291
|
+
export interface ToggleBlock {
|
|
292
|
+
id: string;
|
|
293
|
+
type: "toggle";
|
|
294
|
+
label: string;
|
|
295
|
+
onPackageIndex: number;
|
|
296
|
+
offPackageIndex: number;
|
|
297
|
+
style?: BlockStyle;
|
|
298
|
+
selectedStyle?: BlockStyle;
|
|
299
|
+
visibility?: BlockVisibility;
|
|
300
|
+
}
|
|
301
|
+
|
|
260
302
|
/** The one container block. `layout` picks how children are placed: column /
|
|
261
303
|
* row are flex lines, `stack` layers them (children position with
|
|
262
|
-
* style.inset or the edge offsets),
|
|
304
|
+
* style.inset or the edge offsets), `grid` is an N-column grid, and
|
|
305
|
+
* `carousel` pages through its children one swipe at a time. */
|
|
263
306
|
export interface CardBlock {
|
|
264
307
|
id: string;
|
|
265
308
|
type: "card";
|
|
266
|
-
layout?: "column" | "row" | "stack" | "grid";
|
|
309
|
+
layout?: "column" | "row" | "stack" | "grid" | "carousel";
|
|
310
|
+
/** carousel only: seconds per page before it moves on by itself. */
|
|
311
|
+
autoAdvance?: number;
|
|
267
312
|
/** Renders this container once per package in the attached offering. Its
|
|
268
313
|
* subtree reads the package through tag variables. */
|
|
269
314
|
repeat?: "packages";
|
|
@@ -292,6 +337,9 @@ export type PaywallBlock =
|
|
|
292
337
|
| LinksBlock
|
|
293
338
|
| LineBlock
|
|
294
339
|
| SpacerBlock
|
|
340
|
+
| TimerBlock
|
|
341
|
+
| VideoBlock
|
|
342
|
+
| ToggleBlock
|
|
295
343
|
| CardBlock;
|
|
296
344
|
|
|
297
345
|
/** The published document: screen palette plus the block tree. */
|
|
@@ -345,6 +393,7 @@ export function hasCloseAction(blocks: PaywallBlock[]): boolean {
|
|
|
345
393
|
: b.type === "card"
|
|
346
394
|
? b.repeat === undefined &&
|
|
347
395
|
b.packageIndex === undefined &&
|
|
396
|
+
b.layout !== "carousel" &&
|
|
348
397
|
hasCloseAction(b.children)
|
|
349
398
|
: (b.type === "text" || b.type === "image" || b.type === "button") &&
|
|
350
399
|
b.action === "close"
|