@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.
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/android/build.gradle +50 -0
- package/android/src/main/AndroidManifest.xml +6 -0
- package/android/src/main/java/ai/mobana/sdk/MobanaModule.kt +67 -0
- package/android/src/main/java/ai/mobana/sdk/MobanaPackage.kt +19 -0
- package/app.plugin.js +274 -0
- package/ios/Mobana.h +11 -0
- package/ios/Mobana.m +20 -0
- package/lib/commonjs/Mobana.js +676 -0
- package/lib/commonjs/Mobana.js.map +1 -0
- package/lib/commonjs/NativeMobana.js +53 -0
- package/lib/commonjs/NativeMobana.js.map +1 -0
- package/lib/commonjs/api.js +201 -0
- package/lib/commonjs/api.js.map +1 -0
- package/lib/commonjs/bridge/index.js +19 -0
- package/lib/commonjs/bridge/index.js.map +1 -0
- package/lib/commonjs/bridge/injectBridge.js +528 -0
- package/lib/commonjs/bridge/injectBridge.js.map +1 -0
- package/lib/commonjs/components/FlowWebView.js +676 -0
- package/lib/commonjs/components/FlowWebView.js.map +1 -0
- package/lib/commonjs/components/MobanaProvider.js +275 -0
- package/lib/commonjs/components/MobanaProvider.js.map +1 -0
- package/lib/commonjs/components/index.js +20 -0
- package/lib/commonjs/components/index.js.map +1 -0
- package/lib/commonjs/device.js +49 -0
- package/lib/commonjs/device.js.map +1 -0
- package/lib/commonjs/index.js +20 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/storage.js +277 -0
- package/lib/commonjs/storage.js.map +1 -0
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/module/Mobana.js +673 -0
- package/lib/module/Mobana.js.map +1 -0
- package/lib/module/NativeMobana.js +49 -0
- package/lib/module/NativeMobana.js.map +1 -0
- package/lib/module/api.js +194 -0
- package/lib/module/api.js.map +1 -0
- package/lib/module/bridge/index.js +4 -0
- package/lib/module/bridge/index.js.map +1 -0
- package/lib/module/bridge/injectBridge.js +523 -0
- package/lib/module/bridge/injectBridge.js.map +1 -0
- package/lib/module/components/FlowWebView.js +672 -0
- package/lib/module/components/FlowWebView.js.map +1 -0
- package/lib/module/components/MobanaProvider.js +270 -0
- package/lib/module/components/MobanaProvider.js.map +1 -0
- package/lib/module/components/index.js +5 -0
- package/lib/module/components/index.js.map +1 -0
- package/lib/module/device.js +45 -0
- package/lib/module/device.js.map +1 -0
- package/lib/module/index.js +53 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/storage.js +257 -0
- package/lib/module/storage.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/Mobana.d.ts +209 -0
- package/lib/typescript/Mobana.d.ts.map +1 -0
- package/lib/typescript/NativeMobana.d.ts +11 -0
- package/lib/typescript/NativeMobana.d.ts.map +1 -0
- package/lib/typescript/api.d.ts +34 -0
- package/lib/typescript/api.d.ts.map +1 -0
- package/lib/typescript/bridge/index.d.ts +3 -0
- package/lib/typescript/bridge/index.d.ts.map +1 -0
- package/lib/typescript/bridge/injectBridge.d.ts +23 -0
- package/lib/typescript/bridge/injectBridge.d.ts.map +1 -0
- package/lib/typescript/components/FlowWebView.d.ts +38 -0
- package/lib/typescript/components/FlowWebView.d.ts.map +1 -0
- package/lib/typescript/components/MobanaProvider.d.ts +65 -0
- package/lib/typescript/components/MobanaProvider.d.ts.map +1 -0
- package/lib/typescript/components/index.d.ts +5 -0
- package/lib/typescript/components/index.d.ts.map +1 -0
- package/lib/typescript/device.d.ts +6 -0
- package/lib/typescript/device.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +46 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/storage.d.ts +68 -0
- package/lib/typescript/storage.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +298 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/mobana.podspec +19 -0
- package/package.json +131 -0
- package/src/Mobana.ts +742 -0
- package/src/NativeMobana.ts +61 -0
- package/src/api.ts +259 -0
- package/src/bridge/index.ts +2 -0
- package/src/bridge/injectBridge.ts +542 -0
- package/src/components/FlowWebView.tsx +826 -0
- package/src/components/MobanaProvider.tsx +393 -0
- package/src/components/index.ts +4 -0
- package/src/device.ts +42 -0
- package/src/index.ts +66 -0
- package/src/storage.ts +262 -0
- package/src/types.ts +362 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
createContext,
|
|
3
|
+
useState,
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useRef,
|
|
7
|
+
ReactNode,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import {
|
|
10
|
+
Modal,
|
|
11
|
+
StyleSheet,
|
|
12
|
+
View,
|
|
13
|
+
StatusBar,
|
|
14
|
+
Platform,
|
|
15
|
+
ActivityIndicator,
|
|
16
|
+
useColorScheme,
|
|
17
|
+
} from 'react-native';
|
|
18
|
+
import type { WebViewProps } from 'react-native-webview';
|
|
19
|
+
import type { ModalProps } from 'react-native';
|
|
20
|
+
import type { FlowConfig, FlowResult, FlowOptions, Attribution } from '../types';
|
|
21
|
+
import type { FlowWebViewProps } from './FlowWebView';
|
|
22
|
+
import { trackFlowEvent } from '../api';
|
|
23
|
+
import { generateUUID } from '../storage';
|
|
24
|
+
|
|
25
|
+
// Optional: react-native-webview (required for Flows; checked at provider mount)
|
|
26
|
+
let WebViewAvailable = false;
|
|
27
|
+
try {
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
|
+
require('react-native-webview');
|
|
30
|
+
WebViewAvailable = true;
|
|
31
|
+
} catch {
|
|
32
|
+
// Not installed - provider will throw in __DEV__ if used
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let FlowWebViewComponent: React.ComponentType<FlowWebViewProps> | null = null;
|
|
36
|
+
if (WebViewAvailable) {
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
38
|
+
FlowWebViewComponent = require('./FlowWebView').FlowWebView;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Optional peer dependency for app review
|
|
42
|
+
let InAppReview: { RequestInAppReview: () => Promise<boolean> } | null = null;
|
|
43
|
+
let inAppReviewWarningShown = false;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
47
|
+
InAppReview = require('react-native-in-app-review').default;
|
|
48
|
+
} catch {
|
|
49
|
+
// Not installed
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Internal flow request from SDK
|
|
54
|
+
*/
|
|
55
|
+
export interface FlowRequest {
|
|
56
|
+
slug: string;
|
|
57
|
+
config: FlowConfig;
|
|
58
|
+
installId: string;
|
|
59
|
+
endpoint: string;
|
|
60
|
+
appKey: string;
|
|
61
|
+
attribution: Attribution | null;
|
|
62
|
+
options?: FlowOptions;
|
|
63
|
+
resolve: (result: FlowResult) => void;
|
|
64
|
+
debug?: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Context for flow presentation
|
|
69
|
+
*/
|
|
70
|
+
interface FlowContextValue {
|
|
71
|
+
presentFlow: (request: FlowRequest) => void;
|
|
72
|
+
isProviderMounted: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const FlowContext = createContext<FlowContextValue | null>(null);
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check if provider is mounted (used by SDK)
|
|
79
|
+
*/
|
|
80
|
+
let globalFlowContext: FlowContextValue | null = null;
|
|
81
|
+
|
|
82
|
+
export function getGlobalFlowContext(): FlowContextValue | null {
|
|
83
|
+
return globalFlowContext;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Props for MobanaProvider
|
|
88
|
+
*/
|
|
89
|
+
export interface MobanaProviderProps {
|
|
90
|
+
children: ReactNode;
|
|
91
|
+
/**
|
|
92
|
+
* Custom props for the Modal component
|
|
93
|
+
*/
|
|
94
|
+
modalProps?: Partial<ModalProps>;
|
|
95
|
+
/**
|
|
96
|
+
* Custom props for the WebView component
|
|
97
|
+
*/
|
|
98
|
+
webViewProps?: Partial<WebViewProps>;
|
|
99
|
+
/**
|
|
100
|
+
* Custom loading component to show while flow is loading
|
|
101
|
+
*/
|
|
102
|
+
loadingComponent?: ReactNode;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Provider component for Mobana flows
|
|
107
|
+
*
|
|
108
|
+
* Wrap your app with this component to enable flow presentation:
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```tsx
|
|
112
|
+
* import { MobanaProvider } from '@mobana/react-native-sdk';
|
|
113
|
+
*
|
|
114
|
+
* export default function App() {
|
|
115
|
+
* return (
|
|
116
|
+
* <MobanaProvider>
|
|
117
|
+
* <YourApp />
|
|
118
|
+
* </MobanaProvider>
|
|
119
|
+
* );
|
|
120
|
+
* }
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export function MobanaProvider({
|
|
124
|
+
children,
|
|
125
|
+
modalProps,
|
|
126
|
+
webViewProps,
|
|
127
|
+
loadingComponent,
|
|
128
|
+
}: MobanaProviderProps) {
|
|
129
|
+
const colorScheme = useColorScheme();
|
|
130
|
+
const isDark = colorScheme === 'dark';
|
|
131
|
+
const bgColor = isDark ? '#1c1c1e' : '#FFFFFF';
|
|
132
|
+
|
|
133
|
+
if (__DEV__ && !WebViewAvailable) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
'[Mobana] react-native-webview is required for MobanaProvider.\n\n' +
|
|
136
|
+
'Install it with: npm install react-native-webview\n\n' +
|
|
137
|
+
'If you only need attribution/conversion tracking, you can skip the provider ' +
|
|
138
|
+
'and use Mobana.init, getAttribution, trackConversion directly.'
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const [currentRequest, setCurrentRequest] = useState<FlowRequest | null>(null);
|
|
143
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
144
|
+
const hasTrackedStartRef = useRef(false);
|
|
145
|
+
// Session ID for the current flow presentation (groups all events together)
|
|
146
|
+
const sessionIdRef = useRef<string>('');
|
|
147
|
+
|
|
148
|
+
// Present a flow
|
|
149
|
+
const presentFlow = useCallback((request: FlowRequest) => {
|
|
150
|
+
setIsLoading(true);
|
|
151
|
+
hasTrackedStartRef.current = false;
|
|
152
|
+
// Generate a new session ID for this flow presentation
|
|
153
|
+
sessionIdRef.current = generateUUID();
|
|
154
|
+
setCurrentRequest(request);
|
|
155
|
+
}, []);
|
|
156
|
+
|
|
157
|
+
// Context value
|
|
158
|
+
const contextValue: FlowContextValue = {
|
|
159
|
+
presentFlow,
|
|
160
|
+
isProviderMounted: true,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Register global context for SDK access
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
globalFlowContext = contextValue;
|
|
166
|
+
return () => {
|
|
167
|
+
globalFlowContext = null;
|
|
168
|
+
};
|
|
169
|
+
}, [contextValue]);
|
|
170
|
+
|
|
171
|
+
// Track "__started__" event when flow is shown (system event, not user-callable)
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
if (currentRequest && !hasTrackedStartRef.current) {
|
|
174
|
+
hasTrackedStartRef.current = true;
|
|
175
|
+
trackFlowEvent(
|
|
176
|
+
currentRequest.endpoint,
|
|
177
|
+
currentRequest.appKey,
|
|
178
|
+
currentRequest.slug,
|
|
179
|
+
currentRequest.installId,
|
|
180
|
+
currentRequest.config.versionId,
|
|
181
|
+
sessionIdRef.current,
|
|
182
|
+
'__started__',
|
|
183
|
+
undefined,
|
|
184
|
+
undefined,
|
|
185
|
+
currentRequest.debug
|
|
186
|
+
);
|
|
187
|
+
setIsLoading(false);
|
|
188
|
+
}
|
|
189
|
+
}, [currentRequest]);
|
|
190
|
+
|
|
191
|
+
// Handle flow completion
|
|
192
|
+
const handleComplete = useCallback(
|
|
193
|
+
(data?: Record<string, unknown>) => {
|
|
194
|
+
if (!currentRequest) return;
|
|
195
|
+
|
|
196
|
+
// Capture context for the trackEvent closure (must be done before clearing currentRequest)
|
|
197
|
+
const capturedEndpoint = currentRequest.endpoint;
|
|
198
|
+
const capturedAppKey = currentRequest.appKey;
|
|
199
|
+
const capturedSlug = currentRequest.slug;
|
|
200
|
+
const capturedInstallId = currentRequest.installId;
|
|
201
|
+
const capturedVersionId = currentRequest.config.versionId;
|
|
202
|
+
const capturedSessionId = sessionIdRef.current;
|
|
203
|
+
const capturedDebug = currentRequest.debug;
|
|
204
|
+
|
|
205
|
+
// Track "__completed__" event (system event, not user-callable)
|
|
206
|
+
trackFlowEvent(
|
|
207
|
+
capturedEndpoint,
|
|
208
|
+
capturedAppKey,
|
|
209
|
+
capturedSlug,
|
|
210
|
+
capturedInstallId,
|
|
211
|
+
capturedVersionId,
|
|
212
|
+
capturedSessionId,
|
|
213
|
+
'__completed__',
|
|
214
|
+
undefined,
|
|
215
|
+
data,
|
|
216
|
+
capturedDebug
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// Create trackEvent closure for post-flow event tracking
|
|
220
|
+
const trackEvent = async (event: string, eventData?: Record<string, unknown>): Promise<boolean> => {
|
|
221
|
+
return trackFlowEvent(
|
|
222
|
+
capturedEndpoint,
|
|
223
|
+
capturedAppKey,
|
|
224
|
+
capturedSlug,
|
|
225
|
+
capturedInstallId,
|
|
226
|
+
capturedVersionId,
|
|
227
|
+
capturedSessionId,
|
|
228
|
+
event,
|
|
229
|
+
undefined,
|
|
230
|
+
eventData,
|
|
231
|
+
capturedDebug
|
|
232
|
+
);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Resolve the promise with sessionId and trackEvent for post-flow tracking
|
|
236
|
+
currentRequest.resolve({
|
|
237
|
+
completed: true,
|
|
238
|
+
dismissed: false,
|
|
239
|
+
data,
|
|
240
|
+
sessionId: capturedSessionId,
|
|
241
|
+
trackEvent,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Check for special actions that need to run after modal closes
|
|
245
|
+
const action = data?.action;
|
|
246
|
+
|
|
247
|
+
setCurrentRequest(null);
|
|
248
|
+
|
|
249
|
+
// Handle post-modal actions after a delay to ensure modal is fully closed
|
|
250
|
+
if (action === 'request-app-review') {
|
|
251
|
+
setTimeout(async () => {
|
|
252
|
+
if (InAppReview) {
|
|
253
|
+
try {
|
|
254
|
+
await InAppReview.RequestInAppReview();
|
|
255
|
+
} catch {
|
|
256
|
+
// Silently fail - review request is best-effort
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
if (!inAppReviewWarningShown) {
|
|
260
|
+
inAppReviewWarningShown = true;
|
|
261
|
+
console.warn(
|
|
262
|
+
'[Mobana] react-native-in-app-review is not installed. ' +
|
|
263
|
+
'App store review prompts will not work. To enable this feature, install: ' +
|
|
264
|
+
'npm install react-native-in-app-review'
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}, 300); // Small delay to ensure modal animation completes
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
[currentRequest]
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
// Handle flow dismissal
|
|
275
|
+
const handleDismiss = useCallback(() => {
|
|
276
|
+
if (!currentRequest) return;
|
|
277
|
+
|
|
278
|
+
// Capture context for the trackEvent closure (must be done before clearing currentRequest)
|
|
279
|
+
const capturedEndpoint = currentRequest.endpoint;
|
|
280
|
+
const capturedAppKey = currentRequest.appKey;
|
|
281
|
+
const capturedSlug = currentRequest.slug;
|
|
282
|
+
const capturedInstallId = currentRequest.installId;
|
|
283
|
+
const capturedVersionId = currentRequest.config.versionId;
|
|
284
|
+
const capturedSessionId = sessionIdRef.current;
|
|
285
|
+
const capturedDebug = currentRequest.debug;
|
|
286
|
+
|
|
287
|
+
// Track "__dismissed__" event (system event, not user-callable)
|
|
288
|
+
trackFlowEvent(
|
|
289
|
+
capturedEndpoint,
|
|
290
|
+
capturedAppKey,
|
|
291
|
+
capturedSlug,
|
|
292
|
+
capturedInstallId,
|
|
293
|
+
capturedVersionId,
|
|
294
|
+
capturedSessionId,
|
|
295
|
+
'__dismissed__',
|
|
296
|
+
undefined,
|
|
297
|
+
undefined,
|
|
298
|
+
capturedDebug
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
// Create trackEvent closure for post-flow event tracking
|
|
302
|
+
const trackEvent = async (event: string, eventData?: Record<string, unknown>): Promise<boolean> => {
|
|
303
|
+
return trackFlowEvent(
|
|
304
|
+
capturedEndpoint,
|
|
305
|
+
capturedAppKey,
|
|
306
|
+
capturedSlug,
|
|
307
|
+
capturedInstallId,
|
|
308
|
+
capturedVersionId,
|
|
309
|
+
capturedSessionId,
|
|
310
|
+
event,
|
|
311
|
+
undefined,
|
|
312
|
+
eventData,
|
|
313
|
+
capturedDebug
|
|
314
|
+
);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// Resolve the promise with sessionId and trackEvent for post-flow tracking
|
|
318
|
+
currentRequest.resolve({
|
|
319
|
+
completed: false,
|
|
320
|
+
dismissed: true,
|
|
321
|
+
sessionId: capturedSessionId,
|
|
322
|
+
trackEvent,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
setCurrentRequest(null);
|
|
326
|
+
}, [currentRequest]);
|
|
327
|
+
|
|
328
|
+
// Handle custom events from flow
|
|
329
|
+
const handleEvent = useCallback(
|
|
330
|
+
(name: string) => {
|
|
331
|
+
currentRequest?.options?.onEvent?.(name);
|
|
332
|
+
},
|
|
333
|
+
[currentRequest]
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
return (
|
|
337
|
+
<FlowContext.Provider value={contextValue}>
|
|
338
|
+
{children}
|
|
339
|
+
<Modal
|
|
340
|
+
visible={currentRequest !== null}
|
|
341
|
+
animationType="slide"
|
|
342
|
+
presentationStyle="fullScreen"
|
|
343
|
+
statusBarTranslucent={Platform.OS === 'android'}
|
|
344
|
+
onRequestClose={undefined} // Disable Android back button dismiss
|
|
345
|
+
// navigationBarTranslucent is supported in RN 0.72+ but types may be outdated
|
|
346
|
+
{...(Platform.OS === 'android' ? { navigationBarTranslucent: true } : {})}
|
|
347
|
+
{...modalProps}
|
|
348
|
+
>
|
|
349
|
+
<View style={[styles.modalContainer, { backgroundColor: bgColor }]}>
|
|
350
|
+
{Platform.OS === 'ios' && (
|
|
351
|
+
<StatusBar barStyle={isDark ? 'light-content' : 'dark-content'} />
|
|
352
|
+
)}
|
|
353
|
+
{currentRequest && !isLoading && FlowWebViewComponent && (
|
|
354
|
+
<FlowWebViewComponent
|
|
355
|
+
config={currentRequest.config}
|
|
356
|
+
slug={currentRequest.slug}
|
|
357
|
+
installId={currentRequest.installId}
|
|
358
|
+
endpoint={currentRequest.endpoint}
|
|
359
|
+
appKey={currentRequest.appKey}
|
|
360
|
+
attribution={currentRequest.attribution}
|
|
361
|
+
params={currentRequest.options?.params}
|
|
362
|
+
sessionId={sessionIdRef.current}
|
|
363
|
+
onComplete={handleComplete}
|
|
364
|
+
onDismiss={handleDismiss}
|
|
365
|
+
onEvent={handleEvent}
|
|
366
|
+
onCallback={currentRequest.options?.onCallback}
|
|
367
|
+
webViewProps={webViewProps}
|
|
368
|
+
debug={currentRequest.debug}
|
|
369
|
+
/>
|
|
370
|
+
)}
|
|
371
|
+
{isLoading && (
|
|
372
|
+
<View style={[styles.loadingContainer, { backgroundColor: bgColor }]}>
|
|
373
|
+
{loadingComponent || (
|
|
374
|
+
<ActivityIndicator size="large" color={isDark ? '#0A84FF' : '#007AFF'} />
|
|
375
|
+
)}
|
|
376
|
+
</View>
|
|
377
|
+
)}
|
|
378
|
+
</View>
|
|
379
|
+
</Modal>
|
|
380
|
+
</FlowContext.Provider>
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const styles = StyleSheet.create({
|
|
385
|
+
modalContainer: {
|
|
386
|
+
flex: 1,
|
|
387
|
+
},
|
|
388
|
+
loadingContainer: {
|
|
389
|
+
flex: 1,
|
|
390
|
+
justifyContent: 'center',
|
|
391
|
+
alignItems: 'center',
|
|
392
|
+
},
|
|
393
|
+
});
|
package/src/device.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Platform, Dimensions } from 'react-native';
|
|
2
|
+
import type { DeviceInfo } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Collect device information for attribution matching
|
|
6
|
+
*/
|
|
7
|
+
export function getDeviceInfo(): DeviceInfo {
|
|
8
|
+
const { width, height } = Dimensions.get('screen');
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
platform: Platform.OS === 'ios' ? 'ios' : 'android',
|
|
12
|
+
timezone: getTimezone(),
|
|
13
|
+
screenWidth: Math.round(width),
|
|
14
|
+
screenHeight: Math.round(height),
|
|
15
|
+
language: getLanguage(),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get device timezone
|
|
21
|
+
*/
|
|
22
|
+
function getTimezone(): string | undefined {
|
|
23
|
+
try {
|
|
24
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
25
|
+
} catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get device language in BCP 47 format
|
|
32
|
+
*/
|
|
33
|
+
function getLanguage(): string | undefined {
|
|
34
|
+
try {
|
|
35
|
+
// In React Native, we can access the locale through various means
|
|
36
|
+
// The most reliable is through the Intl API
|
|
37
|
+
const locale = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
38
|
+
return locale;
|
|
39
|
+
} catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mobana/react-native-sdk
|
|
3
|
+
*
|
|
4
|
+
* Simple, privacy-focused mobile app attribution, conversions, and remote flows.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { Mobana, MobanaProvider } from '@mobana/react-native-sdk';
|
|
9
|
+
*
|
|
10
|
+
* // 1. Wrap your app with the provider (in App.tsx)
|
|
11
|
+
* function App() {
|
|
12
|
+
* return (
|
|
13
|
+
* <MobanaProvider>
|
|
14
|
+
* <YourApp />
|
|
15
|
+
* </MobanaProvider>
|
|
16
|
+
* );
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* // 2. Initialize the SDK
|
|
20
|
+
* await Mobana.init({ appId: 'a1b2c3d4' });
|
|
21
|
+
*
|
|
22
|
+
* // 3. Get attribution
|
|
23
|
+
* const attribution = await Mobana.getAttribution();
|
|
24
|
+
*
|
|
25
|
+
* // 4. Track conversions
|
|
26
|
+
* Mobana.trackConversion('signup');
|
|
27
|
+
* Mobana.trackConversion('purchase', 49.99);
|
|
28
|
+
*
|
|
29
|
+
* // 5. Show flows and track post-flow events
|
|
30
|
+
* const result = await Mobana.startFlow('onboarding');
|
|
31
|
+
* if (result.completed) {
|
|
32
|
+
* // Track events after flow closes using result.trackEvent()
|
|
33
|
+
* await result.trackEvent('feature_used');
|
|
34
|
+
* // Link conversions to flow session using result.sessionId
|
|
35
|
+
* await Mobana.trackConversion('purchase', 9.99, result.sessionId);
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @packageDocumentation
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
// Main SDK
|
|
43
|
+
export { Mobana } from './Mobana';
|
|
44
|
+
|
|
45
|
+
// Components
|
|
46
|
+
export { MobanaProvider } from './components/MobanaProvider';
|
|
47
|
+
export type { MobanaProviderProps } from './components/MobanaProvider';
|
|
48
|
+
|
|
49
|
+
// Types - Attribution
|
|
50
|
+
export type {
|
|
51
|
+
MobanaConfig,
|
|
52
|
+
GetAttributionOptions,
|
|
53
|
+
Attribution,
|
|
54
|
+
} from './types';
|
|
55
|
+
|
|
56
|
+
// Types - Flows
|
|
57
|
+
export type {
|
|
58
|
+
FlowConfig,
|
|
59
|
+
FlowResult,
|
|
60
|
+
FlowOptions,
|
|
61
|
+
FlowError,
|
|
62
|
+
HapticStyle,
|
|
63
|
+
LocationPermissionStatus,
|
|
64
|
+
ATTStatus,
|
|
65
|
+
LocationCoordinates,
|
|
66
|
+
} from './types';
|