rampkit-expo-dev 0.0.61 → 0.0.63

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.
@@ -2,7 +2,7 @@
2
2
  * RampKit Event Manager
3
3
  * Handles event tracking for the /app-user-events endpoint
4
4
  */
5
- import { DeviceInfo, EventContext, RampKitEventName } from "./types";
5
+ import { DeviceInfo, EventContext, RampKitEventName, PurchaseCompletedProperties, PurchaseStartedProperties, PurchaseRestoredProperties } from "./types";
6
6
  declare class EventManager {
7
7
  private static _instance;
8
8
  private appId;
@@ -67,50 +67,47 @@ declare class EventManager {
67
67
  * Track app session started
68
68
  */
69
69
  trackAppSessionStarted(isFirstLaunch: boolean, launchCount: number): void;
70
- /**
71
- * Track app backgrounded
72
- */
73
- trackAppBackgrounded(sessionDurationSeconds: number): void;
74
- /**
75
- * Track app foregrounded
76
- */
77
- trackAppForegrounded(): void;
78
- /**
79
- * Track screen view
80
- */
81
- trackScreenView(screenName: string, referrer?: string): void;
82
- /**
83
- * Track CTA tap
84
- */
85
- trackCtaTap(buttonId: string, buttonText?: string): void;
86
70
  /**
87
71
  * Track onboarding started
88
72
  */
89
73
  trackOnboardingStarted(onboardingId: string, totalSteps?: number): void;
90
74
  /**
91
- * Track onboarding screen viewed
75
+ * Track onboarding abandoned
92
76
  */
93
- trackOnboardingScreenViewed(screenName: string, screenIndex: number, totalScreens: number, onboardingId?: string): void;
77
+ trackOnboardingAbandoned(reason: string, lastScreenName?: string, onboardingId?: string): void;
94
78
  /**
95
- * Track onboarding question answered
79
+ * Check if onboarding has already been marked as completed
96
80
  */
97
- trackOnboardingQuestionAnswered(questionId: string, answer: any, questionText?: string, onboardingId?: string): void;
81
+ hasOnboardingBeenCompleted(): Promise<boolean>;
98
82
  /**
99
- * Track onboarding completed
83
+ * Mark onboarding as completed in persistent storage
100
84
  */
101
- trackOnboardingCompleted(completedSteps: number, totalSteps: number, onboardingId?: string): void;
85
+ private markOnboardingAsCompleted;
102
86
  /**
103
- * Track onboarding abandoned
87
+ * Reset onboarding completion status (useful for testing or user reset)
104
88
  */
105
- trackOnboardingAbandoned(reason: string, lastScreenName?: string, onboardingId?: string): void;
89
+ resetOnboardingCompletionStatus(): Promise<void>;
106
90
  /**
107
- * Track notification prompt shown
91
+ * Track onboarding completed event - fires ONCE per user
92
+ * Called when:
93
+ * 1. User completes the onboarding flow (onboarding-finished action)
94
+ * 2. User closes the onboarding (close action)
95
+ * 3. A paywall is shown (show-paywall action)
96
+ *
97
+ * @param trigger - The reason for completion ("finished", "closed", "paywall_shown")
98
+ * @param completedSteps - Number of steps the user completed
99
+ * @param totalSteps - Total number of steps in the onboarding
100
+ * @param onboardingId - The onboarding ID
108
101
  */
109
- trackNotificationsPromptShown(): void;
102
+ trackOnboardingCompletedOnce(trigger: string, completedSteps?: number, totalSteps?: number, onboardingId?: string): Promise<void>;
110
103
  /**
111
104
  * Track notification response
112
105
  */
113
106
  trackNotificationsResponse(status: "granted" | "denied" | "provisional"): void;
107
+ /**
108
+ * Track option selected (interaction event)
109
+ */
110
+ trackOptionSelected(optionId: string, optionValue: any, questionId?: string): void;
114
111
  /**
115
112
  * Track paywall shown
116
113
  */
@@ -119,33 +116,25 @@ declare class EventManager {
119
116
  price?: number;
120
117
  currency?: string;
121
118
  }>): void;
122
- /**
123
- * Track paywall primary action tap
124
- */
125
- trackPaywallPrimaryActionTap(paywallId: string, productId?: string): void;
126
- /**
127
- * Track paywall closed
128
- */
129
- trackPaywallClosed(paywallId: string, reason: "dismissed" | "purchased" | "backgrounded"): void;
130
119
  /**
131
120
  * Track purchase started
121
+ * Call this when user initiates a purchase from a paywall
132
122
  */
133
- trackPurchaseStarted(productId: string, amount?: number, currency?: string): void;
123
+ trackPurchaseStarted(properties: PurchaseStartedProperties): void;
134
124
  /**
135
125
  * Track purchase completed
126
+ * CRITICAL: originalTransactionId is required for attribution
127
+ * Context (paywallId, screenName, flowId) is automatically included
136
128
  */
137
- trackPurchaseCompleted(properties: {
138
- productId: string;
139
- amount: number;
140
- currency: string;
141
- transactionId: string;
142
- originalTransactionId?: string;
143
- purchaseDate?: string;
144
- }): void;
129
+ trackPurchaseCompleted(properties: PurchaseCompletedProperties): void;
145
130
  /**
146
131
  * Track purchase failed
147
132
  */
148
133
  trackPurchaseFailed(productId: string, errorCode: string, errorMessage: string): void;
134
+ /**
135
+ * Track purchase restored
136
+ */
137
+ trackPurchaseRestored(properties: PurchaseRestoredProperties): void;
149
138
  /**
150
139
  * Reset the event manager (e.g., on logout)
151
140
  */
@@ -6,6 +6,7 @@
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.EventManager = exports.eventManager = void 0;
8
8
  const constants_1 = require("./constants");
9
+ const RampKitNative_1 = require("./RampKitNative");
9
10
  /**
10
11
  * Generate a UUID v4 using Math.random
11
12
  * This is sufficient for event IDs - no crypto dependency needed
@@ -202,31 +203,6 @@ class EventManager {
202
203
  trackAppSessionStarted(isFirstLaunch, launchCount) {
203
204
  this.track("app_session_started", { isFirstLaunch, launchCount });
204
205
  }
205
- /**
206
- * Track app backgrounded
207
- */
208
- trackAppBackgrounded(sessionDurationSeconds) {
209
- this.track("app_backgrounded", { sessionDurationSeconds });
210
- }
211
- /**
212
- * Track app foregrounded
213
- */
214
- trackAppForegrounded() {
215
- this.track("app_foregrounded", {});
216
- }
217
- /**
218
- * Track screen view
219
- */
220
- trackScreenView(screenName, referrer) {
221
- this.setCurrentScreen(screenName);
222
- this.track("screen_view", { screenName, referrer });
223
- }
224
- /**
225
- * Track CTA tap
226
- */
227
- trackCtaTap(buttonId, buttonText) {
228
- this.track("cta_tap", { buttonId, buttonText });
229
- }
230
206
  /**
231
207
  * Track onboarding started
232
208
  */
@@ -235,59 +211,88 @@ class EventManager {
235
211
  this.track("onboarding_started", { onboardingId, totalSteps });
236
212
  }
237
213
  /**
238
- * Track onboarding screen viewed
214
+ * Track onboarding abandoned
239
215
  */
240
- trackOnboardingScreenViewed(screenName, screenIndex, totalScreens, onboardingId) {
241
- this.setCurrentScreen(screenName);
242
- this.track("onboarding_screen_viewed", {
216
+ trackOnboardingAbandoned(reason, lastScreenName, onboardingId) {
217
+ const timeSpentSeconds = this.getOnboardingDurationSeconds();
218
+ this.track("onboarding_abandoned", {
243
219
  onboardingId: onboardingId || this.currentOnboardingId,
244
- screenName,
245
- screenIndex,
246
- totalScreens,
220
+ reason,
221
+ lastScreenName,
222
+ timeSpentSeconds,
247
223
  });
224
+ this.endOnboardingTracking();
248
225
  }
226
+ // ============================================================================
227
+ // Onboarding Completion (Once Per User)
228
+ // ============================================================================
249
229
  /**
250
- * Track onboarding question answered
230
+ * Check if onboarding has already been marked as completed
251
231
  */
252
- trackOnboardingQuestionAnswered(questionId, answer, questionText, onboardingId) {
253
- this.track("onboarding_question_answered", {
254
- onboardingId: onboardingId || this.currentOnboardingId,
255
- questionId,
256
- answer,
257
- questionText,
258
- });
232
+ async hasOnboardingBeenCompleted() {
233
+ try {
234
+ const value = await (0, RampKitNative_1.getStoredValue)(constants_1.STORAGE_KEYS.ONBOARDING_COMPLETED);
235
+ return value === "true";
236
+ }
237
+ catch (_a) {
238
+ return false;
239
+ }
259
240
  }
260
241
  /**
261
- * Track onboarding completed
242
+ * Mark onboarding as completed in persistent storage
262
243
  */
263
- trackOnboardingCompleted(completedSteps, totalSteps, onboardingId) {
244
+ async markOnboardingAsCompleted() {
245
+ try {
246
+ await (0, RampKitNative_1.setStoredValue)(constants_1.STORAGE_KEYS.ONBOARDING_COMPLETED, "true");
247
+ console.log("[RampKit] EventManager: onboarding marked as completed (persisted)");
248
+ }
249
+ catch (error) {
250
+ console.warn("[RampKit] EventManager: failed to persist onboarding completion:", error);
251
+ }
252
+ }
253
+ /**
254
+ * Reset onboarding completion status (useful for testing or user reset)
255
+ */
256
+ async resetOnboardingCompletionStatus() {
257
+ try {
258
+ await (0, RampKitNative_1.setStoredValue)(constants_1.STORAGE_KEYS.ONBOARDING_COMPLETED, "");
259
+ console.log("[RampKit] EventManager: onboarding completion status reset");
260
+ }
261
+ catch (error) {
262
+ console.warn("[RampKit] EventManager: failed to reset onboarding completion:", error);
263
+ }
264
+ }
265
+ /**
266
+ * Track onboarding completed event - fires ONCE per user
267
+ * Called when:
268
+ * 1. User completes the onboarding flow (onboarding-finished action)
269
+ * 2. User closes the onboarding (close action)
270
+ * 3. A paywall is shown (show-paywall action)
271
+ *
272
+ * @param trigger - The reason for completion ("finished", "closed", "paywall_shown")
273
+ * @param completedSteps - Number of steps the user completed
274
+ * @param totalSteps - Total number of steps in the onboarding
275
+ * @param onboardingId - The onboarding ID
276
+ */
277
+ async trackOnboardingCompletedOnce(trigger, completedSteps, totalSteps, onboardingId) {
278
+ // Check if already completed - skip if so
279
+ const alreadyCompleted = await this.hasOnboardingBeenCompleted();
280
+ if (alreadyCompleted) {
281
+ console.log(`[RampKit] EventManager: onboarding_completed already sent, skipping (trigger: ${trigger})`);
282
+ return;
283
+ }
284
+ // Mark as completed BEFORE sending to prevent race conditions
285
+ await this.markOnboardingAsCompleted();
264
286
  const timeToCompleteSeconds = this.getOnboardingDurationSeconds();
265
287
  this.track("onboarding_completed", {
266
288
  onboardingId: onboardingId || this.currentOnboardingId,
267
289
  timeToCompleteSeconds,
268
290
  completedSteps,
269
291
  totalSteps,
292
+ trigger,
270
293
  });
271
294
  this.endOnboardingTracking();
272
- }
273
- /**
274
- * Track onboarding abandoned
275
- */
276
- trackOnboardingAbandoned(reason, lastScreenName, onboardingId) {
277
- const timeSpentSeconds = this.getOnboardingDurationSeconds();
278
- this.track("onboarding_abandoned", {
279
- onboardingId: onboardingId || this.currentOnboardingId,
280
- reason,
281
- lastScreenName,
282
- timeSpentSeconds,
283
- });
284
- this.endOnboardingTracking();
285
- }
286
- /**
287
- * Track notification prompt shown
288
- */
289
- trackNotificationsPromptShown() {
290
- this.track("notifications_prompt_shown", {});
295
+ console.log(`[RampKit] EventManager: 📊 onboarding_completed sent (trigger: ${trigger})`);
291
296
  }
292
297
  /**
293
298
  * Track notification response
@@ -295,6 +300,12 @@ class EventManager {
295
300
  trackNotificationsResponse(status) {
296
301
  this.track("notifications_response", { status });
297
302
  }
303
+ /**
304
+ * Track option selected (interaction event)
305
+ */
306
+ trackOptionSelected(optionId, optionValue, questionId) {
307
+ this.track("option_selected", { optionId, optionValue, questionId });
308
+ }
298
309
  /**
299
310
  * Track paywall shown
300
311
  */
@@ -302,37 +313,40 @@ class EventManager {
302
313
  this.setCurrentPaywall(paywallId, placement);
303
314
  this.track("paywall_shown", { paywallId, placement, products }, { paywallId, placement });
304
315
  }
305
- /**
306
- * Track paywall primary action tap
307
- */
308
- trackPaywallPrimaryActionTap(paywallId, productId) {
309
- this.track("paywall_primary_action_tap", { paywallId, productId }, { paywallId });
310
- }
311
- /**
312
- * Track paywall closed
313
- */
314
- trackPaywallClosed(paywallId, reason) {
315
- this.track("paywall_closed", { paywallId, reason }, { paywallId });
316
- this.setCurrentPaywall(null);
317
- }
318
316
  /**
319
317
  * Track purchase started
318
+ * Call this when user initiates a purchase from a paywall
320
319
  */
321
- trackPurchaseStarted(productId, amount, currency) {
322
- this.track("purchase_started", { productId, amount, currency });
320
+ trackPurchaseStarted(properties) {
321
+ // Context (paywallId, placement) is automatically included from current state
322
+ // which was set when trackPaywallShown was called
323
+ console.log(`[RampKit] EventManager: 🛒 purchase_started`, `\n productId: ${properties.productId}`, properties.amount ? `\n amount: ${properties.amount} ${properties.currency || ""}` : "");
324
+ this.track("purchase_started", properties);
323
325
  }
324
326
  /**
325
327
  * Track purchase completed
328
+ * CRITICAL: originalTransactionId is required for attribution
329
+ * Context (paywallId, screenName, flowId) is automatically included
326
330
  */
327
331
  trackPurchaseCompleted(properties) {
332
+ // Context is automatically included from current state (paywallId, placement, etc.)
333
+ console.log(`[RampKit] EventManager: ✅ purchase_completed`, `\n productId: ${properties.productId}`, `\n transactionId: ${properties.transactionId}`, `\n originalTransactionId: ${properties.originalTransactionId}`, properties.isTrial ? `\n isTrial: true` : "", properties.environment ? `\n environment: ${properties.environment}` : "");
328
334
  this.track("purchase_completed", properties);
329
335
  }
330
336
  /**
331
337
  * Track purchase failed
332
338
  */
333
339
  trackPurchaseFailed(productId, errorCode, errorMessage) {
340
+ console.log(`[RampKit] EventManager: ❌ purchase_failed`, `\n productId: ${productId}`, `\n errorCode: ${errorCode}`, `\n errorMessage: ${errorMessage}`);
334
341
  this.track("purchase_failed", { productId, errorCode, errorMessage });
335
342
  }
343
+ /**
344
+ * Track purchase restored
345
+ */
346
+ trackPurchaseRestored(properties) {
347
+ console.log(`[RampKit] EventManager: 🔄 purchase_restored`, `\n productId: ${properties.productId}`, properties.transactionId ? `\n transactionId: ${properties.transactionId}` : "", properties.originalTransactionId ? `\n originalTransactionId: ${properties.originalTransactionId}` : "");
348
+ this.track("purchase_restored", properties);
349
+ }
336
350
  /**
337
351
  * Reset the event manager (e.g., on logout)
338
352
  */
@@ -350,6 +364,8 @@ class EventManager {
350
364
  this.onboardingStartTime = null;
351
365
  this.currentOnboardingId = null;
352
366
  this.initialized = false;
367
+ // Reset onboarding completion status so it can fire again for new user
368
+ this.resetOnboardingCompletionStatus();
353
369
  }
354
370
  }
355
371
  exports.EventManager = EventManager;
@@ -12,8 +12,6 @@ export declare class RampKitCore {
12
12
  private deviceInfo;
13
13
  private onOnboardingFinished?;
14
14
  private onShowPaywall?;
15
- private appStateSubscription;
16
- private lastAppState;
17
15
  private initialized;
18
16
  /** Custom App User ID provided by the developer (alias for their user system) */
19
17
  private appUserID;
@@ -47,10 +45,6 @@ export declare class RampKitCore {
47
45
  * Send user/device data to the /app-users endpoint
48
46
  */
49
47
  private sendUserDataToBackend;
50
- /**
51
- * Setup app state listener for background/foreground tracking
52
- */
53
- private setupAppStateListener;
54
48
  /**
55
49
  * Get the onboarding data
56
50
  */
@@ -87,14 +81,6 @@ export declare class RampKitCore {
87
81
  * Track a custom event
88
82
  */
89
83
  trackEvent(eventName: string, properties?: Record<string, any>, context?: Partial<EventContext>): void;
90
- /**
91
- * Track a screen view
92
- */
93
- trackScreenView(screenName: string, referrer?: string): void;
94
- /**
95
- * Track a CTA tap
96
- */
97
- trackCtaTap(buttonId: string, buttonText?: string): void;
98
84
  /**
99
85
  * Reset the SDK state and re-initialize
100
86
  * Call this when a user logs out or when you need to clear all cached state
package/build/RampKit.js CHANGED
@@ -5,7 +5,6 @@
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.RampKitCore = void 0;
8
- const react_native_1 = require("react-native");
9
8
  const RampkitOverlay_1 = require("./RampkitOverlay");
10
9
  const userId_1 = require("./userId");
11
10
  const DeviceInfoCollector_1 = require("./DeviceInfoCollector");
@@ -20,8 +19,6 @@ class RampKitCore {
20
19
  this.userId = null;
21
20
  this.appId = null;
22
21
  this.deviceInfo = null;
23
- this.appStateSubscription = null;
24
- this.lastAppState = "active";
25
22
  this.initialized = false;
26
23
  /** Custom App User ID provided by the developer (alias for their user system) */
27
24
  this.appUserID = null;
@@ -64,9 +61,7 @@ class RampKitCore {
64
61
  EventManager_1.eventManager.initialize(config.appId, this.deviceInfo);
65
62
  // Step 4: Track app session started
66
63
  EventManager_1.eventManager.trackAppSessionStarted(this.deviceInfo.isFirstLaunch, this.deviceInfo.launchCount);
67
- // Step 5: Setup app state listener for background/foreground tracking
68
- this.setupAppStateListener();
69
- // Step 6: Start transaction observer for automatic purchase tracking
64
+ // Step 5: Start transaction observer for automatic purchase tracking
70
65
  console.log("[RampKit] Configure: Starting transaction observer...");
71
66
  await RampKitNative_1.TransactionObserver.start(config.appId);
72
67
  this.initialized = true;
@@ -195,26 +190,6 @@ class RampKitCore {
195
190
  console.warn(`[RampKit] Configure: Network error sending user data`, `\n Error: ${error instanceof Error ? error.message : String(error)}`);
196
191
  }
197
192
  }
198
- /**
199
- * Setup app state listener for background/foreground tracking
200
- */
201
- setupAppStateListener() {
202
- this.appStateSubscription = react_native_1.AppState.addEventListener("change", (nextAppState) => {
203
- if (this.lastAppState === "active" &&
204
- (nextAppState === "background" || nextAppState === "inactive")) {
205
- // App went to background
206
- const sessionDuration = (0, DeviceInfoCollector_1.getSessionDurationSeconds)();
207
- EventManager_1.eventManager.trackAppBackgrounded(sessionDuration);
208
- }
209
- else if ((this.lastAppState === "background" ||
210
- this.lastAppState === "inactive") &&
211
- nextAppState === "active") {
212
- // App came to foreground
213
- EventManager_1.eventManager.trackAppForegrounded();
214
- }
215
- this.lastAppState = nextAppState;
216
- });
217
- }
218
193
  /**
219
194
  * Get the onboarding data
220
195
  */
@@ -314,28 +289,35 @@ class RampKitCore {
314
289
  navigation,
315
290
  onOnboardingFinished: (payload) => {
316
291
  var _a;
317
- // Track onboarding completed
318
- EventManager_1.eventManager.trackOnboardingCompleted(screens.length, screens.length, onboardingId);
292
+ // Track onboarding completed (once per user) - trigger: finished
293
+ EventManager_1.eventManager.trackOnboardingCompletedOnce("finished", screens.length, screens.length, onboardingId);
319
294
  try {
320
295
  (_a = this.onOnboardingFinished) === null || _a === void 0 ? void 0 : _a.call(this, payload);
321
296
  }
322
297
  catch (_) { }
323
298
  },
324
- onShowPaywall: (opts === null || opts === void 0 ? void 0 : opts.onShowPaywall) || (opts === null || opts === void 0 ? void 0 : opts.showPaywall) || this.onShowPaywall,
325
- onScreenChange: (screenIndex, screenId) => {
326
- // Track screen view within onboarding
327
- EventManager_1.eventManager.trackOnboardingScreenViewed(screenId, screenIndex, screens.length, onboardingId);
299
+ onShowPaywall: (payload) => {
300
+ // Track onboarding completed (once per user) - trigger: paywall_shown
301
+ EventManager_1.eventManager.trackOnboardingCompletedOnce("paywall_shown", screens.length, // We don't know exact step, use total
302
+ screens.length, onboardingId);
303
+ // Call the original callback
304
+ const paywallCallback = (opts === null || opts === void 0 ? void 0 : opts.onShowPaywall) || (opts === null || opts === void 0 ? void 0 : opts.showPaywall) || this.onShowPaywall;
305
+ try {
306
+ paywallCallback === null || paywallCallback === void 0 ? void 0 : paywallCallback(payload);
307
+ }
308
+ catch (_) { }
328
309
  },
329
310
  onOnboardingAbandoned: (reason, lastScreenIndex, lastScreenId) => {
330
311
  // Track onboarding abandoned
331
312
  EventManager_1.eventManager.trackOnboardingAbandoned(reason, lastScreenId, onboardingId);
332
313
  },
333
- onNotificationPermissionRequested: () => {
334
- EventManager_1.eventManager.trackNotificationsPromptShown();
335
- },
336
314
  onNotificationPermissionResult: (granted) => {
337
315
  EventManager_1.eventManager.trackNotificationsResponse(granted ? "granted" : "denied");
338
316
  },
317
+ onCloseAction: (screenIndex, _screenId) => {
318
+ // Track onboarding completed (once per user) - trigger: closed
319
+ EventManager_1.eventManager.trackOnboardingCompletedOnce("closed", screenIndex + 1, screens.length, onboardingId);
320
+ },
339
321
  });
340
322
  }
341
323
  catch (e) {
@@ -357,18 +339,6 @@ class RampKitCore {
357
339
  trackEvent(eventName, properties = {}, context) {
358
340
  EventManager_1.eventManager.track(eventName, properties, context);
359
341
  }
360
- /**
361
- * Track a screen view
362
- */
363
- trackScreenView(screenName, referrer) {
364
- EventManager_1.eventManager.trackScreenView(screenName, referrer);
365
- }
366
- /**
367
- * Track a CTA tap
368
- */
369
- trackCtaTap(buttonId, buttonText) {
370
- EventManager_1.eventManager.trackCtaTap(buttonId, buttonText);
371
- }
372
342
  // Note: Purchase and paywall events are automatically tracked by the native
373
343
  // StoreKit 2 (iOS) and Google Play Billing (Android) transaction observers.
374
344
  // No manual tracking is needed.
@@ -384,11 +354,6 @@ class RampKitCore {
384
354
  console.log("[RampKit] Reset: Clearing SDK state...");
385
355
  // Stop transaction observer
386
356
  await RampKitNative_1.TransactionObserver.stop();
387
- // Remove app state listener
388
- if (this.appStateSubscription) {
389
- this.appStateSubscription.remove();
390
- this.appStateSubscription = null;
391
- }
392
357
  // Reset event manager state
393
358
  EventManager_1.eventManager.reset();
394
359
  // Reset session
@@ -24,6 +24,7 @@ export declare function showRampkitOverlay(opts: {
24
24
  onOnboardingAbandoned?: (reason: string, lastScreenIndex: number, lastScreenId: string) => void;
25
25
  onNotificationPermissionRequested?: () => void;
26
26
  onNotificationPermissionResult?: (granted: boolean) => void;
27
+ onCloseAction?: (screenIndex: number, screenId: string) => void;
27
28
  }): void;
28
29
  export declare function hideRampkitOverlay(): void;
29
30
  export declare function closeRampkitOverlay(): void;
@@ -50,5 +51,6 @@ declare function Overlay(props: {
50
51
  onOnboardingAbandoned?: (reason: string, lastScreenIndex: number, lastScreenId: string) => void;
51
52
  onNotificationPermissionRequested?: () => void;
52
53
  onNotificationPermissionResult?: (granted: boolean) => void;
54
+ onCloseAction?: (screenIndex: number, screenId: string) => void;
53
55
  }): any;
54
56
  export default Overlay;
@@ -580,7 +580,7 @@ function showRampkitOverlay(opts) {
580
580
  (_a = opts.onClose) === null || _a === void 0 ? void 0 : _a.call(opts);
581
581
  }, onOnboardingFinished: opts.onOnboardingFinished, onShowPaywall: opts.onShowPaywall, onRegisterClose: (handler) => {
582
582
  activeCloseHandler = handler;
583
- }, onScreenChange: opts.onScreenChange, onOnboardingAbandoned: opts.onOnboardingAbandoned, onNotificationPermissionRequested: opts.onNotificationPermissionRequested, onNotificationPermissionResult: opts.onNotificationPermissionResult })));
583
+ }, onScreenChange: opts.onScreenChange, onOnboardingAbandoned: opts.onOnboardingAbandoned, onNotificationPermissionRequested: opts.onNotificationPermissionRequested, onNotificationPermissionResult: opts.onNotificationPermissionResult, onCloseAction: opts.onCloseAction })));
584
584
  // Once shown, we can safely discard the preloader sibling if present
585
585
  if (preloadSibling) {
586
586
  preloadSibling.destroy();
@@ -1653,7 +1653,7 @@ function Overlay(props) {
1653
1653
  sendOnboardingStateToWebView(i);
1654
1654
  }
1655
1655
  }, onMessage: (ev) => {
1656
- var _a, _b, _c, _d, _e, _f;
1656
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
1657
1657
  const raw = ev.nativeEvent.data;
1658
1658
  console.log("raw", raw);
1659
1659
  // Accept either raw strings or JSON payloads from your editor
@@ -1819,7 +1819,12 @@ function Overlay(props) {
1819
1819
  return;
1820
1820
  }
1821
1821
  if ((data === null || data === void 0 ? void 0 : data.type) === "rampkit:close") {
1822
- handleRequestClose();
1822
+ // Track close action for onboarding completion
1823
+ try {
1824
+ (_e = props.onCloseAction) === null || _e === void 0 ? void 0 : _e.call(props, i, ((_f = props.screens[i]) === null || _f === void 0 ? void 0 : _f.id) || "");
1825
+ }
1826
+ catch (_) { }
1827
+ handleRequestClose({ completed: true }); // Mark as completed so abandonment isn't tracked
1823
1828
  return;
1824
1829
  }
1825
1830
  if ((data === null || data === void 0 ? void 0 : data.type) === "rampkit:haptic") {
@@ -1827,7 +1832,7 @@ function Overlay(props) {
1827
1832
  return;
1828
1833
  }
1829
1834
  }
1830
- catch (_g) {
1835
+ catch (_l) {
1831
1836
  // String path
1832
1837
  if (raw === "rampkit:tap" ||
1833
1838
  raw === "next" ||
@@ -1854,7 +1859,7 @@ function Overlay(props) {
1854
1859
  if (raw === "rampkit:onboarding-finished") {
1855
1860
  setOnboardingCompleted(true);
1856
1861
  try {
1857
- (_e = props.onOnboardingFinished) === null || _e === void 0 ? void 0 : _e.call(props, undefined);
1862
+ (_g = props.onOnboardingFinished) === null || _g === void 0 ? void 0 : _g.call(props, undefined);
1858
1863
  }
1859
1864
  catch (_) { }
1860
1865
  handleRequestClose({ completed: true });
@@ -1862,7 +1867,7 @@ function Overlay(props) {
1862
1867
  }
1863
1868
  if (raw === "rampkit:show-paywall") {
1864
1869
  try {
1865
- (_f = props.onShowPaywall) === null || _f === void 0 ? void 0 : _f.call(props);
1870
+ (_h = props.onShowPaywall) === null || _h === void 0 ? void 0 : _h.call(props);
1866
1871
  }
1867
1872
  catch (_) { }
1868
1873
  return;
@@ -1891,7 +1896,12 @@ function Overlay(props) {
1891
1896
  return;
1892
1897
  }
1893
1898
  if (raw === "rampkit:close") {
1894
- handleRequestClose();
1899
+ // Track close action for onboarding completion
1900
+ try {
1901
+ (_j = props.onCloseAction) === null || _j === void 0 ? void 0 : _j.call(props, i, ((_k = props.screens[i]) === null || _k === void 0 ? void 0 : _k.id) || "");
1902
+ }
1903
+ catch (_) { }
1904
+ handleRequestClose({ completed: true }); // Mark as completed so abandonment isn't tracked
1895
1905
  return;
1896
1906
  }
1897
1907
  if (raw.startsWith("haptic:")) {
@@ -14,6 +14,7 @@ export declare const STORAGE_KEYS: {
14
14
  readonly LAUNCH_COUNT: "rk_launch_count";
15
15
  readonly LAST_LAUNCH: "rk_last_launch";
16
16
  readonly ONBOARDING_RESPONSES: "rk_onboarding_responses";
17
+ readonly ONBOARDING_COMPLETED: "rk_onboarding_completed";
17
18
  };
18
19
  export declare const CAPABILITIES: readonly ["onboarding", "paywall_event_receiver", "haptic_feedback", "push_notifications"];
19
20
  export declare const SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InV1c3RsenV2am1vY2h4a3hhdGZ4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjIxMDM2NTUsImV4cCI6MjA3NzY3OTY1NX0.d5XsIMGnia4n9Pou0IidipyyEfSlwpXFoeDBufMOEwE";
@@ -20,6 +20,8 @@ exports.STORAGE_KEYS = {
20
20
  LAST_LAUNCH: "rk_last_launch",
21
21
  // Onboarding responses (persisted)
22
22
  ONBOARDING_RESPONSES: "rk_onboarding_responses",
23
+ // Onboarding completion status (persisted) - fires once per user
24
+ ONBOARDING_COMPLETED: "rk_onboarding_completed",
23
25
  };
24
26
  exports.CAPABILITIES = [
25
27
  "onboarding",
package/build/index.d.ts CHANGED
@@ -11,5 +11,5 @@ export { default as RampKitNative } from "./RampKitNative";
11
11
  export type { NativeDeviceInfo, NativeLaunchData } from "./RampKitNative";
12
12
  export { Haptics, StoreReview, Notifications, TransactionObserver } from "./RampKitNative";
13
13
  export type { ImpactStyle, NotificationType, NotificationOptions, NotificationPermissionResult } from "./RampKitNative";
14
- export type { DeviceInfo, RampKitEvent, EventDevice, EventContext, RampKitConfig, RampKitEventName, RampKitContext, RampKitDeviceContext, RampKitUserContext, NavigationData, ScreenPosition, OnboardingResponse, AppSessionStartedProperties, AppSessionEndedProperties, AppBackgroundedProperties, AppForegroundedProperties, OnboardingStartedProperties, OnboardingScreenViewedProperties, OnboardingQuestionAnsweredProperties, OnboardingCompletedProperties, OnboardingAbandonedProperties, ScreenViewProperties, CtaTapProperties, NotificationsPromptShownProperties, NotificationsResponseProperties, PaywallShownProperties, PaywallPrimaryActionTapProperties, PaywallClosedProperties, PurchaseStartedProperties, PurchaseCompletedProperties, PurchaseFailedProperties, } from "./types";
14
+ export type { DeviceInfo, RampKitEvent, EventDevice, EventContext, RampKitConfig, RampKitEventName, RampKitContext, RampKitDeviceContext, RampKitUserContext, NavigationData, ScreenPosition, OnboardingResponse, AppSessionStartedProperties, OnboardingStartedProperties, OnboardingCompletedProperties, OnboardingAbandonedProperties, OptionSelectedProperties, NotificationsResponseProperties, PaywallShownProperties, PurchaseStartedProperties, PurchaseCompletedProperties, PurchaseFailedProperties, PurchaseRestoredProperties, } from "./types";
15
15
  export { SDK_VERSION, CAPABILITIES } from "./constants";
package/build/types.d.ts CHANGED
@@ -81,36 +81,16 @@ export interface AppSessionStartedProperties {
81
81
  isFirstLaunch: boolean;
82
82
  launchCount: number;
83
83
  }
84
- export interface AppSessionEndedProperties {
85
- reason: string;
86
- sessionDurationSeconds: number;
87
- }
88
- export interface AppBackgroundedProperties {
89
- sessionDurationSeconds: number;
90
- }
91
- export interface AppForegroundedProperties {
92
- }
93
84
  export interface OnboardingStartedProperties {
94
85
  onboardingId?: string;
95
86
  totalSteps?: number;
96
87
  }
97
- export interface OnboardingScreenViewedProperties {
98
- onboardingId?: string;
99
- screenName: string;
100
- screenIndex: number;
101
- totalScreens: number;
102
- }
103
- export interface OnboardingQuestionAnsweredProperties {
104
- onboardingId?: string;
105
- questionId: string;
106
- answer: any;
107
- questionText?: string;
108
- }
109
88
  export interface OnboardingCompletedProperties {
110
89
  onboardingId?: string;
111
90
  timeToCompleteSeconds: number;
112
91
  completedSteps: number;
113
92
  totalSteps: number;
93
+ trigger: string;
114
94
  }
115
95
  export interface OnboardingAbandonedProperties {
116
96
  onboardingId?: string;
@@ -118,15 +98,10 @@ export interface OnboardingAbandonedProperties {
118
98
  lastScreenName?: string;
119
99
  timeSpentSeconds: number;
120
100
  }
121
- export interface ScreenViewProperties {
122
- screenName: string;
123
- referrer?: string;
124
- }
125
- export interface CtaTapProperties {
126
- buttonId: string;
127
- buttonText?: string;
128
- }
129
- export interface NotificationsPromptShownProperties {
101
+ export interface OptionSelectedProperties {
102
+ optionId: string;
103
+ optionValue: any;
104
+ questionId?: string;
130
105
  }
131
106
  export interface NotificationsResponseProperties {
132
107
  status: "granted" | "denied" | "provisional";
@@ -140,39 +115,69 @@ export interface PaywallShownProperties {
140
115
  currency?: string;
141
116
  }>;
142
117
  }
143
- export interface PaywallPrimaryActionTapProperties {
144
- paywallId: string;
145
- productId?: string;
146
- }
147
- export interface PaywallClosedProperties {
148
- paywallId: string;
149
- reason: "dismissed" | "purchased" | "backgrounded";
150
- }
151
118
  export interface PurchaseStartedProperties {
152
119
  productId: string;
153
120
  amount?: number;
154
121
  currency?: string;
122
+ priceFormatted?: string;
155
123
  }
124
+ /**
125
+ * Properties for purchase_completed event
126
+ * Critical for attribution: originalTransactionId links renewals to original purchase
127
+ */
156
128
  export interface PurchaseCompletedProperties {
129
+ /** Product identifier (e.g., "com.app.yearly") */
157
130
  productId: string;
158
- amount: number;
159
- currency: string;
131
+ /** Price as number (e.g., 39.99) */
132
+ amount?: number;
133
+ /** Currency code (e.g., "USD") */
134
+ currency?: string;
135
+ /** Formatted price string (e.g., "$39.99") */
136
+ priceFormatted?: string;
137
+ /** Unique transaction ID from App Store/Play Store */
160
138
  transactionId: string;
161
- originalTransactionId?: string;
162
- purchaseDate?: string;
139
+ /** CRITICAL: Original transaction ID - links renewals to original purchase */
140
+ originalTransactionId: string;
141
+ /** ISO 8601 purchase timestamp */
142
+ purchaseDate: string;
143
+ /** ISO 8601 expiration date for subscriptions */
144
+ expirationDate?: string;
145
+ /** Whether this is a free trial */
146
+ isTrial?: boolean;
147
+ /** Whether this is an introductory offer */
148
+ isIntroOffer?: boolean;
149
+ /** Offer type: "introductory", "promotional", "code", or null */
150
+ offerType?: string | null;
151
+ /** Offer ID if applicable */
152
+ offerId?: string;
153
+ /** ISO 8601 duration (e.g., "P1M" for monthly, "P1Y" for yearly) */
154
+ subscriptionPeriod?: string;
155
+ /** Subscription group ID */
156
+ subscriptionGroupId?: string;
157
+ /** App Store storefront country code */
158
+ storefront?: string;
159
+ /** Environment: "Production" or "Sandbox" */
160
+ environment?: string;
161
+ /** Quantity purchased */
162
+ quantity?: number;
163
163
  }
164
164
  export interface PurchaseFailedProperties {
165
165
  productId: string;
166
166
  errorCode: string;
167
167
  errorMessage: string;
168
168
  }
169
- export type AppLifecycleEventName = "app_session_started" | "app_session_ended" | "app_backgrounded" | "app_foregrounded";
170
- export type OnboardingEventName = "onboarding_started" | "onboarding_screen_viewed" | "onboarding_question_answered" | "onboarding_completed" | "onboarding_abandoned";
171
- export type NavigationEventName = "screen_view" | "cta_tap";
172
- export type PermissionEventName = "notifications_prompt_shown" | "notifications_response";
173
- export type PaywallEventName = "paywall_shown" | "paywall_primary_action_tap" | "paywall_closed";
174
- export type PurchaseEventName = "purchase_started" | "purchase_completed" | "purchase_failed";
175
- export type RampKitEventName = AppLifecycleEventName | OnboardingEventName | NavigationEventName | PermissionEventName | PaywallEventName | PurchaseEventName;
169
+ export interface PurchaseRestoredProperties {
170
+ productId: string;
171
+ transactionId?: string;
172
+ originalTransactionId?: string;
173
+ }
174
+ export type AppLifecycleEventName = "app_session_started";
175
+ export type OnboardingEventName = "onboarding_started" | "onboarding_completed" | "onboarding_abandoned";
176
+ export type InteractionEventName = "option_selected";
177
+ export type PermissionEventName = "notifications_response" | "tracking_response";
178
+ export type PaywallEventName = "paywall_shown";
179
+ export type PurchaseEventName = "purchase_started" | "purchase_completed" | "purchase_failed" | "purchase_restored";
180
+ export type RampKitEventName = AppLifecycleEventName | OnboardingEventName | InteractionEventName | PermissionEventName | PaywallEventName | PurchaseEventName;
176
181
  export interface RampKitConfig {
177
182
  appId: string;
178
183
  apiKey?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rampkit-expo-dev",
3
- "version": "0.0.61",
3
+ "version": "0.0.63",
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",