rampkit-expo-dev 0.0.91 → 0.0.92
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/build/EventManager.d.ts +21 -0
- package/build/EventManager.js +55 -1
- package/build/RampKit.d.ts +7 -0
- package/build/RampKit.js +43 -8
- package/build/TargetingContext.d.ts +10 -0
- package/build/TargetingContext.js +100 -0
- package/build/TargetingEngine.d.ts +37 -0
- package/build/TargetingEngine.js +162 -0
- package/build/index.d.ts +4 -1
- package/build/index.js +8 -1
- package/build/types.d.ts +97 -0
- package/package.json +1 -1
package/build/EventManager.d.ts
CHANGED
|
@@ -15,6 +15,9 @@ declare class EventManager {
|
|
|
15
15
|
private currentVariantId;
|
|
16
16
|
private currentPaywallId;
|
|
17
17
|
private currentPlacement;
|
|
18
|
+
private currentTargetId;
|
|
19
|
+
private currentTargetName;
|
|
20
|
+
private currentBucket;
|
|
18
21
|
private onboardingStartTime;
|
|
19
22
|
private currentOnboardingId;
|
|
20
23
|
private onboardingCompletedForSession;
|
|
@@ -40,6 +43,19 @@ declare class EventManager {
|
|
|
40
43
|
* Set current paywall context
|
|
41
44
|
*/
|
|
42
45
|
setCurrentPaywall(paywallId: string | null, placement?: string | null): void;
|
|
46
|
+
/**
|
|
47
|
+
* Set targeting context (called after target evaluation)
|
|
48
|
+
* This persists for all subsequent events
|
|
49
|
+
*/
|
|
50
|
+
setTargetingContext(targetId: string, targetName: string, onboardingId: string, bucket: number): void;
|
|
51
|
+
/**
|
|
52
|
+
* Get current targeting info (for user profile updates)
|
|
53
|
+
*/
|
|
54
|
+
getTargetingInfo(): {
|
|
55
|
+
targetId: string | null;
|
|
56
|
+
targetName: string | null;
|
|
57
|
+
bucket: number | null;
|
|
58
|
+
};
|
|
43
59
|
/**
|
|
44
60
|
* Start onboarding tracking
|
|
45
61
|
*/
|
|
@@ -68,6 +84,11 @@ declare class EventManager {
|
|
|
68
84
|
* Track app session started
|
|
69
85
|
*/
|
|
70
86
|
trackAppSessionStarted(isFirstLaunch: boolean, launchCount: number): void;
|
|
87
|
+
/**
|
|
88
|
+
* Track target matched event
|
|
89
|
+
* Called when targeting evaluation completes and a target is selected
|
|
90
|
+
*/
|
|
91
|
+
trackTargetMatched(targetId: string, targetName: string, onboardingId: string, bucket: number): void;
|
|
71
92
|
/**
|
|
72
93
|
* Track onboarding started
|
|
73
94
|
*/
|
package/build/EventManager.js
CHANGED
|
@@ -31,6 +31,10 @@ class EventManager {
|
|
|
31
31
|
this.currentVariantId = null;
|
|
32
32
|
this.currentPaywallId = null;
|
|
33
33
|
this.currentPlacement = null;
|
|
34
|
+
// Targeting tracking (persists for all events after target match)
|
|
35
|
+
this.currentTargetId = null;
|
|
36
|
+
this.currentTargetName = null;
|
|
37
|
+
this.currentBucket = null;
|
|
34
38
|
// Onboarding tracking
|
|
35
39
|
this.onboardingStartTime = null;
|
|
36
40
|
this.currentOnboardingId = null;
|
|
@@ -96,6 +100,28 @@ class EventManager {
|
|
|
96
100
|
this.currentPlacement = placement;
|
|
97
101
|
}
|
|
98
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Set targeting context (called after target evaluation)
|
|
105
|
+
* This persists for all subsequent events
|
|
106
|
+
*/
|
|
107
|
+
setTargetingContext(targetId, targetName, onboardingId, bucket) {
|
|
108
|
+
this.currentTargetId = targetId;
|
|
109
|
+
this.currentTargetName = targetName;
|
|
110
|
+
this.currentOnboardingId = onboardingId;
|
|
111
|
+
this.currentBucket = bucket;
|
|
112
|
+
this.currentFlowId = onboardingId;
|
|
113
|
+
Logger_1.Logger.verbose("EventManager: Targeting context set", { targetId, targetName, bucket });
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get current targeting info (for user profile updates)
|
|
117
|
+
*/
|
|
118
|
+
getTargetingInfo() {
|
|
119
|
+
return {
|
|
120
|
+
targetId: this.currentTargetId,
|
|
121
|
+
targetName: this.currentTargetName,
|
|
122
|
+
bucket: this.currentBucket,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
99
125
|
/**
|
|
100
126
|
* Start onboarding tracking
|
|
101
127
|
*/
|
|
@@ -147,6 +173,16 @@ class EventManager {
|
|
|
147
173
|
regionCode: (_h = (_g = contextOverrides === null || contextOverrides === void 0 ? void 0 : contextOverrides.regionCode) !== null && _g !== void 0 ? _g : this.baseContext.regionCode) !== null && _h !== void 0 ? _h : null,
|
|
148
174
|
placement: (_j = contextOverrides === null || contextOverrides === void 0 ? void 0 : contextOverrides.placement) !== null && _j !== void 0 ? _j : this.currentPlacement,
|
|
149
175
|
};
|
|
176
|
+
// Include targeting info in all events if available
|
|
177
|
+
const enrichedProperties = {
|
|
178
|
+
...properties,
|
|
179
|
+
};
|
|
180
|
+
if (this.currentTargetId) {
|
|
181
|
+
enrichedProperties.targetId = this.currentTargetId;
|
|
182
|
+
}
|
|
183
|
+
if (this.currentBucket !== null) {
|
|
184
|
+
enrichedProperties.bucket = this.currentBucket;
|
|
185
|
+
}
|
|
150
186
|
const event = {
|
|
151
187
|
appId: this.appId,
|
|
152
188
|
appUserId: this.appUserId,
|
|
@@ -156,7 +192,7 @@ class EventManager {
|
|
|
156
192
|
occurredAt,
|
|
157
193
|
device: this.device,
|
|
158
194
|
context,
|
|
159
|
-
properties,
|
|
195
|
+
properties: enrichedProperties,
|
|
160
196
|
};
|
|
161
197
|
// Fire and forget - don't await
|
|
162
198
|
this.sendEvent(event);
|
|
@@ -205,6 +241,21 @@ class EventManager {
|
|
|
205
241
|
trackAppSessionStarted(isFirstLaunch, launchCount) {
|
|
206
242
|
this.track("app_session_started", { isFirstLaunch, launchCount });
|
|
207
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Track target matched event
|
|
246
|
+
* Called when targeting evaluation completes and a target is selected
|
|
247
|
+
*/
|
|
248
|
+
trackTargetMatched(targetId, targetName, onboardingId, bucket) {
|
|
249
|
+
// Set targeting context for all future events
|
|
250
|
+
this.setTargetingContext(targetId, targetName, onboardingId, bucket);
|
|
251
|
+
// Track the target_matched event
|
|
252
|
+
this.track("target_matched", {
|
|
253
|
+
targetId,
|
|
254
|
+
targetName,
|
|
255
|
+
onboardingId,
|
|
256
|
+
bucket,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
208
259
|
/**
|
|
209
260
|
* Track onboarding started
|
|
210
261
|
*/
|
|
@@ -326,6 +377,9 @@ class EventManager {
|
|
|
326
377
|
this.currentVariantId = null;
|
|
327
378
|
this.currentPaywallId = null;
|
|
328
379
|
this.currentPlacement = null;
|
|
380
|
+
this.currentTargetId = null;
|
|
381
|
+
this.currentTargetName = null;
|
|
382
|
+
this.currentBucket = null;
|
|
329
383
|
this.onboardingStartTime = null;
|
|
330
384
|
this.currentOnboardingId = null;
|
|
331
385
|
this.onboardingCompletedForSession = false;
|
package/build/RampKit.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Main SDK class for RampKit Expo integration
|
|
4
4
|
*/
|
|
5
5
|
import { DeviceInfo, RampKitConfig, EventContext } from "./types";
|
|
6
|
+
import { TargetEvaluationResult } from "./TargetingEngine";
|
|
6
7
|
export declare class RampKitCore {
|
|
7
8
|
private static _instance;
|
|
8
9
|
private config;
|
|
@@ -15,6 +16,8 @@ export declare class RampKitCore {
|
|
|
15
16
|
private initialized;
|
|
16
17
|
/** Custom App User ID provided by the developer (alias for their user system) */
|
|
17
18
|
private appUserID;
|
|
19
|
+
/** Result of target evaluation (for analytics/debugging) */
|
|
20
|
+
private targetingResult;
|
|
18
21
|
static get instance(): RampKitCore;
|
|
19
22
|
/**
|
|
20
23
|
* Configure the RampKit SDK
|
|
@@ -57,6 +60,10 @@ export declare class RampKitCore {
|
|
|
57
60
|
* Get the device info
|
|
58
61
|
*/
|
|
59
62
|
getDeviceInfo(): DeviceInfo | null;
|
|
63
|
+
/**
|
|
64
|
+
* Get the targeting result (which target matched and which onboarding was selected)
|
|
65
|
+
*/
|
|
66
|
+
getTargetingResult(): TargetEvaluationResult | null;
|
|
60
67
|
/**
|
|
61
68
|
* Check if SDK is initialized
|
|
62
69
|
*/
|
package/build/RampKit.js
CHANGED
|
@@ -10,6 +10,8 @@ const userId_1 = require("./userId");
|
|
|
10
10
|
const DeviceInfoCollector_1 = require("./DeviceInfoCollector");
|
|
11
11
|
const EventManager_1 = require("./EventManager");
|
|
12
12
|
const RampKitNative_1 = require("./RampKitNative");
|
|
13
|
+
const TargetingContext_1 = require("./TargetingContext");
|
|
14
|
+
const TargetingEngine_1 = require("./TargetingEngine");
|
|
13
15
|
const constants_1 = require("./constants");
|
|
14
16
|
const OnboardingResponseStorage_1 = require("./OnboardingResponseStorage");
|
|
15
17
|
const Logger_1 = require("./Logger");
|
|
@@ -23,6 +25,8 @@ class RampKitCore {
|
|
|
23
25
|
this.initialized = false;
|
|
24
26
|
/** Custom App User ID provided by the developer (alias for their user system) */
|
|
25
27
|
this.appUserID = null;
|
|
28
|
+
/** Result of target evaluation (for analytics/debugging) */
|
|
29
|
+
this.targetingResult = null;
|
|
26
30
|
}
|
|
27
31
|
static get instance() {
|
|
28
32
|
if (!this._instance)
|
|
@@ -87,21 +91,44 @@ class RampKitCore {
|
|
|
87
91
|
Logger_1.Logger.warn("Failed to resolve user id:", e2);
|
|
88
92
|
}
|
|
89
93
|
}
|
|
90
|
-
// Load onboarding data
|
|
94
|
+
// Load onboarding data with targeting
|
|
91
95
|
Logger_1.Logger.verbose("Loading onboarding data...");
|
|
92
96
|
try {
|
|
93
97
|
const manifestUrl = `${constants_1.MANIFEST_BASE_URL}/${config.appId}/manifest.json`;
|
|
94
98
|
Logger_1.Logger.verbose("Fetching manifest from", manifestUrl);
|
|
95
99
|
const manifestResponse = await globalThis.fetch(manifestUrl);
|
|
96
100
|
const manifest = await manifestResponse.json();
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
// Build targeting context from device info
|
|
102
|
+
const targetingContext = (0, TargetingContext_1.buildTargetingContext)(this.deviceInfo);
|
|
103
|
+
Logger_1.Logger.verbose("Targeting context built:", JSON.stringify(targetingContext, null, 2));
|
|
104
|
+
// Evaluate targets to find matching onboarding
|
|
105
|
+
if (!manifest.targets || manifest.targets.length === 0) {
|
|
106
|
+
throw new Error("No targets found in manifest");
|
|
99
107
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
108
|
+
const result = (0, TargetingEngine_1.evaluateTargets)(manifest.targets, targetingContext, this.userId || "anonymous");
|
|
109
|
+
if (!result) {
|
|
110
|
+
throw new Error("No matching target found in manifest");
|
|
111
|
+
}
|
|
112
|
+
this.targetingResult = result;
|
|
113
|
+
Logger_1.Logger.verbose("Target matched:", `"${result.targetName}" -> onboarding ${result.onboarding.id} (bucket ${result.bucket})`);
|
|
114
|
+
// Track target_matched event (also sets targeting context for all future events)
|
|
115
|
+
EventManager_1.eventManager.trackTargetMatched(result.targetId, result.targetName, result.onboarding.id, result.bucket);
|
|
116
|
+
// Update deviceInfo with targeting data
|
|
117
|
+
if (this.deviceInfo) {
|
|
118
|
+
this.deviceInfo = {
|
|
119
|
+
...this.deviceInfo,
|
|
120
|
+
matchedTargetId: result.targetId,
|
|
121
|
+
matchedTargetName: result.targetName,
|
|
122
|
+
matchedOnboardingId: result.onboarding.id,
|
|
123
|
+
abTestBucket: result.bucket,
|
|
124
|
+
};
|
|
125
|
+
// Sync updated targeting info to backend
|
|
126
|
+
this.sendUserDataToBackend(this.deviceInfo).catch((e) => {
|
|
127
|
+
Logger_1.Logger.warn("Failed to sync targeting info to backend:", e);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
// Fetch the selected onboarding data
|
|
131
|
+
const onboardingResponse = await globalThis.fetch(result.onboarding.url);
|
|
105
132
|
const json = await onboardingResponse.json();
|
|
106
133
|
this.onboardingData = json;
|
|
107
134
|
Logger_1.Logger.verbose("Onboarding loaded, id:", json === null || json === void 0 ? void 0 : json.onboardingId);
|
|
@@ -109,6 +136,7 @@ class RampKitCore {
|
|
|
109
136
|
catch (error) {
|
|
110
137
|
Logger_1.Logger.verbose("Onboarding load failed:", error);
|
|
111
138
|
this.onboardingData = null;
|
|
139
|
+
this.targetingResult = null;
|
|
112
140
|
}
|
|
113
141
|
// Log SDK configured (always shown - single summary line)
|
|
114
142
|
Logger_1.Logger.info(`Configured - appId: ${config.appId}, userId: ${this.userId || "pending"}`);
|
|
@@ -219,6 +247,12 @@ class RampKitCore {
|
|
|
219
247
|
getDeviceInfo() {
|
|
220
248
|
return this.deviceInfo;
|
|
221
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* Get the targeting result (which target matched and which onboarding was selected)
|
|
252
|
+
*/
|
|
253
|
+
getTargetingResult() {
|
|
254
|
+
return this.targetingResult;
|
|
255
|
+
}
|
|
222
256
|
/**
|
|
223
257
|
* Check if SDK is initialized
|
|
224
258
|
*/
|
|
@@ -387,6 +421,7 @@ class RampKitCore {
|
|
|
387
421
|
this.userId = null;
|
|
388
422
|
this.deviceInfo = null;
|
|
389
423
|
this.onboardingData = null;
|
|
424
|
+
this.targetingResult = null;
|
|
390
425
|
this.initialized = false;
|
|
391
426
|
this.appUserID = null;
|
|
392
427
|
// Clear stored onboarding variables
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RampKit Targeting Context
|
|
3
|
+
* Builds targeting context from DeviceInfo for rule evaluation
|
|
4
|
+
*/
|
|
5
|
+
import { DeviceInfo, TargetingContext } from "./types";
|
|
6
|
+
/**
|
|
7
|
+
* Build targeting context from DeviceInfo
|
|
8
|
+
* Maps SDK device info to the attribute structure used by targeting rules
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildTargetingContext(deviceInfo: DeviceInfo | null): TargetingContext;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* RampKit Targeting Context
|
|
4
|
+
* Builds targeting context from DeviceInfo for rule evaluation
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.buildTargetingContext = buildTargetingContext;
|
|
8
|
+
/**
|
|
9
|
+
* Calculate days since install from install date string
|
|
10
|
+
*/
|
|
11
|
+
function calculateDaysSinceInstall(installDateString) {
|
|
12
|
+
try {
|
|
13
|
+
const installDate = new Date(installDateString);
|
|
14
|
+
const now = new Date();
|
|
15
|
+
const diffMs = now.getTime() - installDate.getTime();
|
|
16
|
+
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
17
|
+
return Math.max(0, diffDays);
|
|
18
|
+
}
|
|
19
|
+
catch (_a) {
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build targeting context from DeviceInfo
|
|
25
|
+
* Maps SDK device info to the attribute structure used by targeting rules
|
|
26
|
+
*/
|
|
27
|
+
function buildTargetingContext(deviceInfo) {
|
|
28
|
+
var _a, _b;
|
|
29
|
+
if (!deviceInfo) {
|
|
30
|
+
// Return default context with null values for optional fields
|
|
31
|
+
return {
|
|
32
|
+
user: {
|
|
33
|
+
isNewUser: true,
|
|
34
|
+
daysSinceInstall: 0,
|
|
35
|
+
subscriptionStatus: null,
|
|
36
|
+
hasAppleSearchAdsAttribution: false,
|
|
37
|
+
},
|
|
38
|
+
device: {
|
|
39
|
+
platform: "iOS",
|
|
40
|
+
model: "unknown",
|
|
41
|
+
osVersion: "0",
|
|
42
|
+
interfaceStyle: "light",
|
|
43
|
+
country: "US",
|
|
44
|
+
language: "en",
|
|
45
|
+
locale: "en_US",
|
|
46
|
+
currencyCode: "USD",
|
|
47
|
+
},
|
|
48
|
+
app: {
|
|
49
|
+
version: "1.0.0",
|
|
50
|
+
buildNumber: "1",
|
|
51
|
+
sdkVersion: "1.0.0",
|
|
52
|
+
},
|
|
53
|
+
asa: {
|
|
54
|
+
keyword: null,
|
|
55
|
+
campaignId: null,
|
|
56
|
+
},
|
|
57
|
+
cpp: {
|
|
58
|
+
pageId: null,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// Extract country from regionCode or locale
|
|
63
|
+
const country = deviceInfo.regionCode ||
|
|
64
|
+
((_a = deviceInfo.deviceLocale) === null || _a === void 0 ? void 0 : _a.split("_")[1]) ||
|
|
65
|
+
"US";
|
|
66
|
+
// Extract language from deviceLanguageCode or locale
|
|
67
|
+
const language = deviceInfo.deviceLanguageCode ||
|
|
68
|
+
((_b = deviceInfo.deviceLocale) === null || _b === void 0 ? void 0 : _b.split("_")[0]) ||
|
|
69
|
+
"en";
|
|
70
|
+
return {
|
|
71
|
+
user: {
|
|
72
|
+
isNewUser: deviceInfo.isFirstLaunch,
|
|
73
|
+
daysSinceInstall: calculateDaysSinceInstall(deviceInfo.installDate),
|
|
74
|
+
subscriptionStatus: null, // Not yet collected - will be null
|
|
75
|
+
hasAppleSearchAdsAttribution: deviceInfo.isAppleSearchAdsAttribution,
|
|
76
|
+
},
|
|
77
|
+
device: {
|
|
78
|
+
platform: deviceInfo.platform,
|
|
79
|
+
model: deviceInfo.deviceModel,
|
|
80
|
+
osVersion: deviceInfo.platformVersion,
|
|
81
|
+
interfaceStyle: deviceInfo.interfaceStyle,
|
|
82
|
+
country,
|
|
83
|
+
language,
|
|
84
|
+
locale: deviceInfo.deviceLocale,
|
|
85
|
+
currencyCode: deviceInfo.deviceCurrencyCode || "USD",
|
|
86
|
+
},
|
|
87
|
+
app: {
|
|
88
|
+
version: deviceInfo.appVersion || "1.0.0",
|
|
89
|
+
buildNumber: deviceInfo.buildNumber || "1",
|
|
90
|
+
sdkVersion: deviceInfo.sdkVersion,
|
|
91
|
+
},
|
|
92
|
+
asa: {
|
|
93
|
+
keyword: null, // Not yet collected
|
|
94
|
+
campaignId: null, // Not yet collected
|
|
95
|
+
},
|
|
96
|
+
cpp: {
|
|
97
|
+
pageId: null, // Not yet collected
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RampKit Targeting Engine
|
|
3
|
+
* Evaluates targeting rules and handles A/B allocation
|
|
4
|
+
*/
|
|
5
|
+
import { ManifestTarget, TargetRules, TargetRule, TargetOnboarding, TargetingContext } from "./types";
|
|
6
|
+
/**
|
|
7
|
+
* Result of target evaluation including metadata for logging/analytics
|
|
8
|
+
*/
|
|
9
|
+
export interface TargetEvaluationResult {
|
|
10
|
+
onboarding: TargetOnboarding;
|
|
11
|
+
targetId: string;
|
|
12
|
+
targetName: string;
|
|
13
|
+
bucket: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Evaluate all targets and return the selected onboarding
|
|
17
|
+
* Targets are evaluated in priority order (0 = highest priority)
|
|
18
|
+
* Falls back to lowest priority target if none match
|
|
19
|
+
*/
|
|
20
|
+
export declare function evaluateTargets(targets: ManifestTarget[], context: TargetingContext, userId: string): TargetEvaluationResult | null;
|
|
21
|
+
/**
|
|
22
|
+
* Evaluate a set of rules against the context
|
|
23
|
+
* Empty rules = matches all users
|
|
24
|
+
*/
|
|
25
|
+
export declare function evaluateRules(rules: TargetRules, context: TargetingContext): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Evaluate a single rule against the context
|
|
28
|
+
*/
|
|
29
|
+
export declare function evaluateRule(rule: TargetRule, context: TargetingContext): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Select an onboarding based on allocation percentages
|
|
32
|
+
* Uses deterministic hashing for consistent A/B assignment
|
|
33
|
+
*/
|
|
34
|
+
export declare function selectOnboardingByAllocation(onboardings: TargetOnboarding[], userId: string): {
|
|
35
|
+
onboarding: TargetOnboarding;
|
|
36
|
+
bucket: number;
|
|
37
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* RampKit Targeting Engine
|
|
4
|
+
* Evaluates targeting rules and handles A/B allocation
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.evaluateTargets = evaluateTargets;
|
|
8
|
+
exports.evaluateRules = evaluateRules;
|
|
9
|
+
exports.evaluateRule = evaluateRule;
|
|
10
|
+
exports.selectOnboardingByAllocation = selectOnboardingByAllocation;
|
|
11
|
+
const Logger_1 = require("./Logger");
|
|
12
|
+
/**
|
|
13
|
+
* Evaluate all targets and return the selected onboarding
|
|
14
|
+
* Targets are evaluated in priority order (0 = highest priority)
|
|
15
|
+
* Falls back to lowest priority target if none match
|
|
16
|
+
*/
|
|
17
|
+
function evaluateTargets(targets, context, userId) {
|
|
18
|
+
if (!targets || targets.length === 0) {
|
|
19
|
+
Logger_1.Logger.verbose("TargetingEngine", "No targets in manifest");
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
// Sort by priority ascending (0 = highest priority, evaluated first)
|
|
23
|
+
const sorted = [...targets].sort((a, b) => a.priority - b.priority);
|
|
24
|
+
Logger_1.Logger.verbose("TargetingEngine", `Evaluating ${sorted.length} targets for user ${userId.substring(0, 8)}...`);
|
|
25
|
+
for (const target of sorted) {
|
|
26
|
+
const matches = evaluateRules(target.rules, context);
|
|
27
|
+
Logger_1.Logger.verbose("TargetingEngine", `Target "${target.name}" (priority ${target.priority}): ${matches ? "MATCHED" : "no match"}`);
|
|
28
|
+
if (matches) {
|
|
29
|
+
const { onboarding, bucket } = selectOnboardingByAllocation(target.onboardings, userId);
|
|
30
|
+
Logger_1.Logger.verbose("TargetingEngine", `Selected onboarding ${onboarding.id} (bucket ${bucket}, allocation ${onboarding.allocation}%)`);
|
|
31
|
+
return {
|
|
32
|
+
onboarding,
|
|
33
|
+
targetId: target.id,
|
|
34
|
+
targetName: target.name,
|
|
35
|
+
bucket,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Fallback: use the lowest priority target (last in sorted array)
|
|
40
|
+
const fallbackTarget = sorted[sorted.length - 1];
|
|
41
|
+
Logger_1.Logger.verbose("TargetingEngine", `No targets matched, using fallback target "${fallbackTarget.name}"`);
|
|
42
|
+
const { onboarding, bucket } = selectOnboardingByAllocation(fallbackTarget.onboardings, userId);
|
|
43
|
+
return {
|
|
44
|
+
onboarding,
|
|
45
|
+
targetId: fallbackTarget.id,
|
|
46
|
+
targetName: fallbackTarget.name,
|
|
47
|
+
bucket,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Evaluate a set of rules against the context
|
|
52
|
+
* Empty rules = matches all users
|
|
53
|
+
*/
|
|
54
|
+
function evaluateRules(rules, context) {
|
|
55
|
+
// Empty rules array = match all users
|
|
56
|
+
if (!rules.rules || rules.rules.length === 0) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
if (rules.match === "all") {
|
|
60
|
+
// AND logic - all rules must match
|
|
61
|
+
return rules.rules.every((rule) => evaluateRule(rule, context));
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// OR logic - at least one rule must match
|
|
65
|
+
return rules.rules.some((rule) => evaluateRule(rule, context));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Evaluate a single rule against the context
|
|
70
|
+
*/
|
|
71
|
+
function evaluateRule(rule, context) {
|
|
72
|
+
// Parse attribute path (e.g., "device.country" -> ["device", "country"])
|
|
73
|
+
const parts = rule.attribute.split(".");
|
|
74
|
+
if (parts.length !== 2) {
|
|
75
|
+
Logger_1.Logger.verbose("TargetingEngine", `Invalid attribute format: ${rule.attribute}`);
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
const [category, attr] = parts;
|
|
79
|
+
// Get the actual value from context
|
|
80
|
+
const categoryObj = context[category];
|
|
81
|
+
if (!categoryObj || typeof categoryObj !== "object") {
|
|
82
|
+
Logger_1.Logger.verbose("TargetingEngine", `Unknown category: ${category}`);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
const actualValue = categoryObj[attr];
|
|
86
|
+
// Handle null/undefined - rule doesn't match
|
|
87
|
+
if (actualValue === null || actualValue === undefined) {
|
|
88
|
+
Logger_1.Logger.verbose("TargetingEngine", `Attribute ${rule.attribute} is null/undefined, rule does not match`);
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
// Apply operator
|
|
92
|
+
const result = applyOperator(rule.operator, actualValue, rule.value);
|
|
93
|
+
Logger_1.Logger.verbose("TargetingEngine", `Rule: ${rule.attribute} ${rule.operator} "${rule.value}" => actual: "${actualValue}" => ${result}`);
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Apply an operator to compare actual value with expected value
|
|
98
|
+
*/
|
|
99
|
+
function applyOperator(operator, actualValue, expectedValue) {
|
|
100
|
+
switch (operator) {
|
|
101
|
+
// Text operators
|
|
102
|
+
case "equals":
|
|
103
|
+
return String(actualValue) === expectedValue;
|
|
104
|
+
case "not_equals":
|
|
105
|
+
return String(actualValue) !== expectedValue;
|
|
106
|
+
case "contains":
|
|
107
|
+
return String(actualValue).toLowerCase().includes(expectedValue.toLowerCase());
|
|
108
|
+
case "starts_with":
|
|
109
|
+
return String(actualValue).toLowerCase().startsWith(expectedValue.toLowerCase());
|
|
110
|
+
// Number operators
|
|
111
|
+
case "greater_than":
|
|
112
|
+
return Number(actualValue) > Number(expectedValue);
|
|
113
|
+
case "less_than":
|
|
114
|
+
return Number(actualValue) < Number(expectedValue);
|
|
115
|
+
// Boolean operators
|
|
116
|
+
case "is_true":
|
|
117
|
+
return actualValue === true;
|
|
118
|
+
case "is_false":
|
|
119
|
+
return actualValue === false;
|
|
120
|
+
default:
|
|
121
|
+
Logger_1.Logger.verbose("TargetingEngine", `Unknown operator: ${operator}`);
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Select an onboarding based on allocation percentages
|
|
127
|
+
* Uses deterministic hashing for consistent A/B assignment
|
|
128
|
+
*/
|
|
129
|
+
function selectOnboardingByAllocation(onboardings, userId) {
|
|
130
|
+
if (onboardings.length === 0) {
|
|
131
|
+
throw new Error("No onboardings in target");
|
|
132
|
+
}
|
|
133
|
+
// Single onboarding - no allocation needed
|
|
134
|
+
if (onboardings.length === 1) {
|
|
135
|
+
return { onboarding: onboardings[0], bucket: 0 };
|
|
136
|
+
}
|
|
137
|
+
// Generate deterministic bucket (0-99) from userId
|
|
138
|
+
const bucket = hashUserIdToBucket(userId);
|
|
139
|
+
// Find which allocation bucket the user falls into
|
|
140
|
+
let cumulative = 0;
|
|
141
|
+
for (const onboarding of onboardings) {
|
|
142
|
+
cumulative += onboarding.allocation;
|
|
143
|
+
if (bucket < cumulative) {
|
|
144
|
+
return { onboarding, bucket };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Fallback to last onboarding (handles rounding errors or allocation < 100)
|
|
148
|
+
return { onboarding: onboardings[onboardings.length - 1], bucket };
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Hash userId to a bucket 0-99 using djb2 algorithm
|
|
152
|
+
* This is deterministic - same userId always gets same bucket
|
|
153
|
+
*/
|
|
154
|
+
function hashUserIdToBucket(userId) {
|
|
155
|
+
let hash = 5381;
|
|
156
|
+
for (let i = 0; i < userId.length; i++) {
|
|
157
|
+
const char = userId.charCodeAt(i);
|
|
158
|
+
hash = ((hash << 5) + hash) + char; // hash * 33 + char
|
|
159
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
160
|
+
}
|
|
161
|
+
return Math.abs(hash % 100);
|
|
162
|
+
}
|
package/build/index.d.ts
CHANGED
|
@@ -11,6 +11,9 @@ export { default as RampKitNative } from "./RampKitNative";
|
|
|
11
11
|
export type { NativeDeviceInfo, NativeLaunchData } from "./RampKitNative";
|
|
12
12
|
export { Haptics, StoreReview, Notifications, TransactionObserver, isNativeModuleAvailable } from "./RampKitNative";
|
|
13
13
|
export type { ImpactStyle, NotificationType, NotificationOptions, NotificationPermissionResult, TransactionObserverResult, SentEventResult, TrackedTransactionDetail, EntitlementCheckResult } from "./RampKitNative";
|
|
14
|
-
export type { DeviceInfo, RampKitEvent, EventDevice, EventContext, RampKitConfig, RampKitEventName, RampKitContext, RampKitDeviceContext, RampKitUserContext, NavigationData, ScreenPosition, AppSessionStartedProperties, OnboardingStartedProperties, OnboardingCompletedProperties, OnboardingAbandonedProperties, OptionSelectedProperties, NotificationsResponseProperties, PaywallShownProperties, PurchaseStartedProperties, PurchaseCompletedProperties, PurchaseFailedProperties, PurchaseRestoredProperties, } from "./types";
|
|
14
|
+
export type { DeviceInfo, RampKitEvent, EventDevice, EventContext, RampKitConfig, RampKitEventName, RampKitContext, RampKitDeviceContext, RampKitUserContext, NavigationData, ScreenPosition, Manifest, ManifestTarget, TargetRules, TargetRule, TargetOnboarding, ManifestOnboarding, TargetingContext, AppSessionStartedProperties, OnboardingStartedProperties, OnboardingCompletedProperties, OnboardingAbandonedProperties, OptionSelectedProperties, NotificationsResponseProperties, PaywallShownProperties, PurchaseStartedProperties, PurchaseCompletedProperties, PurchaseFailedProperties, PurchaseRestoredProperties, } from "./types";
|
|
15
|
+
export { buildTargetingContext } from "./TargetingContext";
|
|
16
|
+
export { evaluateTargets, evaluateRules, evaluateRule } from "./TargetingEngine";
|
|
17
|
+
export type { TargetEvaluationResult } from "./TargetingEngine";
|
|
15
18
|
export { SDK_VERSION, CAPABILITIES } from "./constants";
|
|
16
19
|
export { setVerboseLogging } from "./Logger";
|
package/build/index.js
CHANGED
|
@@ -7,7 +7,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
7
7
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
8
|
};
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.setVerboseLogging = exports.CAPABILITIES = exports.SDK_VERSION = exports.isNativeModuleAvailable = exports.TransactionObserver = exports.Notifications = exports.StoreReview = exports.Haptics = exports.RampKitNative = exports.buildRampKitContext = exports.getSessionStartTime = exports.getSessionDurationSeconds = exports.collectDeviceInfo = exports.eventManager = exports.getRampKitUserId = exports.RampKit = void 0;
|
|
10
|
+
exports.setVerboseLogging = exports.CAPABILITIES = exports.SDK_VERSION = exports.evaluateRule = exports.evaluateRules = exports.evaluateTargets = exports.buildTargetingContext = exports.isNativeModuleAvailable = exports.TransactionObserver = exports.Notifications = exports.StoreReview = exports.Haptics = exports.RampKitNative = exports.buildRampKitContext = exports.getSessionStartTime = exports.getSessionDurationSeconds = exports.collectDeviceInfo = exports.eventManager = exports.getRampKitUserId = exports.RampKit = void 0;
|
|
11
11
|
const RampKit_1 = require("./RampKit");
|
|
12
12
|
// Main SDK singleton instance
|
|
13
13
|
exports.RampKit = RampKit_1.RampKitCore.instance;
|
|
@@ -33,6 +33,13 @@ Object.defineProperty(exports, "StoreReview", { enumerable: true, get: function
|
|
|
33
33
|
Object.defineProperty(exports, "Notifications", { enumerable: true, get: function () { return RampKitNative_2.Notifications; } });
|
|
34
34
|
Object.defineProperty(exports, "TransactionObserver", { enumerable: true, get: function () { return RampKitNative_2.TransactionObserver; } });
|
|
35
35
|
Object.defineProperty(exports, "isNativeModuleAvailable", { enumerable: true, get: function () { return RampKitNative_2.isNativeModuleAvailable; } });
|
|
36
|
+
// Export targeting utilities
|
|
37
|
+
var TargetingContext_1 = require("./TargetingContext");
|
|
38
|
+
Object.defineProperty(exports, "buildTargetingContext", { enumerable: true, get: function () { return TargetingContext_1.buildTargetingContext; } });
|
|
39
|
+
var TargetingEngine_1 = require("./TargetingEngine");
|
|
40
|
+
Object.defineProperty(exports, "evaluateTargets", { enumerable: true, get: function () { return TargetingEngine_1.evaluateTargets; } });
|
|
41
|
+
Object.defineProperty(exports, "evaluateRules", { enumerable: true, get: function () { return TargetingEngine_1.evaluateRules; } });
|
|
42
|
+
Object.defineProperty(exports, "evaluateRule", { enumerable: true, get: function () { return TargetingEngine_1.evaluateRule; } });
|
|
36
43
|
// Export constants
|
|
37
44
|
var constants_1 = require("./constants");
|
|
38
45
|
Object.defineProperty(exports, "SDK_VERSION", { enumerable: true, get: function () { return constants_1.SDK_VERSION; } });
|
package/build/types.d.ts
CHANGED
|
@@ -1,6 +1,95 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* RampKit SDK Types
|
|
3
3
|
*/
|
|
4
|
+
/**
|
|
5
|
+
* Root manifest structure fetched from CDN
|
|
6
|
+
*/
|
|
7
|
+
export interface Manifest {
|
|
8
|
+
appId: string;
|
|
9
|
+
appName: string;
|
|
10
|
+
updatedAt: string;
|
|
11
|
+
targets: ManifestTarget[];
|
|
12
|
+
onboardings: ManifestOnboarding[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A target defines rules for matching users and which onboardings to show
|
|
16
|
+
*/
|
|
17
|
+
export interface ManifestTarget {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
priority: number;
|
|
21
|
+
rules: TargetRules;
|
|
22
|
+
onboardings: TargetOnboarding[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Rule matching configuration
|
|
26
|
+
*/
|
|
27
|
+
export interface TargetRules {
|
|
28
|
+
/** "all" = AND logic (all rules must match), "any" = OR logic (at least one must match) */
|
|
29
|
+
match: "all" | "any";
|
|
30
|
+
rules: TargetRule[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Individual targeting rule
|
|
34
|
+
*/
|
|
35
|
+
export interface TargetRule {
|
|
36
|
+
id: string;
|
|
37
|
+
attribute: string;
|
|
38
|
+
operator: string;
|
|
39
|
+
value: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Onboarding reference within a target (includes A/B allocation)
|
|
43
|
+
*/
|
|
44
|
+
export interface TargetOnboarding {
|
|
45
|
+
id: string;
|
|
46
|
+
allocation: number;
|
|
47
|
+
url: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Top-level onboarding reference in manifest (metadata only)
|
|
51
|
+
*/
|
|
52
|
+
export interface ManifestOnboarding {
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
status: string;
|
|
56
|
+
version: number;
|
|
57
|
+
rules: Record<string, unknown>;
|
|
58
|
+
url: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Context used for evaluating targeting rules
|
|
62
|
+
*/
|
|
63
|
+
export interface TargetingContext {
|
|
64
|
+
user: {
|
|
65
|
+
isNewUser: boolean;
|
|
66
|
+
daysSinceInstall: number;
|
|
67
|
+
subscriptionStatus: string | null;
|
|
68
|
+
hasAppleSearchAdsAttribution: boolean;
|
|
69
|
+
};
|
|
70
|
+
device: {
|
|
71
|
+
platform: string;
|
|
72
|
+
model: string;
|
|
73
|
+
osVersion: string;
|
|
74
|
+
interfaceStyle: string;
|
|
75
|
+
country: string;
|
|
76
|
+
language: string;
|
|
77
|
+
locale: string;
|
|
78
|
+
currencyCode: string;
|
|
79
|
+
};
|
|
80
|
+
app: {
|
|
81
|
+
version: string;
|
|
82
|
+
buildNumber: string;
|
|
83
|
+
sdkVersion: string;
|
|
84
|
+
};
|
|
85
|
+
asa: {
|
|
86
|
+
keyword: string | null;
|
|
87
|
+
campaignId: string | null;
|
|
88
|
+
};
|
|
89
|
+
cpp: {
|
|
90
|
+
pageId: string | null;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
4
93
|
export interface DeviceInfo {
|
|
5
94
|
appUserId: string;
|
|
6
95
|
/**
|
|
@@ -48,6 +137,14 @@ export interface DeviceInfo {
|
|
|
48
137
|
capabilities: string[];
|
|
49
138
|
connectionType: string | null;
|
|
50
139
|
collectedAt: string;
|
|
140
|
+
/** The ID of the matched target */
|
|
141
|
+
matchedTargetId?: string | null;
|
|
142
|
+
/** The name of the matched target */
|
|
143
|
+
matchedTargetName?: string | null;
|
|
144
|
+
/** The ID of the selected onboarding */
|
|
145
|
+
matchedOnboardingId?: string | null;
|
|
146
|
+
/** The A/B test bucket (0-99) for deterministic allocation */
|
|
147
|
+
abTestBucket?: number | null;
|
|
51
148
|
}
|
|
52
149
|
export interface EventDevice {
|
|
53
150
|
platform: string;
|
package/package.json
CHANGED