insert-affiliate-react-native-sdk 1.8.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.d.ts +7 -1
- package/dist/DeepLinkIapProvider.js +209 -17
- package/dist/index.d.ts +1 -1
- package/dist/useDeepLinkIapProvider.d.ts +2 -1
- package/dist/useDeepLinkIapProvider.js +2 -1
- 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 +600 -982
- package/src/DeepLinkIapProvider.tsx +256 -28
- package/src/index.ts +1 -1
- package/src/useDeepLinkIapProvider.tsx +2 -0
|
@@ -17,6 +17,12 @@ type T_DEEPLINK_IAP_PROVIDER = {
|
|
|
17
17
|
|
|
18
18
|
export type InsertAffiliateIdentifierChangeCallback = (identifier: string | null) => void;
|
|
19
19
|
|
|
20
|
+
export type AffiliateDetails = {
|
|
21
|
+
affiliateName: string;
|
|
22
|
+
affiliateShortCode: string;
|
|
23
|
+
deeplinkurl: string;
|
|
24
|
+
} | null;
|
|
25
|
+
|
|
20
26
|
type CustomPurchase = {
|
|
21
27
|
[key: string]: any; // Accept any fields to allow it to work wtih multiple IAP libraries
|
|
22
28
|
};
|
|
@@ -39,7 +45,8 @@ type T_DEEPLINK_IAP_CONTEXT = {
|
|
|
39
45
|
purchaseToken: string
|
|
40
46
|
) => Promise<void>;
|
|
41
47
|
trackEvent: (eventName: string) => Promise<void>;
|
|
42
|
-
setShortCode: (shortCode: string) => Promise<
|
|
48
|
+
setShortCode: (shortCode: string) => Promise<boolean>;
|
|
49
|
+
getAffiliateDetails: (affiliateCode: string) => Promise<AffiliateDetails>;
|
|
43
50
|
setInsertAffiliateIdentifier: (
|
|
44
51
|
referringLink: string
|
|
45
52
|
) => Promise<void | string>;
|
|
@@ -73,8 +80,19 @@ const ASYNC_KEYS = {
|
|
|
73
80
|
USER_ACCOUNT_TOKEN: '@app_user_account_token',
|
|
74
81
|
IOS_OFFER_CODE: '@app_ios_offer_code',
|
|
75
82
|
AFFILIATE_STORED_DATE: '@app_affiliate_stored_date',
|
|
83
|
+
SDK_INIT_REPORTED: '@app_sdk_init_reported',
|
|
84
|
+
REPORTED_AFFILIATE_ASSOCIATIONS: '@app_reported_affiliate_associations',
|
|
76
85
|
};
|
|
77
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
|
+
|
|
78
96
|
// STARTING CONTEXT IMPLEMENTATION
|
|
79
97
|
export const DeepLinkIapContext = createContext<T_DEEPLINK_IAP_CONTEXT>({
|
|
80
98
|
referrerLink: '',
|
|
@@ -92,7 +110,8 @@ export const DeepLinkIapContext = createContext<T_DEEPLINK_IAP_CONTEXT>({
|
|
|
92
110
|
returnUserAccountTokenAndStoreExpectedTransaction: async () => '',
|
|
93
111
|
storeExpectedStoreTransaction: async (purchaseToken: string) => {},
|
|
94
112
|
trackEvent: async (eventName: string) => {},
|
|
95
|
-
setShortCode: async (shortCode: string) =>
|
|
113
|
+
setShortCode: async (shortCode: string) => false,
|
|
114
|
+
getAffiliateDetails: async (affiliateCode: string) => null,
|
|
96
115
|
setInsertAffiliateIdentifier: async (referringLink: string) => {},
|
|
97
116
|
setInsertAffiliateIdentifierChangeCallback: (callback: InsertAffiliateIdentifierChangeCallback | null) => {},
|
|
98
117
|
handleInsertLinks: async (url: string) => false,
|
|
@@ -145,6 +164,9 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
145
164
|
console.log('[Insert Affiliate] [VERBOSE] Company code saved to AsyncStorage');
|
|
146
165
|
console.log('[Insert Affiliate] [VERBOSE] SDK marked as initialized');
|
|
147
166
|
}
|
|
167
|
+
|
|
168
|
+
// Report SDK initialization for onboarding verification (fire and forget)
|
|
169
|
+
reportSdkInitIfNeeded(companyCode, verboseLogging);
|
|
148
170
|
} else {
|
|
149
171
|
console.warn(
|
|
150
172
|
'[Insert Affiliate] SDK initialized without a company code.'
|
|
@@ -381,14 +403,26 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
381
403
|
return false;
|
|
382
404
|
}
|
|
383
405
|
|
|
384
|
-
// Parse the URL to extract query parameters
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
+
|
|
388
422
|
if (insertAffiliate && insertAffiliate.length > 0) {
|
|
389
423
|
verboseLog(`Found insertAffiliate parameter: ${insertAffiliate}`);
|
|
390
424
|
|
|
391
|
-
await storeInsertAffiliateIdentifier({ link: insertAffiliate });
|
|
425
|
+
await storeInsertAffiliateIdentifier({ link: insertAffiliate, source: 'deep_link_android' });
|
|
392
426
|
|
|
393
427
|
return true;
|
|
394
428
|
} else {
|
|
@@ -517,7 +551,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
517
551
|
// If we have insertAffiliate parameter, use it as the affiliate identifier
|
|
518
552
|
if (insertAffiliate && insertAffiliate.length > 0) {
|
|
519
553
|
verboseLog(`Found insertAffiliate parameter, setting as affiliate identifier: ${insertAffiliate}`);
|
|
520
|
-
await storeInsertAffiliateIdentifier({ link: insertAffiliate });
|
|
554
|
+
await storeInsertAffiliateIdentifier({ link: insertAffiliate, source: 'install_referrer' });
|
|
521
555
|
|
|
522
556
|
return true;
|
|
523
557
|
} else {
|
|
@@ -594,7 +628,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
594
628
|
}
|
|
595
629
|
|
|
596
630
|
// If URL scheme is used, we can straight away store the short code as the referring link
|
|
597
|
-
await storeInsertAffiliateIdentifier({ link: shortCode });
|
|
631
|
+
await storeInsertAffiliateIdentifier({ link: shortCode, source: 'deep_link_ios' });
|
|
598
632
|
|
|
599
633
|
// Collect and send enhanced system info to backend
|
|
600
634
|
try {
|
|
@@ -728,6 +762,96 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
728
762
|
}
|
|
729
763
|
};
|
|
730
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
|
+
|
|
731
855
|
// MARK: - Deep Linking Utilities
|
|
732
856
|
|
|
733
857
|
// Retrieves and validates clipboard content for UUID format
|
|
@@ -1146,9 +1270,9 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1146
1270
|
const matchFound = response.data.matchFound || false;
|
|
1147
1271
|
if (matchFound && response.data.matched_affiliate_shortCode && response.data.matched_affiliate_shortCode.length > 0) {
|
|
1148
1272
|
const matchedShortCode = response.data.matched_affiliate_shortCode;
|
|
1149
|
-
verboseLog(`Storing Matched short code from backend: ${matchedShortCode}`);
|
|
1150
|
-
|
|
1151
|
-
await storeInsertAffiliateIdentifier({ link: matchedShortCode });
|
|
1273
|
+
verboseLog(`Storing Matched short code from backend: ${matchedShortCode}`);
|
|
1274
|
+
|
|
1275
|
+
await storeInsertAffiliateIdentifier({ link: matchedShortCode, source: 'clipboard_match' });
|
|
1152
1276
|
}
|
|
1153
1277
|
}
|
|
1154
1278
|
|
|
@@ -1174,7 +1298,94 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1174
1298
|
return isValidCharacters && referringLink.length >= 3 && referringLink.length <= 25;
|
|
1175
1299
|
};
|
|
1176
1300
|
|
|
1177
|
-
async
|
|
1301
|
+
const checkAffiliateExists = async (affiliateCode: string): Promise<boolean> => {
|
|
1302
|
+
try {
|
|
1303
|
+
const activeCompanyCode = await getActiveCompanyCode();
|
|
1304
|
+
if (!activeCompanyCode) {
|
|
1305
|
+
verboseLog('Cannot check affiliate: no company code available');
|
|
1306
|
+
return false;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
const url = 'https://api.insertaffiliate.com/V1/checkAffiliateExists';
|
|
1310
|
+
const payload = {
|
|
1311
|
+
companyId: activeCompanyCode,
|
|
1312
|
+
affiliateCode: affiliateCode
|
|
1313
|
+
};
|
|
1314
|
+
|
|
1315
|
+
verboseLog(`Checking if affiliate exists: ${affiliateCode}`);
|
|
1316
|
+
|
|
1317
|
+
const response = await axios.post(url, payload, {
|
|
1318
|
+
headers: {
|
|
1319
|
+
'Content-Type': 'application/json',
|
|
1320
|
+
},
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
verboseLog(`Affiliate check response: ${JSON.stringify(response.data)}`);
|
|
1324
|
+
|
|
1325
|
+
if (response.status === 200 && response.data) {
|
|
1326
|
+
const exists = response.data.exists === true;
|
|
1327
|
+
if (exists) {
|
|
1328
|
+
verboseLog(`Affiliate ${affiliateCode} exists and is valid`);
|
|
1329
|
+
} else {
|
|
1330
|
+
verboseLog(`Affiliate ${affiliateCode} does not exist`);
|
|
1331
|
+
}
|
|
1332
|
+
return exists;
|
|
1333
|
+
} else {
|
|
1334
|
+
verboseLog(`Unexpected response checking affiliate: status ${response.status}`);
|
|
1335
|
+
return false;
|
|
1336
|
+
}
|
|
1337
|
+
} catch (error) {
|
|
1338
|
+
verboseLog(`Error checking affiliate exists: ${error}`);
|
|
1339
|
+
return false;
|
|
1340
|
+
}
|
|
1341
|
+
};
|
|
1342
|
+
|
|
1343
|
+
const getAffiliateDetails = async (affiliateCode: string): Promise<AffiliateDetails> => {
|
|
1344
|
+
try {
|
|
1345
|
+
const activeCompanyCode = await getActiveCompanyCode();
|
|
1346
|
+
if (!activeCompanyCode) {
|
|
1347
|
+
verboseLog('Cannot get affiliate details: no company code available');
|
|
1348
|
+
return null;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
const url = 'https://api.insertaffiliate.com/V1/checkAffiliateExists';
|
|
1352
|
+
const payload = {
|
|
1353
|
+
companyId: activeCompanyCode,
|
|
1354
|
+
affiliateCode: affiliateCode
|
|
1355
|
+
};
|
|
1356
|
+
|
|
1357
|
+
verboseLog(`Getting affiliate details for: ${affiliateCode}`);
|
|
1358
|
+
|
|
1359
|
+
const response = await axios.post(url, payload, {
|
|
1360
|
+
headers: {
|
|
1361
|
+
'Content-Type': 'application/json',
|
|
1362
|
+
},
|
|
1363
|
+
});
|
|
1364
|
+
|
|
1365
|
+
verboseLog(`Affiliate details response: ${JSON.stringify(response.data)}`);
|
|
1366
|
+
|
|
1367
|
+
if (response.status === 200 && response.data && response.data.exists === true) {
|
|
1368
|
+
const affiliate = response.data.affiliate;
|
|
1369
|
+
if (affiliate) {
|
|
1370
|
+
verboseLog(`Retrieved affiliate details: ${JSON.stringify(affiliate)}`);
|
|
1371
|
+
return {
|
|
1372
|
+
affiliateName: affiliate.affiliateName || '',
|
|
1373
|
+
affiliateShortCode: affiliate.affiliateShortCode || '',
|
|
1374
|
+
deeplinkurl: affiliate.deeplinkurl || ''
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
verboseLog(`Affiliate ${affiliateCode} not found or invalid response`);
|
|
1380
|
+
return null;
|
|
1381
|
+
} catch (error) {
|
|
1382
|
+
verboseLog(`Error getting affiliate details: ${error}`);
|
|
1383
|
+
console.error('[Insert Affiliate] Error getting affiliate details:', error);
|
|
1384
|
+
return null;
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
|
|
1388
|
+
async function setShortCode(shortCode: string): Promise<boolean> {
|
|
1178
1389
|
console.log('[Insert Affiliate] Setting short code.');
|
|
1179
1390
|
await generateThenSetUserID();
|
|
1180
1391
|
|
|
@@ -1182,8 +1393,18 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1182
1393
|
const capitalisedShortCode = shortCode.toUpperCase();
|
|
1183
1394
|
isShortCode(capitalisedShortCode);
|
|
1184
1395
|
|
|
1185
|
-
//
|
|
1186
|
-
await
|
|
1396
|
+
// Check if the affiliate exists before storing
|
|
1397
|
+
const exists = await checkAffiliateExists(capitalisedShortCode);
|
|
1398
|
+
|
|
1399
|
+
if (exists) {
|
|
1400
|
+
// If affiliate exists, set the Insert Affiliate Identifier
|
|
1401
|
+
await storeInsertAffiliateIdentifier({ link: capitalisedShortCode, source: 'short_code_manual' });
|
|
1402
|
+
console.log(`[Insert Affiliate] Short code ${capitalisedShortCode} validated and stored successfully.`);
|
|
1403
|
+
return true;
|
|
1404
|
+
} else {
|
|
1405
|
+
console.warn(`[Insert Affiliate] Short code ${capitalisedShortCode} does not exist. Not storing.`);
|
|
1406
|
+
return false;
|
|
1407
|
+
}
|
|
1187
1408
|
}
|
|
1188
1409
|
|
|
1189
1410
|
async function getOrCreateUserAccountToken(): Promise<string> {
|
|
@@ -1352,7 +1573,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1352
1573
|
if (!referringLink) {
|
|
1353
1574
|
console.warn('[Insert Affiliate] Referring link is invalid.');
|
|
1354
1575
|
verboseLog('Referring link is empty or invalid, storing as-is');
|
|
1355
|
-
await storeInsertAffiliateIdentifier({ link: referringLink });
|
|
1576
|
+
await storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
|
|
1356
1577
|
return `${referringLink}-${customerID}`;
|
|
1357
1578
|
}
|
|
1358
1579
|
|
|
@@ -1376,7 +1597,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1376
1597
|
'[Insert Affiliate] Referring link is already a short code.'
|
|
1377
1598
|
);
|
|
1378
1599
|
verboseLog('Link is already a short code, storing directly');
|
|
1379
|
-
await storeInsertAffiliateIdentifier({ link: referringLink });
|
|
1600
|
+
await storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
|
|
1380
1601
|
return `${referringLink}-${customerID}`;
|
|
1381
1602
|
}
|
|
1382
1603
|
|
|
@@ -1389,7 +1610,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1389
1610
|
if (!encodedAffiliateLink) {
|
|
1390
1611
|
console.error('[Insert Affiliate] Failed to encode affiliate link.');
|
|
1391
1612
|
verboseLog('Failed to encode link, storing original');
|
|
1392
|
-
await storeInsertAffiliateIdentifier({ link: referringLink });
|
|
1613
|
+
await storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
|
|
1393
1614
|
return `${referringLink}-${customerID}`;
|
|
1394
1615
|
}
|
|
1395
1616
|
|
|
@@ -1412,14 +1633,14 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1412
1633
|
console.log('[Insert Affiliate] Short link received:', shortLink);
|
|
1413
1634
|
verboseLog(`Successfully converted to short link: ${shortLink}`);
|
|
1414
1635
|
verboseLog('Storing short link to AsyncStorage...');
|
|
1415
|
-
await storeInsertAffiliateIdentifier({ link: shortLink });
|
|
1636
|
+
await storeInsertAffiliateIdentifier({ link: shortLink, source: 'referring_link' });
|
|
1416
1637
|
verboseLog('Short link stored successfully');
|
|
1417
1638
|
return `${shortLink}-${customerID}`;
|
|
1418
1639
|
} else {
|
|
1419
1640
|
console.warn('[Insert Affiliate] Unexpected response format.');
|
|
1420
1641
|
verboseLog(`Unexpected API response. Status: ${response.status}, Data: ${JSON.stringify(response.data)}`);
|
|
1421
1642
|
verboseLog('Storing original link as fallback');
|
|
1422
|
-
await storeInsertAffiliateIdentifier({ link: referringLink });
|
|
1643
|
+
await storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
|
|
1423
1644
|
return `${referringLink}-${customerID}`;
|
|
1424
1645
|
}
|
|
1425
1646
|
} catch (error) {
|
|
@@ -1428,38 +1649,44 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1428
1649
|
}
|
|
1429
1650
|
};
|
|
1430
1651
|
|
|
1431
|
-
async function storeInsertAffiliateIdentifier({ link }: { link: string }) {
|
|
1432
|
-
console.log(`[Insert Affiliate] Storing affiliate identifier: ${link}`);
|
|
1433
|
-
|
|
1652
|
+
async function storeInsertAffiliateIdentifier({ link, source }: { link: string; source: AffiliateAssociationSource }) {
|
|
1653
|
+
console.log(`[Insert Affiliate] Storing affiliate identifier: ${link} (source: ${source})`);
|
|
1654
|
+
|
|
1434
1655
|
// Check if we're trying to store the same link (prevent duplicate storage)
|
|
1435
1656
|
const existingLink = await getValueFromAsync(ASYNC_KEYS.REFERRER_LINK);
|
|
1436
1657
|
if (existingLink === link) {
|
|
1437
1658
|
verboseLog(`Link ${link} is already stored, skipping duplicate storage`);
|
|
1438
1659
|
return;
|
|
1439
1660
|
}
|
|
1440
|
-
|
|
1661
|
+
|
|
1441
1662
|
verboseLog(`Updating React state with referrer link: ${link}`);
|
|
1442
1663
|
setReferrerLink(link);
|
|
1443
1664
|
verboseLog(`Saving referrer link to AsyncStorage...`);
|
|
1444
1665
|
await saveValueInAsync(ASYNC_KEYS.REFERRER_LINK, link);
|
|
1445
|
-
|
|
1666
|
+
|
|
1446
1667
|
// Store the current date/time when the affiliate identifier is stored
|
|
1447
1668
|
const currentDate = new Date().toISOString();
|
|
1448
1669
|
verboseLog(`Saving affiliate stored date: ${currentDate}`);
|
|
1449
1670
|
await saveValueInAsync(ASYNC_KEYS.AFFILIATE_STORED_DATE, currentDate);
|
|
1450
|
-
|
|
1671
|
+
|
|
1451
1672
|
verboseLog(`Referrer link saved to AsyncStorage successfully`);
|
|
1452
|
-
|
|
1673
|
+
|
|
1453
1674
|
// Automatically fetch and store offer code for any affiliate identifier
|
|
1454
1675
|
verboseLog('Attempting to fetch offer code for stored affiliate identifier...');
|
|
1455
1676
|
await retrieveAndStoreOfferCode(link);
|
|
1456
|
-
|
|
1677
|
+
|
|
1457
1678
|
// Trigger callback with the current affiliate identifier
|
|
1458
1679
|
if (insertAffiliateIdentifierChangeCallbackRef.current) {
|
|
1459
1680
|
const currentIdentifier = await returnInsertAffiliateIdentifier();
|
|
1460
1681
|
verboseLog(`Triggering callback with identifier: ${currentIdentifier}`);
|
|
1461
1682
|
insertAffiliateIdentifierChangeCallbackRef.current(currentIdentifier);
|
|
1462
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
|
+
}
|
|
1463
1690
|
}
|
|
1464
1691
|
|
|
1465
1692
|
const validatePurchaseWithIapticAPI = async (
|
|
@@ -1779,6 +2006,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1779
2006
|
userId,
|
|
1780
2007
|
OfferCode,
|
|
1781
2008
|
setShortCode,
|
|
2009
|
+
getAffiliateDetails,
|
|
1782
2010
|
returnInsertAffiliateIdentifier,
|
|
1783
2011
|
isAffiliateAttributionValid,
|
|
1784
2012
|
getAffiliateStoredDate,
|
package/src/index.ts
CHANGED
|
@@ -4,4 +4,4 @@ import useDeepLinkIapProvider from "./useDeepLinkIapProvider";
|
|
|
4
4
|
export { DeepLinkIapProvider, useDeepLinkIapProvider };
|
|
5
5
|
|
|
6
6
|
// Export types
|
|
7
|
-
export type { InsertAffiliateIdentifierChangeCallback } from "./DeepLinkIapProvider";
|
|
7
|
+
export type { InsertAffiliateIdentifierChangeCallback, AffiliateDetails } from "./DeepLinkIapProvider";
|
|
@@ -13,6 +13,7 @@ const useDeepLinkIapProvider = () => {
|
|
|
13
13
|
getAffiliateStoredDate,
|
|
14
14
|
trackEvent,
|
|
15
15
|
setShortCode,
|
|
16
|
+
getAffiliateDetails,
|
|
16
17
|
setInsertAffiliateIdentifier,
|
|
17
18
|
setInsertAffiliateIdentifierChangeCallback,
|
|
18
19
|
handleInsertLinks,
|
|
@@ -32,6 +33,7 @@ const useDeepLinkIapProvider = () => {
|
|
|
32
33
|
getAffiliateStoredDate,
|
|
33
34
|
trackEvent,
|
|
34
35
|
setShortCode,
|
|
36
|
+
getAffiliateDetails,
|
|
35
37
|
setInsertAffiliateIdentifier,
|
|
36
38
|
setInsertAffiliateIdentifierChangeCallback,
|
|
37
39
|
handleInsertLinks,
|