@influto/react-native-sdk 1.0.0
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 +245 -0
- package/lib/InfluTo.d.ts +81 -0
- package/lib/InfluTo.js +270 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +27 -0
- package/lib/types.d.ts +76 -0
- package/lib/types.js +5 -0
- package/package.json +54 -0
- package/src/InfluTo.ts +304 -0
- package/src/index.ts +7 -0
- package/src/types.ts +91 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 InfluTo
|
|
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,245 @@
|
|
|
1
|
+
# InfluTo React Native SDK
|
|
2
|
+
|
|
3
|
+
Track influencer referrals and conversions in your React Native app.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @influto/react-native-sdk
|
|
9
|
+
# or
|
|
10
|
+
yarn add @influto/react-native-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**Peer Dependencies:**
|
|
14
|
+
```bash
|
|
15
|
+
npm install @react-native-async-storage/async-storage
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### 1. Initialize SDK
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import InfluTo from '@influto/react-native-sdk';
|
|
24
|
+
|
|
25
|
+
// In your App.js or root component
|
|
26
|
+
await InfluTo.initialize({
|
|
27
|
+
apiKey: 'it_abc123...', // Get from InfluTo dashboard
|
|
28
|
+
debug: __DEV__ // Enable logging in development
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 2. Check Attribution
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// During onboarding, check if user came from a referral
|
|
36
|
+
const attribution = await InfluTo.checkAttribution();
|
|
37
|
+
|
|
38
|
+
if (attribution.attributed) {
|
|
39
|
+
console.log('User came from:', attribution.referralCode);
|
|
40
|
+
|
|
41
|
+
// Show trial or special offer
|
|
42
|
+
showTrialPaywall(attribution.referralCode);
|
|
43
|
+
} else {
|
|
44
|
+
// Organic user - show regular paywall
|
|
45
|
+
showRegularPaywall();
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 3. Identify User
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// After user completes onboarding or you have their ID
|
|
53
|
+
await InfluTo.identifyUser('revenuecat_user_id_123');
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 4. Track Events (Optional)
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// Track key events for analytics
|
|
60
|
+
await InfluTo.trackEvent({
|
|
61
|
+
eventType: 'trial_started',
|
|
62
|
+
appUserId: 'revenuecat_user_id_123',
|
|
63
|
+
properties: {
|
|
64
|
+
trial_days: 7,
|
|
65
|
+
product_id: 'monthly_subscription'
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Complete Integration Example
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import React, { useEffect, useState } from 'react';
|
|
74
|
+
import InfluTo from '@influto/react-native-sdk';
|
|
75
|
+
import Purchases from 'react-native-purchases';
|
|
76
|
+
|
|
77
|
+
function App() {
|
|
78
|
+
const [hasReferral, setHasReferral] = useState(false);
|
|
79
|
+
const [referralCode, setReferralCode] = useState(null);
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
async function setupInfluTo() {
|
|
83
|
+
// 1. Initialize InfluTo
|
|
84
|
+
await InfluTo.initialize({
|
|
85
|
+
apiKey: 'it_abc123...',
|
|
86
|
+
debug: __DEV__
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// 2. Check attribution
|
|
90
|
+
const attribution = await InfluTo.checkAttribution();
|
|
91
|
+
|
|
92
|
+
if (attribution.attributed) {
|
|
93
|
+
setHasReferral(true);
|
|
94
|
+
setReferralCode(attribution.referralCode);
|
|
95
|
+
|
|
96
|
+
// 3. Store in RevenueCat for webhook attribution
|
|
97
|
+
await Purchases.setAttributes({
|
|
98
|
+
'referral_code': attribution.referralCode
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 4. Identify user when available
|
|
103
|
+
const customerInfo = await Purchases.getCustomerInfo();
|
|
104
|
+
await InfluTo.identifyUser(customerInfo.originalAppUserId);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
setupInfluTo();
|
|
108
|
+
}, []);
|
|
109
|
+
|
|
110
|
+
// Show appropriate paywall based on attribution
|
|
111
|
+
const showPaywall = async () => {
|
|
112
|
+
const offerings = await Purchases.getOfferings();
|
|
113
|
+
|
|
114
|
+
if (hasReferral) {
|
|
115
|
+
// Show trial offering
|
|
116
|
+
const trialOffering = offerings.all['trial'] || offerings.current;
|
|
117
|
+
await RevenueCatUI.presentPaywall({ offering: trialOffering });
|
|
118
|
+
|
|
119
|
+
// Track event
|
|
120
|
+
await InfluTo.trackEvent({
|
|
121
|
+
eventType: 'trial_paywall_shown',
|
|
122
|
+
appUserId: customerInfo.originalAppUserId,
|
|
123
|
+
properties: { referral_code: referralCode }
|
|
124
|
+
});
|
|
125
|
+
} else {
|
|
126
|
+
// Show regular offering
|
|
127
|
+
await RevenueCatUI.presentPaywall();
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<YourApp hasReferral={hasReferral} onShowPaywall={showPaywall} />
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## API Reference
|
|
138
|
+
|
|
139
|
+
### `InfluTo.initialize(config)`
|
|
140
|
+
|
|
141
|
+
Initialize the SDK. Call once when app starts.
|
|
142
|
+
|
|
143
|
+
**Parameters:**
|
|
144
|
+
- `config.apiKey` (required): Your API key from InfluTo dashboard
|
|
145
|
+
- `config.debug` (optional): Enable debug logging (defaults to `false`)
|
|
146
|
+
|
|
147
|
+
**Returns:** `Promise<void>`
|
|
148
|
+
|
|
149
|
+
### `InfluTo.checkAttribution()`
|
|
150
|
+
|
|
151
|
+
Check if user was referred by an influencer.
|
|
152
|
+
|
|
153
|
+
**Returns:** `Promise<AttributionResult>`
|
|
154
|
+
|
|
155
|
+
**Example:**
|
|
156
|
+
```typescript
|
|
157
|
+
const result = await InfluTo.checkAttribution();
|
|
158
|
+
// { attributed: true, referralCode: 'FITGURU25', clickedAt: '2025-12-01T10:30:00Z' }
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### `InfluTo.identifyUser(appUserId, properties?)`
|
|
162
|
+
|
|
163
|
+
Identify user with their app user ID.
|
|
164
|
+
|
|
165
|
+
**Parameters:**
|
|
166
|
+
- `appUserId` (required): RevenueCat ID or your custom user ID
|
|
167
|
+
- `properties` (optional): Additional user properties
|
|
168
|
+
|
|
169
|
+
**Returns:** `Promise<void>`
|
|
170
|
+
|
|
171
|
+
### `InfluTo.trackEvent(options)`
|
|
172
|
+
|
|
173
|
+
Track custom analytics event.
|
|
174
|
+
|
|
175
|
+
**Parameters:**
|
|
176
|
+
- `options.eventType` (required): Event name
|
|
177
|
+
- `options.appUserId` (required): User ID
|
|
178
|
+
- `options.properties` (optional): Event properties
|
|
179
|
+
- `options.referralCode` (optional): Associated referral code
|
|
180
|
+
|
|
181
|
+
**Returns:** `Promise<void>`
|
|
182
|
+
|
|
183
|
+
### `InfluTo.getActiveCampaigns()`
|
|
184
|
+
|
|
185
|
+
Get list of active campaigns for this app.
|
|
186
|
+
|
|
187
|
+
**Returns:** `Promise<Campaign[]>`
|
|
188
|
+
|
|
189
|
+
### `InfluTo.getReferralCode()`
|
|
190
|
+
|
|
191
|
+
Get stored referral code (if any).
|
|
192
|
+
|
|
193
|
+
**Returns:** `Promise<string | null>`
|
|
194
|
+
|
|
195
|
+
### `InfluTo.clearAttribution()`
|
|
196
|
+
|
|
197
|
+
Clear stored attribution data (useful for testing).
|
|
198
|
+
|
|
199
|
+
**Returns:** `Promise<void>`
|
|
200
|
+
|
|
201
|
+
## Integration with RevenueCat
|
|
202
|
+
|
|
203
|
+
InfluTo works seamlessly with RevenueCat:
|
|
204
|
+
|
|
205
|
+
1. **Store referral code in RevenueCat attributes:**
|
|
206
|
+
```typescript
|
|
207
|
+
const attribution = await InfluTo.checkAttribution();
|
|
208
|
+
if (attribution.attributed) {
|
|
209
|
+
await Purchases.setAttributes({
|
|
210
|
+
'referral_code': attribution.referralCode
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
2. **Configure RevenueCat webhook** in InfluTo dashboard
|
|
216
|
+
|
|
217
|
+
3. **InfluTo automatically tracks** subscription events and calculates commissions
|
|
218
|
+
|
|
219
|
+
## Platform Support
|
|
220
|
+
|
|
221
|
+
- ✅ iOS 13.0+
|
|
222
|
+
- ✅ Android 5.0+ (API level 21+)
|
|
223
|
+
- ✅ Expo (managed & bare workflows)
|
|
224
|
+
|
|
225
|
+
## Troubleshooting
|
|
226
|
+
|
|
227
|
+
**Attribution not working?**
|
|
228
|
+
- Ensure you called `initialize()` before `checkAttribution()`
|
|
229
|
+
- Check API key is correct
|
|
230
|
+
- Verify app is configured in InfluTo dashboard
|
|
231
|
+
- Check network connectivity
|
|
232
|
+
|
|
233
|
+
**User not identified?**
|
|
234
|
+
- Make sure you call `identifyUser()` after user completes onboarding
|
|
235
|
+
- Verify user ID matches what RevenueCat sends in webhooks
|
|
236
|
+
|
|
237
|
+
## Support
|
|
238
|
+
|
|
239
|
+
- **Documentation:** https://docs.influ.to
|
|
240
|
+
- **Email:** support@influ.to
|
|
241
|
+
- **Discord:** https://discord.gg/influto
|
|
242
|
+
|
|
243
|
+
## License
|
|
244
|
+
|
|
245
|
+
MIT © InfluTo
|
package/lib/InfluTo.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InfluTo SDK for React Native
|
|
3
|
+
*
|
|
4
|
+
* Track influencer referrals and conversions in your mobile app
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import InfluTo from '@influto/react-native-sdk';
|
|
9
|
+
*
|
|
10
|
+
* // Initialize (same key for dev & production)
|
|
11
|
+
* await InfluTo.initialize({
|
|
12
|
+
* apiKey: 'it_abc123...',
|
|
13
|
+
* debug: __DEV__ // Enable logging in development
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* // Check attribution
|
|
17
|
+
* const attribution = await InfluTo.checkAttribution();
|
|
18
|
+
* if (attribution.attributed) {
|
|
19
|
+
* console.log('User came from referral:', attribution.referralCode);
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import type { InfluToConfig, AttributionResult, Campaign, TrackEventOptions } from './types';
|
|
24
|
+
declare class InfluToSDK {
|
|
25
|
+
private config;
|
|
26
|
+
private apiUrl;
|
|
27
|
+
private isInitialized;
|
|
28
|
+
/**
|
|
29
|
+
* Initialize InfluTo SDK
|
|
30
|
+
*
|
|
31
|
+
* Call this once when your app starts, after user grants tracking permission
|
|
32
|
+
*/
|
|
33
|
+
initialize(config: InfluToConfig): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Check if current user has attribution to a referral
|
|
36
|
+
|
|
37
|
+
*
|
|
38
|
+
* Call this during app onboarding to see if user should get trial/discount
|
|
39
|
+
*
|
|
40
|
+
* @returns Attribution result with referral code if attributed
|
|
41
|
+
*/
|
|
42
|
+
checkAttribution(): Promise<AttributionResult>;
|
|
43
|
+
/**
|
|
44
|
+
* Identify user with app_user_id
|
|
45
|
+
*
|
|
46
|
+
* Call this when you have the user's RevenueCat ID or custom user ID
|
|
47
|
+
*/
|
|
48
|
+
identifyUser(appUserId: string, properties?: Record<string, any>): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Track custom event
|
|
51
|
+
*
|
|
52
|
+
* Use this to track key events like trial_started, paywall_viewed, etc.
|
|
53
|
+
*/
|
|
54
|
+
trackEvent(options: TrackEventOptions): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Get active campaigns
|
|
57
|
+
*
|
|
58
|
+
* Useful for showing available promotions in-app
|
|
59
|
+
*/
|
|
60
|
+
getActiveCampaigns(): Promise<Campaign[]>;
|
|
61
|
+
/**
|
|
62
|
+
* Get stored referral code (if any)
|
|
63
|
+
*/
|
|
64
|
+
getReferralCode(): Promise<string | null>;
|
|
65
|
+
/**
|
|
66
|
+
* Clear stored attribution data
|
|
67
|
+
*
|
|
68
|
+
* Useful for testing or user logout
|
|
69
|
+
*/
|
|
70
|
+
clearAttribution(): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Get device information for fingerprinting
|
|
73
|
+
*/
|
|
74
|
+
private getDeviceInfo;
|
|
75
|
+
/**
|
|
76
|
+
* Make API request with authentication
|
|
77
|
+
*/
|
|
78
|
+
private apiRequest;
|
|
79
|
+
}
|
|
80
|
+
declare const InfluTo: InfluToSDK;
|
|
81
|
+
export default InfluTo;
|
package/lib/InfluTo.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* InfluTo SDK for React Native
|
|
4
|
+
*
|
|
5
|
+
* Track influencer referrals and conversions in your mobile app
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import InfluTo from '@influto/react-native-sdk';
|
|
10
|
+
*
|
|
11
|
+
* // Initialize (same key for dev & production)
|
|
12
|
+
* await InfluTo.initialize({
|
|
13
|
+
* apiKey: 'it_abc123...',
|
|
14
|
+
* debug: __DEV__ // Enable logging in development
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* // Check attribution
|
|
18
|
+
* const attribution = await InfluTo.checkAttribution();
|
|
19
|
+
* if (attribution.attributed) {
|
|
20
|
+
* console.log('User came from referral:', attribution.referralCode);
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
25
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
26
|
+
};
|
|
27
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
+
const react_native_1 = require("react-native");
|
|
29
|
+
const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
|
|
30
|
+
const STORAGE_PREFIX = '@influto/';
|
|
31
|
+
const STORAGE_KEYS = {
|
|
32
|
+
ATTRIBUTION: `${STORAGE_PREFIX}attribution`,
|
|
33
|
+
APP_USER_ID: `${STORAGE_PREFIX}app_user_id`,
|
|
34
|
+
REFERRAL_CODE: `${STORAGE_PREFIX}referral_code`,
|
|
35
|
+
SDK_INITIALIZED: `${STORAGE_PREFIX}initialized`,
|
|
36
|
+
};
|
|
37
|
+
class InfluToSDK {
|
|
38
|
+
constructor() {
|
|
39
|
+
this.config = null;
|
|
40
|
+
this.apiUrl = 'https://api.influ.to/api';
|
|
41
|
+
this.isInitialized = false;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Initialize InfluTo SDK
|
|
45
|
+
*
|
|
46
|
+
* Call this once when your app starts, after user grants tracking permission
|
|
47
|
+
*/
|
|
48
|
+
async initialize(config) {
|
|
49
|
+
this.config = config;
|
|
50
|
+
// Use custom API URL if provided (internal testing only)
|
|
51
|
+
if (config.apiUrl) {
|
|
52
|
+
this.apiUrl = config.apiUrl;
|
|
53
|
+
}
|
|
54
|
+
if (config.debug) {
|
|
55
|
+
console.log('[InfluTo] Initializing SDK...');
|
|
56
|
+
console.log('[InfluTo] API URL:', this.apiUrl);
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
// Call init endpoint
|
|
60
|
+
const response = await this.apiRequest('/sdk/init', {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
body: JSON.stringify({
|
|
63
|
+
app_version: require('react-native').Platform.constants?.version || 'unknown',
|
|
64
|
+
sdk_version: '1.0.0',
|
|
65
|
+
platform: react_native_1.Platform.OS
|
|
66
|
+
})
|
|
67
|
+
});
|
|
68
|
+
if (response.initialized) {
|
|
69
|
+
this.isInitialized = true;
|
|
70
|
+
await async_storage_1.default.setItem(STORAGE_KEYS.SDK_INITIALIZED, 'true');
|
|
71
|
+
if (config.debug) {
|
|
72
|
+
console.log('[InfluTo] SDK initialized successfully');
|
|
73
|
+
console.log('[InfluTo] Active campaigns:', response.campaigns?.length || 0);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.error('[InfluTo] Initialization failed:', error);
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if current user has attribution to a referral
|
|
84
|
+
|
|
85
|
+
*
|
|
86
|
+
* Call this during app onboarding to see if user should get trial/discount
|
|
87
|
+
*
|
|
88
|
+
* @returns Attribution result with referral code if attributed
|
|
89
|
+
*/
|
|
90
|
+
async checkAttribution() {
|
|
91
|
+
if (!this.isInitialized) {
|
|
92
|
+
throw new Error('InfluTo SDK not initialized. Call InfluTo.initialize() first.');
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
// Check if already stored locally
|
|
96
|
+
const storedAttribution = await async_storage_1.default.getItem(STORAGE_KEYS.ATTRIBUTION);
|
|
97
|
+
if (storedAttribution) {
|
|
98
|
+
if (this.config?.debug) {
|
|
99
|
+
console.log('[InfluTo] Attribution found in storage');
|
|
100
|
+
}
|
|
101
|
+
return JSON.parse(storedAttribution);
|
|
102
|
+
}
|
|
103
|
+
// Track install and check attribution
|
|
104
|
+
const deviceInfo = await this.getDeviceInfo();
|
|
105
|
+
const response = await this.apiRequest('/sdk/track-install', {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
body: JSON.stringify(deviceInfo)
|
|
108
|
+
});
|
|
109
|
+
if (response.attributed && response.referral_code) {
|
|
110
|
+
const attribution = {
|
|
111
|
+
attributed: true,
|
|
112
|
+
referralCode: response.referral_code,
|
|
113
|
+
attributionMethod: response.attribution_method,
|
|
114
|
+
clickedAt: response.clicked_at,
|
|
115
|
+
message: response.message
|
|
116
|
+
};
|
|
117
|
+
// Store for future use
|
|
118
|
+
await async_storage_1.default.setItem(STORAGE_KEYS.ATTRIBUTION, JSON.stringify(attribution));
|
|
119
|
+
await async_storage_1.default.setItem(STORAGE_KEYS.REFERRAL_CODE, response.referral_code);
|
|
120
|
+
if (this.config?.debug) {
|
|
121
|
+
console.log('[InfluTo] ✅ Attribution found:', response.referral_code);
|
|
122
|
+
}
|
|
123
|
+
return attribution;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
if (this.config?.debug) {
|
|
127
|
+
console.log('[InfluTo] No attribution found (organic install)');
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
attributed: false,
|
|
131
|
+
message: response.message || 'No attribution found'
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
console.error('[InfluTo] Error checking attribution:', error);
|
|
137
|
+
return {
|
|
138
|
+
attributed: false,
|
|
139
|
+
message: 'Error checking attribution'
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Identify user with app_user_id
|
|
145
|
+
*
|
|
146
|
+
* Call this when you have the user's RevenueCat ID or custom user ID
|
|
147
|
+
*/
|
|
148
|
+
async identifyUser(appUserId, properties) {
|
|
149
|
+
if (!this.isInitialized) {
|
|
150
|
+
console.warn('[InfluTo] SDK not initialized');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
await async_storage_1.default.setItem(STORAGE_KEYS.APP_USER_ID, appUserId);
|
|
154
|
+
try {
|
|
155
|
+
await this.apiRequest('/sdk/identify', {
|
|
156
|
+
method: 'POST',
|
|
157
|
+
body: JSON.stringify({
|
|
158
|
+
app_user_id: appUserId,
|
|
159
|
+
properties: properties || {}
|
|
160
|
+
})
|
|
161
|
+
});
|
|
162
|
+
if (this.config?.debug) {
|
|
163
|
+
console.log('[InfluTo] User identified:', appUserId);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
console.error('[InfluTo] Error identifying user:', error);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Track custom event
|
|
172
|
+
*
|
|
173
|
+
* Use this to track key events like trial_started, paywall_viewed, etc.
|
|
174
|
+
*/
|
|
175
|
+
async trackEvent(options) {
|
|
176
|
+
if (!this.isInitialized) {
|
|
177
|
+
console.warn('[InfluTo] SDK not initialized');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
await this.apiRequest('/sdk/event', {
|
|
182
|
+
method: 'POST',
|
|
183
|
+
body: JSON.stringify(options)
|
|
184
|
+
});
|
|
185
|
+
if (this.config?.debug) {
|
|
186
|
+
console.log('[InfluTo] Event tracked:', options.eventType);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
console.error('[InfluTo] Error tracking event:', error);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Get active campaigns
|
|
195
|
+
*
|
|
196
|
+
* Useful for showing available promotions in-app
|
|
197
|
+
*/
|
|
198
|
+
async getActiveCampaigns() {
|
|
199
|
+
if (!this.isInitialized) {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const response = await this.apiRequest('/sdk/campaigns');
|
|
204
|
+
return response || [];
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
console.error('[InfluTo] Error fetching campaigns:', error);
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Get stored referral code (if any)
|
|
213
|
+
*/
|
|
214
|
+
async getReferralCode() {
|
|
215
|
+
return await async_storage_1.default.getItem(STORAGE_KEYS.REFERRAL_CODE);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Clear stored attribution data
|
|
219
|
+
*
|
|
220
|
+
* Useful for testing or user logout
|
|
221
|
+
*/
|
|
222
|
+
async clearAttribution() {
|
|
223
|
+
await async_storage_1.default.multiRemove([
|
|
224
|
+
STORAGE_KEYS.ATTRIBUTION,
|
|
225
|
+
STORAGE_KEYS.REFERRAL_CODE,
|
|
226
|
+
STORAGE_KEYS.APP_USER_ID
|
|
227
|
+
]);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Get device information for fingerprinting
|
|
231
|
+
*/
|
|
232
|
+
async getDeviceInfo() {
|
|
233
|
+
const DeviceInfo = require('react-native').DeviceInfo || {};
|
|
234
|
+
return {
|
|
235
|
+
platform: react_native_1.Platform.OS,
|
|
236
|
+
deviceId: DeviceInfo.getUniqueId?.() || undefined,
|
|
237
|
+
deviceBrand: DeviceInfo.getBrand?.() || undefined,
|
|
238
|
+
deviceModel: DeviceInfo.getModel?.() || undefined,
|
|
239
|
+
osVersion: DeviceInfo.getSystemVersion?.() || react_native_1.Platform.Version?.toString(),
|
|
240
|
+
screenResolution: `${DeviceInfo.getDeviceWidth?.()}x${DeviceInfo.getDeviceHeight?.()}` || undefined,
|
|
241
|
+
timezone: DeviceInfo.getTimezone?.() || undefined,
|
|
242
|
+
language: DeviceInfo.getPreferredLocales?.()?.[0] || undefined
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Make API request with authentication
|
|
247
|
+
*/
|
|
248
|
+
async apiRequest(endpoint, options = {}) {
|
|
249
|
+
if (!this.config) {
|
|
250
|
+
throw new Error('SDK not configured');
|
|
251
|
+
}
|
|
252
|
+
const url = `${this.apiUrl}${endpoint}`;
|
|
253
|
+
const response = await fetch(url, {
|
|
254
|
+
...options,
|
|
255
|
+
headers: {
|
|
256
|
+
'Content-Type': 'application/json',
|
|
257
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
258
|
+
...options.headers
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
if (!response.ok) {
|
|
262
|
+
const error = await response.text();
|
|
263
|
+
throw new Error(`API request failed: ${response.status} - ${error}`);
|
|
264
|
+
}
|
|
265
|
+
return await response.json();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// Export singleton instance
|
|
269
|
+
const InfluTo = new InfluToSDK();
|
|
270
|
+
exports.default = InfluTo;
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* InfluTo React Native SDK
|
|
4
|
+
* Main exports
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
18
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
19
|
+
};
|
|
20
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
21
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
|
+
};
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.default = void 0;
|
|
25
|
+
var InfluTo_1 = require("./InfluTo");
|
|
26
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(InfluTo_1).default; } });
|
|
27
|
+
__exportStar(require("./types"), exports);
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InfluTo SDK Types
|
|
3
|
+
*/
|
|
4
|
+
export interface InfluToConfig {
|
|
5
|
+
/**
|
|
6
|
+
* Your InfluTo API key from dashboard
|
|
7
|
+
*/
|
|
8
|
+
apiKey: string;
|
|
9
|
+
/**
|
|
10
|
+
* Enable debug logging (useful during development)
|
|
11
|
+
*/
|
|
12
|
+
debug?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* @internal - Custom API URL (not for public use)
|
|
15
|
+
*/
|
|
16
|
+
apiUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface AttributionResult {
|
|
19
|
+
/**
|
|
20
|
+
* Whether user was attributed to a referral
|
|
21
|
+
*/
|
|
22
|
+
attributed: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Referral code if attributed
|
|
25
|
+
*/
|
|
26
|
+
referralCode?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Attribution method used
|
|
29
|
+
*/
|
|
30
|
+
attributionMethod?: string;
|
|
31
|
+
/**
|
|
32
|
+
* When referral link was clicked
|
|
33
|
+
*/
|
|
34
|
+
clickedAt?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Attribution confidence (0.0-1.0)
|
|
37
|
+
*/
|
|
38
|
+
confidence?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Message explaining result
|
|
41
|
+
*/
|
|
42
|
+
message?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface Campaign {
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface TrackEventOptions {
|
|
50
|
+
/**
|
|
51
|
+
* Event name
|
|
52
|
+
*/
|
|
53
|
+
eventType: string;
|
|
54
|
+
/**
|
|
55
|
+
* User identifier (RevenueCat ID or custom)
|
|
56
|
+
*/
|
|
57
|
+
appUserId: string;
|
|
58
|
+
/**
|
|
59
|
+
* Custom event properties
|
|
60
|
+
*/
|
|
61
|
+
properties?: Record<string, any>;
|
|
62
|
+
/**
|
|
63
|
+
* Associated referral code
|
|
64
|
+
*/
|
|
65
|
+
referralCode?: string;
|
|
66
|
+
}
|
|
67
|
+
export interface DeviceInfo {
|
|
68
|
+
platform: 'ios' | 'android';
|
|
69
|
+
deviceId?: string;
|
|
70
|
+
deviceBrand?: string;
|
|
71
|
+
deviceModel?: string;
|
|
72
|
+
osVersion?: string;
|
|
73
|
+
screenResolution?: string;
|
|
74
|
+
timezone?: string;
|
|
75
|
+
language?: string;
|
|
76
|
+
}
|
package/lib/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@influto/react-native-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "InfluTo SDK for React Native - Track influencer referrals and conversions",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib",
|
|
9
|
+
"src",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"watch": "tsc --watch",
|
|
16
|
+
"prepare": "npm run build",
|
|
17
|
+
"prepublishOnly": "npm run build",
|
|
18
|
+
"test": "jest"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"influto",
|
|
22
|
+
"influencer",
|
|
23
|
+
"affiliate",
|
|
24
|
+
"attribution",
|
|
25
|
+
"react-native",
|
|
26
|
+
"ios",
|
|
27
|
+
"android",
|
|
28
|
+
"revenuecat",
|
|
29
|
+
"tracking",
|
|
30
|
+
"analytics",
|
|
31
|
+
"referral"
|
|
32
|
+
],
|
|
33
|
+
"author": "InfluTo <support@influ.to>",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"react": ">=16.8.0",
|
|
37
|
+
"react-native": ">=0.60.0"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@react-native-async-storage/async-storage": "^2.0.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/react": "^18.2.0",
|
|
44
|
+
"@types/react-native": "^0.72.0",
|
|
45
|
+
"typescript": "^5.3.0"
|
|
46
|
+
},
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://influ.to/support"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://influ.to",
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/InfluTo.ts
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InfluTo SDK for React Native
|
|
3
|
+
*
|
|
4
|
+
* Track influencer referrals and conversions in your mobile app
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import InfluTo from '@influto/react-native-sdk';
|
|
9
|
+
*
|
|
10
|
+
* // Initialize (same key for dev & production)
|
|
11
|
+
* await InfluTo.initialize({
|
|
12
|
+
* apiKey: 'it_abc123...',
|
|
13
|
+
* debug: __DEV__ // Enable logging in development
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* // Check attribution
|
|
17
|
+
* const attribution = await InfluTo.checkAttribution();
|
|
18
|
+
* if (attribution.attributed) {
|
|
19
|
+
* console.log('User came from referral:', attribution.referralCode);
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { Platform } from 'react-native';
|
|
25
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
26
|
+
import type {
|
|
27
|
+
InfluToConfig,
|
|
28
|
+
AttributionResult,
|
|
29
|
+
Campaign,
|
|
30
|
+
TrackEventOptions,
|
|
31
|
+
DeviceInfo
|
|
32
|
+
} from './types';
|
|
33
|
+
|
|
34
|
+
const STORAGE_PREFIX = '@influto/';
|
|
35
|
+
const STORAGE_KEYS = {
|
|
36
|
+
ATTRIBUTION: `${STORAGE_PREFIX}attribution`,
|
|
37
|
+
APP_USER_ID: `${STORAGE_PREFIX}app_user_id`,
|
|
38
|
+
REFERRAL_CODE: `${STORAGE_PREFIX}referral_code`,
|
|
39
|
+
SDK_INITIALIZED: `${STORAGE_PREFIX}initialized`,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
class InfluToSDK {
|
|
43
|
+
private config: InfluToConfig | null = null;
|
|
44
|
+
private apiUrl: string = 'https://api.influ.to/api';
|
|
45
|
+
private isInitialized: boolean = false;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Initialize InfluTo SDK
|
|
49
|
+
*
|
|
50
|
+
* Call this once when your app starts, after user grants tracking permission
|
|
51
|
+
*/
|
|
52
|
+
async initialize(config: InfluToConfig): Promise<void> {
|
|
53
|
+
this.config = config;
|
|
54
|
+
|
|
55
|
+
// Use custom API URL if provided (internal testing only)
|
|
56
|
+
if (config.apiUrl) {
|
|
57
|
+
this.apiUrl = config.apiUrl;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (config.debug) {
|
|
61
|
+
console.log('[InfluTo] Initializing SDK...');
|
|
62
|
+
console.log('[InfluTo] API URL:', this.apiUrl);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
// Call init endpoint
|
|
67
|
+
const response = await this.apiRequest('/sdk/init', {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
body: JSON.stringify({
|
|
70
|
+
app_version: require('react-native').Platform.constants?.version || 'unknown',
|
|
71
|
+
sdk_version: '1.0.0',
|
|
72
|
+
platform: Platform.OS
|
|
73
|
+
})
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (response.initialized) {
|
|
77
|
+
this.isInitialized = true;
|
|
78
|
+
await AsyncStorage.setItem(STORAGE_KEYS.SDK_INITIALIZED, 'true');
|
|
79
|
+
|
|
80
|
+
if (config.debug) {
|
|
81
|
+
console.log('[InfluTo] SDK initialized successfully');
|
|
82
|
+
console.log('[InfluTo] Active campaigns:', response.campaigns?.length || 0);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error('[InfluTo] Initialization failed:', error);
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Check if current user has attribution to a referral
|
|
93
|
+
|
|
94
|
+
*
|
|
95
|
+
* Call this during app onboarding to see if user should get trial/discount
|
|
96
|
+
*
|
|
97
|
+
* @returns Attribution result with referral code if attributed
|
|
98
|
+
*/
|
|
99
|
+
async checkAttribution(): Promise<AttributionResult> {
|
|
100
|
+
if (!this.isInitialized) {
|
|
101
|
+
throw new Error('InfluTo SDK not initialized. Call InfluTo.initialize() first.');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
// Check if already stored locally
|
|
106
|
+
const storedAttribution = await AsyncStorage.getItem(STORAGE_KEYS.ATTRIBUTION);
|
|
107
|
+
if (storedAttribution) {
|
|
108
|
+
if (this.config?.debug) {
|
|
109
|
+
console.log('[InfluTo] Attribution found in storage');
|
|
110
|
+
}
|
|
111
|
+
return JSON.parse(storedAttribution);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Track install and check attribution
|
|
115
|
+
const deviceInfo = await this.getDeviceInfo();
|
|
116
|
+
|
|
117
|
+
const response = await this.apiRequest('/sdk/track-install', {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
body: JSON.stringify(deviceInfo)
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (response.attributed && response.referral_code) {
|
|
123
|
+
const attribution: AttributionResult = {
|
|
124
|
+
attributed: true,
|
|
125
|
+
referralCode: response.referral_code,
|
|
126
|
+
attributionMethod: response.attribution_method,
|
|
127
|
+
clickedAt: response.clicked_at,
|
|
128
|
+
message: response.message
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Store for future use
|
|
132
|
+
await AsyncStorage.setItem(STORAGE_KEYS.ATTRIBUTION, JSON.stringify(attribution));
|
|
133
|
+
await AsyncStorage.setItem(STORAGE_KEYS.REFERRAL_CODE, response.referral_code);
|
|
134
|
+
|
|
135
|
+
if (this.config?.debug) {
|
|
136
|
+
console.log('[InfluTo] ✅ Attribution found:', response.referral_code);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return attribution;
|
|
140
|
+
} else {
|
|
141
|
+
if (this.config?.debug) {
|
|
142
|
+
console.log('[InfluTo] No attribution found (organic install)');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
attributed: false,
|
|
147
|
+
message: response.message || 'No attribution found'
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('[InfluTo] Error checking attribution:', error);
|
|
152
|
+
return {
|
|
153
|
+
attributed: false,
|
|
154
|
+
message: 'Error checking attribution'
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Identify user with app_user_id
|
|
161
|
+
*
|
|
162
|
+
* Call this when you have the user's RevenueCat ID or custom user ID
|
|
163
|
+
*/
|
|
164
|
+
async identifyUser(appUserId: string, properties?: Record<string, any>): Promise<void> {
|
|
165
|
+
if (!this.isInitialized) {
|
|
166
|
+
console.warn('[InfluTo] SDK not initialized');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
await AsyncStorage.setItem(STORAGE_KEYS.APP_USER_ID, appUserId);
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
await this.apiRequest('/sdk/identify', {
|
|
174
|
+
method: 'POST',
|
|
175
|
+
body: JSON.stringify({
|
|
176
|
+
app_user_id: appUserId,
|
|
177
|
+
properties: properties || {}
|
|
178
|
+
})
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (this.config?.debug) {
|
|
182
|
+
console.log('[InfluTo] User identified:', appUserId);
|
|
183
|
+
}
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.error('[InfluTo] Error identifying user:', error);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Track custom event
|
|
191
|
+
*
|
|
192
|
+
* Use this to track key events like trial_started, paywall_viewed, etc.
|
|
193
|
+
*/
|
|
194
|
+
async trackEvent(options: TrackEventOptions): Promise<void> {
|
|
195
|
+
if (!this.isInitialized) {
|
|
196
|
+
console.warn('[InfluTo] SDK not initialized');
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
await this.apiRequest('/sdk/event', {
|
|
202
|
+
method: 'POST',
|
|
203
|
+
body: JSON.stringify(options)
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
if (this.config?.debug) {
|
|
207
|
+
console.log('[InfluTo] Event tracked:', options.eventType);
|
|
208
|
+
}
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.error('[InfluTo] Error tracking event:', error);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get active campaigns
|
|
216
|
+
*
|
|
217
|
+
* Useful for showing available promotions in-app
|
|
218
|
+
*/
|
|
219
|
+
async getActiveCampaigns(): Promise<Campaign[]> {
|
|
220
|
+
if (!this.isInitialized) {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
const response = await this.apiRequest('/sdk/campaigns');
|
|
226
|
+
return response || [];
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error('[InfluTo] Error fetching campaigns:', error);
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get stored referral code (if any)
|
|
235
|
+
*/
|
|
236
|
+
async getReferralCode(): Promise<string | null> {
|
|
237
|
+
return await AsyncStorage.getItem(STORAGE_KEYS.REFERRAL_CODE);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Clear stored attribution data
|
|
242
|
+
*
|
|
243
|
+
* Useful for testing or user logout
|
|
244
|
+
*/
|
|
245
|
+
async clearAttribution(): Promise<void> {
|
|
246
|
+
await AsyncStorage.multiRemove([
|
|
247
|
+
STORAGE_KEYS.ATTRIBUTION,
|
|
248
|
+
STORAGE_KEYS.REFERRAL_CODE,
|
|
249
|
+
STORAGE_KEYS.APP_USER_ID
|
|
250
|
+
]);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get device information for fingerprinting
|
|
255
|
+
*/
|
|
256
|
+
private async getDeviceInfo(): Promise<DeviceInfo> {
|
|
257
|
+
const DeviceInfo = require('react-native').DeviceInfo || {};
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
platform: Platform.OS as 'ios' | 'android',
|
|
261
|
+
deviceId: DeviceInfo.getUniqueId?.() || undefined,
|
|
262
|
+
deviceBrand: DeviceInfo.getBrand?.() || undefined,
|
|
263
|
+
deviceModel: DeviceInfo.getModel?.() || undefined,
|
|
264
|
+
osVersion: DeviceInfo.getSystemVersion?.() || Platform.Version?.toString(),
|
|
265
|
+
screenResolution: `${DeviceInfo.getDeviceWidth?.()}x${DeviceInfo.getDeviceHeight?.()}` || undefined,
|
|
266
|
+
timezone: DeviceInfo.getTimezone?.() || undefined,
|
|
267
|
+
language: DeviceInfo.getPreferredLocales?.()?.[0] || undefined
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Make API request with authentication
|
|
273
|
+
*/
|
|
274
|
+
private async apiRequest(
|
|
275
|
+
endpoint: string,
|
|
276
|
+
options: RequestInit = {}
|
|
277
|
+
): Promise<any> {
|
|
278
|
+
if (!this.config) {
|
|
279
|
+
throw new Error('SDK not configured');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const url = `${this.apiUrl}${endpoint}`;
|
|
283
|
+
|
|
284
|
+
const response = await fetch(url, {
|
|
285
|
+
...options,
|
|
286
|
+
headers: {
|
|
287
|
+
'Content-Type': 'application/json',
|
|
288
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
289
|
+
...options.headers
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
if (!response.ok) {
|
|
294
|
+
const error = await response.text();
|
|
295
|
+
throw new Error(`API request failed: ${response.status} - ${error}`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return await response.json();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Export singleton instance
|
|
303
|
+
const InfluTo = new InfluToSDK();
|
|
304
|
+
export default InfluTo;
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InfluTo SDK Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface InfluToConfig {
|
|
6
|
+
/**
|
|
7
|
+
* Your InfluTo API key from dashboard
|
|
8
|
+
*/
|
|
9
|
+
apiKey: string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Enable debug logging (useful during development)
|
|
13
|
+
*/
|
|
14
|
+
debug?: boolean;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @internal - Custom API URL (not for public use)
|
|
18
|
+
*/
|
|
19
|
+
apiUrl?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AttributionResult {
|
|
23
|
+
/**
|
|
24
|
+
* Whether user was attributed to a referral
|
|
25
|
+
*/
|
|
26
|
+
attributed: boolean;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Referral code if attributed
|
|
30
|
+
*/
|
|
31
|
+
referralCode?: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Attribution method used
|
|
35
|
+
*/
|
|
36
|
+
attributionMethod?: string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* When referral link was clicked
|
|
40
|
+
*/
|
|
41
|
+
clickedAt?: string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Attribution confidence (0.0-1.0)
|
|
45
|
+
*/
|
|
46
|
+
confidence?: number;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Message explaining result
|
|
50
|
+
*/
|
|
51
|
+
message?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface Campaign {
|
|
55
|
+
id: string;
|
|
56
|
+
name: string;
|
|
57
|
+
description?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface TrackEventOptions {
|
|
61
|
+
/**
|
|
62
|
+
* Event name
|
|
63
|
+
*/
|
|
64
|
+
eventType: string;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* User identifier (RevenueCat ID or custom)
|
|
68
|
+
*/
|
|
69
|
+
appUserId: string;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Custom event properties
|
|
73
|
+
*/
|
|
74
|
+
properties?: Record<string, any>;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Associated referral code
|
|
78
|
+
*/
|
|
79
|
+
referralCode?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface DeviceInfo {
|
|
83
|
+
platform: 'ios' | 'android';
|
|
84
|
+
deviceId?: string;
|
|
85
|
+
deviceBrand?: string;
|
|
86
|
+
deviceModel?: string;
|
|
87
|
+
osVersion?: string;
|
|
88
|
+
screenResolution?: string;
|
|
89
|
+
timezone?: string;
|
|
90
|
+
language?: string;
|
|
91
|
+
}
|