@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mobana
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://mobana.ai" style="vertical-align: middle;">
|
|
3
|
+
<img alt="Mobana" src="https://mobana.ai/images/logos/mobana-transparent.png" height="32">
|
|
4
|
+
</a>
|
|
5
|
+
<br/>
|
|
6
|
+
<strong style="font-size: 1.3em">Mobana</strong>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
Simple, privacy-focused mobile app attribution, conversions, and remote flows for React Native.
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="https://www.npmjs.com/package/@mobana/react-native-sdk"><img src="https://img.shields.io/npm/v/@mobana/react-native-sdk.svg?style=flat-square" alt="npm version"></a>
|
|
15
|
+
<a href="https://www.npmjs.com/package/@mobana/react-native-sdk"><img src="https://img.shields.io/npm/dm/@mobana/react-native-sdk.svg?style=flat-square" alt="npm downloads"></a>
|
|
16
|
+
<a href="https://github.com/mobana-hq/react-native-sdk/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@mobana/react-native-sdk.svg?style=flat-square" alt="license"></a>
|
|
17
|
+
<img src="https://img.shields.io/badge/platform-iOS%20%7C%20Android-lightgrey?style=flat-square" alt="platforms">
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<a href="https://mobana.ai/docs">Documentation</a> ·
|
|
22
|
+
<a href="https://mobana.ai/docs/quick-start">Quick Start</a> ·
|
|
23
|
+
<a href="https://mobana.ai/docs/sdk/overview">SDK Reference</a> ·
|
|
24
|
+
<a href="https://mobana.ai/docs/flows/bridge-overview">Flow Bridge</a> ·
|
|
25
|
+
<a href="https://mobana.ai/docs/guides/gdpr">Privacy & GDPR</a>
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Core Features
|
|
31
|
+
|
|
32
|
+
- **[Attribution & Deeplinking](https://mobana.ai/attribution)** — Know where your installs come from and pass custom data (promo codes, referral IDs, content) through app store installs. No IDFA/GAID required.
|
|
33
|
+
- **[Conversion Tracking](https://mobana.ai/conversion-tracking)** — Track post-install events and tie them back to campaigns for ROI measurement.
|
|
34
|
+
- **[Flows](https://mobana.ai/flows)** — Display dynamic remote experiences (onboarding, permission prompts, paywalls) built from the Mobana dashboard — no app store updates required.
|
|
35
|
+
|
|
36
|
+
## Requirements
|
|
37
|
+
|
|
38
|
+
| Dependency | Minimum Version |
|
|
39
|
+
|------------|-----------------|
|
|
40
|
+
| React Native | `>= 0.72` |
|
|
41
|
+
| React | `>= 17.0` |
|
|
42
|
+
| Expo SDK | `50+` (Expo Go not supported) |
|
|
43
|
+
| iOS | `13.4+` |
|
|
44
|
+
| Android | API `23+` (Android 6.0) |
|
|
45
|
+
|
|
46
|
+
## Table of Contents
|
|
47
|
+
|
|
48
|
+
- [Installation](#installation)
|
|
49
|
+
- [Bare React Native](#bare-react-native)
|
|
50
|
+
- [Expo](#expo)
|
|
51
|
+
- [Optional Peer Dependencies](#optional-peer-dependencies)
|
|
52
|
+
- [Quick Start](#quick-start)
|
|
53
|
+
- [Initialize the SDK](#1-initialize-the-sdk)
|
|
54
|
+
- [Get Attribution](#2-get-attribution)
|
|
55
|
+
- [Track Conversions](#3-track-conversions)
|
|
56
|
+
- [Show a Flow](#4-show-a-flow-optional)
|
|
57
|
+
- [API](#api)
|
|
58
|
+
- [Flows](#flows)
|
|
59
|
+
- [Privacy & GDPR](#privacy--gdpr)
|
|
60
|
+
- [Documentation](#documentation)
|
|
61
|
+
- [License](#license)
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
The SDK supports both **bare React Native** and **Expo**. Pick the section that matches your project setup — the SDK API is identical in both environments.
|
|
66
|
+
|
|
67
|
+
### Bare React Native
|
|
68
|
+
|
|
69
|
+
Attribution and conversion tracking:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm install @mobana/react-native-sdk \
|
|
73
|
+
@react-native-async-storage/async-storage
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Add [Flows](#flows) support (requires `react-native-webview`):
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm install @mobana/react-native-sdk \
|
|
80
|
+
@react-native-async-storage/async-storage \
|
|
81
|
+
react-native-webview
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
> If your flows use native permissions, you'll need to configure your iOS **Podfile** and Android **AndroidManifest.xml**. See the **[full installation guide](https://mobana.ai/docs/installation)** for platform-specific setup.
|
|
85
|
+
|
|
86
|
+
### Expo
|
|
87
|
+
|
|
88
|
+
Attribution and conversion tracking:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npx expo install @mobana/react-native-sdk \
|
|
92
|
+
@react-native-async-storage/async-storage
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Add [Flows](#flows) support:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npx expo install @mobana/react-native-sdk \
|
|
99
|
+
@react-native-async-storage/async-storage \
|
|
100
|
+
react-native-webview
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Add the plugin to your `app.json`:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"expo": {
|
|
108
|
+
"plugins": ["@mobana/react-native-sdk"]
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
> **Note:** This SDK uses native code — Expo Go is not supported. Use `expo-dev-client` for development builds.
|
|
114
|
+
|
|
115
|
+
### Optional Peer Dependencies
|
|
116
|
+
|
|
117
|
+
If your Flows use permissions, haptics, reviews, or location, install the relevant optional packages:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm install react-native-permissions \
|
|
121
|
+
react-native-haptic-feedback \
|
|
122
|
+
react-native-in-app-review \
|
|
123
|
+
react-native-geolocation-service \
|
|
124
|
+
react-native-safe-area-context
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
> For iOS Podfile setup, Android manifest permissions, and Expo plugin configuration, see the **[full installation guide](https://mobana.ai/docs/installation)**.
|
|
128
|
+
|
|
129
|
+
## Quick Start
|
|
130
|
+
|
|
131
|
+
### 1. Initialize the SDK
|
|
132
|
+
|
|
133
|
+
Call `init` once when your app starts. Get your App ID from the [Mobana dashboard](https://mobana.ai).
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { Mobana } from '@mobana/react-native-sdk';
|
|
137
|
+
|
|
138
|
+
await Mobana.init({
|
|
139
|
+
appId: 'YOUR_APP_ID',
|
|
140
|
+
debug: __DEV__,
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 2. Get Attribution
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
const attribution = await Mobana.getAttribution();
|
|
148
|
+
|
|
149
|
+
if (attribution) {
|
|
150
|
+
console.log(attribution.utm_source); // e.g. "facebook"
|
|
151
|
+
console.log(attribution.utm_campaign); // e.g. "summer_sale"
|
|
152
|
+
console.log(attribution.confidence); // 0.0–1.0
|
|
153
|
+
|
|
154
|
+
if (attribution.data?.promo) {
|
|
155
|
+
applyPromoCode(attribution.data.promo);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`getAttribution()` never throws — it returns `null` when there's no match or on error.
|
|
161
|
+
|
|
162
|
+
### 3. Track Conversions
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
Mobana.trackConversion('signup');
|
|
166
|
+
Mobana.trackConversion('purchase', 49.99);
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 4. Show a Flow (optional)
|
|
170
|
+
|
|
171
|
+
Wrap your app with `MobanaProvider` and start flows by slug:
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
import { MobanaProvider, Mobana } from '@mobana/react-native-sdk';
|
|
175
|
+
|
|
176
|
+
function App() {
|
|
177
|
+
return (
|
|
178
|
+
<MobanaProvider>
|
|
179
|
+
<YourApp />
|
|
180
|
+
</MobanaProvider>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Somewhere in your app
|
|
185
|
+
const result = await Mobana.startFlow('onboarding');
|
|
186
|
+
|
|
187
|
+
if (result.completed) {
|
|
188
|
+
console.log('User completed onboarding!', result.data);
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
> For the full walkthrough, see the **[Quick Start guide](https://mobana.ai/docs/quick-start)**.
|
|
193
|
+
|
|
194
|
+
## API
|
|
195
|
+
|
|
196
|
+
| Method | Description | Reference |
|
|
197
|
+
|--------|-------------|-----------|
|
|
198
|
+
| `Mobana.init(config)` | Initialize the SDK with your App ID | [Docs →](https://mobana.ai/docs/sdk/init) |
|
|
199
|
+
| `Mobana.getAttribution()` | Get install attribution and deeplink data | [Docs →](https://mobana.ai/docs/sdk/get-attribution) |
|
|
200
|
+
| `Mobana.trackConversion(name, value?)` | Track post-install conversion events | [Docs →](https://mobana.ai/docs/sdk/track-conversion) |
|
|
201
|
+
| `Mobana.startFlow(slug, options?)` | Display an in-app flow | [Docs →](https://mobana.ai/docs/sdk/start-flow) |
|
|
202
|
+
| `Mobana.prefetchFlow(slug)` | Prefetch a flow for instant display | [Docs →](https://mobana.ai/docs/sdk/prefetch-flow) |
|
|
203
|
+
| `Mobana.setEnabled(enabled)` | Enable/disable the SDK (GDPR consent) | [Docs →](https://mobana.ai/docs/sdk/set-enabled) |
|
|
204
|
+
| `Mobana.reset()` | Clear stored data, generate new install ID | [Docs →](https://mobana.ai/docs/sdk/reset) |
|
|
205
|
+
| `<MobanaProvider>` | Context provider for Flows (wraps your app) | [Docs →](https://mobana.ai/docs/sdk/provider) |
|
|
206
|
+
|
|
207
|
+
> For full API reference with all options and return types, see the **[SDK Overview](https://mobana.ai/docs/sdk/overview)**.
|
|
208
|
+
|
|
209
|
+
## Flows
|
|
210
|
+
|
|
211
|
+
Flows are rich in-app experiences (onboarding, permission prompts, paywalls, announcements) you build visually in the Mobana dashboard. They run inside a WebView and communicate with your app through a JavaScript bridge.
|
|
212
|
+
|
|
213
|
+
Inside a flow, you have access to attribution data, custom parameters, native permissions, haptics, sounds, and more — all through the `Mobana` bridge object.
|
|
214
|
+
|
|
215
|
+
| Topic | Link |
|
|
216
|
+
|-------|------|
|
|
217
|
+
| What are Flows? | [mobana.ai/flows](https://mobana.ai/flows) |
|
|
218
|
+
| Building Flows | [Guide →](https://mobana.ai/docs/guides/building-flows) |
|
|
219
|
+
| Bridge Overview | [Docs →](https://mobana.ai/docs/flows/bridge-overview) |
|
|
220
|
+
| Permissions | [Docs →](https://mobana.ai/docs/flows/bridge-permissions) |
|
|
221
|
+
| Events & Tracking | [Guide →](https://mobana.ai/docs/guides/flow-events-tracking) |
|
|
222
|
+
| CSS Variables | [Docs →](https://mobana.ai/docs/flows/css-variables) |
|
|
223
|
+
|
|
224
|
+
## Privacy & GDPR
|
|
225
|
+
|
|
226
|
+
Mobana is built with privacy at its core:
|
|
227
|
+
|
|
228
|
+
- **No device IDs** — IDFA/GAID are never required or collected
|
|
229
|
+
- **Privacy-first matching** — attribution works without invasive device fingerprinting
|
|
230
|
+
- **Minimal data** — only what's needed for attribution, nothing more
|
|
231
|
+
- **Opt-out support** — call `Mobana.setEnabled(false)` to disable all tracking
|
|
232
|
+
- **GDPR/CCPA compliant** — see our [GDPR guide](https://mobana.ai/docs/guides/gdpr) and [Privacy Policy](https://mobana.ai/privacy)
|
|
233
|
+
|
|
234
|
+
## Documentation
|
|
235
|
+
|
|
236
|
+
| Resource | Link |
|
|
237
|
+
|----------|------|
|
|
238
|
+
| Full Documentation | [mobana.ai/docs](https://mobana.ai/docs) |
|
|
239
|
+
| Installation Guide | [mobana.ai/docs/installation](https://mobana.ai/docs/installation) |
|
|
240
|
+
| Quick Start | [mobana.ai/docs/quick-start](https://mobana.ai/docs/quick-start) |
|
|
241
|
+
| SDK Reference | [mobana.ai/docs/sdk/overview](https://mobana.ai/docs/sdk/overview) |
|
|
242
|
+
| Flow Bridge API | [mobana.ai/docs/flows/bridge-overview](https://mobana.ai/docs/flows/bridge-overview) |
|
|
243
|
+
| Custom Endpoints | [mobana.ai/docs/guides/custom-endpoints](https://mobana.ai/docs/guides/custom-endpoints) |
|
|
244
|
+
| GDPR & Privacy | [mobana.ai/docs/guides/gdpr](https://mobana.ai/docs/guides/gdpr) |
|
|
245
|
+
| Test Setup | [mobana.ai/docs/test-setup](https://mobana.ai/docs/test-setup) |
|
|
246
|
+
|
|
247
|
+
## License
|
|
248
|
+
|
|
249
|
+
MIT — see [LICENSE](./LICENSE) for details.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
apply plugin: 'com.android.library'
|
|
2
|
+
apply plugin: 'kotlin-android'
|
|
3
|
+
|
|
4
|
+
def safeExtGet(prop, fallback) {
|
|
5
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
android {
|
|
9
|
+
namespace 'ai.mobana.sdk'
|
|
10
|
+
compileSdkVersion safeExtGet('compileSdkVersion', 34)
|
|
11
|
+
|
|
12
|
+
defaultConfig {
|
|
13
|
+
minSdkVersion safeExtGet('minSdkVersion', 23)
|
|
14
|
+
targetSdkVersion safeExtGet('targetSdkVersion', 34)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
buildTypes {
|
|
18
|
+
release {
|
|
19
|
+
minifyEnabled false
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
compileOptions {
|
|
24
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
25
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
kotlinOptions {
|
|
29
|
+
jvmTarget = '17'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
sourceSets {
|
|
33
|
+
main {
|
|
34
|
+
java.srcDirs = ['src/main/java']
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
repositories {
|
|
40
|
+
google()
|
|
41
|
+
mavenCentral()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
dependencies {
|
|
45
|
+
implementation "com.facebook.react:react-native:+"
|
|
46
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:${safeExtGet('kotlinVersion', '1.9.0')}"
|
|
47
|
+
|
|
48
|
+
// Google Play Install Referrer API
|
|
49
|
+
implementation 'com.android.installreferrer:installreferrer:2.2'
|
|
50
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
package ai.mobana.sdk
|
|
2
|
+
|
|
3
|
+
import com.android.installreferrer.api.InstallReferrerClient
|
|
4
|
+
import com.android.installreferrer.api.InstallReferrerStateListener
|
|
5
|
+
import com.facebook.react.bridge.*
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Native module for Mobana SDK
|
|
9
|
+
* Provides Android Install Referrer functionality
|
|
10
|
+
*/
|
|
11
|
+
class MobanaModule(reactContext: ReactApplicationContext) :
|
|
12
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
13
|
+
|
|
14
|
+
override fun getName(): String = "Mobana"
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get the Install Referrer string from Google Play Store
|
|
18
|
+
* Returns the full referrer string which contains UTM params and dacid
|
|
19
|
+
*/
|
|
20
|
+
@ReactMethod
|
|
21
|
+
fun getInstallReferrer(promise: Promise) {
|
|
22
|
+
val context = reactApplicationContext
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
val referrerClient = InstallReferrerClient.newBuilder(context).build()
|
|
26
|
+
|
|
27
|
+
referrerClient.startConnection(object : InstallReferrerStateListener {
|
|
28
|
+
override fun onInstallReferrerSetupFinished(responseCode: Int) {
|
|
29
|
+
when (responseCode) {
|
|
30
|
+
InstallReferrerClient.InstallReferrerResponse.OK -> {
|
|
31
|
+
try {
|
|
32
|
+
val response = referrerClient.installReferrer
|
|
33
|
+
val referrer = response.installReferrer
|
|
34
|
+
referrerClient.endConnection()
|
|
35
|
+
promise.resolve(referrer)
|
|
36
|
+
} catch (e: Exception) {
|
|
37
|
+
referrerClient.endConnection()
|
|
38
|
+
promise.resolve(null)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
InstallReferrerClient.InstallReferrerResponse.FEATURE_NOT_SUPPORTED -> {
|
|
42
|
+
// Install Referrer API not supported on this device
|
|
43
|
+
referrerClient.endConnection()
|
|
44
|
+
promise.resolve(null)
|
|
45
|
+
}
|
|
46
|
+
InstallReferrerClient.InstallReferrerResponse.SERVICE_UNAVAILABLE -> {
|
|
47
|
+
// Connection could not be established
|
|
48
|
+
referrerClient.endConnection()
|
|
49
|
+
promise.resolve(null)
|
|
50
|
+
}
|
|
51
|
+
else -> {
|
|
52
|
+
referrerClient.endConnection()
|
|
53
|
+
promise.resolve(null)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
override fun onInstallReferrerServiceDisconnected() {
|
|
59
|
+
// Connection was lost, but promise may already be resolved
|
|
60
|
+
// Do nothing - don't reject as this can happen after successful read
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
} catch (e: Exception) {
|
|
64
|
+
promise.resolve(null)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
package ai.mobana.sdk
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* React Native package that registers the Mobana native module
|
|
10
|
+
*/
|
|
11
|
+
class MobanaPackage : ReactPackage {
|
|
12
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
13
|
+
return listOf(MobanaModule(reactContext))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
17
|
+
return emptyList()
|
|
18
|
+
}
|
|
19
|
+
}
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expo Config Plugin for @mobana/react-native-sdk
|
|
3
|
+
*
|
|
4
|
+
* This plugin automatically configures native dependencies for Expo projects.
|
|
5
|
+
*
|
|
6
|
+
* Usage in app.json / app.config.js:
|
|
7
|
+
* {
|
|
8
|
+
* "expo": {
|
|
9
|
+
* "plugins": [
|
|
10
|
+
* // Attribution-only (no special permissions needed):
|
|
11
|
+
* "@mobana/react-native-sdk"
|
|
12
|
+
*
|
|
13
|
+
* // Or with Flows that need permissions:
|
|
14
|
+
* ["@mobana/react-native-sdk", {
|
|
15
|
+
* "permissions": ["Notifications", "AppTrackingTransparency"]
|
|
16
|
+
* }]
|
|
17
|
+
* ]
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* Available permissions (opt-in, none enabled by default):
|
|
22
|
+
* - Notifications (for push notification prompts)
|
|
23
|
+
* - AppTrackingTransparency (for ATT prompts on iOS 14.5+)
|
|
24
|
+
* - LocationWhenInUse (for location-based flows)
|
|
25
|
+
* - LocationAlways (for background location flows)
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
withProjectBuildGradle,
|
|
30
|
+
withMainApplication,
|
|
31
|
+
withDangerousMod,
|
|
32
|
+
withInfoPlist,
|
|
33
|
+
withAndroidManifest,
|
|
34
|
+
} = require('@expo/config-plugins');
|
|
35
|
+
const fs = require('fs');
|
|
36
|
+
const path = require('path');
|
|
37
|
+
|
|
38
|
+
// No permissions enabled by default - users must explicitly opt-in
|
|
39
|
+
// This prevents unexpected permission entries in Info.plist / AndroidManifest
|
|
40
|
+
const DEFAULT_PERMISSIONS = [];
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Add Install Referrer dependency to Android build.gradle
|
|
44
|
+
*/
|
|
45
|
+
function withInstallReferrerDependency(config) {
|
|
46
|
+
return withProjectBuildGradle(config, (config) => {
|
|
47
|
+
if (config.modResults.language === 'groovy') {
|
|
48
|
+
const buildGradle = config.modResults.contents;
|
|
49
|
+
|
|
50
|
+
// Check if already added
|
|
51
|
+
if (!buildGradle.includes('com.android.installreferrer:installreferrer')) {
|
|
52
|
+
// Add to allprojects dependencies
|
|
53
|
+
const allProjectsPattern = /allprojects\s*\{[\s\S]*?repositories\s*\{/;
|
|
54
|
+
|
|
55
|
+
if (allProjectsPattern.test(buildGradle)) {
|
|
56
|
+
// Project has allprojects block, the dependency will be resolved
|
|
57
|
+
// through the library's own build.gradle
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return config;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Add MobanaPackage to MainApplication.java
|
|
67
|
+
*/
|
|
68
|
+
function withMobanaPackage(config) {
|
|
69
|
+
return withMainApplication(config, (config) => {
|
|
70
|
+
const mainApplication = config.modResults.contents;
|
|
71
|
+
|
|
72
|
+
// Check if already imported
|
|
73
|
+
if (!mainApplication.includes('ai.mobana.sdk.MobanaPackage')) {
|
|
74
|
+
// Add import
|
|
75
|
+
const importPattern = /^import.*$/m;
|
|
76
|
+
config.modResults.contents = mainApplication.replace(
|
|
77
|
+
importPattern,
|
|
78
|
+
(match) => `${match}\nimport ai.mobana.sdk.MobanaPackage;`
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Add to getPackages
|
|
82
|
+
const packagesPattern = /new PackageList\(this\)\.getPackages\(\)/;
|
|
83
|
+
if (packagesPattern.test(config.modResults.contents)) {
|
|
84
|
+
// Using autolinking, package should be auto-registered
|
|
85
|
+
// No manual addition needed
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return config;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Add required Android permissions to AndroidManifest.xml
|
|
95
|
+
* This enables react-native-permissions to request these permissions at runtime
|
|
96
|
+
*/
|
|
97
|
+
function withAndroidPermissions(config, permissions) {
|
|
98
|
+
// Skip if no permissions configured
|
|
99
|
+
if (!permissions || permissions.length === 0) {
|
|
100
|
+
return config;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return withAndroidManifest(config, (config) => {
|
|
104
|
+
const manifest = config.modResults.manifest;
|
|
105
|
+
|
|
106
|
+
// Ensure uses-permission array exists
|
|
107
|
+
if (!manifest['uses-permission']) {
|
|
108
|
+
manifest['uses-permission'] = [];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Helper to add a permission if not already present
|
|
112
|
+
const addPermission = (permissionName) => {
|
|
113
|
+
const exists = manifest['uses-permission'].some(
|
|
114
|
+
(p) => p.$?.['android:name'] === permissionName
|
|
115
|
+
);
|
|
116
|
+
if (!exists) {
|
|
117
|
+
manifest['uses-permission'].push({
|
|
118
|
+
$: { 'android:name': permissionName },
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Add POST_NOTIFICATIONS for Android 13+ (API 33+)
|
|
124
|
+
if (permissions.includes('Notifications')) {
|
|
125
|
+
addPermission('android.permission.POST_NOTIFICATIONS');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Add location permissions
|
|
129
|
+
if (permissions.includes('LocationWhenInUse') || permissions.includes('LocationAlways')) {
|
|
130
|
+
addPermission('android.permission.ACCESS_FINE_LOCATION');
|
|
131
|
+
addPermission('android.permission.ACCESS_COARSE_LOCATION');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Add background location permission
|
|
135
|
+
if (permissions.includes('LocationAlways')) {
|
|
136
|
+
addPermission('android.permission.ACCESS_BACKGROUND_LOCATION');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return config;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Configure iOS Podfile for react-native-permissions
|
|
145
|
+
* This adds the setup_permissions call to enable the required permission pods
|
|
146
|
+
*/
|
|
147
|
+
function withReactNativePermissionsPodfile(config, permissions) {
|
|
148
|
+
// Skip if no permissions configured - don't inject setup_permissions at all
|
|
149
|
+
if (!permissions || permissions.length === 0) {
|
|
150
|
+
return config;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return withDangerousMod(config, [
|
|
154
|
+
'ios',
|
|
155
|
+
async (config) => {
|
|
156
|
+
const podfilePath = path.join(config.modRequest.platformProjectRoot, 'Podfile');
|
|
157
|
+
|
|
158
|
+
if (!fs.existsSync(podfilePath)) {
|
|
159
|
+
return config;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let podfileContents = fs.readFileSync(podfilePath, 'utf-8');
|
|
163
|
+
|
|
164
|
+
// Check if react-native-permissions setup is already configured
|
|
165
|
+
if (podfileContents.includes('react-native-permissions/scripts/setup')) {
|
|
166
|
+
return config;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Build the setup_permissions call
|
|
170
|
+
const permissionsArray = permissions.map(p => ` '${p}'`).join(',\n');
|
|
171
|
+
const setupBlock = `
|
|
172
|
+
# react-native-permissions setup for Mobana SDK
|
|
173
|
+
def node_require(script)
|
|
174
|
+
require Pod::Executable.execute_command('node', ['-p',
|
|
175
|
+
"require.resolve('\#{script}', {paths: [process.argv[1]]})",
|
|
176
|
+
__dir__]).strip
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
node_require('react-native-permissions/scripts/setup.rb')
|
|
180
|
+
|
|
181
|
+
setup_permissions([
|
|
182
|
+
${permissionsArray}
|
|
183
|
+
])
|
|
184
|
+
`;
|
|
185
|
+
|
|
186
|
+
// Insert after the 'platform :ios' line or at the beginning of target block
|
|
187
|
+
const platformPattern = /^platform :ios.*$/m;
|
|
188
|
+
const targetPattern = /^target\s+['"].*['"]\s+do$/m;
|
|
189
|
+
|
|
190
|
+
if (platformPattern.test(podfileContents)) {
|
|
191
|
+
podfileContents = podfileContents.replace(
|
|
192
|
+
platformPattern,
|
|
193
|
+
(match) => `${match}\n${setupBlock}`
|
|
194
|
+
);
|
|
195
|
+
} else if (targetPattern.test(podfileContents)) {
|
|
196
|
+
podfileContents = podfileContents.replace(
|
|
197
|
+
targetPattern,
|
|
198
|
+
(match) => `${setupBlock}\n${match}`
|
|
199
|
+
);
|
|
200
|
+
} else {
|
|
201
|
+
// Fallback: prepend to file
|
|
202
|
+
podfileContents = setupBlock + '\n' + podfileContents;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
fs.writeFileSync(podfilePath, podfileContents);
|
|
206
|
+
|
|
207
|
+
return config;
|
|
208
|
+
},
|
|
209
|
+
]);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Add required iOS Info.plist entries for permissions
|
|
214
|
+
*/
|
|
215
|
+
function withPermissionInfoPlist(config, permissions) {
|
|
216
|
+
// Skip if no permissions configured
|
|
217
|
+
if (!permissions || permissions.length === 0) {
|
|
218
|
+
return config;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return withInfoPlist(config, (config) => {
|
|
222
|
+
// Add usage description strings for permissions that require them
|
|
223
|
+
if (permissions.includes('Notifications')) {
|
|
224
|
+
// Notifications don't require a usage description, but we can add a background mode
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (permissions.includes('AppTrackingTransparency')) {
|
|
228
|
+
if (!config.modResults.NSUserTrackingUsageDescription) {
|
|
229
|
+
config.modResults.NSUserTrackingUsageDescription =
|
|
230
|
+
'This identifier will be used to deliver personalized ads to you.';
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (permissions.includes('LocationWhenInUse') || permissions.includes('LocationAlways')) {
|
|
235
|
+
if (!config.modResults.NSLocationWhenInUseUsageDescription) {
|
|
236
|
+
config.modResults.NSLocationWhenInUseUsageDescription =
|
|
237
|
+
'This app needs access to your location.';
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (permissions.includes('LocationAlways')) {
|
|
242
|
+
if (!config.modResults.NSLocationAlwaysAndWhenInUseUsageDescription) {
|
|
243
|
+
config.modResults.NSLocationAlwaysAndWhenInUseUsageDescription =
|
|
244
|
+
'This app needs access to your location in the background.';
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return config;
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Main plugin entry point
|
|
254
|
+
* @param {object} config - Expo config
|
|
255
|
+
* @param {object} props - Plugin props
|
|
256
|
+
* @param {string[]} props.permissions - Array of permissions to enable
|
|
257
|
+
*/
|
|
258
|
+
function withMobana(config, props = {}) {
|
|
259
|
+
const permissions = props.permissions || DEFAULT_PERMISSIONS;
|
|
260
|
+
|
|
261
|
+
// Android configuration
|
|
262
|
+
config = withInstallReferrerDependency(config);
|
|
263
|
+
config = withAndroidPermissions(config, permissions);
|
|
264
|
+
// Note: For Expo SDK 50+, native modules are auto-linked
|
|
265
|
+
// withMobanaPackage is kept for older versions
|
|
266
|
+
|
|
267
|
+
// iOS configuration for react-native-permissions
|
|
268
|
+
config = withReactNativePermissionsPodfile(config, permissions);
|
|
269
|
+
config = withPermissionInfoPlist(config, permissions);
|
|
270
|
+
|
|
271
|
+
return config;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
module.exports = withMobana;
|