@mysuperwall/react-native 0.0.0-stage → 1.2.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/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +173 -2
- package/lib/MySuperwall.d.ts +67 -0
- package/lib/MySuperwall.js +261 -0
- package/lib/engine/localRuleEngine.d.ts +44 -0
- package/lib/engine/localRuleEngine.js +318 -0
- package/lib/hooks/usePaywall.d.ts +7 -0
- package/lib/hooks/usePaywall.js +25 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.js +27 -0
- package/lib/models/manifest.d.ts +76 -0
- package/lib/models/manifest.js +2 -0
- package/lib/models/paywall.d.ts +36 -0
- package/lib/models/paywall.js +2 -0
- package/lib/services/anonymousIdentity.d.ts +6 -0
- package/lib/services/anonymousIdentity.js +36 -0
- package/lib/services/followUpService.d.ts +9 -0
- package/lib/services/followUpService.js +26 -0
- package/lib/services/manifestService.d.ts +22 -0
- package/lib/services/manifestService.js +80 -0
- package/lib/services/offlineEventQueue.d.ts +18 -0
- package/lib/services/offlineEventQueue.js +101 -0
- package/lib/ui/PaywallModal.d.ts +8 -0
- package/lib/ui/PaywallModal.js +136 -0
- package/package.json +51 -4
- package/src/MySuperwall.ts +284 -0
- package/src/engine/localRuleEngine.ts +375 -0
- package/src/hooks/usePaywall.ts +27 -0
- package/src/index.ts +12 -0
- package/src/models/manifest.ts +81 -0
- package/src/models/paywall.ts +40 -0
- package/src/services/anonymousIdentity.ts +28 -0
- package/src/services/followUpService.ts +21 -0
- package/src/services/manifestService.ts +63 -0
- package/src/services/offlineEventQueue.ts +88 -0
- package/src/ui/PaywallModal.tsx +122 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LocalRuleEngine = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Pure TypeScript SHA-256 hash to ensure 0-dependency, 0-native-bridge execution on Hermes & JSC.
|
|
6
|
+
*/
|
|
7
|
+
function sha256Bytes(ascii) {
|
|
8
|
+
function rightRotate(value, amount) {
|
|
9
|
+
return (value >>> amount) | (value << (32 - amount));
|
|
10
|
+
}
|
|
11
|
+
const mathPow = Math.pow;
|
|
12
|
+
const maxWord = mathPow(2, 32);
|
|
13
|
+
const lengthProperty = 'length';
|
|
14
|
+
let i = 0, j = 0;
|
|
15
|
+
let result = '';
|
|
16
|
+
const words = [];
|
|
17
|
+
const asciiBitLength = ascii[lengthProperty] * 8;
|
|
18
|
+
const hash = [];
|
|
19
|
+
const k = [];
|
|
20
|
+
let primeCounter = 0;
|
|
21
|
+
const isComposite = {};
|
|
22
|
+
for (let candidate = 2; primeCounter < 64; candidate++) {
|
|
23
|
+
if (!isComposite[candidate]) {
|
|
24
|
+
for (i = 0; i < 313; i += candidate) {
|
|
25
|
+
isComposite[i] = true;
|
|
26
|
+
}
|
|
27
|
+
hash[primeCounter] = (mathPow(candidate, 0.5) * maxWord) | 0;
|
|
28
|
+
k[primeCounter++] = (mathPow(candidate, 1 / 3) * maxWord) | 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
ascii += '\x80';
|
|
32
|
+
while ((ascii[lengthProperty] % 64) - 56)
|
|
33
|
+
ascii += '\x00';
|
|
34
|
+
for (i = 0; i < ascii[lengthProperty]; i++) {
|
|
35
|
+
j = ascii.charCodeAt(i);
|
|
36
|
+
words[i >> 2] |= j << (((3 - i) % 4) * 8);
|
|
37
|
+
}
|
|
38
|
+
words[words[lengthProperty]] = (asciiBitLength / maxWord) | 0;
|
|
39
|
+
words[words[lengthProperty]] = asciiBitLength;
|
|
40
|
+
for (j = 0; j < words[lengthProperty];) {
|
|
41
|
+
const w = words.slice(j, (j += 16));
|
|
42
|
+
const oldHash = hash.slice(0);
|
|
43
|
+
for (i = 0; i < 64; i++) {
|
|
44
|
+
const w15 = w[i - 15], w2 = w[i - 2];
|
|
45
|
+
const s0 = rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3);
|
|
46
|
+
const s1 = rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10);
|
|
47
|
+
w[i] = i < 16 ? w[i] : (w[i - 16] + s0 + w[i - 7] + s1) | 0;
|
|
48
|
+
const s1_maj = rightRotate(hash[0], 2) ^ rightRotate(hash[0], 13) ^ rightRotate(hash[0], 22);
|
|
49
|
+
const maj = (hash[0] & hash[1]) ^ (hash[0] & hash[2]) ^ (hash[1] & hash[2]);
|
|
50
|
+
const t2 = (s1_maj + maj) | 0;
|
|
51
|
+
const s1_ch = rightRotate(hash[4], 6) ^ rightRotate(hash[4], 11) ^ rightRotate(hash[4], 25);
|
|
52
|
+
const ch = (hash[4] & hash[5]) ^ (~hash[4] & hash[6]);
|
|
53
|
+
const t1 = (hash[7] + s1_ch + ch + k[i] + w[i]) | 0;
|
|
54
|
+
hash[7] = hash[6];
|
|
55
|
+
hash[6] = hash[5];
|
|
56
|
+
hash[5] = hash[4];
|
|
57
|
+
hash[4] = (hash[3] + t1) | 0;
|
|
58
|
+
hash[3] = hash[2];
|
|
59
|
+
hash[2] = hash[1];
|
|
60
|
+
hash[1] = hash[0];
|
|
61
|
+
hash[0] = (t1 + t2) | 0;
|
|
62
|
+
}
|
|
63
|
+
for (i = 0; i < 8; i++) {
|
|
64
|
+
hash[i] = (hash[i] + oldHash[i]) | 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const out = new Uint8Array(32);
|
|
68
|
+
for (i = 0; i < 8; i++) {
|
|
69
|
+
out[i * 4] = (hash[i] >>> 24) & 255;
|
|
70
|
+
out[i * 4 + 1] = (hash[i] >>> 16) & 255;
|
|
71
|
+
out[i * 4 + 2] = (hash[i] >>> 8) & 255;
|
|
72
|
+
out[i * 4 + 3] = hash[i] & 255;
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
class LocalRuleEngine {
|
|
77
|
+
/**
|
|
78
|
+
* Deterministic bucket from 0 to 99 matching backend SHA-256 logic.
|
|
79
|
+
*/
|
|
80
|
+
getDeterministicBucket(userId, salt = '') {
|
|
81
|
+
if (!userId) {
|
|
82
|
+
return Math.floor(Math.random() * 100);
|
|
83
|
+
}
|
|
84
|
+
const input = `${userId}:${salt}`;
|
|
85
|
+
// Match backend/native SHA-256 over UTF-8, including non-ASCII user IDs.
|
|
86
|
+
const utf8 = encodeURIComponent(input).replace(/%([0-9A-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
|
|
87
|
+
const bytes = sha256Bytes(utf8);
|
|
88
|
+
const u32 = ((bytes[0] << 24) >>> 0) +
|
|
89
|
+
((bytes[1] << 16) >>> 0) +
|
|
90
|
+
((bytes[2] << 8) >>> 0) +
|
|
91
|
+
(bytes[3] >>> 0);
|
|
92
|
+
return (u32 % 100) >>> 0;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Compares two semantic version strings.
|
|
96
|
+
*/
|
|
97
|
+
compareVersions(v1, v2) {
|
|
98
|
+
const clean1 = (v1 || '').replace(/[^0-9.]/g, '');
|
|
99
|
+
const clean2 = (v2 || '').replace(/[^0-9.]/g, '');
|
|
100
|
+
const p1 = clean1.split('.').map(n => parseInt(n, 10) || 0);
|
|
101
|
+
const p2 = clean2.split('.').map(n => parseInt(n, 10) || 0);
|
|
102
|
+
const maxLen = Math.max(p1.length, p2.length);
|
|
103
|
+
for (let i = 0; i < maxLen; i++) {
|
|
104
|
+
const n1 = p1[i] ?? 0;
|
|
105
|
+
const n2 = p2[i] ?? 0;
|
|
106
|
+
if (n1 > n2)
|
|
107
|
+
return 1;
|
|
108
|
+
if (n1 < n2)
|
|
109
|
+
return -1;
|
|
110
|
+
}
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Resolves nested field access like "metadata.tier".
|
|
115
|
+
*/
|
|
116
|
+
resolveContextValue(field, context) {
|
|
117
|
+
if (field in context)
|
|
118
|
+
return context[field];
|
|
119
|
+
const parts = field.split('.');
|
|
120
|
+
let current = context;
|
|
121
|
+
for (const part of parts) {
|
|
122
|
+
if (current && typeof current === 'object' && part in current) {
|
|
123
|
+
current = current[part];
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return current;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Evaluates a single condition.
|
|
133
|
+
*/
|
|
134
|
+
evaluateCondition(condition, context) {
|
|
135
|
+
const actual = this.resolveContextValue(condition.field, context);
|
|
136
|
+
const op = (condition.operator || 'equals').toLowerCase();
|
|
137
|
+
const target = condition.value;
|
|
138
|
+
switch (op) {
|
|
139
|
+
case 'equals':
|
|
140
|
+
case 'eq':
|
|
141
|
+
if (actual !== undefined && actual !== null && target !== undefined && target !== null) {
|
|
142
|
+
return String(actual).trim().toLowerCase() === String(target).trim().toLowerCase();
|
|
143
|
+
}
|
|
144
|
+
return actual == target;
|
|
145
|
+
case 'not_equals':
|
|
146
|
+
case 'neq':
|
|
147
|
+
if (actual !== undefined && actual !== null && target !== undefined && target !== null) {
|
|
148
|
+
return String(actual).trim().toLowerCase() !== String(target).trim().toLowerCase();
|
|
149
|
+
}
|
|
150
|
+
return actual != target;
|
|
151
|
+
case 'contains':
|
|
152
|
+
if (actual === undefined || actual === null || target === undefined || target === null)
|
|
153
|
+
return false;
|
|
154
|
+
return String(actual).toLowerCase().includes(String(target).toLowerCase());
|
|
155
|
+
case 'not_contains':
|
|
156
|
+
if (actual === undefined || actual === null || target === undefined || target === null)
|
|
157
|
+
return true;
|
|
158
|
+
return !String(actual).toLowerCase().includes(String(target).toLowerCase());
|
|
159
|
+
case 'gt':
|
|
160
|
+
case 'gte':
|
|
161
|
+
case 'lt':
|
|
162
|
+
case 'lte': {
|
|
163
|
+
const aNum = Number(actual);
|
|
164
|
+
const tNum = Number(target);
|
|
165
|
+
if (isNaN(aNum) || isNaN(tNum))
|
|
166
|
+
return false;
|
|
167
|
+
if (op === 'gt')
|
|
168
|
+
return aNum > tNum;
|
|
169
|
+
if (op === 'gte')
|
|
170
|
+
return aNum >= tNum;
|
|
171
|
+
if (op === 'lt')
|
|
172
|
+
return aNum < tNum;
|
|
173
|
+
if (op === 'lte')
|
|
174
|
+
return aNum <= tNum;
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
case 'in_list':
|
|
178
|
+
case 'in': {
|
|
179
|
+
if (actual === undefined || actual === null)
|
|
180
|
+
return false;
|
|
181
|
+
const aStr = String(actual).trim().toLowerCase();
|
|
182
|
+
if (Array.isArray(target)) {
|
|
183
|
+
return target.some(elem => String(elem).trim().toLowerCase() === aStr);
|
|
184
|
+
}
|
|
185
|
+
const parts = String(target).split(',').map(s => s.trim().toLowerCase());
|
|
186
|
+
return parts.includes(aStr);
|
|
187
|
+
}
|
|
188
|
+
case 'not_in_list':
|
|
189
|
+
case 'not_in': {
|
|
190
|
+
if (actual === undefined || actual === null)
|
|
191
|
+
return true;
|
|
192
|
+
const aStr = String(actual).trim().toLowerCase();
|
|
193
|
+
if (Array.isArray(target)) {
|
|
194
|
+
return !target.some(elem => String(elem).trim().toLowerCase() === aStr);
|
|
195
|
+
}
|
|
196
|
+
const parts = String(target).split(',').map(s => s.trim().toLowerCase());
|
|
197
|
+
return !parts.includes(aStr);
|
|
198
|
+
}
|
|
199
|
+
case 'version_gt':
|
|
200
|
+
return this.compareVersions(actual, target) > 0;
|
|
201
|
+
case 'version_gte':
|
|
202
|
+
return this.compareVersions(actual, target) >= 0;
|
|
203
|
+
case 'version_lt':
|
|
204
|
+
return this.compareVersions(actual, target) < 0;
|
|
205
|
+
case 'version_lte':
|
|
206
|
+
return this.compareVersions(actual, target) <= 0;
|
|
207
|
+
default:
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Checks if a rule matches given context.
|
|
213
|
+
*/
|
|
214
|
+
matchesRule(rule, context) {
|
|
215
|
+
if (!rule.conditions || rule.conditions.length === 0)
|
|
216
|
+
return true;
|
|
217
|
+
const isAny = rule.matchType === 'any';
|
|
218
|
+
return isAny
|
|
219
|
+
? rule.conditions.some(c => this.evaluateCondition(c, context))
|
|
220
|
+
: rule.conditions.every(c => this.evaluateCondition(c, context));
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Deterministically selects a variant respecting holdout.
|
|
224
|
+
*/
|
|
225
|
+
selectDeterministicVariant(userId, salt, variants, fallbackPaywallId, holdoutPercentage = 0) {
|
|
226
|
+
if (holdoutPercentage > 0) {
|
|
227
|
+
const holdoutBucket = this.getDeterministicBucket(userId, `${salt}:holdout`);
|
|
228
|
+
if (holdoutBucket < holdoutPercentage) {
|
|
229
|
+
return { paywallId: null, abVariant: null, isHoldout: true };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const validVariants = (variants || []).filter(v => Boolean(v.paywallId));
|
|
233
|
+
if (validVariants.length === 0) {
|
|
234
|
+
return { paywallId: fallbackPaywallId, abVariant: 'A', isHoldout: false };
|
|
235
|
+
}
|
|
236
|
+
const totalWeight = validVariants.reduce((sum, v) => sum + Math.max(1, v.weight ?? 50), 0);
|
|
237
|
+
if (totalWeight <= 0) {
|
|
238
|
+
const first = validVariants[0];
|
|
239
|
+
return { paywallId: first.paywallId, abVariant: first.variant, isHoldout: false };
|
|
240
|
+
}
|
|
241
|
+
const bucket = this.getDeterministicBucket(userId, `${salt}:variant`);
|
|
242
|
+
const target = Math.floor((bucket * totalWeight) / 100);
|
|
243
|
+
let cumulative = 0;
|
|
244
|
+
for (const v of validVariants) {
|
|
245
|
+
cumulative += Math.max(1, v.weight ?? 50);
|
|
246
|
+
if (target < cumulative) {
|
|
247
|
+
return { paywallId: v.paywallId, abVariant: v.variant, isHoldout: false };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const last = validVariants[validVariants.length - 1];
|
|
251
|
+
return { paywallId: last.paywallId, abVariant: last.variant, isHoldout: false };
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Resolves a trigger in-memory with 0 ms latency.
|
|
255
|
+
*/
|
|
256
|
+
resolveTrigger(trigger, userId, context) {
|
|
257
|
+
// 1. Check priority-ordered audience rules
|
|
258
|
+
const activeRules = [...(trigger.rules || [])].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
259
|
+
for (const rule of activeRules) {
|
|
260
|
+
if (this.matchesRule(rule, context)) {
|
|
261
|
+
const { paywallId, abVariant, isHoldout } = this.selectDeterministicVariant(userId, rule.id, rule.variants, rule.paywallId || trigger.paywallId, rule.holdoutPercentage || 0);
|
|
262
|
+
if (isHoldout) {
|
|
263
|
+
return {
|
|
264
|
+
show: false,
|
|
265
|
+
paywallId: null,
|
|
266
|
+
abVariant: null,
|
|
267
|
+
matchedRuleName: rule.name,
|
|
268
|
+
matchedRuleId: rule.id,
|
|
269
|
+
isHoldout: true,
|
|
270
|
+
reason: 'rule_holdout'
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
if (paywallId) {
|
|
274
|
+
return {
|
|
275
|
+
show: true,
|
|
276
|
+
paywallId,
|
|
277
|
+
abVariant: abVariant || 'A',
|
|
278
|
+
matchedRuleName: rule.name,
|
|
279
|
+
matchedRuleId: rule.id,
|
|
280
|
+
isHoldout: false,
|
|
281
|
+
reason: 'rule_matched'
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// 2. Fall back to root trigger variants & holdout
|
|
287
|
+
const { paywallId, abVariant, isHoldout } = this.selectDeterministicVariant(userId, trigger.key, trigger.variants, trigger.paywallId, trigger.holdoutPercentage || 0);
|
|
288
|
+
if (isHoldout) {
|
|
289
|
+
return {
|
|
290
|
+
show: false,
|
|
291
|
+
paywallId: null,
|
|
292
|
+
abVariant: null,
|
|
293
|
+
matchedRuleName: null,
|
|
294
|
+
isHoldout: true,
|
|
295
|
+
reason: 'root_holdout'
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
if (paywallId) {
|
|
299
|
+
return {
|
|
300
|
+
show: true,
|
|
301
|
+
paywallId,
|
|
302
|
+
abVariant: abVariant || 'A',
|
|
303
|
+
matchedRuleName: null,
|
|
304
|
+
isHoldout: false,
|
|
305
|
+
reason: 'root_variant'
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
show: false,
|
|
310
|
+
paywallId: null,
|
|
311
|
+
abVariant: null,
|
|
312
|
+
matchedRuleName: null,
|
|
313
|
+
isHoldout: false,
|
|
314
|
+
reason: 'no_paywall_mapped'
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
exports.LocalRuleEngine = LocalRuleEngine;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.usePaywall = usePaywall;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const MySuperwall_1 = require("../MySuperwall");
|
|
6
|
+
function usePaywall() {
|
|
7
|
+
const [activePaywall, setActivePaywall] = (0, react_1.useState)(null);
|
|
8
|
+
const [callbacks, setCallbacks] = (0, react_1.useState)({});
|
|
9
|
+
(0, react_1.useEffect)(() => {
|
|
10
|
+
const unregister = MySuperwall_1.MySuperwall.shared.registerPresenter((paywall, actions) => {
|
|
11
|
+
setActivePaywall(paywall);
|
|
12
|
+
setCallbacks(actions);
|
|
13
|
+
});
|
|
14
|
+
return unregister;
|
|
15
|
+
}, []);
|
|
16
|
+
const dismiss = (0, react_1.useCallback)(() => {
|
|
17
|
+
setActivePaywall(null);
|
|
18
|
+
}, []);
|
|
19
|
+
return {
|
|
20
|
+
activePaywall,
|
|
21
|
+
isVisible: Boolean(activePaywall),
|
|
22
|
+
callbacks,
|
|
23
|
+
dismiss
|
|
24
|
+
};
|
|
25
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './models/manifest';
|
|
2
|
+
export * from './models/paywall';
|
|
3
|
+
export * from './engine/localRuleEngine';
|
|
4
|
+
export * from './services/manifestService';
|
|
5
|
+
export * from './services/offlineEventQueue';
|
|
6
|
+
export * from './ui/PaywallModal';
|
|
7
|
+
export * from './hooks/usePaywall';
|
|
8
|
+
import { MySuperwall } from './MySuperwall';
|
|
9
|
+
export { MySuperwall };
|
|
10
|
+
export default MySuperwall;
|
|
11
|
+
export type { MySuperwallInitOptions } from './MySuperwall';
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.MySuperwall = void 0;
|
|
18
|
+
__exportStar(require("./models/manifest"), exports);
|
|
19
|
+
__exportStar(require("./models/paywall"), exports);
|
|
20
|
+
__exportStar(require("./engine/localRuleEngine"), exports);
|
|
21
|
+
__exportStar(require("./services/manifestService"), exports);
|
|
22
|
+
__exportStar(require("./services/offlineEventQueue"), exports);
|
|
23
|
+
__exportStar(require("./ui/PaywallModal"), exports);
|
|
24
|
+
__exportStar(require("./hooks/usePaywall"), exports);
|
|
25
|
+
const MySuperwall_1 = require("./MySuperwall");
|
|
26
|
+
Object.defineProperty(exports, "MySuperwall", { enumerable: true, get: function () { return MySuperwall_1.MySuperwall; } });
|
|
27
|
+
exports.default = MySuperwall_1.MySuperwall;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export interface ManifestCondition {
|
|
2
|
+
field: string;
|
|
3
|
+
operator: string;
|
|
4
|
+
value: any;
|
|
5
|
+
}
|
|
6
|
+
export interface ManifestVariant {
|
|
7
|
+
variantId?: string;
|
|
8
|
+
variantLabel?: string;
|
|
9
|
+
variant: string;
|
|
10
|
+
weight: number;
|
|
11
|
+
paywallId: string | null;
|
|
12
|
+
}
|
|
13
|
+
export interface ManifestRule {
|
|
14
|
+
experimentId?: string;
|
|
15
|
+
experimentVersion?: string;
|
|
16
|
+
audienceId?: string;
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
priority: number;
|
|
20
|
+
matchType: 'all' | 'any';
|
|
21
|
+
conditions: ManifestCondition[];
|
|
22
|
+
holdoutPercentage: number;
|
|
23
|
+
paywallId: string | null;
|
|
24
|
+
variants: ManifestVariant[];
|
|
25
|
+
}
|
|
26
|
+
export interface ManifestTrigger {
|
|
27
|
+
experimentId?: string;
|
|
28
|
+
experimentVersion?: string;
|
|
29
|
+
audienceId?: string;
|
|
30
|
+
id: string;
|
|
31
|
+
key: string;
|
|
32
|
+
prewarmPriority?: number;
|
|
33
|
+
holdoutPercentage?: number;
|
|
34
|
+
paywallId: string | null;
|
|
35
|
+
variants: ManifestVariant[];
|
|
36
|
+
rules: ManifestRule[];
|
|
37
|
+
dismissAction?: {
|
|
38
|
+
type: string;
|
|
39
|
+
followUpPaywallId?: string | null;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export interface ManifestPaywallProduct {
|
|
43
|
+
productId: string;
|
|
44
|
+
variableName?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface FollowUpRule {
|
|
47
|
+
enabled?: boolean;
|
|
48
|
+
actionType?: string;
|
|
49
|
+
triggerKey?: string;
|
|
50
|
+
delayMs?: number;
|
|
51
|
+
oncePerSession?: boolean;
|
|
52
|
+
cooldownMs?: number;
|
|
53
|
+
maxChainDepth?: number;
|
|
54
|
+
skipIfPremium?: boolean;
|
|
55
|
+
preventRepeatPaywall?: boolean;
|
|
56
|
+
}
|
|
57
|
+
export interface ManifestPaywall {
|
|
58
|
+
publishedVersion?: number;
|
|
59
|
+
id: string;
|
|
60
|
+
name: string;
|
|
61
|
+
templateKey: string;
|
|
62
|
+
contentJson?: {
|
|
63
|
+
followUpRules?: Record<string, FollowUpRule>;
|
|
64
|
+
};
|
|
65
|
+
html: string;
|
|
66
|
+
productIds: string[];
|
|
67
|
+
products: ManifestPaywallProduct[];
|
|
68
|
+
}
|
|
69
|
+
export interface PaywallManifest {
|
|
70
|
+
ok: boolean;
|
|
71
|
+
notModified?: boolean;
|
|
72
|
+
hash?: string;
|
|
73
|
+
appId?: string;
|
|
74
|
+
triggers: ManifestTrigger[];
|
|
75
|
+
paywalls: Record<string, ManifestPaywall>;
|
|
76
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { FollowUpRule, ManifestPaywallProduct } from './manifest';
|
|
2
|
+
export interface ExperimentContext {
|
|
3
|
+
experimentId: string;
|
|
4
|
+
experimentVersion: string;
|
|
5
|
+
audienceId: string;
|
|
6
|
+
variantId: string;
|
|
7
|
+
variantLabel: string;
|
|
8
|
+
paywallId: string;
|
|
9
|
+
paywallVersion: number;
|
|
10
|
+
}
|
|
11
|
+
export interface PaywallData {
|
|
12
|
+
attributionScope?: string;
|
|
13
|
+
presentationId?: string;
|
|
14
|
+
followUpRules?: Record<string, FollowUpRule>;
|
|
15
|
+
dismissFollowUpPaywallId?: string;
|
|
16
|
+
experimentContext?: ExperimentContext;
|
|
17
|
+
attributionUserId?: string;
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
html: string;
|
|
21
|
+
productIds: string[];
|
|
22
|
+
products: ManifestPaywallProduct[];
|
|
23
|
+
triggerKey?: string;
|
|
24
|
+
abVariant?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface PaywallResult {
|
|
27
|
+
show: boolean;
|
|
28
|
+
paywall: PaywallData | null;
|
|
29
|
+
reason: string;
|
|
30
|
+
}
|
|
31
|
+
export interface PaywallCallbackActions {
|
|
32
|
+
onPurchase?: (productId: string) => void | Promise<void>;
|
|
33
|
+
onRestore?: () => void | Promise<void>;
|
|
34
|
+
onDismiss?: () => void | Promise<void>;
|
|
35
|
+
onOpenUrl?: (url: string) => void | Promise<void>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AnonymousIdentity = void 0;
|
|
4
|
+
/** Stable installation identity, isolated by server and application. */
|
|
5
|
+
class AnonymousIdentity {
|
|
6
|
+
static get(scope) {
|
|
7
|
+
const existing = this.identities.get(scope);
|
|
8
|
+
if (existing)
|
|
9
|
+
return existing;
|
|
10
|
+
const identity = this.load(scope);
|
|
11
|
+
this.identities.set(scope, identity);
|
|
12
|
+
return identity;
|
|
13
|
+
}
|
|
14
|
+
static async load(scope) {
|
|
15
|
+
const key = '@mysuperwall_anonymous_v1:' + encodeURIComponent(scope);
|
|
16
|
+
let storage;
|
|
17
|
+
try {
|
|
18
|
+
storage = require('@react-native-async-storage/async-storage').default;
|
|
19
|
+
}
|
|
20
|
+
catch (_) { }
|
|
21
|
+
try {
|
|
22
|
+
const saved = await storage?.getItem(key);
|
|
23
|
+
if (saved?.startsWith('anon_'))
|
|
24
|
+
return saved;
|
|
25
|
+
}
|
|
26
|
+
catch (_) { }
|
|
27
|
+
const identity = 'anon_' + Date.now().toString(36) + Math.random().toString(36).slice(2);
|
|
28
|
+
try {
|
|
29
|
+
await storage?.setItem(key, identity);
|
|
30
|
+
}
|
|
31
|
+
catch (_) { }
|
|
32
|
+
return identity;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.AnonymousIdentity = AnonymousIdentity;
|
|
36
|
+
AnonymousIdentity.identities = new Map();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FollowUpRule } from '../models/manifest';
|
|
2
|
+
/** Session-scoped policy; all timers and reservations are invalidated on identity/config changes. */
|
|
3
|
+
export declare class FollowUpService {
|
|
4
|
+
private used;
|
|
5
|
+
private timers;
|
|
6
|
+
cancel(): void;
|
|
7
|
+
reset(): void;
|
|
8
|
+
schedule(key: string, rule: FollowUpRule, depth: number, premium: boolean, run: () => void): boolean;
|
|
9
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FollowUpService = void 0;
|
|
4
|
+
/** Session-scoped policy; all timers and reservations are invalidated on identity/config changes. */
|
|
5
|
+
class FollowUpService {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.used = new Map();
|
|
8
|
+
this.timers = new Set();
|
|
9
|
+
}
|
|
10
|
+
cancel() { this.timers.forEach(clearTimeout); this.timers.clear(); }
|
|
11
|
+
reset() { this.cancel(); this.used.clear(); }
|
|
12
|
+
schedule(key, rule, depth, premium, run) {
|
|
13
|
+
const number = (value, fallback, max) => typeof value === 'number' && Number.isFinite(value) ? Math.max(0, Math.min(max, Math.floor(value))) : fallback;
|
|
14
|
+
if (!rule.enabled || (rule.actionType || 'trigger') !== 'trigger' || !rule.triggerKey?.trim() ||
|
|
15
|
+
(rule.skipIfPremium && premium) || depth >= Math.max(1, number(rule.maxChainDepth, 2, 5)))
|
|
16
|
+
return false;
|
|
17
|
+
const previous = this.used.get(key);
|
|
18
|
+
if (previous !== undefined && (rule.oncePerSession !== false || Date.now() - previous < number(rule.cooldownMs, 0, 86400000)))
|
|
19
|
+
return false;
|
|
20
|
+
this.used.set(key, Date.now());
|
|
21
|
+
const timer = setTimeout(() => { this.timers.delete(timer); run(); }, number(rule.delayMs, 0, 10000));
|
|
22
|
+
this.timers.add(timer);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.FollowUpService = FollowUpService;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { PaywallManifest } from '../models/manifest';
|
|
2
|
+
export interface ManifestScope {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
appKey?: string;
|
|
5
|
+
bundleId?: string;
|
|
6
|
+
platform?: string;
|
|
7
|
+
lang?: string;
|
|
8
|
+
substitutionData?: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
export declare class ManifestService {
|
|
11
|
+
private scope;
|
|
12
|
+
private generation;
|
|
13
|
+
manifest: PaywallManifest | null;
|
|
14
|
+
private asyncStorage;
|
|
15
|
+
constructor();
|
|
16
|
+
static scopeKey(options: ManifestScope): string;
|
|
17
|
+
configure(options: ManifestScope): void;
|
|
18
|
+
private key;
|
|
19
|
+
loadFromDisk(): Promise<PaywallManifest | null>;
|
|
20
|
+
saveToDisk(manifest: PaywallManifest): Promise<void>;
|
|
21
|
+
syncInBackground(options: ManifestScope): Promise<PaywallManifest | null>;
|
|
22
|
+
}
|