@mobana/react-native-sdk 0.2.10

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.
Files changed (96) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +249 -0
  3. package/android/build.gradle +50 -0
  4. package/android/src/main/AndroidManifest.xml +6 -0
  5. package/android/src/main/java/ai/mobana/sdk/MobanaModule.kt +67 -0
  6. package/android/src/main/java/ai/mobana/sdk/MobanaPackage.kt +19 -0
  7. package/app.plugin.js +274 -0
  8. package/ios/Mobana.h +11 -0
  9. package/ios/Mobana.m +20 -0
  10. package/lib/commonjs/Mobana.js +676 -0
  11. package/lib/commonjs/Mobana.js.map +1 -0
  12. package/lib/commonjs/NativeMobana.js +53 -0
  13. package/lib/commonjs/NativeMobana.js.map +1 -0
  14. package/lib/commonjs/api.js +201 -0
  15. package/lib/commonjs/api.js.map +1 -0
  16. package/lib/commonjs/bridge/index.js +19 -0
  17. package/lib/commonjs/bridge/index.js.map +1 -0
  18. package/lib/commonjs/bridge/injectBridge.js +528 -0
  19. package/lib/commonjs/bridge/injectBridge.js.map +1 -0
  20. package/lib/commonjs/components/FlowWebView.js +676 -0
  21. package/lib/commonjs/components/FlowWebView.js.map +1 -0
  22. package/lib/commonjs/components/MobanaProvider.js +275 -0
  23. package/lib/commonjs/components/MobanaProvider.js.map +1 -0
  24. package/lib/commonjs/components/index.js +20 -0
  25. package/lib/commonjs/components/index.js.map +1 -0
  26. package/lib/commonjs/device.js +49 -0
  27. package/lib/commonjs/device.js.map +1 -0
  28. package/lib/commonjs/index.js +20 -0
  29. package/lib/commonjs/index.js.map +1 -0
  30. package/lib/commonjs/package.json +1 -0
  31. package/lib/commonjs/storage.js +277 -0
  32. package/lib/commonjs/storage.js.map +1 -0
  33. package/lib/commonjs/types.js +2 -0
  34. package/lib/commonjs/types.js.map +1 -0
  35. package/lib/module/Mobana.js +673 -0
  36. package/lib/module/Mobana.js.map +1 -0
  37. package/lib/module/NativeMobana.js +49 -0
  38. package/lib/module/NativeMobana.js.map +1 -0
  39. package/lib/module/api.js +194 -0
  40. package/lib/module/api.js.map +1 -0
  41. package/lib/module/bridge/index.js +4 -0
  42. package/lib/module/bridge/index.js.map +1 -0
  43. package/lib/module/bridge/injectBridge.js +523 -0
  44. package/lib/module/bridge/injectBridge.js.map +1 -0
  45. package/lib/module/components/FlowWebView.js +672 -0
  46. package/lib/module/components/FlowWebView.js.map +1 -0
  47. package/lib/module/components/MobanaProvider.js +270 -0
  48. package/lib/module/components/MobanaProvider.js.map +1 -0
  49. package/lib/module/components/index.js +5 -0
  50. package/lib/module/components/index.js.map +1 -0
  51. package/lib/module/device.js +45 -0
  52. package/lib/module/device.js.map +1 -0
  53. package/lib/module/index.js +53 -0
  54. package/lib/module/index.js.map +1 -0
  55. package/lib/module/storage.js +257 -0
  56. package/lib/module/storage.js.map +1 -0
  57. package/lib/module/types.js +2 -0
  58. package/lib/module/types.js.map +1 -0
  59. package/lib/typescript/Mobana.d.ts +209 -0
  60. package/lib/typescript/Mobana.d.ts.map +1 -0
  61. package/lib/typescript/NativeMobana.d.ts +11 -0
  62. package/lib/typescript/NativeMobana.d.ts.map +1 -0
  63. package/lib/typescript/api.d.ts +34 -0
  64. package/lib/typescript/api.d.ts.map +1 -0
  65. package/lib/typescript/bridge/index.d.ts +3 -0
  66. package/lib/typescript/bridge/index.d.ts.map +1 -0
  67. package/lib/typescript/bridge/injectBridge.d.ts +23 -0
  68. package/lib/typescript/bridge/injectBridge.d.ts.map +1 -0
  69. package/lib/typescript/components/FlowWebView.d.ts +38 -0
  70. package/lib/typescript/components/FlowWebView.d.ts.map +1 -0
  71. package/lib/typescript/components/MobanaProvider.d.ts +65 -0
  72. package/lib/typescript/components/MobanaProvider.d.ts.map +1 -0
  73. package/lib/typescript/components/index.d.ts +5 -0
  74. package/lib/typescript/components/index.d.ts.map +1 -0
  75. package/lib/typescript/device.d.ts +6 -0
  76. package/lib/typescript/device.d.ts.map +1 -0
  77. package/lib/typescript/index.d.ts +46 -0
  78. package/lib/typescript/index.d.ts.map +1 -0
  79. package/lib/typescript/storage.d.ts +68 -0
  80. package/lib/typescript/storage.d.ts.map +1 -0
  81. package/lib/typescript/types.d.ts +298 -0
  82. package/lib/typescript/types.d.ts.map +1 -0
  83. package/mobana.podspec +19 -0
  84. package/package.json +131 -0
  85. package/src/Mobana.ts +742 -0
  86. package/src/NativeMobana.ts +61 -0
  87. package/src/api.ts +259 -0
  88. package/src/bridge/index.ts +2 -0
  89. package/src/bridge/injectBridge.ts +542 -0
  90. package/src/components/FlowWebView.tsx +826 -0
  91. package/src/components/MobanaProvider.tsx +393 -0
  92. package/src/components/index.ts +4 -0
  93. package/src/device.ts +42 -0
  94. package/src/index.ts +66 -0
  95. package/src/storage.ts +262 -0
  96. package/src/types.ts +362 -0
