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
|
@@ -80,8 +80,19 @@ const ASYNC_KEYS = {
|
|
|
80
80
|
USER_ACCOUNT_TOKEN: '@app_user_account_token',
|
|
81
81
|
IOS_OFFER_CODE: '@app_ios_offer_code',
|
|
82
82
|
AFFILIATE_STORED_DATE: '@app_affiliate_stored_date',
|
|
83
|
+
SDK_INIT_REPORTED: '@app_sdk_init_reported',
|
|
84
|
+
REPORTED_AFFILIATE_ASSOCIATIONS: '@app_reported_affiliate_associations',
|
|
83
85
|
};
|
|
84
86
|
|
|
87
|
+
// Source types for affiliate association tracking
|
|
88
|
+
type AffiliateAssociationSource =
|
|
89
|
+
| 'deep_link_ios' // iOS custom URL scheme (ia-companycode://shortcode)
|
|
90
|
+
| 'deep_link_android' // Android deep link with ?insertAffiliate= param
|
|
91
|
+
| 'install_referrer' // Android Play Store install referrer
|
|
92
|
+
| 'clipboard_match' // iOS clipboard UUID match from backend
|
|
93
|
+
| 'short_code_manual' // Developer called setShortCode()
|
|
94
|
+
| 'referring_link'; // Developer called setInsertAffiliateIdentifier()
|
|
95
|
+
|
|
85
96
|
// STARTING CONTEXT IMPLEMENTATION
|
|
86
97
|
export const DeepLinkIapContext = createContext<T_DEEPLINK_IAP_CONTEXT>({
|
|
87
98
|
referrerLink: '',
|
|
@@ -153,6 +164,9 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
153
164
|
console.log('[Insert Affiliate] [VERBOSE] Company code saved to AsyncStorage');
|
|
154
165
|
console.log('[Insert Affiliate] [VERBOSE] SDK marked as initialized');
|
|
155
166
|
}
|
|
167
|
+
|
|
168
|
+
// Report SDK initialization for onboarding verification (fire and forget)
|
|
169
|
+
reportSdkInitIfNeeded(companyCode, verboseLogging);
|
|
156
170
|
} else {
|
|
157
171
|
console.warn(
|
|
158
172
|
'[Insert Affiliate] SDK initialized without a company code.'
|
|
@@ -389,14 +403,26 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
389
403
|
return false;
|
|
390
404
|
}
|
|
391
405
|
|
|
392
|
-
// Parse the URL to extract query parameters
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
406
|
+
// Parse the URL to extract query parameters (React Native compatible)
|
|
407
|
+
// URLSearchParams is not available in React Native, so parse manually
|
|
408
|
+
let insertAffiliate: string | null = null;
|
|
409
|
+
const queryIndex = url.indexOf('?');
|
|
410
|
+
if (queryIndex !== -1) {
|
|
411
|
+
const queryString = url.substring(queryIndex + 1);
|
|
412
|
+
const params = queryString.split('&');
|
|
413
|
+
for (const param of params) {
|
|
414
|
+
const [key, value] = param.split('=');
|
|
415
|
+
if (key === 'insertAffiliate' && value) {
|
|
416
|
+
insertAffiliate = decodeURIComponent(value);
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
396
422
|
if (insertAffiliate && insertAffiliate.length > 0) {
|
|
397
423
|
verboseLog(`Found insertAffiliate parameter: ${insertAffiliate}`);
|
|
398
424
|
|
|
399
|
-
await storeInsertAffiliateIdentifier({ link: insertAffiliate });
|
|
425
|
+
await storeInsertAffiliateIdentifier({ link: insertAffiliate, source: 'deep_link_android' });
|
|
400
426
|
|
|
401
427
|
return true;
|
|
402
428
|
} else {
|
|
@@ -525,7 +551,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
525
551
|
// If we have insertAffiliate parameter, use it as the affiliate identifier
|
|
526
552
|
if (insertAffiliate && insertAffiliate.length > 0) {
|
|
527
553
|
verboseLog(`Found insertAffiliate parameter, setting as affiliate identifier: ${insertAffiliate}`);
|
|
528
|
-
await storeInsertAffiliateIdentifier({ link: insertAffiliate });
|
|
554
|
+
await storeInsertAffiliateIdentifier({ link: insertAffiliate, source: 'install_referrer' });
|
|
529
555
|
|
|
530
556
|
return true;
|
|
531
557
|
} else {
|
|
@@ -602,7 +628,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
602
628
|
}
|
|
603
629
|
|
|
604
630
|
// If URL scheme is used, we can straight away store the short code as the referring link
|
|
605
|
-
await storeInsertAffiliateIdentifier({ link: shortCode });
|
|
631
|
+
await storeInsertAffiliateIdentifier({ link: shortCode, source: 'deep_link_ios' });
|
|
606
632
|
|
|
607
633
|
// Collect and send enhanced system info to backend
|
|
608
634
|
try {
|
|
@@ -736,6 +762,96 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
736
762
|
}
|
|
737
763
|
};
|
|
738
764
|
|
|
765
|
+
// Reports a new affiliate association to the backend for tracking.
|
|
766
|
+
// Only reports each unique affiliateIdentifier once to prevent duplicates.
|
|
767
|
+
const reportAffiliateAssociationIfNeeded = async (
|
|
768
|
+
affiliateIdentifier: string,
|
|
769
|
+
source: AffiliateAssociationSource
|
|
770
|
+
): Promise<void> => {
|
|
771
|
+
try {
|
|
772
|
+
const activeCompanyCode = await getActiveCompanyCode();
|
|
773
|
+
if (!activeCompanyCode) {
|
|
774
|
+
verboseLog('Cannot report affiliate association: no company code available');
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Get the set of already-reported affiliate identifiers
|
|
779
|
+
const reportedAssociationsJson = await AsyncStorage.getItem(ASYNC_KEYS.REPORTED_AFFILIATE_ASSOCIATIONS);
|
|
780
|
+
const reportedAssociations: string[] = reportedAssociationsJson ? JSON.parse(reportedAssociationsJson) : [];
|
|
781
|
+
|
|
782
|
+
// Check if this affiliate identifier has already been reported
|
|
783
|
+
if (reportedAssociations.includes(affiliateIdentifier)) {
|
|
784
|
+
verboseLog(`Affiliate association already reported for: ${affiliateIdentifier}, skipping`);
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
verboseLog(`Reporting new affiliate association: ${affiliateIdentifier} (source: ${source})`);
|
|
789
|
+
|
|
790
|
+
const response = await fetch('https://api.insertaffiliate.com/V1/onboarding/affiliate-associated', {
|
|
791
|
+
method: 'POST',
|
|
792
|
+
headers: {
|
|
793
|
+
'Content-Type': 'application/json',
|
|
794
|
+
},
|
|
795
|
+
body: JSON.stringify({
|
|
796
|
+
companyId: activeCompanyCode,
|
|
797
|
+
affiliateIdentifier: affiliateIdentifier,
|
|
798
|
+
source: source,
|
|
799
|
+
timestamp: new Date().toISOString(),
|
|
800
|
+
}),
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
if (response.ok) {
|
|
804
|
+
// Add to reported set and persist
|
|
805
|
+
reportedAssociations.push(affiliateIdentifier);
|
|
806
|
+
await AsyncStorage.setItem(ASYNC_KEYS.REPORTED_AFFILIATE_ASSOCIATIONS, JSON.stringify(reportedAssociations));
|
|
807
|
+
verboseLog(`Affiliate association reported successfully for: ${affiliateIdentifier}`);
|
|
808
|
+
} else {
|
|
809
|
+
verboseLog(`Affiliate association report failed with status: ${response.status}`);
|
|
810
|
+
}
|
|
811
|
+
} catch (error) {
|
|
812
|
+
// Silently fail - this is non-critical telemetry
|
|
813
|
+
verboseLog(`Affiliate association report error: ${error}`);
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
// Reports SDK initialization to the backend for onboarding verification.
|
|
818
|
+
// Only reports once per install to minimize server load.
|
|
819
|
+
const reportSdkInitIfNeeded = async (companyCode: string, verboseLogging: boolean): Promise<void> => {
|
|
820
|
+
try {
|
|
821
|
+
// Only report once per install
|
|
822
|
+
const alreadyReported = await AsyncStorage.getItem(ASYNC_KEYS.SDK_INIT_REPORTED);
|
|
823
|
+
if (alreadyReported === 'true') {
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (verboseLogging) {
|
|
828
|
+
console.log('[Insert Affiliate] Reporting SDK initialization for onboarding verification...');
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const response = await fetch('https://api.insertaffiliate.com/V1/onboarding/sdk-init', {
|
|
832
|
+
method: 'POST',
|
|
833
|
+
headers: {
|
|
834
|
+
'Content-Type': 'application/json',
|
|
835
|
+
},
|
|
836
|
+
body: JSON.stringify({ companyId: companyCode }),
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
if (response.ok) {
|
|
840
|
+
await AsyncStorage.setItem(ASYNC_KEYS.SDK_INIT_REPORTED, 'true');
|
|
841
|
+
if (verboseLogging) {
|
|
842
|
+
console.log('[Insert Affiliate] SDK initialization reported successfully');
|
|
843
|
+
}
|
|
844
|
+
} else if (verboseLogging) {
|
|
845
|
+
console.log(`[Insert Affiliate] SDK initialization report failed with status: ${response.status}`);
|
|
846
|
+
}
|
|
847
|
+
} catch (error) {
|
|
848
|
+
// Silently fail - this is non-critical telemetry
|
|
849
|
+
if (verboseLogging) {
|
|
850
|
+
console.log(`[Insert Affiliate] SDK initialization report error: ${error}`);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
|
|
739
855
|
// MARK: - Deep Linking Utilities
|
|
740
856
|
|
|
741
857
|
// Retrieves and validates clipboard content for UUID format
|
|
@@ -1154,9 +1270,9 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1154
1270
|
const matchFound = response.data.matchFound || false;
|
|
1155
1271
|
if (matchFound && response.data.matched_affiliate_shortCode && response.data.matched_affiliate_shortCode.length > 0) {
|
|
1156
1272
|
const matchedShortCode = response.data.matched_affiliate_shortCode;
|
|
1157
|
-
verboseLog(`Storing Matched short code from backend: ${matchedShortCode}`);
|
|
1158
|
-
|
|
1159
|
-
await storeInsertAffiliateIdentifier({ link: matchedShortCode });
|
|
1273
|
+
verboseLog(`Storing Matched short code from backend: ${matchedShortCode}`);
|
|
1274
|
+
|
|
1275
|
+
await storeInsertAffiliateIdentifier({ link: matchedShortCode, source: 'clipboard_match' });
|
|
1160
1276
|
}
|
|
1161
1277
|
}
|
|
1162
1278
|
|
|
@@ -1282,7 +1398,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1282
1398
|
|
|
1283
1399
|
if (exists) {
|
|
1284
1400
|
// If affiliate exists, set the Insert Affiliate Identifier
|
|
1285
|
-
await storeInsertAffiliateIdentifier({ link: capitalisedShortCode });
|
|
1401
|
+
await storeInsertAffiliateIdentifier({ link: capitalisedShortCode, source: 'short_code_manual' });
|
|
1286
1402
|
console.log(`[Insert Affiliate] Short code ${capitalisedShortCode} validated and stored successfully.`);
|
|
1287
1403
|
return true;
|
|
1288
1404
|
} else {
|
|
@@ -1457,7 +1573,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1457
1573
|
if (!referringLink) {
|
|
1458
1574
|
console.warn('[Insert Affiliate] Referring link is invalid.');
|
|
1459
1575
|
verboseLog('Referring link is empty or invalid, storing as-is');
|
|
1460
|
-
await storeInsertAffiliateIdentifier({ link: referringLink });
|
|
1576
|
+
await storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
|
|
1461
1577
|
return `${referringLink}-${customerID}`;
|
|
1462
1578
|
}
|
|
1463
1579
|
|
|
@@ -1481,7 +1597,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1481
1597
|
'[Insert Affiliate] Referring link is already a short code.'
|
|
1482
1598
|
);
|
|
1483
1599
|
verboseLog('Link is already a short code, storing directly');
|
|
1484
|
-
await storeInsertAffiliateIdentifier({ link: referringLink });
|
|
1600
|
+
await storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
|
|
1485
1601
|
return `${referringLink}-${customerID}`;
|
|
1486
1602
|
}
|
|
1487
1603
|
|
|
@@ -1494,7 +1610,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1494
1610
|
if (!encodedAffiliateLink) {
|
|
1495
1611
|
console.error('[Insert Affiliate] Failed to encode affiliate link.');
|
|
1496
1612
|
verboseLog('Failed to encode link, storing original');
|
|
1497
|
-
await storeInsertAffiliateIdentifier({ link: referringLink });
|
|
1613
|
+
await storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
|
|
1498
1614
|
return `${referringLink}-${customerID}`;
|
|
1499
1615
|
}
|
|
1500
1616
|
|
|
@@ -1517,14 +1633,14 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1517
1633
|
console.log('[Insert Affiliate] Short link received:', shortLink);
|
|
1518
1634
|
verboseLog(`Successfully converted to short link: ${shortLink}`);
|
|
1519
1635
|
verboseLog('Storing short link to AsyncStorage...');
|
|
1520
|
-
await storeInsertAffiliateIdentifier({ link: shortLink });
|
|
1636
|
+
await storeInsertAffiliateIdentifier({ link: shortLink, source: 'referring_link' });
|
|
1521
1637
|
verboseLog('Short link stored successfully');
|
|
1522
1638
|
return `${shortLink}-${customerID}`;
|
|
1523
1639
|
} else {
|
|
1524
1640
|
console.warn('[Insert Affiliate] Unexpected response format.');
|
|
1525
1641
|
verboseLog(`Unexpected API response. Status: ${response.status}, Data: ${JSON.stringify(response.data)}`);
|
|
1526
1642
|
verboseLog('Storing original link as fallback');
|
|
1527
|
-
await storeInsertAffiliateIdentifier({ link: referringLink });
|
|
1643
|
+
await storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
|
|
1528
1644
|
return `${referringLink}-${customerID}`;
|
|
1529
1645
|
}
|
|
1530
1646
|
} catch (error) {
|
|
@@ -1533,38 +1649,44 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1533
1649
|
}
|
|
1534
1650
|
};
|
|
1535
1651
|
|
|
1536
|
-
async function storeInsertAffiliateIdentifier({ link }: { link: string }) {
|
|
1537
|
-
console.log(`[Insert Affiliate] Storing affiliate identifier: ${link}`);
|
|
1538
|
-
|
|
1652
|
+
async function storeInsertAffiliateIdentifier({ link, source }: { link: string; source: AffiliateAssociationSource }) {
|
|
1653
|
+
console.log(`[Insert Affiliate] Storing affiliate identifier: ${link} (source: ${source})`);
|
|
1654
|
+
|
|
1539
1655
|
// Check if we're trying to store the same link (prevent duplicate storage)
|
|
1540
1656
|
const existingLink = await getValueFromAsync(ASYNC_KEYS.REFERRER_LINK);
|
|
1541
1657
|
if (existingLink === link) {
|
|
1542
1658
|
verboseLog(`Link ${link} is already stored, skipping duplicate storage`);
|
|
1543
1659
|
return;
|
|
1544
1660
|
}
|
|
1545
|
-
|
|
1661
|
+
|
|
1546
1662
|
verboseLog(`Updating React state with referrer link: ${link}`);
|
|
1547
1663
|
setReferrerLink(link);
|
|
1548
1664
|
verboseLog(`Saving referrer link to AsyncStorage...`);
|
|
1549
1665
|
await saveValueInAsync(ASYNC_KEYS.REFERRER_LINK, link);
|
|
1550
|
-
|
|
1666
|
+
|
|
1551
1667
|
// Store the current date/time when the affiliate identifier is stored
|
|
1552
1668
|
const currentDate = new Date().toISOString();
|
|
1553
1669
|
verboseLog(`Saving affiliate stored date: ${currentDate}`);
|
|
1554
1670
|
await saveValueInAsync(ASYNC_KEYS.AFFILIATE_STORED_DATE, currentDate);
|
|
1555
|
-
|
|
1671
|
+
|
|
1556
1672
|
verboseLog(`Referrer link saved to AsyncStorage successfully`);
|
|
1557
|
-
|
|
1673
|
+
|
|
1558
1674
|
// Automatically fetch and store offer code for any affiliate identifier
|
|
1559
1675
|
verboseLog('Attempting to fetch offer code for stored affiliate identifier...');
|
|
1560
1676
|
await retrieveAndStoreOfferCode(link);
|
|
1561
|
-
|
|
1677
|
+
|
|
1562
1678
|
// Trigger callback with the current affiliate identifier
|
|
1563
1679
|
if (insertAffiliateIdentifierChangeCallbackRef.current) {
|
|
1564
1680
|
const currentIdentifier = await returnInsertAffiliateIdentifier();
|
|
1565
1681
|
verboseLog(`Triggering callback with identifier: ${currentIdentifier}`);
|
|
1566
1682
|
insertAffiliateIdentifierChangeCallbackRef.current(currentIdentifier);
|
|
1567
1683
|
}
|
|
1684
|
+
|
|
1685
|
+
// Report this new affiliate association to the backend (fire and forget)
|
|
1686
|
+
const fullIdentifier = await returnInsertAffiliateIdentifier();
|
|
1687
|
+
if (fullIdentifier) {
|
|
1688
|
+
reportAffiliateAssociationIfNeeded(fullIdentifier, source);
|
|
1689
|
+
}
|
|
1568
1690
|
}
|
|
1569
1691
|
|
|
1570
1692
|
const validatePurchaseWithIapticAPI = async (
|