insert-affiliate-react-native-sdk 1.9.0 → 1.10.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/dist/DeepLinkIapProvider.js +117 -15
- package/docs/deep-linking-appsflyer.md +364 -0
- package/docs/deep-linking-branch.md +330 -0
- package/docs/dynamic-offer-codes.md +369 -0
- package/package.json +1 -1
- package/readme.md +592 -1073
- package/src/DeepLinkIapProvider.tsx +146 -24
|
@@ -54,6 +54,8 @@ const ASYNC_KEYS = {
|
|
|
54
54
|
USER_ACCOUNT_TOKEN: '@app_user_account_token',
|
|
55
55
|
IOS_OFFER_CODE: '@app_ios_offer_code',
|
|
56
56
|
AFFILIATE_STORED_DATE: '@app_affiliate_stored_date',
|
|
57
|
+
SDK_INIT_REPORTED: '@app_sdk_init_reported',
|
|
58
|
+
REPORTED_AFFILIATE_ASSOCIATIONS: '@app_reported_affiliate_associations',
|
|
57
59
|
};
|
|
58
60
|
// STARTING CONTEXT IMPLEMENTATION
|
|
59
61
|
exports.DeepLinkIapContext = (0, react_1.createContext)({
|
|
@@ -112,6 +114,8 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
112
114
|
console.log('[Insert Affiliate] [VERBOSE] Company code saved to AsyncStorage');
|
|
113
115
|
console.log('[Insert Affiliate] [VERBOSE] SDK marked as initialized');
|
|
114
116
|
}
|
|
117
|
+
// Report SDK initialization for onboarding verification (fire and forget)
|
|
118
|
+
reportSdkInitIfNeeded(companyCode, verboseLogging);
|
|
115
119
|
}
|
|
116
120
|
else {
|
|
117
121
|
console.warn('[Insert Affiliate] SDK initialized without a company code.');
|
|
@@ -330,12 +334,24 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
330
334
|
verboseLog('Invalid URL provided to handleInsertLinkAndroid');
|
|
331
335
|
return false;
|
|
332
336
|
}
|
|
333
|
-
// Parse the URL to extract query parameters
|
|
334
|
-
|
|
335
|
-
|
|
337
|
+
// Parse the URL to extract query parameters (React Native compatible)
|
|
338
|
+
// URLSearchParams is not available in React Native, so parse manually
|
|
339
|
+
let insertAffiliate = null;
|
|
340
|
+
const queryIndex = url.indexOf('?');
|
|
341
|
+
if (queryIndex !== -1) {
|
|
342
|
+
const queryString = url.substring(queryIndex + 1);
|
|
343
|
+
const params = queryString.split('&');
|
|
344
|
+
for (const param of params) {
|
|
345
|
+
const [key, value] = param.split('=');
|
|
346
|
+
if (key === 'insertAffiliate' && value) {
|
|
347
|
+
insertAffiliate = decodeURIComponent(value);
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
336
352
|
if (insertAffiliate && insertAffiliate.length > 0) {
|
|
337
353
|
verboseLog(`Found insertAffiliate parameter: ${insertAffiliate}`);
|
|
338
|
-
yield storeInsertAffiliateIdentifier({ link: insertAffiliate });
|
|
354
|
+
yield storeInsertAffiliateIdentifier({ link: insertAffiliate, source: 'deep_link_android' });
|
|
339
355
|
return true;
|
|
340
356
|
}
|
|
341
357
|
else {
|
|
@@ -449,7 +465,7 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
449
465
|
// If we have insertAffiliate parameter, use it as the affiliate identifier
|
|
450
466
|
if (insertAffiliate && insertAffiliate.length > 0) {
|
|
451
467
|
verboseLog(`Found insertAffiliate parameter, setting as affiliate identifier: ${insertAffiliate}`);
|
|
452
|
-
yield storeInsertAffiliateIdentifier({ link: insertAffiliate });
|
|
468
|
+
yield storeInsertAffiliateIdentifier({ link: insertAffiliate, source: 'install_referrer' });
|
|
453
469
|
return true;
|
|
454
470
|
}
|
|
455
471
|
else {
|
|
@@ -513,7 +529,7 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
513
529
|
console.log(`[Insert Affiliate] Warning: URL company code (${companyCode}) doesn't match initialized company code (${activeCompanyCode})`);
|
|
514
530
|
}
|
|
515
531
|
// If URL scheme is used, we can straight away store the short code as the referring link
|
|
516
|
-
yield storeInsertAffiliateIdentifier({ link: shortCode });
|
|
532
|
+
yield storeInsertAffiliateIdentifier({ link: shortCode, source: 'deep_link_ios' });
|
|
517
533
|
// Collect and send enhanced system info to backend
|
|
518
534
|
try {
|
|
519
535
|
const enhancedSystemInfo = yield getEnhancedSystemInfo();
|
|
@@ -630,6 +646,87 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
630
646
|
break;
|
|
631
647
|
}
|
|
632
648
|
};
|
|
649
|
+
// Reports a new affiliate association to the backend for tracking.
|
|
650
|
+
// Only reports each unique affiliateIdentifier once to prevent duplicates.
|
|
651
|
+
const reportAffiliateAssociationIfNeeded = (affiliateIdentifier, source) => __awaiter(void 0, void 0, void 0, function* () {
|
|
652
|
+
try {
|
|
653
|
+
const activeCompanyCode = yield getActiveCompanyCode();
|
|
654
|
+
if (!activeCompanyCode) {
|
|
655
|
+
verboseLog('Cannot report affiliate association: no company code available');
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
// Get the set of already-reported affiliate identifiers
|
|
659
|
+
const reportedAssociationsJson = yield async_storage_1.default.getItem(ASYNC_KEYS.REPORTED_AFFILIATE_ASSOCIATIONS);
|
|
660
|
+
const reportedAssociations = reportedAssociationsJson ? JSON.parse(reportedAssociationsJson) : [];
|
|
661
|
+
// Check if this affiliate identifier has already been reported
|
|
662
|
+
if (reportedAssociations.includes(affiliateIdentifier)) {
|
|
663
|
+
verboseLog(`Affiliate association already reported for: ${affiliateIdentifier}, skipping`);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
verboseLog(`Reporting new affiliate association: ${affiliateIdentifier} (source: ${source})`);
|
|
667
|
+
const response = yield fetch('https://api.insertaffiliate.com/V1/onboarding/affiliate-associated', {
|
|
668
|
+
method: 'POST',
|
|
669
|
+
headers: {
|
|
670
|
+
'Content-Type': 'application/json',
|
|
671
|
+
},
|
|
672
|
+
body: JSON.stringify({
|
|
673
|
+
companyId: activeCompanyCode,
|
|
674
|
+
affiliateIdentifier: affiliateIdentifier,
|
|
675
|
+
source: source,
|
|
676
|
+
timestamp: new Date().toISOString(),
|
|
677
|
+
}),
|
|
678
|
+
});
|
|
679
|
+
if (response.ok) {
|
|
680
|
+
// Add to reported set and persist
|
|
681
|
+
reportedAssociations.push(affiliateIdentifier);
|
|
682
|
+
yield async_storage_1.default.setItem(ASYNC_KEYS.REPORTED_AFFILIATE_ASSOCIATIONS, JSON.stringify(reportedAssociations));
|
|
683
|
+
verboseLog(`Affiliate association reported successfully for: ${affiliateIdentifier}`);
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
verboseLog(`Affiliate association report failed with status: ${response.status}`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
catch (error) {
|
|
690
|
+
// Silently fail - this is non-critical telemetry
|
|
691
|
+
verboseLog(`Affiliate association report error: ${error}`);
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
// Reports SDK initialization to the backend for onboarding verification.
|
|
695
|
+
// Only reports once per install to minimize server load.
|
|
696
|
+
const reportSdkInitIfNeeded = (companyCode, verboseLogging) => __awaiter(void 0, void 0, void 0, function* () {
|
|
697
|
+
try {
|
|
698
|
+
// Only report once per install
|
|
699
|
+
const alreadyReported = yield async_storage_1.default.getItem(ASYNC_KEYS.SDK_INIT_REPORTED);
|
|
700
|
+
if (alreadyReported === 'true') {
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
if (verboseLogging) {
|
|
704
|
+
console.log('[Insert Affiliate] Reporting SDK initialization for onboarding verification...');
|
|
705
|
+
}
|
|
706
|
+
const response = yield fetch('https://api.insertaffiliate.com/V1/onboarding/sdk-init', {
|
|
707
|
+
method: 'POST',
|
|
708
|
+
headers: {
|
|
709
|
+
'Content-Type': 'application/json',
|
|
710
|
+
},
|
|
711
|
+
body: JSON.stringify({ companyId: companyCode }),
|
|
712
|
+
});
|
|
713
|
+
if (response.ok) {
|
|
714
|
+
yield async_storage_1.default.setItem(ASYNC_KEYS.SDK_INIT_REPORTED, 'true');
|
|
715
|
+
if (verboseLogging) {
|
|
716
|
+
console.log('[Insert Affiliate] SDK initialization reported successfully');
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
else if (verboseLogging) {
|
|
720
|
+
console.log(`[Insert Affiliate] SDK initialization report failed with status: ${response.status}`);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
catch (error) {
|
|
724
|
+
// Silently fail - this is non-critical telemetry
|
|
725
|
+
if (verboseLogging) {
|
|
726
|
+
console.log(`[Insert Affiliate] SDK initialization report error: ${error}`);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
});
|
|
633
730
|
// MARK: - Deep Linking Utilities
|
|
634
731
|
// Retrieves and validates clipboard content for UUID format
|
|
635
732
|
const getClipboardUUID = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -995,7 +1092,7 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
995
1092
|
if (matchFound && response.data.matched_affiliate_shortCode && response.data.matched_affiliate_shortCode.length > 0) {
|
|
996
1093
|
const matchedShortCode = response.data.matched_affiliate_shortCode;
|
|
997
1094
|
verboseLog(`Storing Matched short code from backend: ${matchedShortCode}`);
|
|
998
|
-
yield storeInsertAffiliateIdentifier({ link: matchedShortCode });
|
|
1095
|
+
yield storeInsertAffiliateIdentifier({ link: matchedShortCode, source: 'clipboard_match' });
|
|
999
1096
|
}
|
|
1000
1097
|
}
|
|
1001
1098
|
// Check for a successful response
|
|
@@ -1109,7 +1206,7 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
1109
1206
|
const exists = yield checkAffiliateExists(capitalisedShortCode);
|
|
1110
1207
|
if (exists) {
|
|
1111
1208
|
// If affiliate exists, set the Insert Affiliate Identifier
|
|
1112
|
-
yield storeInsertAffiliateIdentifier({ link: capitalisedShortCode });
|
|
1209
|
+
yield storeInsertAffiliateIdentifier({ link: capitalisedShortCode, source: 'short_code_manual' });
|
|
1113
1210
|
console.log(`[Insert Affiliate] Short code ${capitalisedShortCode} validated and stored successfully.`);
|
|
1114
1211
|
return true;
|
|
1115
1212
|
}
|
|
@@ -1262,7 +1359,7 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
1262
1359
|
if (!referringLink) {
|
|
1263
1360
|
console.warn('[Insert Affiliate] Referring link is invalid.');
|
|
1264
1361
|
verboseLog('Referring link is empty or invalid, storing as-is');
|
|
1265
|
-
yield storeInsertAffiliateIdentifier({ link: referringLink });
|
|
1362
|
+
yield storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
|
|
1266
1363
|
return `${referringLink}-${customerID}`;
|
|
1267
1364
|
}
|
|
1268
1365
|
// Get company code from state or storage
|
|
@@ -1279,7 +1376,7 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
1279
1376
|
if (isShortCode(referringLink)) {
|
|
1280
1377
|
console.log('[Insert Affiliate] Referring link is already a short code.');
|
|
1281
1378
|
verboseLog('Link is already a short code, storing directly');
|
|
1282
|
-
yield storeInsertAffiliateIdentifier({ link: referringLink });
|
|
1379
|
+
yield storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
|
|
1283
1380
|
return `${referringLink}-${customerID}`;
|
|
1284
1381
|
}
|
|
1285
1382
|
verboseLog('Link is not a short code, will convert via API');
|
|
@@ -1290,7 +1387,7 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
1290
1387
|
if (!encodedAffiliateLink) {
|
|
1291
1388
|
console.error('[Insert Affiliate] Failed to encode affiliate link.');
|
|
1292
1389
|
verboseLog('Failed to encode link, storing original');
|
|
1293
|
-
yield storeInsertAffiliateIdentifier({ link: referringLink });
|
|
1390
|
+
yield storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
|
|
1294
1391
|
return `${referringLink}-${customerID}`;
|
|
1295
1392
|
}
|
|
1296
1393
|
// Create the request URL
|
|
@@ -1309,7 +1406,7 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
1309
1406
|
console.log('[Insert Affiliate] Short link received:', shortLink);
|
|
1310
1407
|
verboseLog(`Successfully converted to short link: ${shortLink}`);
|
|
1311
1408
|
verboseLog('Storing short link to AsyncStorage...');
|
|
1312
|
-
yield storeInsertAffiliateIdentifier({ link: shortLink });
|
|
1409
|
+
yield storeInsertAffiliateIdentifier({ link: shortLink, source: 'referring_link' });
|
|
1313
1410
|
verboseLog('Short link stored successfully');
|
|
1314
1411
|
return `${shortLink}-${customerID}`;
|
|
1315
1412
|
}
|
|
@@ -1317,7 +1414,7 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
1317
1414
|
console.warn('[Insert Affiliate] Unexpected response format.');
|
|
1318
1415
|
verboseLog(`Unexpected API response. Status: ${response.status}, Data: ${JSON.stringify(response.data)}`);
|
|
1319
1416
|
verboseLog('Storing original link as fallback');
|
|
1320
|
-
yield storeInsertAffiliateIdentifier({ link: referringLink });
|
|
1417
|
+
yield storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
|
|
1321
1418
|
return `${referringLink}-${customerID}`;
|
|
1322
1419
|
}
|
|
1323
1420
|
}
|
|
@@ -1329,8 +1426,8 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
1329
1426
|
}
|
|
1330
1427
|
;
|
|
1331
1428
|
function storeInsertAffiliateIdentifier(_a) {
|
|
1332
|
-
return __awaiter(this, arguments, void 0, function* ({ link }) {
|
|
1333
|
-
console.log(`[Insert Affiliate] Storing affiliate identifier: ${link}`);
|
|
1429
|
+
return __awaiter(this, arguments, void 0, function* ({ link, source }) {
|
|
1430
|
+
console.log(`[Insert Affiliate] Storing affiliate identifier: ${link} (source: ${source})`);
|
|
1334
1431
|
// Check if we're trying to store the same link (prevent duplicate storage)
|
|
1335
1432
|
const existingLink = yield getValueFromAsync(ASYNC_KEYS.REFERRER_LINK);
|
|
1336
1433
|
if (existingLink === link) {
|
|
@@ -1355,6 +1452,11 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
1355
1452
|
verboseLog(`Triggering callback with identifier: ${currentIdentifier}`);
|
|
1356
1453
|
insertAffiliateIdentifierChangeCallbackRef.current(currentIdentifier);
|
|
1357
1454
|
}
|
|
1455
|
+
// Report this new affiliate association to the backend (fire and forget)
|
|
1456
|
+
const fullIdentifier = yield returnInsertAffiliateIdentifier();
|
|
1457
|
+
if (fullIdentifier) {
|
|
1458
|
+
reportAffiliateAssociationIfNeeded(fullIdentifier, source);
|
|
1459
|
+
}
|
|
1358
1460
|
});
|
|
1359
1461
|
}
|
|
1360
1462
|
const validatePurchaseWithIapticAPI = (jsonIapPurchase, iapticAppId, iapticAppName, iapticPublicKey) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# AppsFlyer Deep Linking Integration
|
|
2
|
+
|
|
3
|
+
This guide shows how to integrate InsertAffiliateReactNative SDK with AppsFlyer for deep linking attribution.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- [AppsFlyer SDK for React Native](https://dev.appsflyer.com/hc/docs/react-native-plugin) installed and configured
|
|
8
|
+
- Create an AppsFlyer OneLink and provide it to affiliates via the [Insert Affiliate dashboard](https://app.insertaffiliate.com/affiliates)
|
|
9
|
+
|
|
10
|
+
## Platform Setup
|
|
11
|
+
|
|
12
|
+
Complete the deep linking setup for AppsFlyer by following their official documentation:
|
|
13
|
+
- [AppsFlyer Deferred Deep Link Integration Guide](https://dev.appsflyer.com/hc/docs/deeplinkintegrate)
|
|
14
|
+
|
|
15
|
+
This covers all platform-specific configurations including:
|
|
16
|
+
- iOS: Info.plist configuration, AppDelegate setup, and universal links
|
|
17
|
+
- Android: AndroidManifest.xml intent filters, MainActivity setup, and App Links
|
|
18
|
+
- Testing and troubleshooting for both platforms
|
|
19
|
+
|
|
20
|
+
## Integration Examples
|
|
21
|
+
|
|
22
|
+
Choose the example that matches your IAP verification platform:
|
|
23
|
+
|
|
24
|
+
### Example with RevenueCat
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
import React, { useEffect } from 'react';
|
|
28
|
+
import { AppRegistry, Platform } from 'react-native';
|
|
29
|
+
import appsFlyer from 'react-native-appsflyer';
|
|
30
|
+
import Purchases from 'react-native-purchases';
|
|
31
|
+
import { useDeepLinkIapProvider, DeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
|
|
32
|
+
import App from './App';
|
|
33
|
+
import { name as appName } from './app.json';
|
|
34
|
+
|
|
35
|
+
const DeepLinkHandler = () => {
|
|
36
|
+
const { setInsertAffiliateIdentifier, isInitialized } = useDeepLinkIapProvider();
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!isInitialized) return;
|
|
40
|
+
|
|
41
|
+
// Initialize AppsFlyer
|
|
42
|
+
const initAppsFlyer = async () => {
|
|
43
|
+
try {
|
|
44
|
+
const initOptions = {
|
|
45
|
+
devKey: 'YOUR_APPSFLYER_DEV_KEY',
|
|
46
|
+
isDebug: true,
|
|
47
|
+
appId: Platform.OS === 'ios' ? 'YOUR_IOS_APP_ID' : 'YOUR_ANDROID_PACKAGE_NAME',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
await appsFlyer.initSdk(initOptions);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error('AppsFlyer initialization error:', error);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Handle deep link data
|
|
57
|
+
const handleDeepLink = async (deepLinkData) => {
|
|
58
|
+
if (deepLinkData && deepLinkData.data) {
|
|
59
|
+
const referringLink = deepLinkData.data.link || deepLinkData.data.deep_link_value;
|
|
60
|
+
|
|
61
|
+
if (referringLink) {
|
|
62
|
+
try {
|
|
63
|
+
const insertAffiliateIdentifier = await setInsertAffiliateIdentifier(referringLink);
|
|
64
|
+
|
|
65
|
+
if (insertAffiliateIdentifier) {
|
|
66
|
+
await Purchases.setAttributes({ "insert_affiliate": insertAffiliateIdentifier });
|
|
67
|
+
}
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error('Error setting affiliate identifier:', err);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Listen for all deep link types
|
|
76
|
+
appsFlyer.onDeepLink(handleDeepLink);
|
|
77
|
+
appsFlyer.onAppOpenAttribution(handleDeepLink);
|
|
78
|
+
appsFlyer.onInstallConversionData(handleDeepLink);
|
|
79
|
+
|
|
80
|
+
initAppsFlyer();
|
|
81
|
+
}, [setInsertAffiliateIdentifier, isInitialized]);
|
|
82
|
+
|
|
83
|
+
return <App />;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const RootComponent = () => {
|
|
87
|
+
return (
|
|
88
|
+
<DeepLinkIapProvider>
|
|
89
|
+
<DeepLinkHandler />
|
|
90
|
+
</DeepLinkIapProvider>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
AppRegistry.registerComponent(appName, () => RootComponent);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Replace the following:**
|
|
98
|
+
- `YOUR_APPSFLYER_DEV_KEY` with your AppsFlyer Dev Key
|
|
99
|
+
- `YOUR_IOS_APP_ID` with your iOS App ID (numbers only, e.g., "123456789")
|
|
100
|
+
- `YOUR_ANDROID_PACKAGE_NAME` with your Android package name
|
|
101
|
+
|
|
102
|
+
### Example with Adapty
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
import React, { useEffect } from 'react';
|
|
106
|
+
import { AppRegistry, Platform } from 'react-native';
|
|
107
|
+
import appsFlyer from 'react-native-appsflyer';
|
|
108
|
+
import { adapty } from 'react-native-adapty';
|
|
109
|
+
import { useDeepLinkIapProvider, DeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
|
|
110
|
+
import App from './App';
|
|
111
|
+
import { name as appName } from './app.json';
|
|
112
|
+
|
|
113
|
+
const DeepLinkHandler = () => {
|
|
114
|
+
const { setInsertAffiliateIdentifier, isInitialized } = useDeepLinkIapProvider();
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (!isInitialized) return;
|
|
118
|
+
|
|
119
|
+
const initAppsFlyer = async () => {
|
|
120
|
+
try {
|
|
121
|
+
const initOptions = {
|
|
122
|
+
devKey: 'YOUR_APPSFLYER_DEV_KEY',
|
|
123
|
+
isDebug: true,
|
|
124
|
+
appId: Platform.OS === 'ios' ? 'YOUR_IOS_APP_ID' : 'YOUR_ANDROID_PACKAGE_NAME',
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
await appsFlyer.initSdk(initOptions);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('AppsFlyer initialization error:', error);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const handleDeepLink = async (deepLinkData) => {
|
|
134
|
+
if (deepLinkData && deepLinkData.data) {
|
|
135
|
+
const referringLink = deepLinkData.data.link || deepLinkData.data.deep_link_value;
|
|
136
|
+
|
|
137
|
+
if (referringLink) {
|
|
138
|
+
try {
|
|
139
|
+
const insertAffiliateIdentifier = await setInsertAffiliateIdentifier(referringLink);
|
|
140
|
+
|
|
141
|
+
if (insertAffiliateIdentifier) {
|
|
142
|
+
await adapty.updateProfile({
|
|
143
|
+
codableCustomAttributes: {
|
|
144
|
+
insert_affiliate: insertAffiliateIdentifier,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
} catch (err) {
|
|
149
|
+
console.error('Error setting affiliate identifier:', err);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
appsFlyer.onDeepLink(handleDeepLink);
|
|
156
|
+
appsFlyer.onAppOpenAttribution(handleDeepLink);
|
|
157
|
+
appsFlyer.onInstallConversionData(handleDeepLink);
|
|
158
|
+
|
|
159
|
+
initAppsFlyer();
|
|
160
|
+
}, [setInsertAffiliateIdentifier, isInitialized]);
|
|
161
|
+
|
|
162
|
+
return <App />;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const RootComponent = () => {
|
|
166
|
+
return (
|
|
167
|
+
<DeepLinkIapProvider>
|
|
168
|
+
<DeepLinkHandler />
|
|
169
|
+
</DeepLinkIapProvider>
|
|
170
|
+
);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
AppRegistry.registerComponent(appName, () => RootComponent);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Example with Apphud
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
import React, { useEffect } from 'react';
|
|
180
|
+
import { AppRegistry, Platform } from 'react-native';
|
|
181
|
+
import appsFlyer from 'react-native-appsflyer';
|
|
182
|
+
import Apphud from 'react-native-apphud';
|
|
183
|
+
import { useDeepLinkIapProvider, DeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
|
|
184
|
+
import App from './App';
|
|
185
|
+
import { name as appName } from './app.json';
|
|
186
|
+
|
|
187
|
+
const DeepLinkHandler = () => {
|
|
188
|
+
const { setInsertAffiliateIdentifier, isInitialized } = useDeepLinkIapProvider();
|
|
189
|
+
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
if (!isInitialized) return;
|
|
192
|
+
|
|
193
|
+
const initAppsFlyer = async () => {
|
|
194
|
+
try {
|
|
195
|
+
const initOptions = {
|
|
196
|
+
devKey: 'YOUR_APPSFLYER_DEV_KEY',
|
|
197
|
+
isDebug: true,
|
|
198
|
+
appId: Platform.OS === 'ios' ? 'YOUR_IOS_APP_ID' : 'YOUR_ANDROID_PACKAGE_NAME',
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
await appsFlyer.initSdk(initOptions);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error('AppsFlyer initialization error:', error);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const handleDeepLink = async (deepLinkData) => {
|
|
208
|
+
if (deepLinkData && deepLinkData.data) {
|
|
209
|
+
const referringLink = deepLinkData.data.link || deepLinkData.data.deep_link_value;
|
|
210
|
+
|
|
211
|
+
if (referringLink) {
|
|
212
|
+
try {
|
|
213
|
+
const insertAffiliateIdentifier = await setInsertAffiliateIdentifier(referringLink);
|
|
214
|
+
|
|
215
|
+
if (insertAffiliateIdentifier) {
|
|
216
|
+
await Apphud.setUserProperty("insert_affiliate", insertAffiliateIdentifier, false);
|
|
217
|
+
}
|
|
218
|
+
} catch (err) {
|
|
219
|
+
console.error('Error setting affiliate identifier:', err);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
appsFlyer.onDeepLink(handleDeepLink);
|
|
226
|
+
appsFlyer.onAppOpenAttribution(handleDeepLink);
|
|
227
|
+
appsFlyer.onInstallConversionData(handleDeepLink);
|
|
228
|
+
|
|
229
|
+
initAppsFlyer();
|
|
230
|
+
}, [setInsertAffiliateIdentifier, isInitialized]);
|
|
231
|
+
|
|
232
|
+
return <App />;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const RootComponent = () => {
|
|
236
|
+
return (
|
|
237
|
+
<DeepLinkIapProvider>
|
|
238
|
+
<DeepLinkHandler />
|
|
239
|
+
</DeepLinkIapProvider>
|
|
240
|
+
);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
AppRegistry.registerComponent(appName, () => RootComponent);
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Example with Iaptic / Store Direct Integration
|
|
247
|
+
|
|
248
|
+
```javascript
|
|
249
|
+
import React, { useEffect } from 'react';
|
|
250
|
+
import { AppRegistry, Platform } from 'react-native';
|
|
251
|
+
import appsFlyer from 'react-native-appsflyer';
|
|
252
|
+
import { useDeepLinkIapProvider, DeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
|
|
253
|
+
import App from './App';
|
|
254
|
+
import { name as appName } from './app.json';
|
|
255
|
+
|
|
256
|
+
const DeepLinkHandler = () => {
|
|
257
|
+
const { setInsertAffiliateIdentifier, isInitialized } = useDeepLinkIapProvider();
|
|
258
|
+
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
if (!isInitialized) return;
|
|
261
|
+
|
|
262
|
+
const initAppsFlyer = async () => {
|
|
263
|
+
try {
|
|
264
|
+
const initOptions = {
|
|
265
|
+
devKey: 'YOUR_APPSFLYER_DEV_KEY',
|
|
266
|
+
isDebug: true,
|
|
267
|
+
appId: Platform.OS === 'ios' ? 'YOUR_IOS_APP_ID' : 'YOUR_ANDROID_PACKAGE_NAME',
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
await appsFlyer.initSdk(initOptions);
|
|
271
|
+
} catch (error) {
|
|
272
|
+
console.error('AppsFlyer initialization error:', error);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const handleDeepLink = async (deepLinkData) => {
|
|
277
|
+
if (deepLinkData && deepLinkData.data) {
|
|
278
|
+
const referringLink = deepLinkData.data.link || deepLinkData.data.deep_link_value;
|
|
279
|
+
|
|
280
|
+
if (referringLink) {
|
|
281
|
+
try {
|
|
282
|
+
await setInsertAffiliateIdentifier(referringLink);
|
|
283
|
+
// Affiliate identifier is stored automatically for Iaptic/direct store integration
|
|
284
|
+
} catch (err) {
|
|
285
|
+
console.error('Error setting affiliate identifier:', err);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
appsFlyer.onDeepLink(handleDeepLink);
|
|
292
|
+
appsFlyer.onAppOpenAttribution(handleDeepLink);
|
|
293
|
+
appsFlyer.onInstallConversionData(handleDeepLink);
|
|
294
|
+
|
|
295
|
+
initAppsFlyer();
|
|
296
|
+
}, [setInsertAffiliateIdentifier, isInitialized]);
|
|
297
|
+
|
|
298
|
+
return <App />;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const RootComponent = () => {
|
|
302
|
+
return (
|
|
303
|
+
<DeepLinkIapProvider>
|
|
304
|
+
<DeepLinkHandler />
|
|
305
|
+
</DeepLinkIapProvider>
|
|
306
|
+
);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
AppRegistry.registerComponent(appName, () => RootComponent);
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Deep Link Listener Types
|
|
313
|
+
|
|
314
|
+
AppsFlyer provides three types of deep link callbacks:
|
|
315
|
+
|
|
316
|
+
| Callback | When It Fires | Use Case |
|
|
317
|
+
|----------|---------------|----------|
|
|
318
|
+
| `onDeepLink` | App opened via deep link (app already installed) | Direct attribution |
|
|
319
|
+
| `onAppOpenAttribution` | App opened via deep link with attribution data | Re-engagement campaigns |
|
|
320
|
+
| `onInstallConversionData` | First app launch after install | Deferred deep linking |
|
|
321
|
+
|
|
322
|
+
For comprehensive affiliate tracking, we recommend listening to all three as shown in the examples above.
|
|
323
|
+
|
|
324
|
+
## Testing
|
|
325
|
+
|
|
326
|
+
Test your AppsFlyer deep link integration:
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
# Test with your OneLink URL (iOS Simulator)
|
|
330
|
+
xcrun simctl openurl booted "https://your-app.onelink.me/abc123"
|
|
331
|
+
|
|
332
|
+
# Test with your OneLink URL (Android Emulator)
|
|
333
|
+
adb shell am start -W -a android.intent.action.VIEW -d "https://your-app.onelink.me/abc123"
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Troubleshooting
|
|
337
|
+
|
|
338
|
+
**Problem:** Attribution callback not firing
|
|
339
|
+
- **Solution:** Ensure AppsFlyer SDK is initialized with correct dev key and app ID
|
|
340
|
+
- Check AppsFlyer dashboard to verify OneLink is active
|
|
341
|
+
- Verify `isInitialized` is `true` before setting up listeners
|
|
342
|
+
|
|
343
|
+
**Problem:** Deep link parameters not captured
|
|
344
|
+
- **Solution:** Verify deep link contains correct parameters in AppsFlyer dashboard
|
|
345
|
+
- Check Info.plist has correct URL schemes and associated domains (iOS)
|
|
346
|
+
- Check AndroidManifest.xml has correct intent filters (Android)
|
|
347
|
+
|
|
348
|
+
**Problem:** Deferred deep linking not working
|
|
349
|
+
- **Solution:** Make sure `onInstallConversionData` listener is set up
|
|
350
|
+
- Test with a fresh app install (uninstall/reinstall)
|
|
351
|
+
- Verify AppsFlyer's "Deferred Deep Linking" is enabled in dashboard
|
|
352
|
+
|
|
353
|
+
**Problem:** `deep_link_value` is undefined
|
|
354
|
+
- **Solution:** Ensure you're accessing the correct property path in the callback data
|
|
355
|
+
- Log the full `deepLinkData` object to see available fields
|
|
356
|
+
|
|
357
|
+
## Next Steps
|
|
358
|
+
|
|
359
|
+
After completing AppsFlyer integration:
|
|
360
|
+
1. Test deep link attribution with a test affiliate link
|
|
361
|
+
2. Verify affiliate identifier is stored correctly
|
|
362
|
+
3. Make a test purchase to confirm tracking works end-to-end
|
|
363
|
+
|
|
364
|
+
[Back to Main README](../readme.md)
|