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
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { RevnixPaywall, DEFAULT_PAYWALL_THEME, LIGHT_PAYWALL_THEME, } from "./RevnixPaywall.js";
|
|
2
|
+
export { RevnixBlockPaywall, } from "./RevnixBlockPaywall.js";
|
|
3
|
+
export { DESIGN_CANVAS, } from "../blocks/types.js";
|
|
4
|
+
export { backgroundBaseColor, backgroundLayers, coverPlacement, } from "../blocks/background.js";
|
|
5
|
+
export { detectLocale, linkLabels, localeChain, localizeBlockDoc, normalizeLocale, setLocale, } from "../blocks/locale.js";
|
|
6
|
+
export { minorUnitsForCurrency, resolveTags } from "../blocks/tags.js";
|
package/dist/iap.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { RegisterPurchaseInput, Store } from "./types.js";
|
|
2
|
+
export interface IapPurchase {
|
|
3
|
+
productId: string;
|
|
4
|
+
transactionId?: string | null;
|
|
5
|
+
/** Unix ms. */
|
|
6
|
+
transactionDate?: number | null;
|
|
7
|
+
/** Android purchase token. react-native-iap v13+ also sets it on iOS,
|
|
8
|
+
* where it is the StoreKit 2 JWS — not the token Revnix needs, but the
|
|
9
|
+
* proof it wants (`signedTransactionInfo`), so the Apple branch picks it
|
|
10
|
+
* up from here when it has the JWS shape. */
|
|
11
|
+
purchaseToken?: string | null;
|
|
12
|
+
/** StoreKit 2 JWS (`VerificationResult.jwsRepresentation`), for IAP
|
|
13
|
+
* libraries that expose it under its own name. */
|
|
14
|
+
jwsRepresentation?: string | null;
|
|
15
|
+
originalTransactionIdentifierIOS?: string | null;
|
|
16
|
+
}
|
|
17
|
+
export declare function purchaseInputFromIap(purchase: IapPurchase, source: Store, extras?: {
|
|
18
|
+
expiresAt?: number;
|
|
19
|
+
rawPayload?: unknown;
|
|
20
|
+
}): RegisterPurchaseInput;
|
package/dist/iap.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.purchaseInputFromIap = purchaseInputFromIap;
|
|
4
|
+
// A JWS is three dot-separated base64url segments. Older react-native-iap
|
|
5
|
+
// versions put other receipt shapes in purchaseToken on iOS — shape-check so
|
|
6
|
+
// only a real JWS is sent as proof (the server rejects a malformed one).
|
|
7
|
+
const JWS_SHAPE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
|
|
8
|
+
function jwsFromIap(purchase) {
|
|
9
|
+
for (const candidate of [purchase.jwsRepresentation, purchase.purchaseToken]) {
|
|
10
|
+
if (candidate && JWS_SHAPE.test(candidate))
|
|
11
|
+
return candidate;
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
// REV-200 (CTO-R2 M25): base64url → string without atob (not available on
|
|
16
|
+
// every React Native runtime). The ids read from the payload are ASCII;
|
|
17
|
+
// multi-byte content is never consulted.
|
|
18
|
+
const B64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
19
|
+
function base64UrlDecode(segment) {
|
|
20
|
+
const b64 = segment.replace(/-/g, "+").replace(/_/g, "/");
|
|
21
|
+
let out = "";
|
|
22
|
+
let buffer = 0;
|
|
23
|
+
let bits = 0;
|
|
24
|
+
for (const ch of b64) {
|
|
25
|
+
const idx = B64_ALPHABET.indexOf(ch);
|
|
26
|
+
if (idx === -1)
|
|
27
|
+
continue; // padding / stray characters
|
|
28
|
+
buffer = (buffer << 6) | idx;
|
|
29
|
+
bits += 6;
|
|
30
|
+
if (bits >= 8) {
|
|
31
|
+
bits -= 8;
|
|
32
|
+
out += String.fromCharCode((buffer >> bits) & 0xff);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
// The JWS payload's own ids — read WITHOUT verifying the signature, which is
|
|
38
|
+
// fine here: the server verifies the chain and only trusts the proof when the
|
|
39
|
+
// verified payload's ids match what we send. Reading them locally is exactly
|
|
40
|
+
// how we make them match.
|
|
41
|
+
function jwsPayload(jws) {
|
|
42
|
+
try {
|
|
43
|
+
const segment = jws.split(".")[1];
|
|
44
|
+
if (!segment)
|
|
45
|
+
return undefined;
|
|
46
|
+
const parsed = JSON.parse(base64UrlDecode(segment));
|
|
47
|
+
return typeof parsed === "object" && parsed !== null
|
|
48
|
+
? parsed
|
|
49
|
+
: undefined;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function purchaseInputFromIap(purchase, source, extras) {
|
|
56
|
+
if (source === "apple") {
|
|
57
|
+
const transactionId = purchase.transactionId;
|
|
58
|
+
if (!transactionId) {
|
|
59
|
+
throw new Error("Revnix: Apple purchase is missing transactionId");
|
|
60
|
+
}
|
|
61
|
+
const jws = jwsFromIap(purchase);
|
|
62
|
+
const proof = jws ? jwsPayload(jws) : undefined;
|
|
63
|
+
return {
|
|
64
|
+
source,
|
|
65
|
+
// REV-200 (M25): token precedence. The old fallback (original id
|
|
66
|
+
// missing → the renewal's own transactionId) minted a purchaseKey the
|
|
67
|
+
// store webhook would never produce — a second purchase.completed and
|
|
68
|
+
// double-counted revenue — AND failed the server's proof check, which
|
|
69
|
+
// requires the verified JWS's originalTransactionId to EQUAL this
|
|
70
|
+
// token, silently downgrading a proof-carrying purchase to
|
|
71
|
+
// provisional. The JWS payload names the true original id, so when a
|
|
72
|
+
// JWS is present it wins; the IAP library's field is next; the bare
|
|
73
|
+
// transactionId remains only for the proof-less first purchase, where
|
|
74
|
+
// original === transaction anyway.
|
|
75
|
+
token: proof?.originalTransactionId ??
|
|
76
|
+
purchase.originalTransactionIdentifierIOS ??
|
|
77
|
+
transactionId,
|
|
78
|
+
productId: purchase.productId,
|
|
79
|
+
transactionId,
|
|
80
|
+
occurredAt: purchase.transactionDate ?? undefined,
|
|
81
|
+
expiresAt: extras?.expiresAt,
|
|
82
|
+
rawPayload: extras?.rawPayload,
|
|
83
|
+
// Without this the purchase lands PROVISIONAL (see
|
|
84
|
+
// RegisterPurchaseInput.signedTransactionInfo).
|
|
85
|
+
signedTransactionInfo: jws,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const token = purchase.purchaseToken;
|
|
89
|
+
if (!token) {
|
|
90
|
+
throw new Error("Revnix: Google purchase is missing purchaseToken");
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
source,
|
|
94
|
+
token,
|
|
95
|
+
productId: purchase.productId,
|
|
96
|
+
// The Google webhook adapter (convex/lib/google.ts) reports the
|
|
97
|
+
// purchaseToken as the transactionId — send the same, never the Play
|
|
98
|
+
// orderId, or client-register and webhook won't share a purchaseKey.
|
|
99
|
+
transactionId: token,
|
|
100
|
+
occurredAt: purchase.transactionDate ?? undefined,
|
|
101
|
+
expiresAt: extras?.expiresAt,
|
|
102
|
+
rawPayload: extras?.rawPayload,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** The six reserved keys. Lowercase throughout: a placement key must match
|
|
2
|
+
* `^[a-z0-9][a-z0-9._-]{0,63}$` server-side, which is why this is
|
|
3
|
+
* `deeplink_open` and not Superwall's `deepLink_open`. */
|
|
4
|
+
export declare const IMPLICIT_PLACEMENTS: readonly ["app_install", "app_launch", "session_start", "deeplink_open", "paywall_decline", "transaction_abandon"];
|
|
5
|
+
export type ImplicitPlacement = (typeof IMPLICIT_PLACEMENTS)[number];
|
|
6
|
+
/** Why a trigger came back with no paywall — as opposed to a placement that
|
|
7
|
+
* resolved fine and simply has none attached, which is `null`. */
|
|
8
|
+
export type ImplicitSkipReason = "not_configured" | "same_paywall";
|
|
9
|
+
/** The one moment the SDK cannot see unaided: the URL that opened the app is
|
|
10
|
+
* delivered to the host's own entry point, and an SDK trying to intercept it
|
|
11
|
+
* would be fighting the host's router. Call `handleDeepLink(url)`. */
|
|
12
|
+
export declare const HOST_CALL_PLACEMENT: ImplicitPlacement;
|
|
13
|
+
/** The placementKey `handleDeepLink` reports for a dashboard QR/link preview
|
|
14
|
+
* (`<scheme>://revnix-preview?revnix_preview=<token>`) — not one of the six
|
|
15
|
+
* above, and never sent to /v1/placements/triggered. */
|
|
16
|
+
export declare const PREVIEW_PLACEMENT_KEY = "revnix_preview";
|
|
17
|
+
/** How long the app must have been backgrounded for the return to count as a
|
|
18
|
+
* new session rather than an app switch. Matches Superwall's own definition
|
|
19
|
+
* so a team moving over gets the same numbers. */
|
|
20
|
+
export declare const DEFAULT_SESSION_TIMEOUT_MS: number;
|
|
21
|
+
/** How long after a FAILED /v1/config read the SDK waits before asking again.
|
|
22
|
+
* See RevnixClient.implicitConfigRetryAt. */
|
|
23
|
+
export declare const IMPLICIT_CONFIG_RETRY_MS = 60000;
|
|
24
|
+
/** Where the app is. Both transitions matter: a session is defined by how
|
|
25
|
+
* long the app was in the BACKGROUND, which cannot be known from foreground
|
|
26
|
+
* events alone — "time since the last return" would mint a session after 35
|
|
27
|
+
* minutes of continuous use plus a three-second app switch. */
|
|
28
|
+
export type RevnixAppState = "foreground" | "background";
|
|
29
|
+
/** The platform seam. The SDK ships a browser implementation (see
|
|
30
|
+
* `browserLifecycle`); React Native passes an adapter over `AppState`, which
|
|
31
|
+
* is a few lines and keeps `react-native` out of this package's dependencies:
|
|
32
|
+
*
|
|
33
|
+
* lifecycle: {
|
|
34
|
+
* onStateChange: (handler) => {
|
|
35
|
+
* const sub = AppState.addEventListener("change", (s) => {
|
|
36
|
+
* handler(s === "active" ? "foreground" : "background");
|
|
37
|
+
* });
|
|
38
|
+
* return () => sub.remove();
|
|
39
|
+
* },
|
|
40
|
+
* }
|
|
41
|
+
*/
|
|
42
|
+
export interface RevnixLifecycle {
|
|
43
|
+
/** Subscribe to app state transitions. Returns an unsubscribe. Called at
|
|
44
|
+
* most once per client; the client calls the unsubscribe from dispose().
|
|
45
|
+
* Repeated reports of the same state are harmless. */
|
|
46
|
+
onStateChange(handler: (state: RevnixAppState) => void): () => void;
|
|
47
|
+
}
|
|
48
|
+
/** Foreground detection in a browser tab. `visibilitychange` rather than
|
|
49
|
+
* `focus`: switching windows fires focus without the page ever having been
|
|
50
|
+
* hidden, which would mint sessions for someone alt-tabbing. Returns null
|
|
51
|
+
* where there is no document (React Native, Node, SSR) — the host passes its
|
|
52
|
+
* own adapter there. */
|
|
53
|
+
export declare function browserLifecycle(): RevnixLifecycle | null;
|
|
54
|
+
/** What the host is handed when a moment resolved to a paywall. */
|
|
55
|
+
export interface ImplicitTrigger<TResolution> {
|
|
56
|
+
/** Which of the six fired, or `PREVIEW_PLACEMENT_KEY` for a dashboard preview. */
|
|
57
|
+
placement: ImplicitPlacement | typeof PREVIEW_PLACEMENT_KEY;
|
|
58
|
+
/** The resolution, exactly as `resolvePlacement` would have returned it —
|
|
59
|
+
* offering, paywall, experiment and targeting all included. */
|
|
60
|
+
resolution: TResolution;
|
|
61
|
+
}
|
|
62
|
+
/** The trigger response body. The resolve shape plus the two fields the
|
|
63
|
+
* trigger route adds. */
|
|
64
|
+
export interface ImplicitTriggerResponse {
|
|
65
|
+
skipReason: ImplicitSkipReason | null;
|
|
66
|
+
/** True when this call appended a NEW ledger event (false for a duplicate,
|
|
67
|
+
* and for the three moments that reuse an event reported elsewhere). */
|
|
68
|
+
recorded: boolean;
|
|
69
|
+
}
|
package/dist/implicit.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// REV-272: implicit placements — the six moments the SDK reports on its own.
|
|
3
|
+
//
|
|
4
|
+
// A placement is normally a location the HOST resolves by name, so every new
|
|
5
|
+
// place a paywall could appear costs a code change and a release. Six moments
|
|
6
|
+
// are the same in every app and visible to the SDK without the host saying
|
|
7
|
+
// anything, so an operator can attach a paywall to them from the dashboard
|
|
8
|
+
// alone. This module owns the vocabulary and the platform-lifecycle seam;
|
|
9
|
+
// client.ts owns the firing.
|
|
10
|
+
//
|
|
11
|
+
// Mirrors convex/lib/implicitPlacements.ts in revnix-app — the backend is the
|
|
12
|
+
// source of truth for the key spellings, and a mismatch here is silent (the
|
|
13
|
+
// server 400s a key it does not know, and the moment is simply never
|
|
14
|
+
// configured), so the two lists are checked against each other in the tests.
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.IMPLICIT_CONFIG_RETRY_MS = exports.DEFAULT_SESSION_TIMEOUT_MS = exports.PREVIEW_PLACEMENT_KEY = exports.HOST_CALL_PLACEMENT = exports.IMPLICIT_PLACEMENTS = void 0;
|
|
17
|
+
exports.browserLifecycle = browserLifecycle;
|
|
18
|
+
/** The six reserved keys. Lowercase throughout: a placement key must match
|
|
19
|
+
* `^[a-z0-9][a-z0-9._-]{0,63}$` server-side, which is why this is
|
|
20
|
+
* `deeplink_open` and not Superwall's `deepLink_open`. */
|
|
21
|
+
exports.IMPLICIT_PLACEMENTS = [
|
|
22
|
+
"app_install",
|
|
23
|
+
"app_launch",
|
|
24
|
+
"session_start",
|
|
25
|
+
"deeplink_open",
|
|
26
|
+
"paywall_decline",
|
|
27
|
+
"transaction_abandon",
|
|
28
|
+
];
|
|
29
|
+
/** The one moment the SDK cannot see unaided: the URL that opened the app is
|
|
30
|
+
* delivered to the host's own entry point, and an SDK trying to intercept it
|
|
31
|
+
* would be fighting the host's router. Call `handleDeepLink(url)`. */
|
|
32
|
+
exports.HOST_CALL_PLACEMENT = "deeplink_open";
|
|
33
|
+
/** The placementKey `handleDeepLink` reports for a dashboard QR/link preview
|
|
34
|
+
* (`<scheme>://revnix-preview?revnix_preview=<token>`) — not one of the six
|
|
35
|
+
* above, and never sent to /v1/placements/triggered. */
|
|
36
|
+
exports.PREVIEW_PLACEMENT_KEY = "revnix_preview";
|
|
37
|
+
/** How long the app must have been backgrounded for the return to count as a
|
|
38
|
+
* new session rather than an app switch. Matches Superwall's own definition
|
|
39
|
+
* so a team moving over gets the same numbers. */
|
|
40
|
+
exports.DEFAULT_SESSION_TIMEOUT_MS = 30 * 60000;
|
|
41
|
+
/** How long after a FAILED /v1/config read the SDK waits before asking again.
|
|
42
|
+
* See RevnixClient.implicitConfigRetryAt. */
|
|
43
|
+
exports.IMPLICIT_CONFIG_RETRY_MS = 60000;
|
|
44
|
+
/** Foreground detection in a browser tab. `visibilitychange` rather than
|
|
45
|
+
* `focus`: switching windows fires focus without the page ever having been
|
|
46
|
+
* hidden, which would mint sessions for someone alt-tabbing. Returns null
|
|
47
|
+
* where there is no document (React Native, Node, SSR) — the host passes its
|
|
48
|
+
* own adapter there. */
|
|
49
|
+
function browserLifecycle() {
|
|
50
|
+
const doc = globalThis.document;
|
|
51
|
+
if (!doc ||
|
|
52
|
+
typeof doc.addEventListener !== "function" ||
|
|
53
|
+
typeof doc.removeEventListener !== "function") {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const add = doc.addEventListener.bind(doc);
|
|
57
|
+
const remove = doc.removeEventListener.bind(doc);
|
|
58
|
+
return {
|
|
59
|
+
onStateChange(handler) {
|
|
60
|
+
const listener = () => {
|
|
61
|
+
handler(doc.visibilityState === "visible" ? "foreground" : "background");
|
|
62
|
+
};
|
|
63
|
+
add("visibilitychange", listener);
|
|
64
|
+
return () => remove("visibilitychange", listener);
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RevnixClient } from "./client.js";
|
|
2
|
+
import { setLocale } from "./blocks/locale.js";
|
|
3
|
+
import type { RevnixConfig } from "./types.js";
|
|
4
|
+
export { RevnixClient } from "./client.js";
|
|
5
|
+
export { PurchaseBlockedError, RevnixAuthError, RevnixError, RevnixNetworkError, RevnixRateLimitError, RevnixTimeoutError, } from "./errors.js";
|
|
6
|
+
export { purchaseInputFromIap, type IapPurchase } from "./iap.js";
|
|
7
|
+
export { memoryStorage, generateAnonymousId } from "./storage.js";
|
|
8
|
+
export { SDK_VERSION } from "./version.js";
|
|
9
|
+
export { DEVICE_HEADER, encodeDeviceHeader } from "./device.js";
|
|
10
|
+
export { browserLifecycle, DEFAULT_SESSION_TIMEOUT_MS, HOST_CALL_PLACEMENT, IMPLICIT_PLACEMENTS, PREVIEW_PLACEMENT_KEY, type ImplicitPlacement, type ImplicitSkipReason, type ImplicitTrigger, type ImplicitTriggerResponse, type RevnixLifecycle, } from "./implicit.js";
|
|
11
|
+
export type { AdRevenue, AliasResult, CustomerEntitlements, DeviceFacts, Entitlement, EntitlementSource, Environment, IdentifyResult, IdentifyStrategy, IdentityInfo, ImplicitResolution, LastDeepLink, MmpAttribution, MmpProvider, PaywallConfig, PaywallInteraction, PaywallPurchaseOutcome, PlacementOffering, PlacementPackage, PlacementPaywall, PlacementProduct, PlacementResolution, RegisterPurchaseInput, RegisterPurchaseResult, RevnixAttribution, RevnixConfig, RevnixDiagnostic, RevnixStorage, Store, } from "./types.js";
|
|
12
|
+
/** Singleton facade — configure once at app start, then import `Revnix`
|
|
13
|
+
* anywhere. Prefer constructing RevnixClient directly in tests. */
|
|
14
|
+
export declare const Revnix: {
|
|
15
|
+
configure(config: RevnixConfig): RevnixClient;
|
|
16
|
+
setLocale: typeof setLocale;
|
|
17
|
+
readonly client: RevnixClient;
|
|
18
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
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;
|
|
4
|
+
const client_js_1 = require("./client.js");
|
|
5
|
+
const locale_js_1 = require("./blocks/locale.js");
|
|
6
|
+
var client_js_2 = require("./client.js");
|
|
7
|
+
Object.defineProperty(exports, "RevnixClient", { enumerable: true, get: function () { return client_js_2.RevnixClient; } });
|
|
8
|
+
var errors_js_1 = require("./errors.js");
|
|
9
|
+
Object.defineProperty(exports, "PurchaseBlockedError", { enumerable: true, get: function () { return errors_js_1.PurchaseBlockedError; } });
|
|
10
|
+
Object.defineProperty(exports, "RevnixAuthError", { enumerable: true, get: function () { return errors_js_1.RevnixAuthError; } });
|
|
11
|
+
Object.defineProperty(exports, "RevnixError", { enumerable: true, get: function () { return errors_js_1.RevnixError; } });
|
|
12
|
+
Object.defineProperty(exports, "RevnixNetworkError", { enumerable: true, get: function () { return errors_js_1.RevnixNetworkError; } });
|
|
13
|
+
Object.defineProperty(exports, "RevnixRateLimitError", { enumerable: true, get: function () { return errors_js_1.RevnixRateLimitError; } });
|
|
14
|
+
Object.defineProperty(exports, "RevnixTimeoutError", { enumerable: true, get: function () { return errors_js_1.RevnixTimeoutError; } });
|
|
15
|
+
var iap_js_1 = require("./iap.js");
|
|
16
|
+
Object.defineProperty(exports, "purchaseInputFromIap", { enumerable: true, get: function () { return iap_js_1.purchaseInputFromIap; } });
|
|
17
|
+
var storage_js_1 = require("./storage.js");
|
|
18
|
+
Object.defineProperty(exports, "memoryStorage", { enumerable: true, get: function () { return storage_js_1.memoryStorage; } });
|
|
19
|
+
Object.defineProperty(exports, "generateAnonymousId", { enumerable: true, get: function () { return storage_js_1.generateAnonymousId; } });
|
|
20
|
+
var version_js_1 = require("./version.js");
|
|
21
|
+
Object.defineProperty(exports, "SDK_VERSION", { enumerable: true, get: function () { return version_js_1.SDK_VERSION; } });
|
|
22
|
+
var device_js_1 = require("./device.js");
|
|
23
|
+
Object.defineProperty(exports, "DEVICE_HEADER", { enumerable: true, get: function () { return device_js_1.DEVICE_HEADER; } });
|
|
24
|
+
Object.defineProperty(exports, "encodeDeviceHeader", { enumerable: true, get: function () { return device_js_1.encodeDeviceHeader; } });
|
|
25
|
+
var implicit_js_1 = require("./implicit.js");
|
|
26
|
+
Object.defineProperty(exports, "browserLifecycle", { enumerable: true, get: function () { return implicit_js_1.browserLifecycle; } });
|
|
27
|
+
Object.defineProperty(exports, "DEFAULT_SESSION_TIMEOUT_MS", { enumerable: true, get: function () { return implicit_js_1.DEFAULT_SESSION_TIMEOUT_MS; } });
|
|
28
|
+
Object.defineProperty(exports, "HOST_CALL_PLACEMENT", { enumerable: true, get: function () { return implicit_js_1.HOST_CALL_PLACEMENT; } });
|
|
29
|
+
Object.defineProperty(exports, "IMPLICIT_PLACEMENTS", { enumerable: true, get: function () { return implicit_js_1.IMPLICIT_PLACEMENTS; } });
|
|
30
|
+
Object.defineProperty(exports, "PREVIEW_PLACEMENT_KEY", { enumerable: true, get: function () { return implicit_js_1.PREVIEW_PLACEMENT_KEY; } });
|
|
31
|
+
let instance = null;
|
|
32
|
+
/** Singleton facade — configure once at app start, then import `Revnix`
|
|
33
|
+
* anywhere. Prefer constructing RevnixClient directly in tests. */
|
|
34
|
+
exports.Revnix = {
|
|
35
|
+
configure(config) {
|
|
36
|
+
// REV-204 (L10): dev fast-refresh re-runs configure(); without teardown
|
|
37
|
+
// the old client kept its retry/prefetch background work running
|
|
38
|
+
// alongside the new one.
|
|
39
|
+
instance?.dispose();
|
|
40
|
+
instance = new client_js_1.RevnixClient(config);
|
|
41
|
+
return instance;
|
|
42
|
+
},
|
|
43
|
+
setLocale: locale_js_1.setLocale,
|
|
44
|
+
get client() {
|
|
45
|
+
if (!instance) {
|
|
46
|
+
throw new Error("Revnix: call Revnix.configure(...) before use");
|
|
47
|
+
}
|
|
48
|
+
return instance;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RevnixStorage } from "./types.js";
|
|
2
|
+
/** Default storage: survives only the current JS runtime. Real apps must
|
|
3
|
+
* pass a persistent adapter (MMKV, AsyncStorage, localStorage) or every
|
|
4
|
+
* launch becomes a new anonymous customer. */
|
|
5
|
+
export declare function memoryStorage(): RevnixStorage;
|
|
6
|
+
export declare function resolveStorage(configured?: RevnixStorage): {
|
|
7
|
+
storage: RevnixStorage;
|
|
8
|
+
persistent: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare function generateAnonymousId(): string;
|
|
11
|
+
/** One id per paywall display (REV-094) — the backend dedupes retries on it
|
|
12
|
+
* while genuine repeat views (fresh ids) count separately. */
|
|
13
|
+
export declare function generateViewId(): string;
|
|
14
|
+
export declare function generateEventId(): string;
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.memoryStorage = memoryStorage;
|
|
4
|
+
exports.resolveStorage = resolveStorage;
|
|
5
|
+
exports.generateAnonymousId = generateAnonymousId;
|
|
6
|
+
exports.generateViewId = generateViewId;
|
|
7
|
+
exports.generateEventId = generateEventId;
|
|
8
|
+
/** Default storage: survives only the current JS runtime. Real apps must
|
|
9
|
+
* pass a persistent adapter (MMKV, AsyncStorage, localStorage) or every
|
|
10
|
+
* launch becomes a new anonymous customer. */
|
|
11
|
+
function memoryStorage() {
|
|
12
|
+
const map = new Map();
|
|
13
|
+
return {
|
|
14
|
+
getItem: (key) => map.get(key) ?? null,
|
|
15
|
+
setItem: (key, value) => {
|
|
16
|
+
map.set(key, value);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// CTO-SCALE-REVIEW M5: what actually backs the client when `storage` is
|
|
21
|
+
// omitted. Memory-only used to be the silent default — every launch became a
|
|
22
|
+
// new anonymous customer + a new install report (analytics inflated by the
|
|
23
|
+
// DAU multiplier, purchases orphaned on the previous launch's id). Now:
|
|
24
|
+
// 1. explicit adapter → use it (unchanged);
|
|
25
|
+
// 2. web (localStorage usable) → use localStorage automatically;
|
|
26
|
+
// 3. truly nothing (React Native without an adapter) → memory, but WARN
|
|
27
|
+
// loudly, and the client skips install reporting (see client.ts) so a
|
|
28
|
+
// forgotten config line can't fabricate fleet-wide installs.
|
|
29
|
+
function resolveStorage(configured) {
|
|
30
|
+
if (configured)
|
|
31
|
+
return { storage: configured, persistent: true };
|
|
32
|
+
const ls = globalThis.localStorage;
|
|
33
|
+
if (ls) {
|
|
34
|
+
try {
|
|
35
|
+
const probe = "revnix.__probe";
|
|
36
|
+
ls.setItem(probe, "1");
|
|
37
|
+
ls.removeItem(probe);
|
|
38
|
+
return {
|
|
39
|
+
storage: {
|
|
40
|
+
getItem: (key) => ls.getItem(key),
|
|
41
|
+
setItem: (key, value) => {
|
|
42
|
+
ls.setItem(key, value);
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
persistent: true,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Private mode / quota quirks — fall through to memory.
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
console.warn("[revnix] No `storage` adapter configured and no localStorage available; " +
|
|
53
|
+
"the customer id lives in memory only. Every launch will look like a NEW " +
|
|
54
|
+
"anonymous customer: prior purchases won't be attached and analytics " +
|
|
55
|
+
"would be inflated, so install reporting and experiment enrollment are " +
|
|
56
|
+
"disabled. Pass a persistent adapter (AsyncStorage/MMKV on React " +
|
|
57
|
+
"Native) to fix this.");
|
|
58
|
+
return { storage: memoryStorage(), persistent: false };
|
|
59
|
+
}
|
|
60
|
+
function randomHex(byteLength) {
|
|
61
|
+
const bytes = new Uint8Array(byteLength);
|
|
62
|
+
const cryptoObj = globalThis.crypto;
|
|
63
|
+
// L2: no Math.random fallback — a guessable customer id is readable by
|
|
64
|
+
// anyone holding the public key, so failing loudly beats failing silently.
|
|
65
|
+
// Every supported runtime (browsers, RN ≥0.72 via Hermes, Node ≥19) exposes
|
|
66
|
+
// getRandomValues; older Node needs `globalThis.crypto = require("crypto").webcrypto`.
|
|
67
|
+
if (!cryptoObj?.getRandomValues) {
|
|
68
|
+
throw new Error("[revnix] crypto.getRandomValues is unavailable in this runtime; " +
|
|
69
|
+
"cannot generate a secure customer id. Polyfill globalThis.crypto " +
|
|
70
|
+
"or pass an explicit `customerId` in configure().");
|
|
71
|
+
}
|
|
72
|
+
cryptoObj.getRandomValues(bytes);
|
|
73
|
+
return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
74
|
+
}
|
|
75
|
+
function generateAnonymousId() {
|
|
76
|
+
return `rvx_anon_${randomHex(16)}`;
|
|
77
|
+
}
|
|
78
|
+
/** One id per paywall display (REV-094) — the backend dedupes retries on it
|
|
79
|
+
* while genuine repeat views (fresh ids) count separately. */
|
|
80
|
+
function generateViewId() {
|
|
81
|
+
return `rvx_view_${randomHex(16)}`;
|
|
82
|
+
}
|
|
83
|
+
function generateEventId() {
|
|
84
|
+
return `rvx_evt_${randomHex(16)}`;
|
|
85
|
+
}
|