insert-affiliate-react-native-sdk 1.9.0 → 1.11.1

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.
@@ -1,4 +1,4 @@
1
- import React, { createContext, useEffect, useState, useRef } from 'react';
1
+ import React, { createContext, useEffect, useState, useRef, useCallback } from 'react';
2
2
  import { Platform, Linking, Dimensions, PixelRatio } from 'react-native';
3
3
  import axios from 'axios';
4
4
  import AsyncStorage from '@react-native-async-storage/async-storage';
@@ -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: '',
@@ -121,49 +132,74 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
121
132
  const [OfferCode, setOfferCode] = useState<string | null>(null);
122
133
  const [affiliateAttributionActiveTime, setAffiliateAttributionActiveTime] = useState<number | null>(null);
123
134
  const insertAffiliateIdentifierChangeCallbackRef = useRef<InsertAffiliateIdentifierChangeCallback | null>(null);
135
+ const isInitializingRef = useRef<boolean>(false);
136
+
137
+ // Refs for values that need to be current inside callbacks (to avoid stale closures)
138
+ const companyCodeRef = useRef<string | null>(null);
139
+ const verboseLoggingRef = useRef<boolean>(false);
140
+
141
+ // Refs for implementation functions (ref callback pattern for stable + fresh)
142
+ const initializeImplRef = useRef<(code: string | null, verboseLogging?: boolean, insertLinksEnabled?: boolean, insertLinksClipboardEnabled?: boolean, affiliateAttributionActiveTime?: number) => Promise<void>>(null as any);
143
+ const setShortCodeImplRef = useRef<(shortCode: string) => Promise<boolean>>(null as any);
144
+ const getAffiliateDetailsImplRef = useRef<(affiliateCode: string) => Promise<AffiliateDetails>>(null as any);
145
+ const returnInsertAffiliateIdentifierImplRef = useRef<(ignoreTimeout?: boolean) => Promise<string | null>>(null as any);
146
+ const isAffiliateAttributionValidImplRef = useRef<() => Promise<boolean>>(null as any);
147
+ const getAffiliateStoredDateImplRef = useRef<() => Promise<Date | null>>(null as any);
148
+ const storeExpectedStoreTransactionImplRef = useRef<(purchaseToken: string) => Promise<void>>(null as any);
149
+ const returnUserAccountTokenAndStoreExpectedTransactionImplRef = useRef<() => Promise<string | null>>(null as any);
150
+ const validatePurchaseWithIapticAPIImplRef = useRef<(jsonIapPurchase: CustomPurchase, iapticAppId: string, iapticAppName: string, iapticPublicKey: string) => Promise<boolean>>(null as any);
151
+ const trackEventImplRef = useRef<(eventName: string) => Promise<void>>(null as any);
152
+ const setInsertAffiliateIdentifierImplRef = useRef<(referringLink: string) => Promise<void | string>>(null as any);
153
+ const handleInsertLinksImplRef = useRef<(url: string) => Promise<boolean>>(null as any);
124
154
 
125
155
  // MARK: Initialize the SDK