package/ios/Mobana.h ADDED
@@ -0,0 +1,11 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ /**
4
+ * Mobana native module for iOS
5
+ *
6
+ * Note: iOS does not have Install Referrer like Android.
7
+ * This module exists for API parity but getInstallReferrer always returns nil.
8
+ */
9
+ @interface Mobana : NSObject <RCTBridgeModule>
10
+
11
+ @end
package/ios/Mobana.m ADDED
@@ -0,0 +1,20 @@
1
+ #import "Mobana.h"
2
+
3
+ @implementation Mobana
4
+
5
+ RCT_EXPORT_MODULE()
6
+
7
+ /**
8
+ * Get Install Referrer - not available on iOS, always returns nil
9
+ *
10
+ * iOS does not have an equivalent to Android's Install Referrer API.
11
+ * Attribution on iOS relies on probabilistic matching.
12
+ */
13
+ RCT_EXPORT_METHOD(getInstallReferrer:(RCTPromiseResolveBlock)resolve
14
+ rejecter:(RCTPromiseRejectBlock)reject)
15
+ {
16
+ // iOS doesn't have Install Referrer, return nil
17
+ resolve(nil);
18
+ }
19
+
20
+ @end
@@ -0,0 +1,676 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.MobanaSDK = exports.Mobana = void 0;
7
+ var _storage = require("./storage");
8
+ var _api = require("./api");
9
+ var _device = require("./device");
10
+ var _NativeMobana = require("./NativeMobana");
11
+ var _MobanaProvider = require("./components/MobanaProvider");
12
+ const DEFAULT_ENDPOINT = 'https://{appId}.mobana.ai';
13
+ const DEFAULT_TIMEOUT = 10000;
14
+
15
+ /**
16
+ * Mobana SDK for React Native
17
+ *
18
+ * Simple, privacy-focused mobile app attribution, conversions, and remote flows.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * import { Mobana, MobanaProvider } from '@mobana/react-native-sdk';
23
+ *
24
+ * // Wrap your app with the provider (in App.tsx)
25
+ * function App() {
26
+ * return (
27
+ * <MobanaProvider>
28
+ * <YourApp />
29
+ * </MobanaProvider>
30
+ * );
31
+ * }
32
+ *
33
+ * // Initialize once on app start
34
+ * await Mobana.init({ appId: 'a1b2c3d4' });
35
+ *
36
+ * // Get attribution (handles caching, retries, Android Install Referrer)
37
+ * const attribution = await Mobana.getAttribution();
38
+ *
39
+ * // Track conversions
40
+ * Mobana.trackConversion('signup');
41
+ * Mobana.trackConversion('purchase', 49.99);
42
+ *
43
+ * // Show a flow
44
+ * const result = await Mobana.startFlow('onboarding');
45
+ * if (result.completed) {
46
+ * console.log('User completed onboarding!', result.data);
47
+ * }
48
+ * ```
49
+ */
50
+ class MobanaSDK {
51
+ config = null;
52
+ isConfigured = false;
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
+ attributionPromise = null;
55
+ // In-memory cache for attribution (faster than AsyncStorage)
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ cachedAttribution = null;
58
+ attributionChecked = false;
59
+
60
+ /**
61
+ * Initialize the SDK with your app settings
62
+ * Must be called before any other SDK methods
63
+ *
64
+ * @param config - Configuration options (appId is required)
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * // Basic initialization
69
+ * await Mobana.init({ appId: 'a1b2c3d4' });
70
+ *
71
+ * // With custom endpoint (for domain proxying)
72
+ * await Mobana.init({
73
+ * appId: 'a1b2c3d4',
74
+ * endpoint: 'https://myapp.com/d',
75
+ * });
76
+ *
77
+ * // With all options
78
+ * await Mobana.init({
79
+ * appId: 'a1b2c3d4',
80
+ * endpoint: 'https://myapp.com/d', // Optional
81
+ * enabled: userHasConsented, // Optional, default: true
82
+ * debug: __DEV__, // Optional, default: false
83
+ * });
84
+ * ```
85
+ */
86
+ async init(config) {
87
+ if (!config.appId) {
88
+ console.warn('[Mobana] appId is required');
89
+ return;
90
+ }
91
+ if (!config.appKey) {
92
+ console.warn('[Mobana] appKey is required');
93
+ return;
94
+ }
95
+ this.config = {
96
+ enabled: true,
97
+ debug: false,
98
+ ...config
99
+ };
100
+ this.isConfigured = true;
101
+
102
+ // Eagerly generate/retrieve the install ID so it's ready before
103
+ // the first attribution or conversion call.
104
+ const installId = await (0, _storage.getInstallId)();
105
+ if (this.config.debug) {
106
+ console.log('[Mobana] Initialized:', {
107
+ appId: this.config.appId,
108
+ endpoint: this.config.endpoint,
109
+ enabled: this.config.enabled,
110
+ installId
111
+ });
112
+ }
113
+
114
+ // Flush any queued conversions when SDK is initialized
115
+ await this.flushConversionQueue();
116
+ }
117
+
118
+ /**
119
+ * Enable or disable the SDK dynamically
120
+ * Useful for GDPR consent flows
121
+ *
122
+ * @param enabled - Whether the SDK should be enabled
123
+ */
124
+ setEnabled(enabled) {
125
+ if (!this.config) {
126
+ console.warn('[Mobana] SDK not configured. Call init() first.');
127
+ return;
128
+ }
129
+ this.config.enabled = enabled;
130
+ if (this.config.debug) {
131
+ console.log(`[Mobana] ${enabled ? 'Enabled' : 'Disabled'}`);
132
+ }
133
+ if (enabled) {
134
+ this.flushConversionQueue();
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Get attribution data for this install
140
+ *
141
+ * Returns cached result if available, otherwise fetches from server.
142
+ * Never throws - returns null on error or no match.
143
+ *
144
+ * @param options - Optional settings for the attribution request
145
+ * @returns Attribution data or null if not available
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * const attribution = await Mobana.getAttribution();
150
+ *
151
+ * if (attribution) {
152
+ * YourAnalyticsProvider.track('App Installed', {
153
+ * source: attribution.utm_source,
154
+ * campaign: attribution.utm_campaign,
155
+ * });
156
+ *
157
+ * if (attribution.data?.promo) {
158
+ * applyPromoCode(attribution.data.promo);
159
+ * }
160
+ * }
161
+ * ```
162
+ *
163
+ * @example
164
+ * // With TypeScript generics for typed data
165
+ * interface MyDeeplinkData {
166
+ * promo?: string;
167
+ * referrer?: string;
168
+ * }
169
+ *
170
+ * const attribution = await Mobana.getAttribution<MyDeeplinkData>();
171
+ * // attribution.data is now typed as MyDeeplinkData
172
+ */
173
+ async getAttribution(options = {}) {
174
+ if (!this.isConfigured || !this.config) {
175
+ console.warn('[Mobana] SDK not configured. Call init() first.');
176
+ return null;
177
+ }
178
+ if (!this.config.enabled) {
179
+ if (this.config.debug) {
180
+ console.log('[Mobana] SDK disabled, returning null');
181
+ }
182
+ return null;
183
+ }
184
+
185
+ // Return in-memory cache if available (fastest)
186
+ if (this.attributionChecked) {
187
+ return this.cachedAttribution;
188
+ }
189
+
190
+ // Check AsyncStorage cache
191
+ const cached = await (0, _storage.getCachedResult)();
192
+ if (cached) {
193
+ if (this.config.debug) {
194
+ console.log('[Mobana] Returning cached result, matched:', cached.matched);
195
+ }
196
+ // Update in-memory cache
197
+ this.attributionChecked = true;
198
+ this.cachedAttribution = cached.matched ? cached.attribution ?? null : null;
199
+ return this.cachedAttribution;
200
+ }
201
+
202
+ // Prevent duplicate concurrent requests
203
+ if (this.attributionPromise) {
204
+ return this.attributionPromise;
205
+ }
206
+ this.attributionPromise = this.fetchAttribution(options);
207
+ const result = await this.attributionPromise;
208
+ this.attributionPromise = null;
209
+
210
+ // Update in-memory cache
211
+ this.attributionChecked = true;
212
+ this.cachedAttribution = result;
213
+ return result;
214
+ }
215
+
216
+ /**
217
+ * Track a conversion event
218
+ *
219
+ * Conversions are linked to the original attribution via installId.
220
+ * If offline, conversions are queued and sent when back online.
221
+ * Never throws - silently handles errors.
222
+ *
223
+ * @param name - Conversion name (must be configured in app settings)
224
+ * @param value - Optional monetary value
225
+ * @param flowSessionId - Optional flow session ID to link conversion to a specific flow presentation
226
+ *
227
+ * @example
228
+ * ```typescript
229
+ * // Simple conversion
230
+ * Mobana.trackConversion('signup');
231
+ *
232
+ * // Conversion with value
233
+ * Mobana.trackConversion('purchase', 49.99);
234
+ *
235
+ * // Conversion linked to a flow session
236
+ * const result = await Mobana.startFlow('pre-purchase');
237
+ * // ... user makes purchase via paywall ...
238
+ * await Mobana.trackConversion('purchase', 49.99, result.sessionId);
239
+ * ```
240
+ */
241
+ async trackConversion(name, value, flowSessionId) {
242
+ if (!this.isConfigured || !this.config) {
243
+ if (this.config?.debug) {
244
+ console.log('[Mobana] SDK not configured, skipping conversion');
245
+ }
246
+ return;
247
+ }
248
+ if (!this.config.enabled) {
249
+ if (this.config.debug) {
250
+ console.log('[Mobana] SDK disabled, skipping conversion');
251
+ }
252
+ return;
253
+ }
254
+ const installId = await (0, _storage.getInstallId)();
255
+ const event = {
256
+ installId,
257
+ name,
258
+ value,
259
+ timestamp: Date.now(),
260
+ flowSessionId
261
+ };
262
+
263
+ // Ensure Install record exists on server (created on first getAttribution call).
264
+ // This is fast after first call — returns from in-memory cache without network.
265
+ // We don't care about the result; conversions work for organic installs too.
266
+ await this.getAttribution();
267
+
268
+ // Try to send immediately
269
+ const success = await this.sendConversion(event);
270
+ if (!success) {
271
+ // Queue for later if failed (offline, etc.)
272
+ await (0, _storage.queueConversion)(event);
273
+ if (this.config.debug) {
274
+ console.log('[Mobana] Conversion queued for later');
275
+ }
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Reset all stored attribution data
281
+ * Useful for testing or when user logs out
282
+ *
283
+ * Note: This generates a new installId, so subsequent attributions
284
+ * will be treated as a new install.
285
+ */
286
+ async reset() {
287
+ // Clear in-memory cache
288
+ this.cachedAttribution = null;
289
+ this.attributionChecked = false;
290
+ this.attributionPromise = null;
291
+
292
+ // Clear persistent storage
293
+ await (0, _storage.clearAttribution)();
294
+ await (0, _storage.clearConversionQueue)();
295
+ await (0, _storage.clearAllCachedFlows)();
296
+ await (0, _storage.clearLocalData)();
297
+ if (this.config?.debug) {
298
+ console.log('[Mobana] Reset complete');
299
+ }
300
+ }
301
+
302
+ // ============================================
303
+ // Flows
304
+ // ============================================
305
+
306
+ /**
307
+ * Start and display a flow
308
+ *
309
+ * Fetches the flow content (or uses cache) and presents it in a full-screen modal.
310
+ * The promise resolves when the user completes or dismisses the flow.
311
+ *
312
+ * Requires MobanaProvider to be mounted in your app.
313
+ *
314
+ * @param slug - Flow identifier (from dashboard)
315
+ * @param options - Optional flow configuration
316
+ * @returns Flow result with completion status and optional data
317
+ *
318
+ * @example
319
+ * ```typescript
320
+ * // Basic usage
321
+ * const result = await Mobana.startFlow('onboarding');
322
+ *
323
+ * if (result.completed) {
324
+ * console.log('Onboarding completed!', result.data);
325
+ * } else if (result.error) {
326
+ * console.log('Flow error:', result.error);
327
+ * }
328
+ *
329
+ * // With custom parameters
330
+ * const result = await Mobana.startFlow('welcome', {
331
+ * params: { userName: 'John', isPremium: true },
332
+ * onEvent: (name) => {
333
+ * analytics.track(name);
334
+ * },
335
+ * });
336
+ * ```
337
+ */
338
+ async startFlow(slug, options) {
339
+ // Check if SDK is configured
340
+ if (!this.isConfigured || !this.config) {
341
+ console.warn('[Mobana] SDK not configured. Call init() first.');
342
+ return {
343
+ completed: false,
344
+ dismissed: true,
345
+ error: 'SDK_NOT_CONFIGURED'
346
+ };
347
+ }
348
+
349
+ // Check if SDK is enabled
350
+ if (!this.config.enabled) {
351
+ if (this.config.debug) {
352
+ console.log('[Mobana] SDK disabled, cannot start flow');
353
+ }
354
+ return {
355
+ completed: false,
356
+ dismissed: true,
357
+ error: 'SDK_NOT_CONFIGURED'
358
+ };
359
+ }
360
+
361
+ // Check if provider is mounted
362
+ const flowContext = (0, _MobanaProvider.getGlobalFlowContext)();
363
+ if (!flowContext?.isProviderMounted) {
364
+ console.warn('[Mobana] startFlow() called but MobanaProvider is not mounted. ' + 'Wrap your app with <MobanaProvider> to enable flows.');
365
+ return {
366
+ completed: false,
367
+ dismissed: true,
368
+ error: 'PROVIDER_NOT_MOUNTED'
369
+ };
370
+ }
371
+ try {
372
+ const endpoint = this.getEndpoint();
373
+ const installId = await (0, _storage.getInstallId)();
374
+
375
+ // Ensure attribution is loaded (for passing to flow context).
376
+ // Fast after first call — returns from in-memory cache without network.
377
+ // We don't fail if attribution isn't matched; flows work for organic installs too.
378
+ await this.getAttribution();
379
+
380
+ // Check cache for this flow
381
+ const cached = await (0, _storage.getCachedFlow)(slug);
382
+ if (this.config.debug) {
383
+ console.log(`[Mobana] Starting flow: ${slug}`, {
384
+ hasCached: !!cached,
385
+ cachedVersionId: cached?.versionId
386
+ });
387
+ }
388
+
389
+ // Fetch flow from server (with cache validation)
390
+ const response = await (0, _api.fetchFlow)(endpoint, this.config.appKey, slug, installId, cached?.versionId, DEFAULT_TIMEOUT, this.config.debug);
391
+
392
+ // Handle network error
393
+ if (!response) {
394
+ // If we have a cached version, use it
395
+ if (cached) {
396
+ if (this.config.debug) {
397
+ console.log('[Mobana] Network error, using cached flow');
398
+ }
399
+ return this.presentFlowToUser(flowContext, {
400
+ slug,
401
+ config: cached,
402
+ installId,
403
+ endpoint,
404
+ appKey: this.config.appKey,
405
+ options
406
+ });
407
+ }
408
+ return {
409
+ completed: false,
410
+ dismissed: true,
411
+ error: 'NETWORK_ERROR'
412
+ };
413
+ }
414
+
415
+ // Handle server errors
416
+ if (response.error) {
417
+ if (this.config.debug) {
418
+ console.log(`[Mobana] Flow error: ${response.error}`);
419
+ }
420
+ return {
421
+ completed: false,
422
+ dismissed: true,
423
+ error: response.error
424
+ };
425
+ }
426
+
427
+ // Determine flow content to use
428
+ let flowConfig;
429
+ if (response.cached && cached) {
430
+ // Server confirmed our cached version is current
431
+ flowConfig = cached;
432
+ if (this.config.debug) {
433
+ console.log('[Mobana] Using cached flow (validated)');
434
+ }
435
+ } else if (response.versionId && response.html) {
436
+ // New content from server
437
+ flowConfig = {
438
+ versionId: response.versionId,
439
+ html: response.html,
440
+ css: response.css,
441
+ js: response.js
442
+ };
443
+ // Cache for next time
444
+ await (0, _storage.setCachedFlow)(slug, flowConfig);
445
+ if (this.config.debug) {
446
+ console.log('[Mobana] Using fresh flow, cached for next time');
447
+ }
448
+ } else {
449
+ // Unexpected response
450
+ if (this.config.debug) {
451
+ console.log('[Mobana] Unexpected flow response');
452
+ }
453
+ return {
454
+ completed: false,
455
+ dismissed: true,
456
+ error: 'SERVER_ERROR'
457
+ };
458
+ }
459
+
460
+ // Present the flow
461
+ return this.presentFlowToUser(flowContext, {
462
+ slug,
463
+ config: flowConfig,
464
+ installId,
465
+ endpoint,
466
+ appKey: this.config.appKey,
467
+ options
468
+ });
469
+ } catch (error) {
470
+ if (this.config.debug) {
471
+ console.log('[Mobana] Error starting flow:', error);
472
+ }
473
+ return {
474
+ completed: false,
475
+ dismissed: true,
476
+ error: 'SERVER_ERROR'
477
+ };
478
+ }
479
+ }
480
+
481
+ /**
482
+ * Prefetch a flow for faster display later
483
+ *
484
+ * Downloads and caches the flow content without displaying it.
485
+ * Call this ahead of time if you know a flow will be shown soon.
486
+ *
487
+ * @param slug - Flow identifier (from dashboard)
488
+ *
489
+ * @example
490
+ * ```typescript
491
+ * // Prefetch during app startup
492
+ * Mobana.prefetchFlow('onboarding');
493
+ *
494
+ * // Later, when ready to show (will be instant if prefetched)
495
+ * const result = await Mobana.startFlow('onboarding');
496
+ * ```
497
+ */
498
+ async prefetchFlow(slug) {
499
+ if (!this.isConfigured || !this.config) {
500
+ return;
501
+ }
502
+ if (!this.config.enabled) {
503
+ return;
504
+ }
505
+ try {
506
+ const endpoint = this.getEndpoint();
507
+ const installId = await (0, _storage.getInstallId)();
508
+ const cached = await (0, _storage.getCachedFlow)(slug);
509
+ if (this.config.debug) {
510
+ console.log(`[Mobana] Prefetching flow: ${slug}`);
511
+ }
512
+ const response = await (0, _api.fetchFlow)(endpoint, this.config.appKey, slug, installId, cached?.versionId, DEFAULT_TIMEOUT, this.config.debug);
513
+ if (response && !response.error && !response.cached && response.versionId && response.html) {
514
+ // Cache the new content
515
+ await (0, _storage.setCachedFlow)(slug, {
516
+ versionId: response.versionId,
517
+ html: response.html,
518
+ css: response.css,
519
+ js: response.js
520
+ });
521
+ if (this.config.debug) {
522
+ console.log(`[Mobana] Flow "${slug}" prefetched and cached`);
523
+ }
524
+ } else if (response?.cached) {
525
+ if (this.config.debug) {
526
+ console.log(`[Mobana] Flow "${slug}" already cached and current`);
527
+ }
528
+ }
529
+ } catch (error) {
530
+ if (this.config.debug) {
531
+ console.log('[Mobana] Error prefetching flow:', error);
532
+ }
533
+ }
534
+ }
535
+
536
+ /**
537
+ * Present a flow to the user via the provider
538
+ */
539
+ presentFlowToUser(flowContext, params) {
540
+ return new Promise(resolve => {
541
+ flowContext.presentFlow({
542
+ slug: params.slug,
543
+ config: params.config,
544
+ installId: params.installId,
545
+ endpoint: params.endpoint,
546
+ appKey: params.appKey,
547
+ attribution: this.cachedAttribution,
548
+ options: params.options,
549
+ resolve,
550
+ debug: this.config?.debug
551
+ });
552
+ });
553
+ }
554
+
555
+ // ============================================
556
+ // Private methods
557
+ // ============================================
558
+
559
+ getEndpoint() {
560
+ if (this.config?.endpoint) {
561
+ // Remove trailing slash
562
+ return this.config.endpoint.replace(/\/$/, '');
563
+ }
564
+ if (this.config?.appId) {
565
+ return DEFAULT_ENDPOINT.replace('{appId}', this.config.appId);
566
+ }
567
+ throw new Error('No endpoint configured');
568
+ }
569
+ async fetchAttribution(options) {
570
+ const {
571
+ timeout = DEFAULT_TIMEOUT
572
+ } = options;
573
+ try {
574
+ const endpoint = this.getEndpoint();
575
+ const installId = await (0, _storage.getInstallId)();
576
+ const deviceInfo = (0, _device.getDeviceInfo)();
577
+ if (this.config?.debug) {
578
+ console.log('[Mobana] Fetching attribution...');
579
+ console.log('[Mobana] Device info:', deviceInfo);
580
+ }
581
+
582
+ // Get Android Install Referrer for deterministic attribution
583
+ let dacid = null;
584
+ if (deviceInfo.platform === 'android') {
585
+ dacid = await (0, _NativeMobana.getInstallReferrer)();
586
+ if (this.config?.debug) {
587
+ console.log('[Mobana] Install Referrer dacid:', dacid || '(not available)');
588
+ }
589
+ }
590
+
591
+ // Make API request
592
+ const response = await (0, _api.findAttribution)(endpoint, this.config.appKey, installId, deviceInfo, dacid, timeout, this.config?.debug ?? false);
593
+
594
+ // If no response (network error, timeout), don't cache - allow retry
595
+ if (!response) {
596
+ if (this.config?.debug) {
597
+ console.log('[Mobana] No response from server');
598
+ }
599
+ return null;
600
+ }
601
+
602
+ // Cache the response if server returned a valid response with matched key
603
+ // This prevents retrying on every startup
604
+ if (typeof response.matched === 'boolean') {
605
+ if (response.matched && response.attribution) {
606
+ // Build attribution object
607
+ const attribution = {
608
+ ...response.attribution,
609
+ confidence: response.confidence ?? 0
610
+ };
611
+
612
+ // Cache matched result
613
+ await (0, _storage.setCachedResult)(true, attribution);
614
+ if (this.config?.debug) {
615
+ console.log('[Mobana] Attribution matched:', attribution);
616
+ }
617
+ return attribution;
618
+ } else {
619
+ // Cache unmatched result - prevents retry on next startup
620
+ await (0, _storage.setCachedResult)(false);
621
+ if (this.config?.debug) {
622
+ console.log('[Mobana] No match found (cached)');
623
+ }
624
+ return null;
625
+ }
626
+ }
627
+
628
+ // Unexpected response format
629
+ if (this.config?.debug) {
630
+ console.log('[Mobana] Unexpected response format');
631
+ }
632
+ return null;
633
+ } catch (error) {
634
+ if (this.config?.debug) {
635
+ console.log('[Mobana] Error fetching attribution:', error);
636
+ }
637
+ return null;
638
+ }
639
+ }
640
+ async sendConversion(event) {
641
+ try {
642
+ const endpoint = this.getEndpoint();
643
+ return await (0, _api.trackConversionApi)(endpoint, this.config.appKey, event, this.config?.debug ?? false);
644
+ } catch {
645
+ return false;
646
+ }
647
+ }
648
+ async flushConversionQueue() {
649
+ if (!this.config?.enabled) {
650
+ return;
651
+ }
652
+ const queue = await (0, _storage.getConversionQueue)();
653
+ if (queue.length === 0) {
654
+ return;
655
+ }
656
+ if (this.config?.debug) {
657
+ console.log(`[Mobana] Flushing ${queue.length} queued conversions`);
658
+ }
659
+
660
+ // Send all queued conversions
661
+ const results = await Promise.all(queue.map(event => this.sendConversion(event)));
662
+
663
+ // Clear the queue, then re-queue only the failures (avoids duplicate sends)
664
+ await (0, _storage.clearConversionQueue)();
665
+ const failed = queue.filter((_, i) => !results[i]);
666
+ for (const event of failed) {
667
+ await (0, _storage.queueConversion)(event);
668
+ }
669
+ }
670
+ }
671
+
672
+ // Export class for testing (create fresh instances without shared state)
673
+ exports.MobanaSDK = MobanaSDK;
674
+ // Export singleton instance
675
+ const Mobana = exports.Mobana = new MobanaSDK();
676
+ //# sourceMappingURL=Mobana.js.map