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.
@@ -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<void>;
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
- const urlObj = new URL(url);
386
- const insertAffiliate = urlObj.searchParams.get('insertAffiliate');
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 function setShortCode(shortCode: string): Promise<void> {
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
- // If all checks pass, set the Insert Affiliate Identifier
1186
- await storeInsertAffiliateIdentifier({ link: capitalisedShortCode });
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,