@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 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
@@ -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
@@ -0,0 +1,6 @@
1
+ /**
2
+ * InfluTo React Native SDK
3
+ * Main exports
4
+ */
5
+ export { default } from './InfluTo';
6
+ export * from './types';
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
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ /**
3
+ * InfluTo SDK Types
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
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
@@ -0,0 +1,7 @@
1
+ /**
2
+ * InfluTo React Native SDK
3
+ * Main exports
4
+ */
5
+
6
+ export { default } from './InfluTo';
7
+ export * from './types';
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
+ }