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.
@@ -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
  */
@@ -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;
@@ -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
- if (!manifest.onboardings || manifest.onboardings.length === 0) {
98
- throw new Error("No onboardings found in manifest");
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
- // Use the first onboarding
101
- const firstOnboarding = manifest.onboardings[0];
102
- Logger_1.Logger.verbose("Using onboarding:", firstOnboarding.name);
103
- // Fetch the actual onboarding data
104
- const onboardingResponse = await globalThis.fetch(firstOnboarding.url);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rampkit-expo-dev",
3
- "version": "0.0.91",
3
+ "version": "0.0.92",
4
4
  "description": "The Expo SDK for RampKit. Build, test, and personalize app onboardings with instant updates.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",