appsprint-react-native 0.2.0 → 1.0.4
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/README.md +173 -68
- package/android/build.gradle +4 -2
- package/android/libs/appsprint-sdk.aar +0 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/kotlin/com/appsprint/AppSprintBridgeModule.kt +125 -29
- package/appsprint-react-native.podspec +6 -1
- package/ios/AppSprintBridge.m +6 -0
- package/ios/AppSprintBridge.swift +92 -16
- package/ios/AppSprintSDK.xcframework/ios-arm64/AppSprintSDK.framework/AppSprintSDK +0 -0
- package/ios/AppSprintSDK.xcframework/ios-arm64/AppSprintSDK.framework/Info.plist +0 -0
- package/ios/AppSprintSDK.xcframework/ios-arm64/AppSprintSDK.framework/Modules/AppSprintSDK.swiftmodule/arm64-apple-ios.abi.json +4757 -952
- package/ios/AppSprintSDK.xcframework/ios-arm64/AppSprintSDK.framework/Modules/AppSprintSDK.swiftmodule/arm64-apple-ios.package.swiftinterface +102 -30
- package/ios/AppSprintSDK.xcframework/ios-arm64/AppSprintSDK.framework/Modules/AppSprintSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +102 -30
- package/ios/AppSprintSDK.xcframework/ios-arm64/AppSprintSDK.framework/Modules/AppSprintSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/AppSprintSDK.xcframework/ios-arm64/AppSprintSDK.framework/Modules/AppSprintSDK.swiftmodule/arm64-apple-ios.swiftinterface +102 -30
- package/ios/AppSprintSDK.xcframework/ios-arm64/AppSprintSDK.framework/PrivacyInfo.xcprivacy +91 -0
- package/ios/AppSprintSDK.xcframework/ios-arm64_x86_64-simulator/AppSprintSDK.framework/AppSprintSDK +0 -0
- package/ios/AppSprintSDK.xcframework/ios-arm64_x86_64-simulator/AppSprintSDK.framework/Info.plist +0 -0
- package/ios/AppSprintSDK.xcframework/ios-arm64_x86_64-simulator/AppSprintSDK.framework/Modules/AppSprintSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +4757 -952
- package/ios/AppSprintSDK.xcframework/ios-arm64_x86_64-simulator/AppSprintSDK.framework/Modules/AppSprintSDK.swiftmodule/arm64-apple-ios-simulator.package.swiftinterface +102 -30
- package/ios/AppSprintSDK.xcframework/ios-arm64_x86_64-simulator/AppSprintSDK.framework/Modules/AppSprintSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +102 -30
- package/ios/AppSprintSDK.xcframework/ios-arm64_x86_64-simulator/AppSprintSDK.framework/Modules/AppSprintSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/AppSprintSDK.xcframework/ios-arm64_x86_64-simulator/AppSprintSDK.framework/Modules/AppSprintSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +102 -30
- package/ios/AppSprintSDK.xcframework/ios-arm64_x86_64-simulator/AppSprintSDK.framework/Modules/AppSprintSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +4757 -952
- package/ios/AppSprintSDK.xcframework/ios-arm64_x86_64-simulator/AppSprintSDK.framework/Modules/AppSprintSDK.swiftmodule/x86_64-apple-ios-simulator.package.swiftinterface +102 -30
- package/ios/AppSprintSDK.xcframework/ios-arm64_x86_64-simulator/AppSprintSDK.framework/Modules/AppSprintSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +102 -30
- package/ios/AppSprintSDK.xcframework/ios-arm64_x86_64-simulator/AppSprintSDK.framework/Modules/AppSprintSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/AppSprintSDK.xcframework/ios-arm64_x86_64-simulator/AppSprintSDK.framework/Modules/AppSprintSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +102 -30
- package/ios/AppSprintSDK.xcframework/ios-arm64_x86_64-simulator/AppSprintSDK.framework/PrivacyInfo.xcprivacy +91 -0
- package/ios/AppSprintSDK.xcframework/ios-arm64_x86_64-simulator/AppSprintSDK.framework/_CodeSignature/CodeResources +1 -1
- package/lib/commonjs/AppSprint.js +40 -4
- package/lib/commonjs/NativeAppSprint.js +15 -3
- package/lib/module/AppSprint.js +40 -4
- package/lib/module/NativeAppSprint.js +15 -3
- package/lib/typescript/AppSprint.d.ts +6 -4
- package/lib/typescript/index.d.ts +1 -1
- package/lib/typescript/types.d.ts +53 -7
- package/package.json +5 -2
- package/plugin/build/index.js +37 -10
- package/plugin/src/index.ts +44 -1
- package/src/AppSprint.ts +85 -10
- package/src/NativeAppSprint.ts +15 -3
- package/src/index.ts +1 -0
- package/src/types.ts +87 -7
package/README.md
CHANGED
|
@@ -1,22 +1,41 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AppSprint for React Native
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Mobile attribution and event tracking for React Native, with native iOS and Android SDKs bundled inside. Works with bare React Native and Expo. The JS bridge is thin: it forwards calls to the same native engines as our standalone iOS and Android SDKs.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- React Native 0.71 or later
|
|
8
|
+
- React 18 or later
|
|
9
|
+
- iOS 14.0 or later
|
|
10
|
+
- Android 7.0 (API 24) or later
|
|
11
|
+
|
|
12
|
+
## Install
|
|
6
13
|
|
|
7
14
|
```bash
|
|
8
15
|
npm install appsprint-react-native
|
|
9
16
|
```
|
|
10
17
|
|
|
18
|
+
or
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
yarn add appsprint-react-native
|
|
22
|
+
```
|
|
23
|
+
|
|
11
24
|
### iOS
|
|
12
25
|
|
|
13
26
|
```bash
|
|
14
27
|
cd ios && pod install
|
|
15
28
|
```
|
|
16
29
|
|
|
17
|
-
|
|
30
|
+
The native pod is vendored inside the package, so no extra repository setup is needed.
|
|
31
|
+
|
|
32
|
+
### Android
|
|
33
|
+
|
|
34
|
+
Auto-linking handles the Android side. The package's manifest declares `INTERNET` and `com.google.android.gms.permission.AD_ID`, which merge into your app at build time.
|
|
35
|
+
|
|
36
|
+
### Expo
|
|
18
37
|
|
|
19
|
-
If you use Expo prebuild, add the plugin to
|
|
38
|
+
If you use Expo prebuild, add the config plugin to `app.json` or `app.config.js`:
|
|
20
39
|
|
|
21
40
|
```json
|
|
22
41
|
{
|
|
@@ -24,23 +43,23 @@ If you use Expo prebuild, add the plugin to your app config:
|
|
|
24
43
|
[
|
|
25
44
|
"appsprint-react-native",
|
|
26
45
|
{
|
|
27
|
-
"trackingDescription": "This identifier
|
|
46
|
+
"trackingDescription": "This identifier helps us deliver personalized ads."
|
|
28
47
|
}
|
|
29
48
|
]
|
|
30
49
|
]
|
|
31
50
|
}
|
|
32
51
|
```
|
|
33
52
|
|
|
34
|
-
|
|
53
|
+
The plugin injects `NSUserTrackingUsageDescription` on iOS and the Android permissions during prebuild.
|
|
35
54
|
|
|
36
|
-
|
|
|
55
|
+
| Plugin option | Type | Description | Default |
|
|
37
56
|
|---|---|---|---|
|
|
38
|
-
| `trackingDescription` | `string` |
|
|
39
|
-
| `advertisingAttributionEndpoint` | `string` | Sets `NSAdvertisingAttributionReportEndpoint
|
|
57
|
+
| `trackingDescription` | `string` | Text for the ATT permission prompt. | `"This identifier will be used to deliver personalized ads to you."` |
|
|
58
|
+
| `advertisingAttributionEndpoint` | `string` | Sets `NSAdvertisingAttributionReportEndpoint`. | none |
|
|
40
59
|
|
|
41
|
-
##
|
|
60
|
+
## Configure
|
|
42
61
|
|
|
43
|
-
|
|
62
|
+
Call `configure` once at app startup. It returns a promise that resolves after local state is restored; install registration runs in the background:
|
|
44
63
|
|
|
45
64
|
```tsx
|
|
46
65
|
import { AppSprint } from "appsprint-react-native";
|
|
@@ -50,28 +69,48 @@ await AppSprint.configure({
|
|
|
50
69
|
});
|
|
51
70
|
```
|
|
52
71
|
|
|
53
|
-
|
|
72
|
+
A typical app calls this from `App.tsx` (or `app/_layout.tsx` on Expo Router):
|
|
54
73
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
```tsx
|
|
75
|
+
import { useEffect } from "react";
|
|
76
|
+
import { AppSprint, NativeAppSprint } from "appsprint-react-native";
|
|
77
|
+
|
|
78
|
+
export default function App() {
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
(async () => {
|
|
81
|
+
await AppSprint.configure({ apiKey: "YOUR_API_KEY" });
|
|
82
|
+
|
|
83
|
+
// iOS only. Skipped at runtime on Android.
|
|
84
|
+
await NativeAppSprint.requestTrackingAuthorization();
|
|
85
|
+
})();
|
|
86
|
+
}, []);
|
|
63
87
|
|
|
64
|
-
|
|
88
|
+
return <RootNavigator />;
|
|
89
|
+
}
|
|
90
|
+
```
|
|
65
91
|
|
|
66
|
-
|
|
92
|
+
### Configuration options
|
|
67
93
|
|
|
68
|
-
|
|
94
|
+
| Option | Type | Default | What it does |
|
|
95
|
+
|---|---|---|---|
|
|
96
|
+
| `apiKey` | `string` | required | Your AppSprint app key. |
|
|
97
|
+
| `apiUrl` | `string` | `https://api.appsprint.app` | Override for staging or self-hosted environments. |
|
|
98
|
+
| `endpointBaseUrl` | `string` | alias for `apiUrl` | Accepted for compatibility. |
|
|
99
|
+
| `enableAppleAdsAttribution` | `boolean` | `true` | iOS only. Fetches Apple AdServices at install time. |
|
|
100
|
+
| `customerUserId` | `string \| null` | `null` | Your internal user ID. Persists across launches and replays if the first send fails. |
|
|
101
|
+
| `autoTrackSessions` | `boolean` | `true` | Fires `session_start` on `configure()` and on foreground, debounced to one event per 30 minutes. |
|
|
102
|
+
| `autoRefreshAttribution` | `boolean` | `true` | Refreshes attribution from the backend on `configure()` and on foreground. |
|
|
103
|
+
| `isDebug` | `boolean` | `false` | Forces debug-level logging on the native side. |
|
|
104
|
+
| `logLevel` | `0 \| 1 \| 2 \| 3` | `2` | `0 = debug`, `1 = info`, `2 = warn`, `3 = error`. |
|
|
105
|
+
|
|
106
|
+
## Track events
|
|
69
107
|
|
|
70
108
|
```tsx
|
|
71
109
|
import { AppSprint } from "appsprint-react-native";
|
|
72
110
|
|
|
73
111
|
await AppSprint.sendEvent("login");
|
|
74
112
|
await AppSprint.sendEvent("sign_up");
|
|
113
|
+
|
|
75
114
|
await AppSprint.sendEvent("purchase", null, {
|
|
76
115
|
revenue: 9.99,
|
|
77
116
|
currency: "USD",
|
|
@@ -83,52 +122,65 @@ await AppSprint.sendEvent("custom", "onboarding_step", {
|
|
|
83
122
|
});
|
|
84
123
|
```
|
|
85
124
|
|
|
86
|
-
|
|
125
|
+
`sendEvent` resolves once the native side has queued the event locally. The actual HTTP send happens on the next flush trigger (foreground, background, or another `sendEvent`).
|
|
87
126
|
|
|
88
|
-
|
|
127
|
+
### Built-in event types
|
|
89
128
|
|
|
90
|
-
|
|
129
|
+
`session_start`, `login`, `sign_up`, `register`, `purchase`, `subscribe`, `start_trial`, `add_payment_info`, `add_to_cart`, `add_to_wishlist`, `initiate_checkout`, `view_content`, `view_item`, `search`, `share`, `tutorial_complete`, `achieve_level`, `level_start`, `level_complete`, `custom`.
|
|
91
130
|
|
|
92
|
-
|
|
93
|
-
- Revenue fields are accepted through `params.revenue` and `params.currency`.
|
|
94
|
-
- If an event cannot be delivered, it is queued locally and retried on the next initialization or explicit flush.
|
|
131
|
+
### Revenue events
|
|
95
132
|
|
|
96
|
-
|
|
133
|
+
Pass `revenue` (or `price` as an alias) plus `currency`. Currency must be a 3-letter ISO code; anything else is dropped on the native side before the request goes out.
|
|
97
134
|
|
|
98
|
-
|
|
135
|
+
```tsx
|
|
136
|
+
await AppSprint.sendEvent("subscribe", null, {
|
|
137
|
+
revenue: 4.99,
|
|
138
|
+
currency: "EUR",
|
|
139
|
+
plan: "monthly",
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Custom events
|
|
99
144
|
|
|
100
145
|
```tsx
|
|
101
|
-
|
|
146
|
+
await AppSprint.sendEvent("custom", "level_skip", { level: 12 });
|
|
102
147
|
```
|
|
103
148
|
|
|
104
|
-
|
|
149
|
+
Use the second argument (`name`) to label the event. Keep it stable so your dashboard groups it correctly.
|
|
105
150
|
|
|
106
|
-
|
|
107
|
-
- `sendEvent(eventType, name?, params?)` sends or queues an event.
|
|
108
|
-
- `sendTestEvent()` sends a diagnostic event and returns `{ success, message }`.
|
|
109
|
-
- `flush()` retries queued events immediately.
|
|
110
|
-
- `clearData()` clears cached SDK state and the local event queue.
|
|
111
|
-
- `isSdkDisabled()` returns whether the SDK has been disabled because the API key was rejected.
|
|
112
|
-
- `setCustomerUserId(userId)` updates the customer user id locally and remotely when possible.
|
|
113
|
-
- `getAppSprintId()` returns the cached AppSprint install identifier, if available.
|
|
114
|
-
- `getAttribution()` returns the last cached attribution result, if available.
|
|
115
|
-
- `enableAppleAdsAttribution()` re-enables Apple Ads attribution in the current runtime config.
|
|
116
|
-
- `isInitialized()` reports whether `configure()` completed.
|
|
117
|
-
- `destroy()` removes SDK listeners.
|
|
151
|
+
## Read attribution
|
|
118
152
|
|
|
119
|
-
|
|
153
|
+
Once an install registers, attribution is cached on the native side. You can read it any time:
|
|
120
154
|
|
|
121
155
|
```tsx
|
|
122
|
-
|
|
156
|
+
const attribution = await AppSprint.getAttribution();
|
|
157
|
+
const appsprintId = await AppSprint.getAppSprintId();
|
|
158
|
+
const params = await AppSprint.getAttributionParams();
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`AttributionResult.source` is one of `apple_ads`, `tracking_link`, or `organic`.
|
|
162
|
+
|
|
163
|
+
### Forward to RevenueCat or Superwall
|
|
164
|
+
|
|
165
|
+
`getAttributionParams()` returns a flat `Record<string, string>` shaped for partner SDKs:
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
import Purchases from "react-native-purchases";
|
|
169
|
+
|
|
170
|
+
const params = await AppSprint.getAttributionParams();
|
|
171
|
+
Purchases.setAttributes(params);
|
|
123
172
|
```
|
|
124
173
|
|
|
125
|
-
|
|
174
|
+
### Manual refresh
|
|
126
175
|
|
|
127
|
-
- `
|
|
128
|
-
- `getAdServicesToken()`
|
|
129
|
-
- `requestTrackingAuthorization()`
|
|
176
|
+
If you need the latest server-side resolution (for example after granting ATT mid-session), call `refreshAttribution()`:
|
|
130
177
|
|
|
131
|
-
|
|
178
|
+
```tsx
|
|
179
|
+
const updated = await AppSprint.refreshAttribution();
|
|
180
|
+
console.log("source =", updated?.source);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## App Tracking Transparency (iOS only)
|
|
132
184
|
|
|
133
185
|
```tsx
|
|
134
186
|
import { NativeAppSprint } from "appsprint-react-native";
|
|
@@ -136,38 +188,91 @@ import { NativeAppSprint } from "appsprint-react-native";
|
|
|
136
188
|
const authorized = await NativeAppSprint.requestTrackingAuthorization();
|
|
137
189
|
```
|
|
138
190
|
|
|
139
|
-
|
|
191
|
+
The helper waits internally for the app to reach foreground-active before showing the system prompt. If you call it during initial mount, it will queue and run when the user gets to your first screen.
|
|
140
192
|
|
|
141
|
-
|
|
193
|
+
For bare React Native apps, add `NSUserTrackingUsageDescription` to `ios/<App>/Info.plist`. Expo users get this through the config plugin's `trackingDescription` option.
|
|
142
194
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
195
|
+
`NativeAppSprint.requestTrackingAuthorization()` resolves `true` on Android without prompting; ATT is iOS-only.
|
|
196
|
+
|
|
197
|
+
## Google Advertising ID (Android only)
|
|
198
|
+
|
|
199
|
+
The native Android SDK reads GAID during install registration, off the main thread, honoring Limit Ad Tracking and dropping the all-zero ID. If your app cannot collect advertising IDs (children's apps, regional policies), remove the permission in your host app manifest:
|
|
200
|
+
|
|
201
|
+
```xml
|
|
202
|
+
<manifest xmlns:tools="http://schemas.android.com/tools" ...>
|
|
203
|
+
<uses-permission
|
|
204
|
+
android:name="com.google.android.gms.permission.AD_ID"
|
|
205
|
+
tools:node="remove" />
|
|
206
|
+
</manifest>
|
|
146
207
|
```
|
|
147
208
|
|
|
148
|
-
|
|
209
|
+
## What happens behind the scenes
|
|
149
210
|
|
|
150
|
-
`
|
|
211
|
+
- `configure()` resolves after local-state restore. Install registration runs in the background and retries with backoff on transient failures.
|
|
212
|
+
- Events queue locally on native storage and survive app restarts.
|
|
213
|
+
- iOS uses connectivity-aware networking, so transient offline windows queue inside the OS rather than failing fast.
|
|
214
|
+
- A rejected API key (`401` or `403`) disables the SDK on the native side. Future events drop until `clearData()` is called.
|
|
215
|
+
- Late identity updates (`setCustomerUserId`, iOS Apple Ads opt-in) retry automatically on the next `configure()` or foreground.
|
|
151
216
|
|
|
152
|
-
##
|
|
217
|
+
## Privacy
|
|
153
218
|
|
|
154
|
-
|
|
155
|
-
- Queued events are flushed after `configure()` and when the app moves to the background.
|
|
156
|
-
- Failed flushes keep the unsent events queued for a later retry.
|
|
157
|
-
- A rejected API key (`401` or `403`) disables the SDK and drops future events until cached data is cleared.
|
|
219
|
+
The vendored iOS framework ships a `PrivacyInfo.xcprivacy` manifest declaring `UserDefaults` access plus `DeviceID`, `ProductInteraction`, `UserID`, `CoarseLocation`, and `OtherDataTypes` collection, all marked `Tracking: true`, with `api.appsprint.app` listed as a tracking domain.
|
|
158
220
|
|
|
159
|
-
|
|
221
|
+
For Android, include advertising ID collection, device IDs, app activity, and (if you set `customerUserId`) user ID in your Play Console Data safety answers.
|
|
160
222
|
|
|
161
|
-
|
|
223
|
+
Don't pass raw PII through `params` or `customerUserId`. Both persist to native storage for retry durability. Use hashed or opaque identifiers instead (SHA-256 of an email, RevenueCat or Superwall `app_user_id`, your internal user UUID).
|
|
224
|
+
|
|
225
|
+
## Local development
|
|
162
226
|
|
|
163
227
|
```tsx
|
|
164
228
|
await AppSprint.configure({
|
|
165
|
-
apiKey: "
|
|
229
|
+
apiKey: "YOUR_DEV_KEY",
|
|
166
230
|
apiUrl: "http://localhost:3000",
|
|
167
231
|
isDebug: true,
|
|
168
232
|
});
|
|
169
233
|
```
|
|
170
234
|
|
|
235
|
+
On Android emulator, use `http://10.0.2.2:3000` to reach the host machine's localhost.
|
|
236
|
+
|
|
237
|
+
`isDebug: true` raises native log level to `debug`. iOS logs flow into Console.app; Android logs flow into `logcat` under the `AppSprint` tag.
|
|
238
|
+
|
|
239
|
+
## Public API reference
|
|
240
|
+
|
|
241
|
+
### `AppSprint`
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
import { AppSprint } from "appsprint-react-native";
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
- `configure(config)` initializes the SDK.
|
|
248
|
+
- `sendEvent(eventType, name?, params?)` enqueues an event.
|
|
249
|
+
- `flush()` drains the queue immediately.
|
|
250
|
+
- `refreshAttribution()` fetches the latest attribution from the backend.
|
|
251
|
+
- `setCustomerUserId(userId)` updates the customer user ID.
|
|
252
|
+
- `getAttribution()` returns the cached attribution.
|
|
253
|
+
- `getAttributionParams()` returns the partner-ready payload.
|
|
254
|
+
- `getAppSprintId()` returns the SDK install identifier.
|
|
255
|
+
- `enableAppleAdsAttribution()` re-enables Apple Ads at runtime on iOS; returns `false` on Android.
|
|
256
|
+
- `sendTestEvent()` posts a diagnostic event and resolves to `{ success, message }`.
|
|
257
|
+
- `isInitialized()` reports whether `configure()` resolved.
|
|
258
|
+
- `isSdkDisabled()` reports whether a rejected API key disabled the SDK.
|
|
259
|
+
- `clearData()` wipes local state.
|
|
260
|
+
- `destroy()` removes native lifecycle observers.
|
|
261
|
+
|
|
262
|
+
### `NativeAppSprint`
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
import { NativeAppSprint } from "appsprint-react-native";
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
- `getDeviceInfo()` returns the device fingerprint payload.
|
|
269
|
+
- `getAdServicesToken()` returns Apple's AdServices token on iOS; `null` on Android.
|
|
270
|
+
- `requestTrackingAuthorization()` shows the ATT prompt on iOS; resolves `true` on Android.
|
|
271
|
+
|
|
272
|
+
## Support
|
|
273
|
+
|
|
274
|
+
Issues and feature requests on the [GitHub repo](https://github.com/getappsprint/appsprint-react-native). Direct support at support@appsprint.app.
|
|
275
|
+
|
|
171
276
|
## License
|
|
172
277
|
|
|
173
278
|
MIT
|
package/android/build.gradle
CHANGED
|
@@ -9,7 +9,7 @@ apply plugin: 'kotlin-android'
|
|
|
9
9
|
|
|
10
10
|
android {
|
|
11
11
|
namespace "com.appsprint"
|
|
12
|
-
compileSdkVersion safeExtGet('compileSdkVersion',
|
|
12
|
+
compileSdkVersion safeExtGet('compileSdkVersion', 36)
|
|
13
13
|
|
|
14
14
|
defaultConfig {
|
|
15
15
|
minSdkVersion safeExtGet('minSdkVersion', 24)
|
|
@@ -35,5 +35,7 @@ android {
|
|
|
35
35
|
dependencies {
|
|
36
36
|
implementation "com.facebook.react:react-android:+"
|
|
37
37
|
implementation files('libs/appsprint-sdk.aar')
|
|
38
|
-
implementation "androidx.lifecycle:lifecycle-process:2.
|
|
38
|
+
implementation "androidx.lifecycle:lifecycle-process:2.10.0"
|
|
39
|
+
implementation "com.google.android.gms:play-services-ads-identifier:18.3.0"
|
|
40
|
+
implementation "com.android.installreferrer:installreferrer:2.2"
|
|
39
41
|
}
|
|
Binary file
|
|
@@ -3,14 +3,31 @@ package com.appsprint
|
|
|
3
3
|
import com.appsprint.sdk.AppSprint
|
|
4
4
|
import com.appsprint.sdk.AppSprintConfig
|
|
5
5
|
import com.appsprint.sdk.AppSprintEventType
|
|
6
|
+
import com.appsprint.sdk.AppSprintNative
|
|
7
|
+
import com.appsprint.sdk.AttributionResult
|
|
8
|
+
import com.appsprint.sdk.GoogleAdsConsent
|
|
9
|
+
import com.appsprint.sdk.GoogleAdsConsentStatus
|
|
6
10
|
import com.facebook.react.bridge.*
|
|
7
|
-
import
|
|
11
|
+
import java.util.concurrent.ExecutorService
|
|
12
|
+
import java.util.concurrent.Executors
|
|
8
13
|
|
|
9
14
|
class AppSprintBridgeModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
|
10
15
|
|
|
11
16
|
override fun getName(): String = "AppSprintModule"
|
|
12
17
|
|
|
18
|
+
@Volatile private var cachedSdk: AppSprint? = null
|
|
19
|
+
private val bridgeExecutor: ExecutorService = Executors.newSingleThreadExecutor { runnable ->
|
|
20
|
+
Thread(runnable, "AppSprintRNBridge").apply { isDaemon = true }
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
private fun sdk(): AppSprint {
|
|
24
|
+
cachedSdk?.let { return it }
|
|
25
|
+
return synchronized(this) {
|
|
26
|
+
cachedSdk ?: resolveSdk().also { cachedSdk = it }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private fun resolveSdk(): AppSprint {
|
|
14
31
|
val sdkClass = AppSprint::class.java
|
|
15
32
|
|
|
16
33
|
runCatching {
|
|
@@ -24,15 +41,23 @@ class AppSprintBridgeModule(reactContext: ReactApplicationContext) : ReactContex
|
|
|
24
41
|
}
|
|
25
42
|
|
|
26
43
|
private fun runAsync(code: String, promise: Promise, block: () -> Unit) {
|
|
27
|
-
|
|
44
|
+
bridgeExecutor.execute {
|
|
28
45
|
try {
|
|
29
46
|
block()
|
|
30
|
-
} catch (
|
|
31
|
-
promise.reject(code,
|
|
47
|
+
} catch (t: Throwable) {
|
|
48
|
+
promise.reject(code, t.message, t)
|
|
32
49
|
}
|
|
33
50
|
}
|
|
34
51
|
}
|
|
35
52
|
|
|
53
|
+
private fun resolveSync(code: String, promise: Promise, block: () -> Any?) {
|
|
54
|
+
try {
|
|
55
|
+
promise.resolve(block())
|
|
56
|
+
} catch (t: Throwable) {
|
|
57
|
+
promise.reject(code, t.message, t)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
36
61
|
// Core SDK
|
|
37
62
|
|
|
38
63
|
@ReactMethod
|
|
@@ -46,14 +71,21 @@ class AppSprintBridgeModule(reactContext: ReactApplicationContext) : ReactContex
|
|
|
46
71
|
runAsync("CONFIGURE_ERROR", promise) {
|
|
47
72
|
val sdkConfig = AppSprintConfig(
|
|
48
73
|
apiKey = apiKey,
|
|
49
|
-
apiUrl =
|
|
74
|
+
apiUrl = when {
|
|
75
|
+
config.hasKey("apiUrl") -> config.getString("apiUrl") ?: "https://api.appsprint.app"
|
|
76
|
+
config.hasKey("endpointBaseUrl") -> config.getString("endpointBaseUrl") ?: "https://api.appsprint.app"
|
|
77
|
+
else -> "https://api.appsprint.app"
|
|
78
|
+
},
|
|
50
79
|
enableAppleAdsAttribution = if (config.hasKey("enableAppleAdsAttribution")) config.getBoolean("enableAppleAdsAttribution") else true,
|
|
51
80
|
isDebug = if (config.hasKey("isDebug")) config.getBoolean("isDebug") else false,
|
|
52
81
|
logLevel = if (config.hasKey("logLevel")) config.getInt("logLevel") else if (config.hasKey("isDebug") && config.getBoolean("isDebug")) 0 else 2,
|
|
53
82
|
customerUserId = if (config.hasKey("customerUserId")) config.getString("customerUserId") else null,
|
|
83
|
+
autoTrackSessions = if (config.hasKey("autoTrackSessions")) config.getBoolean("autoTrackSessions") else true,
|
|
84
|
+
autoRefreshAttribution = if (config.hasKey("autoRefreshAttribution")) config.getBoolean("autoRefreshAttribution") else true,
|
|
85
|
+
googleAdsConsent = googleAdsConsentFrom(config),
|
|
54
86
|
)
|
|
55
87
|
sdk().configure(sdkConfig)
|
|
56
|
-
promise.resolve(
|
|
88
|
+
promise.resolve(true)
|
|
57
89
|
}
|
|
58
90
|
}
|
|
59
91
|
|
|
@@ -63,10 +95,10 @@ class AppSprintBridgeModule(reactContext: ReactApplicationContext) : ReactContex
|
|
|
63
95
|
val type = AppSprintEventType.entries.find { it.wireValue == eventType } ?: AppSprintEventType.CUSTOM
|
|
64
96
|
val params = mutableMapOf<String, Any?>()
|
|
65
97
|
parameters?.toHashMap()?.forEach { (key, value) -> params[key] = value }
|
|
66
|
-
if (revenue != null
|
|
98
|
+
if (revenue != null) params["revenue"] = revenue
|
|
67
99
|
if (currency != null) params["currency"] = currency
|
|
68
100
|
sdk().sendEvent(type, name, if (params.isNotEmpty()) params else null)
|
|
69
|
-
promise.resolve(
|
|
101
|
+
promise.resolve(true)
|
|
70
102
|
}
|
|
71
103
|
}
|
|
72
104
|
|
|
@@ -105,42 +137,101 @@ class AppSprintBridgeModule(reactContext: ReactApplicationContext) : ReactContex
|
|
|
105
137
|
}
|
|
106
138
|
}
|
|
107
139
|
|
|
140
|
+
@ReactMethod
|
|
141
|
+
fun refreshAttribution(promise: Promise) {
|
|
142
|
+
runAsync("REFRESH_ATTRIBUTION_ERROR", promise) {
|
|
143
|
+
promise.resolve(sdk().refreshAttribution()?.let { attributionToMap(it) })
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
108
147
|
@ReactMethod
|
|
109
148
|
fun enableAppleAdsAttribution(promise: Promise) {
|
|
110
|
-
|
|
111
|
-
|
|
149
|
+
runAsync("APPLE_ADS_ERROR", promise) {
|
|
150
|
+
promise.resolve(sdk().enableAppleAdsAttribution())
|
|
151
|
+
}
|
|
112
152
|
}
|
|
113
153
|
|
|
114
154
|
@ReactMethod
|
|
115
155
|
fun getAppSprintId(promise: Promise) {
|
|
116
|
-
promise
|
|
156
|
+
resolveSync("GET_APPSPRINT_ID_ERROR", promise) { sdk().getAppSprintId() }
|
|
117
157
|
}
|
|
118
158
|
|
|
119
159
|
@ReactMethod
|
|
120
160
|
fun getAttribution(promise: Promise) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
promise.resolve(null)
|
|
124
|
-
return
|
|
161
|
+
resolveSync("GET_ATTRIBUTION_ERROR", promise) {
|
|
162
|
+
sdk().getAttribution()?.let { attributionToMap(it) }
|
|
125
163
|
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@ReactMethod
|
|
167
|
+
fun getAttributionParams(promise: Promise) {
|
|
168
|
+
resolveSync("GET_ATTRIBUTION_PARAMS_ERROR", promise) {
|
|
169
|
+
val map = Arguments.createMap()
|
|
170
|
+
sdk().getAttributionParams().forEach { (key, value) -> map.putString(key, value) }
|
|
171
|
+
map
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private fun attributionToMap(attr: AttributionResult): WritableMap {
|
|
126
176
|
val map = Arguments.createMap()
|
|
177
|
+
map.putBoolean("isAttributed", attr.isAttributed)
|
|
127
178
|
map.putString("source", attr.source)
|
|
128
179
|
map.putDouble("confidence", attr.confidence)
|
|
180
|
+
attr.matchType?.let { map.putString("matchType", it) }
|
|
129
181
|
attr.campaignName?.let { map.putString("campaignName", it) }
|
|
182
|
+
attr.link?.let {
|
|
183
|
+
val link = Arguments.createMap()
|
|
184
|
+
link.putString("id", it.id)
|
|
185
|
+
link.putString("name", it.name)
|
|
186
|
+
map.putMap("link", link)
|
|
187
|
+
}
|
|
188
|
+
attr.appleAds?.let {
|
|
189
|
+
val appleAds = Arguments.createMap()
|
|
190
|
+
appleAds.putString("campaignId", it.campaignId)
|
|
191
|
+
it.orgId?.let { value -> appleAds.putString("orgId", value) }
|
|
192
|
+
it.adGroupId?.let { value -> appleAds.putString("adGroupId", value) }
|
|
193
|
+
it.keywordId?.let { value -> appleAds.putString("keywordId", value) }
|
|
194
|
+
it.adId?.let { value -> appleAds.putString("adId", value) }
|
|
195
|
+
it.countryOrRegion?.let { value -> appleAds.putString("countryOrRegion", value) }
|
|
196
|
+
it.claimType?.let { value -> appleAds.putString("claimType", value) }
|
|
197
|
+
it.clickDate?.let { value -> appleAds.putString("clickDate", value) }
|
|
198
|
+
it.impressionDate?.let { value -> appleAds.putString("impressionDate", value) }
|
|
199
|
+
it.conversionType?.let { value -> appleAds.putString("conversionType", value) }
|
|
200
|
+
it.supplyPlacement?.let { value -> appleAds.putString("supplyPlacement", value) }
|
|
201
|
+
map.putMap("appleAds", appleAds)
|
|
202
|
+
}
|
|
130
203
|
attr.utmSource?.let { map.putString("utmSource", it) }
|
|
131
204
|
attr.utmMedium?.let { map.putString("utmMedium", it) }
|
|
132
205
|
attr.utmCampaign?.let { map.putString("utmCampaign", it) }
|
|
133
|
-
|
|
206
|
+
attr.utmContent?.let { map.putString("utmContent", it) }
|
|
207
|
+
attr.utmTerm?.let { map.putString("utmTerm", it) }
|
|
208
|
+
return map
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private fun googleAdsConsentFrom(config: ReadableMap): GoogleAdsConsent? {
|
|
212
|
+
if (!config.hasKey("googleAdsConsent") || config.isNull("googleAdsConsent")) return null
|
|
213
|
+
val consent = config.getMap("googleAdsConsent") ?: return null
|
|
214
|
+
if (!consent.hasKey("adUserData") || consent.isNull("adUserData")) return null
|
|
215
|
+
if (consent.getType("adUserData") != ReadableType.String) return null
|
|
216
|
+
return googleAdsConsentStatus(consent.getString("adUserData"))
|
|
217
|
+
?.let { GoogleAdsConsent(it) }
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private fun googleAdsConsentStatus(value: String?): GoogleAdsConsentStatus? {
|
|
221
|
+
val normalized = value?.trim()?.uppercase(java.util.Locale.US) ?: return null
|
|
222
|
+
return GoogleAdsConsentStatus.entries.firstOrNull {
|
|
223
|
+
it.wireValue == normalized || it.name == normalized
|
|
224
|
+
}
|
|
134
225
|
}
|
|
135
226
|
|
|
136
227
|
@ReactMethod
|
|
137
228
|
fun isInitialized(promise: Promise) {
|
|
138
|
-
promise
|
|
229
|
+
resolveSync("IS_INITIALIZED_ERROR", promise) { sdk().isInitialized() }
|
|
139
230
|
}
|
|
140
231
|
|
|
141
232
|
@ReactMethod
|
|
142
233
|
fun isSdkDisabled(promise: Promise) {
|
|
143
|
-
promise
|
|
234
|
+
resolveSync("SDK_DISABLED_ERROR", promise) { sdk().isSdkDisabled() }
|
|
144
235
|
}
|
|
145
236
|
|
|
146
237
|
@ReactMethod
|
|
@@ -155,18 +246,18 @@ class AppSprintBridgeModule(reactContext: ReactApplicationContext) : ReactContex
|
|
|
155
246
|
|
|
156
247
|
@ReactMethod
|
|
157
248
|
fun getDeviceInfo(promise: Promise) {
|
|
158
|
-
|
|
249
|
+
runAsync("DEVICE_INFO_ERROR", promise) {
|
|
250
|
+
val deviceInfo = AppSprintNative(reactApplicationContext).getDeviceInfo(includeAdvertisingId = true)
|
|
159
251
|
val info = Arguments.createMap()
|
|
160
|
-
info.putString("deviceModel",
|
|
161
|
-
|
|
162
|
-
info.putInt("
|
|
163
|
-
info.
|
|
164
|
-
info.putString("
|
|
165
|
-
info.putString("
|
|
166
|
-
info.putString("
|
|
252
|
+
deviceInfo.deviceModel?.let { info.putString("deviceModel", it) }
|
|
253
|
+
deviceInfo.screenWidth?.let { info.putInt("screenWidth", it) }
|
|
254
|
+
deviceInfo.screenHeight?.let { info.putInt("screenHeight", it) }
|
|
255
|
+
deviceInfo.locale?.let { info.putString("locale", it) }
|
|
256
|
+
deviceInfo.timezone?.let { info.putString("timezone", it) }
|
|
257
|
+
deviceInfo.osVersion?.let { info.putString("osVersion", it) }
|
|
258
|
+
deviceInfo.appVersion?.let { info.putString("appVersion", it) }
|
|
259
|
+
deviceInfo.gaid?.let { info.putString("gaid", it) }
|
|
167
260
|
promise.resolve(info)
|
|
168
|
-
} catch (e: Exception) {
|
|
169
|
-
promise.reject("DEVICE_INFO_ERROR", e.message, e)
|
|
170
261
|
}
|
|
171
262
|
}
|
|
172
263
|
|
|
@@ -177,6 +268,11 @@ class AppSprintBridgeModule(reactContext: ReactApplicationContext) : ReactContex
|
|
|
177
268
|
|
|
178
269
|
@ReactMethod
|
|
179
270
|
fun requestTrackingAuthorization(promise: Promise) {
|
|
180
|
-
promise.resolve(
|
|
271
|
+
promise.resolve(AppSprintNative(reactApplicationContext).requestTrackingAuthorization())
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
override fun invalidate() {
|
|
275
|
+
bridgeExecutor.shutdown()
|
|
276
|
+
super.invalidate()
|
|
181
277
|
}
|
|
182
278
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
|
|
3
3
|
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
repository = package["repository"]
|
|
5
|
+
repository_url = repository.is_a?(Hash) ? repository["url"] : repository
|
|
6
|
+
repository_url = repository_url&.sub(/^git\+/, "")
|
|
7
|
+
|
|
8
|
+
raise "package.json repository.url must be set" if repository_url.nil? || repository_url.empty?
|
|
4
9
|
|
|
5
10
|
Pod::Spec.new do |s|
|
|
6
11
|
s.name = "appsprint-react-native"
|
|
@@ -11,7 +16,7 @@ Pod::Spec.new do |s|
|
|
|
11
16
|
s.authors = package["author"]
|
|
12
17
|
|
|
13
18
|
s.platforms = { :ios => "14.0" }
|
|
14
|
-
s.source = { :git =>
|
|
19
|
+
s.source = { :git => repository_url, :tag => "v#{s.version}" }
|
|
15
20
|
|
|
16
21
|
s.source_files = "ios/AppSprintBridge.{swift,m}"
|
|
17
22
|
s.swift_version = "5.0"
|
package/ios/AppSprintBridge.m
CHANGED
|
@@ -28,6 +28,9 @@ RCT_EXTERN_METHOD(setCustomerUserId:(NSString *)userId
|
|
|
28
28
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
29
29
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
30
30
|
|
|
31
|
+
RCT_EXTERN_METHOD(refreshAttribution:(RCTPromiseResolveBlock)resolve
|
|
32
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
33
|
+
|
|
31
34
|
RCT_EXTERN_METHOD(enableAppleAdsAttribution:(RCTPromiseResolveBlock)resolve
|
|
32
35
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
33
36
|
|
|
@@ -37,6 +40,9 @@ RCT_EXTERN_METHOD(getAppSprintId:(RCTPromiseResolveBlock)resolve
|
|
|
37
40
|
RCT_EXTERN_METHOD(getAttribution:(RCTPromiseResolveBlock)resolve
|
|
38
41
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
39
42
|
|
|
43
|
+
RCT_EXTERN_METHOD(getAttributionParams:(RCTPromiseResolveBlock)resolve
|
|
44
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
45
|
+
|
|
40
46
|
RCT_EXTERN_METHOD(isInitialized:(RCTPromiseResolveBlock)resolve
|
|
41
47
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
42
48
|
|