126
- const initialize = async (companyCode: string | null, verboseLogging: boolean = false, insertLinksEnabled: boolean = false, insertLinksClipboardEnabled: boolean = false, affiliateAttributionActiveTime?: number): Promise<void> => {
127
- setVerboseLogging(verboseLogging);
128
- setInsertLinksEnabled(insertLinksEnabled);
129
- setInsertLinksClipboardEnabled(insertLinksClipboardEnabled);
130
- if (affiliateAttributionActiveTime !== undefined) {
131
- setAffiliateAttributionActiveTime(affiliateAttributionActiveTime);
156
+ const initializeImpl = async (companyCodeParam: string | null, verboseLoggingParam: boolean = false, insertLinksEnabledParam: boolean = false, insertLinksClipboardEnabledParam: boolean = false, affiliateAttributionActiveTimeParam?: number): Promise<void> => {
157
+ // Prevent multiple concurrent initialization attempts
158
+ if (isInitialized || isInitializingRef.current) {
159
+ return;
132
160
  }
133
-
134
- if (verboseLogging) {
135
- console.log('[Insert Affiliate] [VERBOSE] Starting SDK initialization...');
136
- console.log('[Insert Affiliate] [VERBOSE] Company code provided:', companyCode ? 'Yes' : 'No');
137
- console.log('[Insert Affiliate] [VERBOSE] Verbose logging enabled');
161
+ isInitializingRef.current = true;
162
+
163
+ setVerboseLogging(verboseLoggingParam);
164
+ verboseLoggingRef.current = verboseLoggingParam;
165
+ setInsertLinksEnabled(insertLinksEnabledParam);
166
+ setInsertLinksClipboardEnabled(insertLinksClipboardEnabledParam);
167
+ if (affiliateAttributionActiveTimeParam !== undefined) {
168
+ setAffiliateAttributionActiveTime(affiliateAttributionActiveTimeParam);
138
169
  }
139
170
 
140
- if (isInitialized) {
141
- console.error('[Insert Affiliate] SDK is already initialized.');
142
- return;
171
+ if (verboseLoggingParam) {
172
+ console.log('[Insert Affiliate] [VERBOSE] Starting SDK initialization...');
173
+ console.log('[Insert Affiliate] [VERBOSE] Company code provided:', companyCodeParam ? 'Yes' : 'No');
174
+ console.log('[Insert Affiliate] [VERBOSE] Verbose logging enabled');
143
175
  }
144
176
 
145
- if (companyCode && companyCode.trim() !== '') {
146
- setCompanyCode(companyCode);
147
- await saveValueInAsync(ASYNC_KEYS.COMPANY_CODE, companyCode);
177
+ if (companyCodeParam && companyCodeParam.trim() !== '') {
178
+ setCompanyCode(companyCodeParam);
179
+ companyCodeRef.current = companyCodeParam;
180
+ await saveValueInAsync(ASYNC_KEYS.COMPANY_CODE, companyCodeParam);
148
181
  setIsInitialized(true);
149
182
  console.log(
150
- `[Insert Affiliate] SDK initialized with company code: ${companyCode}`
183
+ `[Insert Affiliate] SDK initialized with company code: ${companyCodeParam}`
151
184
  );
152
- if (verboseLogging) {
185
+ if (verboseLoggingParam) {
153
186
  console.log('[Insert Affiliate] [VERBOSE] Company code saved to AsyncStorage');
154
187
  console.log('[Insert Affiliate] [VERBOSE] SDK marked as initialized');
155
188
  }
189
+
190
+ // Report SDK initialization for onboarding verification (fire and forget)
191
+ reportSdkInitIfNeeded(companyCodeParam, verboseLoggingParam);
156
192
  } else {
157
193
  console.warn(
158
194
  '[Insert Affiliate] SDK initialized without a company code.'
159
195
  );
160
196
  setIsInitialized(true);
161
- if (verboseLogging) {
197
+ if (verboseLoggingParam) {
162
198
  console.log('[Insert Affiliate] [VERBOSE] No company code provided, SDK initialized in limited mode');
163
199
  }
164
200
  }
165
201
 
166
- if (insertLinksEnabled && Platform.OS === 'ios') {
202
+ if (insertLinksEnabledParam && Platform.OS === 'ios') {
167
203
  try {
168
204
  const enhancedSystemInfo = await getEnhancedSystemInfo();
169
205
  await sendSystemInfoToBackend(enhancedSystemInfo);
@@ -197,6 +233,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
197
233
 
198
234
  if (companyCodeFromStorage) {
199
235
  setCompanyCode(companyCodeFromStorage);
236
+ companyCodeRef.current = companyCodeFromStorage;
200
237
  verboseLog('Company code restored from storage');
201
238
  }
202
239
 
@@ -337,13 +374,6 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
337
374
  console.log('[Insert Affiliate] SDK has been reset.');
338
375
  };
339
376
 
340
- // MARK: Callback Management
341
- // Sets a callback that will be triggered whenever storeInsertAffiliateIdentifier is called
342
- // The callback receives the current affiliate identifier (returnInsertAffiliateIdentifier result)
343
- const setInsertAffiliateIdentifierChangeCallbackHandler = (callback: InsertAffiliateIdentifierChangeCallback | null): void => {
344
- insertAffiliateIdentifierChangeCallbackRef.current = callback;
345
- };
346
-
347
377
  // MARK: Deep Link Handling
348
378
  // Helper function to parse URLs in React Native compatible way
349
379
  const parseURL = (url: string) => {
@@ -389,14 +419,26 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
389
419
  return false;
390
420
  }
391
421
 
392
- // Parse the URL to extract query parameters
393
- const urlObj = new URL(url);
394
- const insertAffiliate = urlObj.searchParams.get('insertAffiliate');
395
-
422
+ // Parse the URL to extract query parameters (React Native compatible)
423
+ // URLSearchParams is not available in React Native, so parse manually
424
+ let insertAffiliate: string | null = null;
425
+ const queryIndex = url.indexOf('?');
426
+ if (queryIndex !== -1) {
427
+ const queryString = url.substring(queryIndex + 1);
428
+ const params = queryString.split('&');
429
+ for (const param of params) {
430
+ const [key, value] = param.split('=');
431
+ if (key === 'insertAffiliate' && value) {
432
+ insertAffiliate = decodeURIComponent(value);
433
+ break;
434
+ }
435
+ }
436
+ }
437
+
396
438
  if (insertAffiliate && insertAffiliate.length > 0) {
397
439
  verboseLog(`Found insertAffiliate parameter: ${insertAffiliate}`);
398
440
 
399
- await storeInsertAffiliateIdentifier({ link: insertAffiliate });
441
+ await storeInsertAffiliateIdentifier({ link: insertAffiliate, source: 'deep_link_android' });
400
442
 
401
443
  return true;
402
444
  } else {
@@ -525,7 +567,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
525
567
  // If we have insertAffiliate parameter, use it as the affiliate identifier
526
568
  if (insertAffiliate && insertAffiliate.length > 0) {
527
569
  verboseLog(`Found insertAffiliate parameter, setting as affiliate identifier: ${insertAffiliate}`);
528
- await storeInsertAffiliateIdentifier({ link: insertAffiliate });
570
+ await storeInsertAffiliateIdentifier({ link: insertAffiliate, source: 'install_referrer' });
529
571
 
530
572
  return true;
531
573
  } else {
@@ -540,10 +582,10 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
540
582
  };
541
583
 
542
584
  // Handles Insert Links deep linking - equivalent to iOS handleInsertLinks
543
- const handleInsertLinks = async (url: string): Promise<boolean> => {
585
+ const handleInsertLinksImpl = async (url: string): Promise<boolean> => {
544
586
  try {
545
587
  console.log(`[Insert Affiliate] Attempting to handle URL: ${url}`);
546
-
588
+
547
589
  if (!url || typeof url !== 'string') {
548
590
  console.log('[Insert Affiliate] Invalid URL provided to handleInsertLinks');
549
591
  return false;
@@ -602,7 +644,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
602
644
  }
603
645
 
604
646
  // If URL scheme is used, we can straight away store the short code as the referring link
605
- await storeInsertAffiliateIdentifier({ link: shortCode });
647
+ await storeInsertAffiliateIdentifier({ link: shortCode, source: 'deep_link_ios' });
606
648
 
607
649
  // Collect and send enhanced system info to backend
608
650
  try {
@@ -664,14 +706,48 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
664
706
  }
665
707
  };
666
708
 
709
+ // Parse shortcode from query parameter (new format: scheme://insert-affiliate?code=SHORTCODE)
710
+ const parseShortCodeFromQuery = (url: string): string | null => {
711
+ try {
712
+ const queryIndex = url.indexOf('?');
713
+ if (queryIndex !== -1) {
714
+ const queryString = url.substring(queryIndex + 1);
715
+ const params = queryString.split('&');
716
+ for (const param of params) {
717
+ const [key, value] = param.split('=');
718
+ if (key === 'code' && value) {
719
+ return decodeURIComponent(value);
720
+ }
721
+ }
722
+ }
723
+ return null;
724
+ } catch (error) {
725
+ verboseLog(`Error parsing short code from query: ${error}`);
726
+ return null;
727
+ }
728
+ };
729
+
667
730
  const parseShortCodeFromURLString = (url: string): string | null => {
668
731
  try {
669
- // For custom schemes like ia-companycode://shortcode, everything after :// is the short code
732
+ // First try to extract from query parameter (new format: scheme://insert-affiliate?code=SHORTCODE)
733
+ const queryCode = parseShortCodeFromQuery(url);
734
+ if (queryCode) {
735
+ console.log(`[Insert Affiliate] Found short code in query parameter: ${queryCode}`);
736
+ return queryCode;
737
+ }
738
+
739
+ // Fall back to path format (legacy: scheme://SHORTCODE)
670
740
  const match = url.match(/^[^:]+:\/\/(.+)$/);
671
741
  if (match) {
672
- const shortCode = match[1];
742
+ let shortCode = match[1];
673
743
  // Remove leading slash if present
674
- return shortCode.startsWith('/') ? shortCode.substring(1) : shortCode;
744
+ shortCode = shortCode.startsWith('/') ? shortCode.substring(1) : shortCode;
745
+ // If the path is 'insert-affiliate' (from new format without code param), return null
746
+ if (shortCode === 'insert-affiliate' || shortCode.startsWith('insert-affiliate?')) {
747
+ return null;
748
+ }
749
+ console.log(`[Insert Affiliate] Found short code in URL path (legacy format): ${shortCode}`);
750
+ return shortCode;
675
751
  }
676
752
  return null;
677
753
  } catch (error) {
@@ -694,29 +770,30 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
694
770
  await AsyncStorage.clear();
695
771
  };
696
772
 
697
- // Helper function to get company code from state or storage
773
+ // Helper function to get company code from ref or storage (uses ref to avoid stale closures)
698
774
  const getActiveCompanyCode = async (): Promise<string | null> => {
699
775
  verboseLog('Getting active company code...');
700
- let activeCompanyCode = companyCode;
701
- verboseLog(`Company code in React state: ${activeCompanyCode || 'empty'}`);
702
-
776
+ let activeCompanyCode = companyCodeRef.current;
777
+ verboseLog(`Company code in ref: ${activeCompanyCode || 'empty'}`);
778
+
703
779
  if (!activeCompanyCode || (activeCompanyCode.trim() === '' && activeCompanyCode !== null)) {
704
- verboseLog('Company code not in state, checking AsyncStorage...');
780
+ verboseLog('Company code not in ref, checking AsyncStorage...');
705
781
  activeCompanyCode = await getValueFromAsync(ASYNC_KEYS.COMPANY_CODE);
706
782
  verboseLog(`Company code in AsyncStorage: ${activeCompanyCode || 'empty'}`);
707
-
783
+
708
784
  if (activeCompanyCode) {
709
- // Update state for future use
785
+ // Update ref and state for future use
786
+ companyCodeRef.current = activeCompanyCode;
710
787
  setCompanyCode(activeCompanyCode);
711
- verboseLog('Updated React state with company code from storage');
788
+ verboseLog('Updated ref and React state with company code from storage');
712
789
  }
713
790
  }
714
791
  return activeCompanyCode;
715
792
  };
716
793
 
717
- // Helper function for verbose logging
794
+ // Helper function for verbose logging (uses ref to avoid stale closures)
718
795
  const verboseLog = (message: string) => {
719
- if (verboseLogging) {
796
+ if (verboseLoggingRef.current) {
720
797
  console.log(`[Insert Affiliate] [VERBOSE] ${message}`);
721
798
  }
722
799
  };
@@ -736,6 +813,96 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
736
813
  }
737
814
  };
738
815
 
816
+ // Reports a new affiliate association to the backend for tracking.
817
+ // Only reports each unique affiliateIdentifier once to prevent duplicates.
818
+ const reportAffiliateAssociationIfNeeded = async (
819
+ affiliateIdentifier: string,
820
+ source: AffiliateAssociationSource
821
+ ): Promise<void> => {
822
+ try {
823
+ const activeCompanyCode = await getActiveCompanyCode();
824
+ if (!activeCompanyCode) {
825
+ verboseLog('Cannot report affiliate association: no company code available');
826
+ return;
827
+ }
828
+
829
+ // Get the set of already-reported affiliate identifiers
830
+ const reportedAssociationsJson = await AsyncStorage.getItem(ASYNC_KEYS.REPORTED_AFFILIATE_ASSOCIATIONS);
831
+ const reportedAssociations: string[] = reportedAssociationsJson ? JSON.parse(reportedAssociationsJson) : [];
832
+
833
+ // Check if this affiliate identifier has already been reported
834
+ if (reportedAssociations.includes(affiliateIdentifier)) {
835
+ verboseLog(`Affiliate association already reported for: ${affiliateIdentifier}, skipping`);
836
+ return;
837
+ }
838
+
839
+ verboseLog(`Reporting new affiliate association: ${affiliateIdentifier} (source: ${source})`);
840
+
841
+ const response = await fetch('https://api.insertaffiliate.com/V1/onboarding/affiliate-associated', {
842
+ method: 'POST',
843
+ headers: {
844
+ 'Content-Type': 'application/json',
845
+ },
846
+ body: JSON.stringify({
847
+ companyId: activeCompanyCode,
848
+ affiliateIdentifier: affiliateIdentifier,
849
+ source: source,
850
+ timestamp: new Date().toISOString(),
851
+ }),
852
+ });
853
+
854
+ if (response.ok) {
855
+ // Add to reported set and persist
856
+ reportedAssociations.push(affiliateIdentifier);
857
+ await AsyncStorage.setItem(ASYNC_KEYS.REPORTED_AFFILIATE_ASSOCIATIONS, JSON.stringify(reportedAssociations));
858
+ verboseLog(`Affiliate association reported successfully for: ${affiliateIdentifier}`);
859
+ } else {
860
+ verboseLog(`Affiliate association report failed with status: ${response.status}`);
861
+ }
862
+ } catch (error) {
863
+ // Silently fail - this is non-critical telemetry
864
+ verboseLog(`Affiliate association report error: ${error}`);
865
+ }
866
+ };
867
+
868
+ // Reports SDK initialization to the backend for onboarding verification.
869
+ // Only reports once per install to minimize server load.
870
+ const reportSdkInitIfNeeded = async (companyCode: string, verboseLogging: boolean): Promise<void> => {
871
+ try {
872
+ // Only report once per install
873
+ const alreadyReported = await AsyncStorage.getItem(ASYNC_KEYS.SDK_INIT_REPORTED);
874
+ if (alreadyReported === 'true') {
875
+ return;
876
+ }
877
+
878
+ if (verboseLogging) {
879
+ console.log('[Insert Affiliate] Reporting SDK initialization for onboarding verification...');
880
+ }
881
+
882
+ const response = await fetch('https://api.insertaffiliate.com/V1/onboarding/sdk-init', {
883
+ method: 'POST',
884
+ headers: {
885
+ 'Content-Type': 'application/json',
886
+ },
887
+ body: JSON.stringify({ companyId: companyCode }),
888
+ });
889
+
890
+ if (response.ok) {
891
+ await AsyncStorage.setItem(ASYNC_KEYS.SDK_INIT_REPORTED, 'true');
892
+ if (verboseLogging) {
893
+ console.log('[Insert Affiliate] SDK initialization reported successfully');
894
+ }
895
+ } else if (verboseLogging) {
896
+ console.log(`[Insert Affiliate] SDK initialization report failed with status: ${response.status}`);
897
+ }
898
+ } catch (error) {
899
+ // Silently fail - this is non-critical telemetry
900
+ if (verboseLogging) {
901
+ console.log(`[Insert Affiliate] SDK initialization report error: ${error}`);
902
+ }
903
+ }
904
+ };
905
+
739
906
  // MARK: - Deep Linking Utilities
740
907
 
741
908
  // Retrieves and validates clipboard content for UUID format
@@ -1154,9 +1321,9 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1154
1321
  const matchFound = response.data.matchFound || false;
1155
1322
  if (matchFound && response.data.matched_affiliate_shortCode && response.data.matched_affiliate_shortCode.length > 0) {
1156
1323
  const matchedShortCode = response.data.matched_affiliate_shortCode;
1157
- verboseLog(`Storing Matched short code from backend: ${matchedShortCode}`);
1158
-
1159
- await storeInsertAffiliateIdentifier({ link: matchedShortCode });
1324
+ verboseLog(`Storing Matched short code from backend: ${matchedShortCode}`);
1325
+
1326
+ await storeInsertAffiliateIdentifier({ link: matchedShortCode, source: 'clipboard_match' });
1160
1327
  }
1161
1328
  }
1162
1329
 
@@ -1224,7 +1391,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1224
1391
  }
1225
1392
  };
1226
1393
 
1227
- const getAffiliateDetails = async (affiliateCode: string): Promise<AffiliateDetails> => {
1394
+ const getAffiliateDetailsImpl = async (affiliateCode: string): Promise<AffiliateDetails> => {
1228
1395
  try {
1229
1396
  const activeCompanyCode = await getActiveCompanyCode();
1230
1397
  if (!activeCompanyCode) {
@@ -1269,7 +1436,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1269
1436
  }
1270
1437
  };
1271
1438
 
1272
- async function setShortCode(shortCode: string): Promise<boolean> {
1439
+ const setShortCodeImpl = async (shortCode: string): Promise<boolean> => {
1273
1440
  console.log('[Insert Affiliate] Setting short code.');
1274
1441
  await generateThenSetUserID();
1275
1442
 
@@ -1282,14 +1449,14 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1282
1449
 
1283
1450
  if (exists) {
1284
1451
  // If affiliate exists, set the Insert Affiliate Identifier
1285
- await storeInsertAffiliateIdentifier({ link: capitalisedShortCode });
1452
+ await storeInsertAffiliateIdentifier({ link: capitalisedShortCode, source: 'short_code_manual' });
1286
1453
  console.log(`[Insert Affiliate] Short code ${capitalisedShortCode} validated and stored successfully.`);
1287
1454
  return true;
1288
1455
  } else {
1289
1456
  console.warn(`[Insert Affiliate] Short code ${capitalisedShortCode} does not exist. Not storing.`);
1290
1457
  return false;
1291
1458
  }
1292
- }
1459
+ };
1293
1460
 
1294
1461
  async function getOrCreateUserAccountToken(): Promise<string> {
1295
1462
  let userAccountToken = await getValueFromAsync(ASYNC_KEYS.USER_ACCOUNT_TOKEN);
@@ -1302,9 +1469,9 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1302
1469
  return userAccountToken;
1303
1470
  };
1304
1471
 
1305
- const returnUserAccountTokenAndStoreExpectedTransaction = async (): Promise<string | null> => {
1472
+ const returnUserAccountTokenAndStoreExpectedTransactionImpl = async (): Promise<string | null> => {
1306
1473
  try {
1307
- const shortCode = await returnInsertAffiliateIdentifier();
1474
+ const shortCode = await returnInsertAffiliateIdentifierImpl();
1308
1475
  if (!shortCode) {
1309
1476
  console.log('[Insert Affiliate] No affiliate stored - not saving expected transaction.');
1310
1477
  return null;
@@ -1317,14 +1484,14 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1317
1484
  console.error('[Insert Affiliate] Failed to generate user account token.');
1318
1485
  return null;
1319
1486
  } else {
1320
- await storeExpectedStoreTransaction(userAccountToken);
1487
+ await storeExpectedStoreTransactionImpl(userAccountToken);
1321
1488
  return userAccountToken;
1322
1489
  }
1323
1490
  } catch (error) {
1324
1491
  // Handle E_IAP_NOT_AVAILABLE error gracefully
1325
- if ((error as any)?.code === 'E_IAP_NOT_AVAILABLE' ||
1492
+ if ((error as any)?.code === 'E_IAP_NOT_AVAILABLE' ||
1326
1493
  (error instanceof Error && error.message.includes('E_IAP_NOT_AVAILABLE'))) {
1327
-
1494
+
1328
1495
  if (isDevelopmentEnvironment) {
1329
1496
  console.warn('[Insert Affiliate] IAP not available in development environment. Cannot store expected transaction.');
1330
1497
  verboseLog('E_IAP_NOT_AVAILABLE error in returnUserAccountTokenAndStoreExpectedTransaction - gracefully handling in development');
@@ -1338,50 +1505,50 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1338
1505
  };
1339
1506
 
1340
1507
  // MARK: Return Insert Affiliate Identifier
1341
- const returnInsertAffiliateIdentifier = async (ignoreTimeout: boolean = false): Promise<string | null> => {
1508
+ const returnInsertAffiliateIdentifierImpl = async (ignoreTimeout: boolean = false): Promise<string | null> => {
1342
1509
  try {
1343
1510
  verboseLog(`Getting insert affiliate identifier (ignoreTimeout: ${ignoreTimeout})...`);
1344
-
1511
+
1345
1512
  // If timeout is enabled and we're not ignoring it, check validity first
1346
1513
  if (!ignoreTimeout && affiliateAttributionActiveTime) {
1347
- const isValid = await isAffiliateAttributionValid();
1514
+ const isValid = await isAffiliateAttributionValidImpl();
1348
1515
  if (!isValid) {
1349
1516
  verboseLog('Attribution has expired, returning null');
1350
1517
  return null;
1351
1518
  }
1352
1519
  }
1353
-
1520
+
1354
1521
  // Now get the actual identifier
1355
1522
  verboseLog(`React state - referrerLink: ${referrerLink || 'empty'}, userId: ${userId || 'empty'}`);
1356
-
1523
+
1357
1524
  // Try React state first
1358
1525
  if (referrerLink && userId) {
1359
1526
  const identifier = `${referrerLink}-${userId}`;
1360
1527
  verboseLog(`Found identifier in React state: ${identifier}`);
1361
1528
  return identifier;
1362
1529
  }
1363
-
1530
+
1364
1531
  verboseLog('React state empty, checking AsyncStorage...');
1365
-
1532
+
1366
1533
  // Fallback to async storage if React state is empty
1367
1534
  const storedLink = await getValueFromAsync(ASYNC_KEYS.REFERRER_LINK);
1368
1535
  let storedUserId = await getValueFromAsync(ASYNC_KEYS.USER_ID);
1369
-
1536
+
1370
1537
  verboseLog(`AsyncStorage - storedLink: ${storedLink || 'empty'}, storedUserId: ${storedUserId || 'empty'}`);
1371
-
1538
+
1372
1539
  // If we have a stored link but no user ID, generate one now
1373
1540
  if (storedLink && !storedUserId) {
1374
1541
  verboseLog('Found stored link but no user ID, generating user ID now...');
1375
1542
  storedUserId = await generateThenSetUserID();
1376
1543
  verboseLog(`Generated user ID: ${storedUserId}`);
1377
1544
  }
1378
-
1545
+
1379
1546
  if (storedLink && storedUserId) {
1380
1547
  const identifier = `${storedLink}-${storedUserId}`;
1381
1548
  verboseLog(`Found identifier in AsyncStorage: ${identifier}`);
1382
1549
  return identifier;
1383
1550
  }
1384
-
1551
+
1385
1552
  verboseLog('No affiliate identifier found in state or storage');
1386
1553
  return null;
1387
1554
  } catch (error) {
@@ -1391,44 +1558,44 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1391
1558
  };
1392
1559
 
1393
1560
  // MARK: Attribution Timeout Functions
1394
-
1561
+
1395
1562
  // Check if the current affiliate attribution is still valid based on timeout
1396
- const isAffiliateAttributionValid = async (): Promise<boolean> => {
1563
+ const isAffiliateAttributionValidImpl = async (): Promise<boolean> => {
1397
1564
  try {
1398
1565
  // If no timeout is set, attribution is always valid
1399
1566
  if (!affiliateAttributionActiveTime) {
1400
1567
  verboseLog('No attribution timeout set, attribution is valid');
1401
1568
  return true;
1402
1569
  }
1403
-
1404
- const storedDate = await getAffiliateStoredDate();
1570
+
1571
+ const storedDate = await getAffiliateStoredDateImpl();
1405
1572
  if (!storedDate) {
1406
1573
  verboseLog('No stored date found, attribution is invalid');
1407
1574
  return false;
1408
1575
  }
1409
-
1576
+
1410
1577
  const now = new Date();
1411
1578
  const timeDifferenceSeconds = Math.floor((now.getTime() - storedDate.getTime()) / 1000);
1412
1579
  const isValid = timeDifferenceSeconds <= affiliateAttributionActiveTime;
1413
-
1580
+
1414
1581
  verboseLog(`Attribution timeout check: stored=${storedDate.toISOString()}, now=${now.toISOString()}, diff=${timeDifferenceSeconds}s, timeout=${affiliateAttributionActiveTime}s, valid=${isValid}`);
1415
-
1582
+
1416
1583
  return isValid;
1417
1584
  } catch (error) {
1418
1585
  verboseLog(`Error checking attribution validity: ${error}`);
1419
1586
  return false;
1420
1587
  }
1421
1588
  };
1422
-
1589
+
1423
1590
  // Get the date when the affiliate identifier was stored
1424
- const getAffiliateStoredDate = async (): Promise<Date | null> => {
1591
+ const getAffiliateStoredDateImpl = async (): Promise<Date | null> => {
1425
1592
  try {
1426
1593
  const storedDateString = await getValueFromAsync(ASYNC_KEYS.AFFILIATE_STORED_DATE);
1427
1594
  if (!storedDateString) {
1428
1595
  verboseLog('No affiliate stored date found');
1429
1596
  return null;
1430
1597
  }
1431
-
1598
+
1432
1599
  const storedDate = new Date(storedDateString);
1433
1600
  verboseLog(`Retrieved affiliate stored date: ${storedDate.toISOString()}`);
1434
1601
  return storedDate;
@@ -1440,9 +1607,9 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1440
1607
 
1441
1608
  // MARK: Insert Affiliate Identifier
1442
1609
 
1443
- async function setInsertAffiliateIdentifier(
1610
+ const setInsertAffiliateIdentifierImpl = async (
1444
1611
  referringLink: string
1445
- ): Promise<void | string> {
1612
+ ): Promise<void | string> => {
1446
1613
  console.log('[Insert Affiliate] Setting affiliate identifier.');
1447
1614
  verboseLog(`Input referringLink: ${referringLink}`);
1448
1615
 
@@ -1457,7 +1624,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1457
1624
  if (!referringLink) {
1458
1625
  console.warn('[Insert Affiliate] Referring link is invalid.');
1459
1626
  verboseLog('Referring link is empty or invalid, storing as-is');
1460
- await storeInsertAffiliateIdentifier({ link: referringLink });
1627
+ await storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
1461
1628
  return `${referringLink}-${customerID}`;
1462
1629
  }
1463
1630
 
@@ -1481,7 +1648,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1481
1648
  '[Insert Affiliate] Referring link is already a short code.'
1482
1649
  );
1483
1650
  verboseLog('Link is already a short code, storing directly');
1484
- await storeInsertAffiliateIdentifier({ link: referringLink });
1651
+ await storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
1485
1652
  return `${referringLink}-${customerID}`;
1486
1653
  }
1487
1654
 
@@ -1494,7 +1661,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1494
1661
  if (!encodedAffiliateLink) {
1495
1662
  console.error('[Insert Affiliate] Failed to encode affiliate link.');
1496
1663
  verboseLog('Failed to encode link, storing original');
1497
- await storeInsertAffiliateIdentifier({ link: referringLink });
1664
+ await storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
1498
1665
  return `${referringLink}-${customerID}`;
1499
1666
  }
1500
1667
 
@@ -1517,14 +1684,14 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1517
1684
  console.log('[Insert Affiliate] Short link received:', shortLink);
1518
1685
  verboseLog(`Successfully converted to short link: ${shortLink}`);
1519
1686
  verboseLog('Storing short link to AsyncStorage...');
1520
- await storeInsertAffiliateIdentifier({ link: shortLink });
1687
+ await storeInsertAffiliateIdentifier({ link: shortLink, source: 'referring_link' });
1521
1688
  verboseLog('Short link stored successfully');
1522
1689
  return `${shortLink}-${customerID}`;
1523
1690
  } else {
1524
1691
  console.warn('[Insert Affiliate] Unexpected response format.');
1525
1692
  verboseLog(`Unexpected API response. Status: ${response.status}, Data: ${JSON.stringify(response.data)}`);
1526
1693
  verboseLog('Storing original link as fallback');
1527
- await storeInsertAffiliateIdentifier({ link: referringLink });
1694
+ await storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
1528
1695
  return `${referringLink}-${customerID}`;
1529
1696
  }
1530
1697
  } catch (error) {
@@ -1532,42 +1699,48 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1532
1699
  verboseLog(`Error in setInsertAffiliateIdentifier: ${error}`);
1533
1700
  }
1534
1701
  };
1535
-
1536
- async function storeInsertAffiliateIdentifier({ link }: { link: string }) {
1537
- console.log(`[Insert Affiliate] Storing affiliate identifier: ${link}`);
1538
-
1702
+
1703
+ async function storeInsertAffiliateIdentifier({ link, source }: { link: string; source: AffiliateAssociationSource }) {
1704
+ console.log(`[Insert Affiliate] Storing affiliate identifier: ${link} (source: ${source})`);
1705
+
1539
1706
  // Check if we're trying to store the same link (prevent duplicate storage)
1540
1707
  const existingLink = await getValueFromAsync(ASYNC_KEYS.REFERRER_LINK);
1541
1708
  if (existingLink === link) {
1542
1709
  verboseLog(`Link ${link} is already stored, skipping duplicate storage`);
1543
1710
  return;
1544
1711
  }
1545
-
1712
+
1546
1713
  verboseLog(`Updating React state with referrer link: ${link}`);
1547
1714
  setReferrerLink(link);
1548
1715
  verboseLog(`Saving referrer link to AsyncStorage...`);
1549
1716
  await saveValueInAsync(ASYNC_KEYS.REFERRER_LINK, link);
1550
-
1717
+
1551
1718
  // Store the current date/time when the affiliate identifier is stored
1552
1719
  const currentDate = new Date().toISOString();
1553
1720
  verboseLog(`Saving affiliate stored date: ${currentDate}`);
1554
1721
  await saveValueInAsync(ASYNC_KEYS.AFFILIATE_STORED_DATE, currentDate);
1555
-
1722
+
1556
1723
  verboseLog(`Referrer link saved to AsyncStorage successfully`);
1557
-
1724
+
1558
1725
  // Automatically fetch and store offer code for any affiliate identifier
1559
1726
  verboseLog('Attempting to fetch offer code for stored affiliate identifier...');
1560
1727
  await retrieveAndStoreOfferCode(link);
1561
-
1728
+
1562
1729
  // Trigger callback with the current affiliate identifier
1563
1730
  if (insertAffiliateIdentifierChangeCallbackRef.current) {
1564
- const currentIdentifier = await returnInsertAffiliateIdentifier();
1731
+ const currentIdentifier = await returnInsertAffiliateIdentifierImpl();
1565
1732
  verboseLog(`Triggering callback with identifier: ${currentIdentifier}`);
1566
1733
  insertAffiliateIdentifierChangeCallbackRef.current(currentIdentifier);
1567
1734
  }
1735
+
1736
+ // Report this new affiliate association to the backend (fire and forget)
1737
+ const fullIdentifier = await returnInsertAffiliateIdentifierImpl();
1738
+ if (fullIdentifier) {
1739
+ reportAffiliateAssociationIfNeeded(fullIdentifier, source);
1740
+ }
1568
1741
  }
1569
1742
 
1570
- const validatePurchaseWithIapticAPI = async (
1743
+ const validatePurchaseWithIapticAPIImpl = async (
1571
1744
  jsonIapPurchase: CustomPurchase,
1572
1745
  iapticAppId: string,
1573
1746
  iapticAppName: string,
@@ -1575,10 +1748,10 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1575
1748
  ): Promise<boolean> => {
1576
1749
  try {
1577
1750
  // Check for E_IAP_NOT_AVAILABLE error in development environment
1578
- if ((jsonIapPurchase as any)?.error?.code === 'E_IAP_NOT_AVAILABLE' ||
1751
+ if ((jsonIapPurchase as any)?.error?.code === 'E_IAP_NOT_AVAILABLE' ||
1579
1752
  (jsonIapPurchase as any)?.code === 'E_IAP_NOT_AVAILABLE' ||
1580
1753
  (typeof jsonIapPurchase === 'string' && (jsonIapPurchase as string).includes('E_IAP_NOT_AVAILABLE'))) {
1581
-
1754
+
1582
1755
  if (isDevelopmentEnvironment) {
1583
1756
  console.warn('[Insert Affiliate] IAP not available in development environment. This is expected behavior.');
1584
1757
  verboseLog('E_IAP_NOT_AVAILABLE error detected in development - gracefully handling');
@@ -1619,7 +1792,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1619
1792
  transaction,
1620
1793
  };
1621
1794
 
1622
- let insertAffiliateIdentifier = await returnInsertAffiliateIdentifier();
1795
+ let insertAffiliateIdentifier = await returnInsertAffiliateIdentifierImpl();
1623
1796
 
1624
1797
  if (insertAffiliateIdentifier) {
1625
1798
  requestBody.additionalData = {
@@ -1647,9 +1820,9 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1647
1820
  }
1648
1821
  } catch (error) {
1649
1822
  // Handle E_IAP_NOT_AVAILABLE error gracefully
1650
- if ((error as any)?.code === 'E_IAP_NOT_AVAILABLE' ||
1823
+ if ((error as any)?.code === 'E_IAP_NOT_AVAILABLE' ||
1651
1824
  (error instanceof Error && error.message.includes('E_IAP_NOT_AVAILABLE'))) {
1652
-
1825
+
1653
1826
  if (isDevelopmentEnvironment) {
1654
1827
  console.warn('[Insert Affiliate] IAP not available in development environment. SDK will continue without purchase validation.');
1655
1828
  verboseLog('E_IAP_NOT_AVAILABLE error caught in validatePurchaseWithIapticAPI - gracefully handling in development');
@@ -1673,9 +1846,9 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1673
1846
  }
1674
1847
  };
1675
1848
 
1676
- const storeExpectedStoreTransaction = async (purchaseToken: string): Promise<void> => {
1849
+ const storeExpectedStoreTransactionImpl = async (purchaseToken: string): Promise<void> => {
1677
1850
  verboseLog(`Storing expected store transaction with token: ${purchaseToken}`);
1678
-
1851
+
1679
1852
  const activeCompanyCode = await getActiveCompanyCode();
1680
1853
  if (!activeCompanyCode) {
1681
1854
  console.error("[Insert Affiliate] Company code is not set. Please initialize the SDK with a valid company code.");
@@ -1683,7 +1856,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1683
1856
  return;
1684
1857
  }
1685
1858
 
1686
- const shortCode = await returnInsertAffiliateIdentifier();
1859
+ const shortCode = await returnInsertAffiliateIdentifierImpl();
1687
1860
  if (!shortCode) {
1688
1861
  console.error("[Insert Affiliate] No affiliate identifier found. Please set one before tracking events.");
1689
1862
  verboseLog("Cannot store transaction: no affiliate identifier available");
@@ -1729,10 +1902,10 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1729
1902
  };
1730
1903
 
1731
1904
  // MARK: Track Event
1732
- const trackEvent = async (eventName: string): Promise<void> => {
1905
+ const trackEventImpl = async (eventName: string): Promise<void> => {
1733
1906
  try {
1734
1907
  verboseLog(`Tracking event: ${eventName}`);
1735
-
1908
+
1736
1909
  const activeCompanyCode = await getActiveCompanyCode();
1737
1910
  if (!activeCompanyCode) {
1738
1911
  console.error("[Insert Affiliate] Company code is not set. Please initialize the SDK with a valid company code.");
@@ -1762,7 +1935,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1762
1935
  verboseLog(`Track event payload: ${JSON.stringify(payload)}`);
1763
1936
 
1764
1937
  verboseLog("Making API call to track event...");
1765
-
1938
+
1766
1939
  const response = await axios.post(
1767
1940
  'https://api.insertaffiliate.com/v1/trackEvent',
1768
1941
  payload,
@@ -1877,6 +2050,89 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1877
2050
  return removeSpecialCharacters(offerCode);
1878
2051
  };
1879
2052
 
2053
+ // ============================================================================
2054
+ // REF CALLBACK PATTERN: Update refs on every render for fresh closures
2055
+ // ============================================================================
2056
+ initializeImplRef.current = initializeImpl;
2057
+ setShortCodeImplRef.current = setShortCodeImpl;
2058
+ getAffiliateDetailsImplRef.current = getAffiliateDetailsImpl;
2059
+ returnInsertAffiliateIdentifierImplRef.current = returnInsertAffiliateIdentifierImpl;
2060
+ isAffiliateAttributionValidImplRef.current = isAffiliateAttributionValidImpl;
2061
+ getAffiliateStoredDateImplRef.current = getAffiliateStoredDateImpl;
2062
+ storeExpectedStoreTransactionImplRef.current = storeExpectedStoreTransactionImpl;
2063
+ returnUserAccountTokenAndStoreExpectedTransactionImplRef.current = returnUserAccountTokenAndStoreExpectedTransactionImpl;
2064
+ validatePurchaseWithIapticAPIImplRef.current = validatePurchaseWithIapticAPIImpl;
2065
+ trackEventImplRef.current = trackEventImpl;
2066
+ setInsertAffiliateIdentifierImplRef.current = setInsertAffiliateIdentifierImpl;
2067
+ handleInsertLinksImplRef.current = handleInsertLinksImpl;
2068
+
2069
+ // ============================================================================
2070
+ // STABLE WRAPPERS: useCallback with [] deps that delegate to refs
2071
+ // These provide stable function references that always call current implementations
2072
+ // ============================================================================
2073
+ const initialize = useCallback(async (
2074
+ code: string | null,
2075
+ verboseLogging?: boolean,
2076
+ insertLinksEnabled?: boolean,
2077
+ insertLinksClipboardEnabled?: boolean,
2078
+ affiliateAttributionActiveTime?: number
2079
+ ): Promise<void> => {
2080
+ return initializeImplRef.current(code, verboseLogging, insertLinksEnabled, insertLinksClipboardEnabled, affiliateAttributionActiveTime);
2081
+ }, []);
2082
+
2083
+ const setShortCode = useCallback(async (shortCode: string): Promise<boolean> => {
2084
+ return setShortCodeImplRef.current(shortCode);
2085
+ }, []);
2086
+
2087
+ const getAffiliateDetails = useCallback(async (affiliateCode: string): Promise<AffiliateDetails> => {
2088
+ return getAffiliateDetailsImplRef.current(affiliateCode);
2089
+ }, []);
2090
+
2091
+ const returnInsertAffiliateIdentifier = useCallback(async (ignoreTimeout?: boolean): Promise<string | null> => {
2092
+ return returnInsertAffiliateIdentifierImplRef.current(ignoreTimeout);
2093
+ }, []);
2094
+
2095
+ const isAffiliateAttributionValid = useCallback(async (): Promise<boolean> => {
2096
+ return isAffiliateAttributionValidImplRef.current();
2097
+ }, []);
2098
+
2099
+ const getAffiliateStoredDate = useCallback(async (): Promise<Date | null> => {
2100
+ return getAffiliateStoredDateImplRef.current();
2101
+ }, []);
2102
+
2103
+ const storeExpectedStoreTransaction = useCallback(async (purchaseToken: string): Promise<void> => {
2104
+ return storeExpectedStoreTransactionImplRef.current(purchaseToken);
2105
+ }, []);
2106
+
2107
+ const returnUserAccountTokenAndStoreExpectedTransaction = useCallback(async (): Promise<string | null> => {
2108
+ return returnUserAccountTokenAndStoreExpectedTransactionImplRef.current();
2109
+ }, []);
2110
+
2111
+ const validatePurchaseWithIapticAPI = useCallback(async (
2112
+ jsonIapPurchase: CustomPurchase,
2113
+ iapticAppId: string,
2114
+ iapticAppName: string,
2115
+ iapticPublicKey: string
2116
+ ): Promise<boolean> => {
2117
+ return validatePurchaseWithIapticAPIImplRef.current(jsonIapPurchase, iapticAppId, iapticAppName, iapticPublicKey);
2118
+ }, []);
2119
+
2120
+ const trackEvent = useCallback(async (eventName: string): Promise<void> => {
2121
+ return trackEventImplRef.current(eventName);
2122
+ }, []);
2123
+
2124
+ const setInsertAffiliateIdentifier = useCallback(async (referringLink: string): Promise<void | string> => {
2125
+ return setInsertAffiliateIdentifierImplRef.current(referringLink);
2126
+ }, []);
2127
+
2128
+ const setInsertAffiliateIdentifierChangeCallbackHandler = useCallback((callback: InsertAffiliateIdentifierChangeCallback | null): void => {
2129
+ insertAffiliateIdentifierChangeCallbackRef.current = callback;
2130
+ }, []);
2131
+
2132
+ const handleInsertLinks = useCallback(async (url: string): Promise<boolean> => {
2133
+ return handleInsertLinksImplRef.current(url);
2134
+ }, []);
2135
+
1880
2136
  return (
1881
2137
  <DeepLinkIapContext.Provider
1882
2138
  value={{