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.
- package/LICENSE +21 -0
- package/README.md +766 -0
- package/dist/blocks/background.d.ts +101 -0
- package/dist/blocks/background.js +165 -0
- package/dist/blocks/color.d.ts +88 -0
- package/dist/blocks/color.js +329 -0
- package/dist/blocks/locale.d.ts +42 -0
- package/dist/blocks/locale.js +263 -0
- package/dist/blocks/selection.d.ts +49 -0
- package/dist/blocks/selection.js +109 -0
- package/dist/blocks/style.d.ts +93 -0
- package/dist/blocks/style.js +365 -0
- package/dist/blocks/tags.d.ts +12 -0
- package/dist/blocks/tags.js +121 -0
- package/dist/blocks/types.d.ts +301 -0
- package/dist/blocks/types.js +44 -0
- package/dist/client.d.ts +402 -0
- package/dist/client.js +1653 -0
- package/dist/device.d.ts +29 -0
- package/dist/device.js +137 -0
- package/dist/errors.d.ts +39 -0
- package/dist/errors.js +68 -0
- package/dist/esm/blocks/background.js +160 -0
- package/dist/esm/blocks/color.js +318 -0
- package/dist/esm/blocks/locale.js +255 -0
- package/dist/esm/blocks/selection.js +100 -0
- package/dist/esm/blocks/style.js +356 -0
- package/dist/esm/blocks/tags.js +117 -0
- package/dist/esm/blocks/types.js +40 -0
- package/dist/esm/client.js +1649 -0
- package/dist/esm/device.js +130 -0
- package/dist/esm/errors.js +59 -0
- package/dist/esm/iap.js +101 -0
- package/dist/esm/implicit.js +63 -0
- package/dist/esm/index.js +29 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/storage.js +78 -0
- package/dist/esm/types.js +4 -0
- package/dist/esm/ui/RevnixBlockPaywall.js +706 -0
- package/dist/esm/ui/RevnixPaywall.js +890 -0
- package/dist/esm/ui/index.js +6 -0
- package/dist/esm/version.js +4 -0
- package/dist/iap.d.ts +20 -0
- package/dist/iap.js +104 -0
- package/dist/implicit.d.ts +69 -0
- package/dist/implicit.js +67 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +50 -0
- package/dist/storage.d.ts +14 -0
- package/dist/storage.js +85 -0
- package/dist/types.d.ts +463 -0
- package/dist/types.js +5 -0
- package/dist/ui/RevnixBlockPaywall.d.ts +96 -0
- package/dist/ui/RevnixBlockPaywall.js +742 -0
- package/dist/ui/RevnixPaywall.d.ts +148 -0
- package/dist/ui/RevnixPaywall.js +927 -0
- package/dist/ui/index.d.ts +7 -0
- package/dist/ui/index.js +25 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +7 -0
- package/package.json +79 -0
- package/src/blocks/background.ts +232 -0
- package/src/blocks/color.ts +352 -0
- package/src/blocks/locale.ts +271 -0
- package/src/blocks/paywall-background-wire.json +12 -0
- package/src/blocks/paywall-selection-wire.json +149 -0
- package/src/blocks/selection.ts +161 -0
- package/src/blocks/style.ts +411 -0
- package/src/blocks/tags.ts +125 -0
- package/src/blocks/types.ts +352 -0
- package/src/client.ts +1993 -0
- package/src/device.ts +146 -0
- package/src/errors.ts +69 -0
- package/src/iap.ts +137 -0
- package/src/implicit.ts +129 -0
- package/src/index.ts +82 -0
- package/src/storage.ts +92 -0
- package/src/types.ts +528 -0
- package/src/ui/RevnixBlockPaywall.tsx +1423 -0
- package/src/ui/RevnixPaywall.tsx +1542 -0
- package/src/ui/index.ts +43 -0
- package/src/version.ts +4 -0
- package/ui.d.ts +1 -0
- package/ui.js +5 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
// Response and input types for the Revnix /v1 API. These mirror the shapes
|
|
2
|
+
// returned by convex/http.ts in revnix-app — the backend is the source of
|
|
3
|
+
// truth; update here when the API surface changes.
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
ImplicitTrigger,
|
|
7
|
+
ImplicitTriggerResponse,
|
|
8
|
+
RevnixLifecycle,
|
|
9
|
+
} from "./implicit.js";
|
|
10
|
+
|
|
11
|
+
export type Environment = "sandbox" | "production";
|
|
12
|
+
export type Store = "apple" | "google";
|
|
13
|
+
|
|
14
|
+
export interface RevnixConfig {
|
|
15
|
+
/** API key issued from the Revnix dashboard — publishable (rvx_pk_live_… /
|
|
16
|
+
* rvx_pk_test_…, safe on-device) or secret (rvx_sk_…, server-side only;
|
|
17
|
+
* required for identify/alias). Legacy rvx_live_…/rvx_test_… keys have
|
|
18
|
+
* full access. The key fixes both the app and the environment server-side. */
|
|
19
|
+
apiKey: string;
|
|
20
|
+
/** Convex site URL of the Revnix deployment, e.g. https://xyz.convex.site */
|
|
21
|
+
baseUrl: string;
|
|
22
|
+
/** CTO-SCALE-REVIEW M9: retry delays for the read-your-writes poll after
|
|
23
|
+
* registerPurchase (entitlements re-fetched until the read's cursor
|
|
24
|
+
* reflects the purchase). Pass [] to disable. Default [250,500,1000,2000]. */
|
|
25
|
+
readYourWritesDelaysMs?: number[];
|
|
26
|
+
/** Persistent key-value storage for the customer id. Pass an adapter over
|
|
27
|
+
* MMKV/AsyncStorage in React Native; defaults to in-memory (ids reset on
|
|
28
|
+
* every app launch — fine for tests, wrong for production). */
|
|
29
|
+
storage?: RevnixStorage;
|
|
30
|
+
/** Start as this customer instead of generating/loading an anonymous id. */
|
|
31
|
+
customerId?: string;
|
|
32
|
+
/** Custom fetch implementation (tests, polyfills). Defaults to global fetch. */
|
|
33
|
+
fetch?: typeof fetch;
|
|
34
|
+
/** Report an install (POST /v1/installs) when a fresh anonymous id is
|
|
35
|
+
* generated — powers the dashboard's Installs / install-conversion charts.
|
|
36
|
+
* Fire-and-forget; never blocks startup. Defaults to true. */
|
|
37
|
+
reportInstalls?: boolean;
|
|
38
|
+
/** Optional device context attached to the install report. Superseded by
|
|
39
|
+
* `device` (REV-268), which feeds the install report too; kept so
|
|
40
|
+
* existing configurations keep working. */
|
|
41
|
+
installMetadata?: { platform?: string; appVersion?: string };
|
|
42
|
+
/** REV-268: facts about the device, sent with every placement resolve so
|
|
43
|
+
* targeting rules ("US storefront", "app version at least 3") can be
|
|
44
|
+
* evaluated on the request that serves the paywall, and stored on the
|
|
45
|
+
* customer as reserved `device.*` attributes visible in the dashboard.
|
|
46
|
+
*
|
|
47
|
+
* In a browser the SDK detects platform, OS version and locale itself;
|
|
48
|
+
* everything else — and everything, under React Native — comes from
|
|
49
|
+
* here (react-native-device-info has all of it). Fields you leave out are
|
|
50
|
+
* simply not reported. Pass `false` to send nothing at all. */
|
|
51
|
+
device?: DeviceFacts | false;
|
|
52
|
+
/** C7: per-request timeout in ms (AbortController-based). Defaults to 10000.
|
|
53
|
+
* A slow or dead backend then fails fast with RevnixTimeoutError instead of
|
|
54
|
+
* hanging the app until the OS TCP timeout (60–120s+). */
|
|
55
|
+
requestTimeoutMs?: number;
|
|
56
|
+
/** REV-157: placement keys to resolve-and-cache at construction (fire and
|
|
57
|
+
* forget, never blocks startup), so the session's first paywall can render
|
|
58
|
+
* from cache even when the device is offline by the time it is shown. */
|
|
59
|
+
prefetchPlacements?: string[];
|
|
60
|
+
/** REV-198 (M23): how old a cached entitlements snapshot may be and still
|
|
61
|
+
* grant access when served offline. Beyond this (or when the device clock
|
|
62
|
+
* sits BEFORE the snapshot was written — a rollback), cached entitlements
|
|
63
|
+
* are served inactive. Default 14 days. */
|
|
64
|
+
offlineMaxCacheAgeMs?: number;
|
|
65
|
+
/** REV-199 (M21): soft TTL on entitlement reads — a fresh network read
|
|
66
|
+
* younger than this answers getEntitlements()/isEntitled() from memory,
|
|
67
|
+
* so gating several screens costs one request, not one each. Pass 0 for
|
|
68
|
+
* the old always-fetch behavior. Default 30000. */
|
|
69
|
+
entitlementsTtlMs?: number;
|
|
70
|
+
/** REV-200 (M26): called whenever a background operation fails — purchase
|
|
71
|
+
* retries, placement prefetch, install/paywall reporting, storage
|
|
72
|
+
* read/writes. These were all silent (`catch(() => {})`), so a fleet
|
|
73
|
+
* failing BEFORE the server (DNS block, broken storage adapter) produced
|
|
74
|
+
* no signal anywhere. Wire this to your crash/analytics reporter. Must
|
|
75
|
+
* not throw; exceptions from it are swallowed. */
|
|
76
|
+
onDiagnostic?: (event: RevnixDiagnostic) => void;
|
|
77
|
+
/** REV-272: called when one of the six implicit moments resolved to a
|
|
78
|
+
* paywall — an app launch, a session start, a deep link, a dismissed
|
|
79
|
+
* paywall, an abandoned checkout, or the install itself. Present it however
|
|
80
|
+
* your app presents paywalls; the SDK deliberately does not present for
|
|
81
|
+
* you, because it does not own your navigation stack and a paywall pushed
|
|
82
|
+
* over a splash screen is worse than no paywall.
|
|
83
|
+
*
|
|
84
|
+
* Providing this handler is what TURNS IMPLICIT PLACEMENTS ON. Without it
|
|
85
|
+
* the SDK makes no extra requests at all — there would be nothing to do
|
|
86
|
+
* with the answer. With it, the SDK asks GET /v1/config once at
|
|
87
|
+
* construction and then fires only for the moments this app has actually
|
|
88
|
+
* configured in the dashboard, so an app that configured none still costs
|
|
89
|
+
* one cached response per launch. The one exception is `handleDeepLink`:
|
|
90
|
+
* it always reports the URL it is handed, handler or not, because the
|
|
91
|
+
* server files that link's attribution facts from the report itself.
|
|
92
|
+
*
|
|
93
|
+
* ⚠️ Pass `trigger.resolution.placementKey` to `logPaywallShown` for the
|
|
94
|
+
* display you present. That is what tells the SDK this display came FROM an
|
|
95
|
+
* implicit trigger, and it is the only thing that stops a `paywall_decline`
|
|
96
|
+
* paywall from firing `paywall_decline` again when the customer dismisses
|
|
97
|
+
* it — a loop with no way out but force-quitting. The server refuses to
|
|
98
|
+
* serve back the very same paywall as a backstop, but it cannot see a rule
|
|
99
|
+
* that points at a DIFFERENT paywall which points back.
|
|
100
|
+
*
|
|
101
|
+
* Never throws into your app: an exception from this handler is reported
|
|
102
|
+
* through `onDiagnostic` and swallowed. */
|
|
103
|
+
onImplicitPaywall?: (trigger: ImplicitTrigger<ImplicitResolution>) => void;
|
|
104
|
+
/** REV-272: explicit off switch for implicit placements, even when
|
|
105
|
+
* `onImplicitPaywall` is set. Defaults to whether that handler is present.
|
|
106
|
+
* Does not stop `handleDeepLink` from reporting the link it is handed —
|
|
107
|
+
* that report still lands the link's attribution facts on the customer,
|
|
108
|
+
* it just never presents a paywall for an ordinary deep link. A dashboard
|
|
109
|
+
* preview link is the exception: it always presents. */
|
|
110
|
+
implicitPlacements?: boolean;
|
|
111
|
+
/** REV-299: the link the customer clicked before they had the app.
|
|
112
|
+
* `POST /v1/installs` returns it whenever the install matched an ad click
|
|
113
|
+
* (Android: exact, from the Play Install Referrer; iOS: probabilistic IP
|
|
114
|
+
* match) — on Android that can be every referrer call. The SDK delivers it
|
|
115
|
+
* to this handler at most once per install. Route it yourself — you may
|
|
116
|
+
* also pass it to `handleDeepLink` for `deeplink_open` paywall rules.
|
|
117
|
+
* Never throws into your app: an exception from this handler is reported
|
|
118
|
+
* through `onDiagnostic` and swallowed. */
|
|
119
|
+
onDeferredDeepLink?: (
|
|
120
|
+
url: string,
|
|
121
|
+
match: "exact" | "probabilistic"
|
|
122
|
+
) => void;
|
|
123
|
+
/** REV-272: how the SDK learns the app came to the foreground, which is what
|
|
124
|
+
* `session_start` is built on. Defaults to a `visibilitychange` listener in
|
|
125
|
+
* a browser and to nothing under React Native — pass an adapter over
|
|
126
|
+
* `AppState` there (see RevnixLifecycle). `false` disables foreground
|
|
127
|
+
* detection; launch-time moments still fire. */
|
|
128
|
+
lifecycle?: RevnixLifecycle | false;
|
|
129
|
+
/** REV-272: how long the app must have been backgrounded for the return to
|
|
130
|
+
* count as a new session rather than an app switch. Default 30 minutes. */
|
|
131
|
+
sessionTimeoutMs?: number;
|
|
132
|
+
/** AT11: called whenever the install-attribution verdict changes — a fresh
|
|
133
|
+
* Play referrer, an ASA token resolving, or a re-attribution genuinely
|
|
134
|
+
* changes the answer, so this can fire more than once across a session
|
|
135
|
+
* (Adjust's `attributionChanged` semantics). Fired after the cold-start
|
|
136
|
+
* install report resolves, and again after `handleInstallReferrer` or
|
|
137
|
+
* `handleAttributionToken` bring in a late signal. Call `getAttribution()`
|
|
138
|
+
* for the same answer on demand.
|
|
139
|
+
* Never throws into your app: an exception from this handler is reported
|
|
140
|
+
* through `onDiagnostic` and swallowed. */
|
|
141
|
+
onAttribution?: (attribution: RevnixAttribution) => void;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** AT11: the install-attribution verdict — GET
|
|
145
|
+
* /v1/customers/{id}/attribution. `installMatch: "unknown"` (no install
|
|
146
|
+
* recorded yet, a normal cold-start race) is never sent to hosts as such;
|
|
147
|
+
* `getAttribution()` and `onAttribution` both surface it as `null` instead,
|
|
148
|
+
* so there is one "no attribution yet" representation, not two. */
|
|
149
|
+
export interface RevnixAttribution {
|
|
150
|
+
installMatch: "referrer" | "click" | "impression" | "organic";
|
|
151
|
+
/** Unix ms. */
|
|
152
|
+
attributedAt: number;
|
|
153
|
+
/** Unix ms — set when the customer was later re-attributed. */
|
|
154
|
+
reattributedAt?: number;
|
|
155
|
+
linkToken?: string;
|
|
156
|
+
referrerSource?: string;
|
|
157
|
+
matchSignals?: string[];
|
|
158
|
+
source?: string;
|
|
159
|
+
medium?: string;
|
|
160
|
+
campaign?: string;
|
|
161
|
+
term?: string;
|
|
162
|
+
content?: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** REV-272: what an implicit trigger resolves to — an ordinary placement
|
|
166
|
+
* resolution plus why it carries no paywall, when it does not. */
|
|
167
|
+
export type ImplicitResolution = PlacementResolution & ImplicitTriggerResponse;
|
|
168
|
+
|
|
169
|
+
/** REV-268: the app-suppliable half of the device attribute contract. The
|
|
170
|
+
* SDK adds `sdkVersion`, `installedAt` and `firstOpen` itself. Every field
|
|
171
|
+
* is optional; the server normalizes case, locale separators and
|
|
172
|
+
* alpha-3 storefronts, so pass what the platform gives you. */
|
|
173
|
+
export interface DeviceFacts {
|
|
174
|
+
/** "ios", "android", "web", … — lowercase family name. */
|
|
175
|
+
platform?: string;
|
|
176
|
+
/** e.g. "18.1". */
|
|
177
|
+
osVersion?: string;
|
|
178
|
+
/** The host app's version string, e.g. "1.2.10". The server derives a
|
|
179
|
+
* zero-padded sortable form (`device.appVersionPadded`) from it. */
|
|
180
|
+
appVersion?: string;
|
|
181
|
+
/** "en-US" or "en_US" — either separator. */
|
|
182
|
+
locale?: string;
|
|
183
|
+
/** ISO 4217, e.g. "USD". */
|
|
184
|
+
currency?: string;
|
|
185
|
+
/** App Store / Play storefront country — alpha-2 ("US") or alpha-3 ("USA"). */
|
|
186
|
+
storefront?: string;
|
|
187
|
+
/** Hardware model identifier, e.g. "iPhone15,3". */
|
|
188
|
+
model?: string;
|
|
189
|
+
/** True in a sandbox / debug build. */
|
|
190
|
+
sandbox?: boolean;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** REV-200 (M26): one background failure. */
|
|
194
|
+
export interface RevnixDiagnostic {
|
|
195
|
+
/** Which background operation failed, e.g. "retryPendingPurchases",
|
|
196
|
+
* "prefetchPlacement", "reportInstall", "logPaywallShown",
|
|
197
|
+
* "waitForEntitlements", "storageWrite", "storageRead". */
|
|
198
|
+
op: string;
|
|
199
|
+
error: unknown;
|
|
200
|
+
/** Unix ms. */
|
|
201
|
+
at: number;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface RevnixStorage {
|
|
205
|
+
getItem(key: string): string | null | Promise<string | null>;
|
|
206
|
+
setItem(key: string, value: string): void | Promise<void>;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ——— POST /v1/purchases ———
|
|
210
|
+
|
|
211
|
+
export interface RegisterPurchaseInput {
|
|
212
|
+
source: Store;
|
|
213
|
+
/** Apple: originalTransactionId · Google: purchaseToken. */
|
|
214
|
+
token: string;
|
|
215
|
+
productId: string;
|
|
216
|
+
transactionId: string;
|
|
217
|
+
/** Unix ms. Defaults server-side to registration time. */
|
|
218
|
+
occurredAt?: number;
|
|
219
|
+
/** Unix ms subscription expiry, when the client knows it. */
|
|
220
|
+
expiresAt?: number;
|
|
221
|
+
/** Raw store payload to retain in the ledger for reprocessing. */
|
|
222
|
+
rawPayload?: unknown;
|
|
223
|
+
/** iOS only — the JWS StoreKit 2 hands you on every transaction
|
|
224
|
+
* (`VerificationResult.jwsRepresentation`). Send it and the purchase is
|
|
225
|
+
* proven: Revnix verifies it against Apple's certificate chain, checks it
|
|
226
|
+
* names this exact transaction, and records a full purchase.
|
|
227
|
+
*
|
|
228
|
+
* Without it the purchase is accepted but PROVISIONAL — a publishable key
|
|
229
|
+
* ships inside your app binary, so an unproven claim is only an assertion.
|
|
230
|
+
* A provisional purchase still grants the entitlement, for a short window,
|
|
231
|
+
* and is promoted automatically when Apple's or Google's own notification
|
|
232
|
+
* confirms the charge. Always send it if you can; apps that enable
|
|
233
|
+
* "require store-verified purchases" reject claims that lack it. */
|
|
234
|
+
signedTransactionInfo?: string;
|
|
235
|
+
/** REV-261: which paywall sold it. Pass the `viewId` `logPaywallShown`
|
|
236
|
+
* returned for the display the customer bought from and the server
|
|
237
|
+
* resolves it to that view's paywall and placement, so per-paywall
|
|
238
|
+
* analytics credit the purchase (and every renewal on it) exactly.
|
|
239
|
+
* Without it the server attributes the purchase to the customer's last
|
|
240
|
+
* paywall view inside the previous 24 hours. */
|
|
241
|
+
viewId?: string;
|
|
242
|
+
/** Explicit paywall / placement; each wins over the view's when sent. */
|
|
243
|
+
paywallId?: string;
|
|
244
|
+
placementKey?: string;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export interface RegisterPurchaseResult {
|
|
248
|
+
eventId: string;
|
|
249
|
+
/** Ledger position of the write — read-your-writes is assertable (ADR 0004). */
|
|
250
|
+
seq: number;
|
|
251
|
+
/** True when the store webhook already recorded this purchase. */
|
|
252
|
+
duplicate: boolean;
|
|
253
|
+
/** The id THIS caller should use going forward — its own canonical id.
|
|
254
|
+
* Differs from the id you sent only when identity resolution merged it
|
|
255
|
+
* away, so following it is always safe. */
|
|
256
|
+
customerId: string;
|
|
257
|
+
/** True when the store token belongs to a different customer than the
|
|
258
|
+
* caller: the purchase was recorded against them, not you. Safe for every
|
|
259
|
+
* caller — it reports the outcome without naming anyone. */
|
|
260
|
+
ownedByOtherCustomer?: boolean;
|
|
261
|
+
/** Which customer holds the token, when it is not you. Present only for
|
|
262
|
+
* secret-key callers — a publishable key learns the outcome above but
|
|
263
|
+
* never another customer's id. */
|
|
264
|
+
tokenOwner?: string;
|
|
265
|
+
/** True when the token was reassigned TO this caller. */
|
|
266
|
+
transferred: boolean;
|
|
267
|
+
/** Set when an identity side-effect (a transfer, or absorbing an imported
|
|
268
|
+
* history) was refused because the claim carried no proof. Send
|
|
269
|
+
* `signedTransactionInfo`, or register from your server with a secret
|
|
270
|
+
* key. */
|
|
271
|
+
refused?: "unverified-claim";
|
|
272
|
+
/** True when this transaction was already in the ledger — a StoreKit
|
|
273
|
+
* replay or restore, not a new sale. */
|
|
274
|
+
restored?: boolean;
|
|
275
|
+
/** True when the purchase was recorded without store proof: the entitlement
|
|
276
|
+
* is live but time-boxed until the store confirms the charge. Send
|
|
277
|
+
* `signedTransactionInfo` (iOS) to avoid it. */
|
|
278
|
+
provisional?: boolean;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ——— GET /v1/customers/{id}/entitlements ———
|
|
282
|
+
|
|
283
|
+
export interface EntitlementSource {
|
|
284
|
+
kind: "subscription" | "grant";
|
|
285
|
+
key: string;
|
|
286
|
+
isActive: boolean;
|
|
287
|
+
expiresAt?: number;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export interface Entitlement {
|
|
291
|
+
entitlementId: string;
|
|
292
|
+
isActive: boolean;
|
|
293
|
+
expiresAt?: number;
|
|
294
|
+
sources: EntitlementSource[];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export interface CustomerEntitlements {
|
|
298
|
+
customerId: string;
|
|
299
|
+
/** Ledger cursor this read reflects. */
|
|
300
|
+
cursor: number;
|
|
301
|
+
entitlements: Entitlement[];
|
|
302
|
+
/** C7 (SDK-populated): true when this was served from the local cache
|
|
303
|
+
* because the live read failed (offline / incident) — a paying customer is
|
|
304
|
+
* shown as subscribed from cache rather than as unsubscribed. false on a
|
|
305
|
+
* fresh server read. */
|
|
306
|
+
stale?: boolean;
|
|
307
|
+
/** C7 (SDK-populated): unix ms these entitlements were fetched/cached. */
|
|
308
|
+
fetchedAt?: number;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ——— POST /v1/identify · POST /v1/customers/{id}/alias ———
|
|
312
|
+
|
|
313
|
+
export type IdentifyStrategy = "merge" | "block";
|
|
314
|
+
|
|
315
|
+
export interface IdentifyResult {
|
|
316
|
+
canonicalId: string;
|
|
317
|
+
merged: boolean;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export interface AliasResult {
|
|
321
|
+
canonicalId: string;
|
|
322
|
+
aliased: boolean;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ——— GET /v1/customers/{id}/identity ———
|
|
326
|
+
|
|
327
|
+
export interface IdentityInfo {
|
|
328
|
+
canonicalId: string;
|
|
329
|
+
isAlias: boolean;
|
|
330
|
+
anonymous: boolean;
|
|
331
|
+
linkedIds?: string[];
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export interface LastDeepLink {
|
|
335
|
+
url: string;
|
|
336
|
+
receivedAt: number;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ——— GET /v1/placements/{key}/offering ———
|
|
340
|
+
|
|
341
|
+
export interface PlacementPackage {
|
|
342
|
+
packageId: string;
|
|
343
|
+
productId: string;
|
|
344
|
+
metadata?: Record<string, unknown>;
|
|
345
|
+
product: PlacementProduct | null;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export interface PlacementProduct {
|
|
349
|
+
productId: string;
|
|
350
|
+
displayName: string;
|
|
351
|
+
entitlementIds: string[];
|
|
352
|
+
prices?: Array<{
|
|
353
|
+
store: Store | "stripe";
|
|
354
|
+
currency: string;
|
|
355
|
+
amountMinor: number;
|
|
356
|
+
}>;
|
|
357
|
+
metadata?: Record<string, unknown>;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export interface PlacementOffering {
|
|
361
|
+
offeringId: string;
|
|
362
|
+
displayName: string;
|
|
363
|
+
metadata?: Record<string, unknown>;
|
|
364
|
+
packages: PlacementPackage[];
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// REV-028: remote paywall design attached to a placement. Render contract —
|
|
368
|
+
// the app draws this with its own components; prices still come from the
|
|
369
|
+
// store (StoreKit / Play Billing) so the display never disagrees with the
|
|
370
|
+
// charge.
|
|
371
|
+
export interface PaywallConfig {
|
|
372
|
+
/** Layout — the screen structure RevnixPaywall renders. The dashboard's
|
|
373
|
+
* template gallery is presets over these layouts; the first three are the
|
|
374
|
+
* original templates and render exactly as before. */
|
|
375
|
+
template:
|
|
376
|
+
| "focus"
|
|
377
|
+
| "feature-list"
|
|
378
|
+
| "minimal"
|
|
379
|
+
| "hero"
|
|
380
|
+
| "timeline"
|
|
381
|
+
| "plans"
|
|
382
|
+
| "feature-grid"
|
|
383
|
+
| "offer"
|
|
384
|
+
| "reveal";
|
|
385
|
+
/** Color scheme the paywall renders in. Absent (legacy config) = dark.
|
|
386
|
+
* The host app's explicit `theme` prop still overrides either scheme. */
|
|
387
|
+
mode?: "dark" | "light";
|
|
388
|
+
headline: string;
|
|
389
|
+
subheadline?: string;
|
|
390
|
+
features: Array<{ icon?: string; title: string; description?: string }>;
|
|
391
|
+
ctaLabel: string;
|
|
392
|
+
/** packageId of the visually highlighted package. */
|
|
393
|
+
highlightPackageId?: string;
|
|
394
|
+
/** Badge on the highlighted package, e.g. "SAVE 17%". */
|
|
395
|
+
badgeText?: string;
|
|
396
|
+
/** Accent hex like "#6478ff"; fall back to the app theme when absent. */
|
|
397
|
+
accent?: string;
|
|
398
|
+
/** Hero image URL rendered above the headline in place of the icon tile. */
|
|
399
|
+
heroImageUrl?: string;
|
|
400
|
+
/** Social proof, dashboard-configured. Any layout renders the pieces that
|
|
401
|
+
* are set: stars/quote card above the packages, `count` under the CTA. */
|
|
402
|
+
review?: {
|
|
403
|
+
/** 0–5; rendered as a star row. */
|
|
404
|
+
rating?: number;
|
|
405
|
+
quote?: string;
|
|
406
|
+
author?: string;
|
|
407
|
+
/** e.g. "Join 2M+ users" — small line under the CTA. */
|
|
408
|
+
count?: string;
|
|
409
|
+
};
|
|
410
|
+
/** Win-back/offer presentation: anchor price struck through on the
|
|
411
|
+
* highlighted package, urgency line above the CTA. Any layout. */
|
|
412
|
+
offer?: {
|
|
413
|
+
strikethroughPrice?: string;
|
|
414
|
+
urgencyText?: string;
|
|
415
|
+
};
|
|
416
|
+
/** Footer links, dashboard-configured. Absent (legacy config) = show all
|
|
417
|
+
* three. When a URL is set the SDK opens it directly; otherwise the host
|
|
418
|
+
* app's onTerms/onPrivacy handler runs. */
|
|
419
|
+
footer?: {
|
|
420
|
+
showRestore: boolean;
|
|
421
|
+
showTerms: boolean;
|
|
422
|
+
showPrivacy: boolean;
|
|
423
|
+
termsUrl?: string;
|
|
424
|
+
privacyUrl?: string;
|
|
425
|
+
};
|
|
426
|
+
/** A designed paywall: the block tree the dashboard's builder authored.
|
|
427
|
+
* When present RevnixPaywall renders THIS and the fields above act as the
|
|
428
|
+
* fallback for clients that predate block rendering — so an older app
|
|
429
|
+
* keeps showing a sane classic screen instead of nothing. Typed as the
|
|
430
|
+
* block document in "revnix-react/ui"; the core entrypoint keeps it opaque
|
|
431
|
+
* so non-UI consumers pull in no renderer types. */
|
|
432
|
+
blocks?: unknown;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export interface PlacementPaywall {
|
|
436
|
+
paywallId: string;
|
|
437
|
+
name: string;
|
|
438
|
+
config: PaywallConfig;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export interface PlacementResolution {
|
|
442
|
+
status: "ok";
|
|
443
|
+
placementKey: string;
|
|
444
|
+
revision: number;
|
|
445
|
+
placementMetadata?: Record<string, unknown>;
|
|
446
|
+
offering: PlacementOffering | null;
|
|
447
|
+
paywall: PlacementPaywall | null;
|
|
448
|
+
/** REV-219: the A/B test serving this resolution — set when the customer
|
|
449
|
+
* is enrolled in an experiment on this placement (the offering/paywall
|
|
450
|
+
* above are the assigned variant's). Null when no experiment applies.
|
|
451
|
+
* Assignment is sticky per customer, survives identity merges, and the
|
|
452
|
+
* enrollment is recorded as an `experiment.enrolled` ledger event. */
|
|
453
|
+
experiment: { key: string; variantId: string } | null;
|
|
454
|
+
/** REV-157 (SDK-populated): true when this was served from the local cache
|
|
455
|
+
* because the live read failed (offline / incident) — the paywall renders
|
|
456
|
+
* the last published resolution rather than not at all. false on a fresh
|
|
457
|
+
* server read. */
|
|
458
|
+
stale?: boolean;
|
|
459
|
+
/** REV-157 (SDK-populated): unix ms this resolution was fetched/cached. */
|
|
460
|
+
fetchedAt?: number;
|
|
461
|
+
/** Set only on a dashboard preview resolution, never on a real resolve. */
|
|
462
|
+
preview?: true;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** REV-263: the six paywall interactions `logPaywallEvent` can report — what
|
|
466
|
+
* the customer did on a display, between the view that opened it and the
|
|
467
|
+
* close or purchase that ended it. The server turns each into the ledger
|
|
468
|
+
* type `paywall.<name>`. */
|
|
469
|
+
export type PaywallInteraction =
|
|
470
|
+
| "selected"
|
|
471
|
+
| "purchase_started"
|
|
472
|
+
| "purchase_abandoned"
|
|
473
|
+
| "purchase_failed"
|
|
474
|
+
| "restore"
|
|
475
|
+
| "error";
|
|
476
|
+
|
|
477
|
+
/** What the host tells RevnixPaywall about a purchase it performed (REV-263).
|
|
478
|
+
* Returning it from `onPurchase` is what lets the paywall report
|
|
479
|
+
* `purchase_abandoned` / `purchase_failed` itself — without it the SDK sees
|
|
480
|
+
* only that checkout STARTED, because the store call happens in the host.
|
|
481
|
+
*
|
|
482
|
+
* `completed` needs no report of its own: the purchase lands as
|
|
483
|
+
* `purchase.completed` through registerPurchase. */
|
|
484
|
+
export interface PaywallPurchaseOutcome {
|
|
485
|
+
status: "completed" | "cancelled" | "failed";
|
|
486
|
+
/** Store error code — a StoreKit `SKError` code, a Play
|
|
487
|
+
* `BillingResponseCode`, or whatever your layer calls it. */
|
|
488
|
+
code?: string;
|
|
489
|
+
message?: string;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** Input for `logAdRevenue` — one ad impression's payout. */
|
|
493
|
+
export interface AdRevenue {
|
|
494
|
+
/** Major units, e.g. 0.0032. Must be > 0. */
|
|
495
|
+
revenue: number;
|
|
496
|
+
/** ISO 4217, e.g. "USD". */
|
|
497
|
+
currency: string;
|
|
498
|
+
/** Monetization network that paid, e.g. "admob". */
|
|
499
|
+
network?: string;
|
|
500
|
+
/** Mediation platform that reported it, e.g. "applovin_max". */
|
|
501
|
+
mediation?: string;
|
|
502
|
+
adUnit?: string;
|
|
503
|
+
placement?: string;
|
|
504
|
+
/** "banner" | "interstitial" | "rewarded" | … free text. */
|
|
505
|
+
format?: string;
|
|
506
|
+
/** Idempotency key for this impression; omit to let the server generate one. */
|
|
507
|
+
eventId?: string;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/** MMP that produced an attribution callback. */
|
|
511
|
+
export type MmpProvider =
|
|
512
|
+
| "adjust"
|
|
513
|
+
| "appsflyer"
|
|
514
|
+
| "singular"
|
|
515
|
+
| "branch"
|
|
516
|
+
| "kochava"
|
|
517
|
+
| "tenjin"
|
|
518
|
+
| "airbridge";
|
|
519
|
+
|
|
520
|
+
/** Input for `setAttribution` — the network/campaign an MMP's attribution
|
|
521
|
+
* callback (Adjust, AppsFlyer, …) resolved for this install. */
|
|
522
|
+
export interface MmpAttribution {
|
|
523
|
+
provider: MmpProvider;
|
|
524
|
+
network: string;
|
|
525
|
+
campaign?: string;
|
|
526
|
+
adGroup?: string;
|
|
527
|
+
creative?: string;
|
|
528
|
+
}
|