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.
@@ -54,6 +54,8 @@ const ASYNC_KEYS = {
54
54
  USER_ACCOUNT_TOKEN: '@app_user_account_token',
55
55
  IOS_OFFER_CODE: '@app_ios_offer_code',
56
56
  AFFILIATE_STORED_DATE: '@app_affiliate_stored_date',
57
+ SDK_INIT_REPORTED: '@app_sdk_init_reported',
58
+ REPORTED_AFFILIATE_ASSOCIATIONS: '@app_reported_affiliate_associations',
57
59
  };
58
60
  // STARTING CONTEXT IMPLEMENTATION
59
61
  exports.DeepLinkIapContext = (0, react_1.createContext)({
@@ -86,41 +88,63 @@ const DeepLinkIapProvider = ({ children, }) => {
86
88
  const [OfferCode, setOfferCode] = (0, react_1.useState)(null);
87
89
  const [affiliateAttributionActiveTime, setAffiliateAttributionActiveTime] = (0, react_1.useState)(null);
88
90
  const insertAffiliateIdentifierChangeCallbackRef = (0, react_1.useRef)(null);
91
+ const isInitializingRef = (0, react_1.useRef)(false);
92
+ // Refs for values that need to be current inside callbacks (to avoid stale closures)
93
+ const companyCodeRef = (0, react_1.useRef)(null);
94
+ const verboseLoggingRef = (0, react_1.useRef)(false);
95
+ // Refs for implementation functions (ref callback pattern for stable + fresh)
96
+ const initializeImplRef = (0, react_1.useRef)(null);
97
+ const setShortCodeImplRef = (0, react_1.useRef)(null);
98
+ const getAffiliateDetailsImplRef = (0, react_1.useRef)(null);
99
+ const returnInsertAffiliateIdentifierImplRef = (0, react_1.useRef)(null);
100
+ const isAffiliateAttributionValidImplRef = (0, react_1.useRef)(null);
101
+ const getAffiliateStoredDateImplRef = (0, react_1.useRef)(null);
102
+ const storeExpectedStoreTransactionImplRef = (0, react_1.useRef)(null);
103
+ const returnUserAccountTokenAndStoreExpectedTransactionImplRef = (0, react_1.useRef)(null);
104
+ const validatePurchaseWithIapticAPIImplRef = (0, react_1.useRef)(null);
105
+ const trackEventImplRef = (0, react_1.useRef)(null);
106
+ const setInsertAffiliateIdentifierImplRef = (0, react_1.useRef)(null);
107
+ const handleInsertLinksImplRef = (0, react_1.useRef)(null);
89
108
  // MARK: Initialize the SDK
90
- const initialize = (companyCode_1, ...args_1) => __awaiter(void 0, [companyCode_1, ...args_1], void 0, function* (companyCode, verboseLogging = false, insertLinksEnabled = false, insertLinksClipboardEnabled = false, affiliateAttributionActiveTime) {
91
- setVerboseLogging(verboseLogging);
92
- setInsertLinksEnabled(insertLinksEnabled);
93
- setInsertLinksClipboardEnabled(insertLinksClipboardEnabled);
94
- if (affiliateAttributionActiveTime !== undefined) {
95
- setAffiliateAttributionActiveTime(affiliateAttributionActiveTime);
109
+ const initializeImpl = (companyCodeParam_1, ...args_1) => __awaiter(void 0, [companyCodeParam_1, ...args_1], void 0, function* (companyCodeParam, verboseLoggingParam = false, insertLinksEnabledParam = false, insertLinksClipboardEnabledParam = false, affiliateAttributionActiveTimeParam) {
110
+ // Prevent multiple concurrent initialization attempts
111
+ if (isInitialized || isInitializingRef.current) {
112
+ return;
96
113
  }
97
- if (verboseLogging) {
114
+ isInitializingRef.current = true;
115
+ setVerboseLogging(verboseLoggingParam);
116
+ verboseLoggingRef.current = verboseLoggingParam;
117
+ setInsertLinksEnabled(insertLinksEnabledParam);
118
+ setInsertLinksClipboardEnabled(insertLinksClipboardEnabledParam);
119
+ if (affiliateAttributionActiveTimeParam !== undefined) {
120
+ setAffiliateAttributionActiveTime(affiliateAttributionActiveTimeParam);
121
+ }
122
+ if (verboseLoggingParam) {
98
123
  console.log('[Insert Affiliate] [VERBOSE] Starting SDK initialization...');
99
- console.log('[Insert Affiliate] [VERBOSE] Company code provided:', companyCode ? 'Yes' : 'No');
124
+ console.log('[Insert Affiliate] [VERBOSE] Company code provided:', companyCodeParam ? 'Yes' : 'No');
100
125
  console.log('[Insert Affiliate] [VERBOSE] Verbose logging enabled');
101
126
  }
102
- if (isInitialized) {
103
- console.error('[Insert Affiliate] SDK is already initialized.');
104
- return;
105
- }
106
- if (companyCode && companyCode.trim() !== '') {
107
- setCompanyCode(companyCode);
108
- yield saveValueInAsync(ASYNC_KEYS.COMPANY_CODE, companyCode);
127
+ if (companyCodeParam && companyCodeParam.trim() !== '') {
128
+ setCompanyCode(companyCodeParam);
129
+ companyCodeRef.current = companyCodeParam;
130
+ yield saveValueInAsync(ASYNC_KEYS.COMPANY_CODE, companyCodeParam);
109
131
  setIsInitialized(true);
110
- console.log(`[Insert Affiliate] SDK initialized with company code: ${companyCode}`);
111
- if (verboseLogging) {
132
+ console.log(`[Insert Affiliate] SDK initialized with company code: ${companyCodeParam}`);
133
+ if (verboseLoggingParam) {
112
134
  console.log('[Insert Affiliate] [VERBOSE] Company code saved to AsyncStorage');
113
135
  console.log('[Insert Affiliate] [VERBOSE] SDK marked as initialized');
114
136
  }
137
+ // Report SDK initialization for onboarding verification (fire and forget)
138
+ reportSdkInitIfNeeded(companyCodeParam, verboseLoggingParam);
115
139
  }
116
140
  else {
117
141
  console.warn('[Insert Affiliate] SDK initialized without a company code.');
118
142
  setIsInitialized(true);
119
- if (verboseLogging) {
143
+ if (verboseLoggingParam) {
120
144
  console.log('[Insert Affiliate] [VERBOSE] No company code provided, SDK initialized in limited mode');
121
145
  }
122
146
  }
123
- if (insertLinksEnabled && react_native_1.Platform.OS === 'ios') {
147
+ if (insertLinksEnabledParam && react_native_1.Platform.OS === 'ios') {
124
148
  try {
125
149
  const enhancedSystemInfo = yield getEnhancedSystemInfo();
126
150
  yield sendSystemInfoToBackend(enhancedSystemInfo);
@@ -151,6 +175,7 @@ const DeepLinkIapProvider = ({ children, }) => {
151
175
  }
152
176
  if (companyCodeFromStorage) {
153
177
  setCompanyCode(companyCodeFromStorage);
178
+ companyCodeRef.current = companyCodeFromStorage;
154
179
  verboseLog('Company code restored from storage');
155
180
  }
156
181
  if (storedOfferCode) {
@@ -284,12 +309,6 @@ const DeepLinkIapProvider = ({ children, }) => {
284
309
  setIsInitialized(false);
285
310
  console.log('[Insert Affiliate] SDK has been reset.');
286
311
  };
287
- // MARK: Callback Management
288
- // Sets a callback that will be triggered whenever storeInsertAffiliateIdentifier is called
289
- // The callback receives the current affiliate identifier (returnInsertAffiliateIdentifier result)
290
- const setInsertAffiliateIdentifierChangeCallbackHandler = (callback) => {
291
- insertAffiliateIdentifierChangeCallbackRef.current = callback;
292
- };
293
312
  // MARK: Deep Link Handling
294
313
  // Helper function to parse URLs in React Native compatible way
295
314
  const parseURL = (url) => {
@@ -330,12 +349,24 @@ const DeepLinkIapProvider = ({ children, }) => {
330
349
  verboseLog('Invalid URL provided to handleInsertLinkAndroid');
331
350
  return false;
332
351
  }
333
- // Parse the URL to extract query parameters
334
- const urlObj = new URL(url);
335
- const insertAffiliate = urlObj.searchParams.get('insertAffiliate');
352
+ // Parse the URL to extract query parameters (React Native compatible)
353
+ // URLSearchParams is not available in React Native, so parse manually
354
+ let insertAffiliate = null;
355
+ const queryIndex = url.indexOf('?');
356
+ if (queryIndex !== -1) {
357
+ const queryString = url.substring(queryIndex + 1);
358
+ const params = queryString.split('&');
359
+ for (const param of params) {
360
+ const [key, value] = param.split('=');
361
+ if (key === 'insertAffiliate' && value) {
362
+ insertAffiliate = decodeURIComponent(value);
363
+ break;
364
+ }
365
+ }
366
+ }
336
367
  if (insertAffiliate && insertAffiliate.length > 0) {
337
368
  verboseLog(`Found insertAffiliate parameter: ${insertAffiliate}`);
338
- yield storeInsertAffiliateIdentifier({ link: insertAffiliate });
369
+ yield storeInsertAffiliateIdentifier({ link: insertAffiliate, source: 'deep_link_android' });
339
370
  return true;
340
371
  }
341
372
  else {
@@ -449,7 +480,7 @@ const DeepLinkIapProvider = ({ children, }) => {
449
480
  // If we have insertAffiliate parameter, use it as the affiliate identifier
450
481
  if (insertAffiliate && insertAffiliate.length > 0) {
451
482
  verboseLog(`Found insertAffiliate parameter, setting as affiliate identifier: ${insertAffiliate}`);
452
- yield storeInsertAffiliateIdentifier({ link: insertAffiliate });
483
+ yield storeInsertAffiliateIdentifier({ link: insertAffiliate, source: 'install_referrer' });
453
484
  return true;
454
485
  }
455
486
  else {
@@ -463,7 +494,7 @@ const DeepLinkIapProvider = ({ children, }) => {
463
494
  }
464
495
  });
465
496
  // Handles Insert Links deep linking - equivalent to iOS handleInsertLinks
466
- const handleInsertLinks = (url) => __awaiter(void 0, void 0, void 0, function* () {
497
+ const handleInsertLinksImpl = (url) => __awaiter(void 0, void 0, void 0, function* () {
467
498
  try {
468
499
  console.log(`[Insert Affiliate] Attempting to handle URL: ${url}`);
469
500
  if (!url || typeof url !== 'string') {
@@ -513,7 +544,7 @@ const DeepLinkIapProvider = ({ children, }) => {
513
544
  console.log(`[Insert Affiliate] Warning: URL company code (${companyCode}) doesn't match initialized company code (${activeCompanyCode})`);
514
545
  }
515
546
  // If URL scheme is used, we can straight away store the short code as the referring link
516
- yield storeInsertAffiliateIdentifier({ link: shortCode });
547
+ yield storeInsertAffiliateIdentifier({ link: shortCode, source: 'deep_link_ios' });
517
548
  // Collect and send enhanced system info to backend
518
549
  try {
519
550
  const enhancedSystemInfo = yield getEnhancedSystemInfo();
@@ -566,14 +597,47 @@ const DeepLinkIapProvider = ({ children, }) => {
566
597
  return null;
567
598
  }
568
599
  };
600
+ // Parse shortcode from query parameter (new format: scheme://insert-affiliate?code=SHORTCODE)
601
+ const parseShortCodeFromQuery = (url) => {
602
+ try {
603
+ const queryIndex = url.indexOf('?');
604
+ if (queryIndex !== -1) {
605
+ const queryString = url.substring(queryIndex + 1);
606
+ const params = queryString.split('&');
607
+ for (const param of params) {
608
+ const [key, value] = param.split('=');
609
+ if (key === 'code' && value) {
610
+ return decodeURIComponent(value);
611
+ }
612
+ }
613
+ }
614
+ return null;
615
+ }
616
+ catch (error) {
617
+ verboseLog(`Error parsing short code from query: ${error}`);
618
+ return null;
619
+ }
620
+ };
569
621
  const parseShortCodeFromURLString = (url) => {
570
622
  try {
571
- // For custom schemes like ia-companycode://shortcode, everything after :// is the short code
623
+ // First try to extract from query parameter (new format: scheme://insert-affiliate?code=SHORTCODE)
624
+ const queryCode = parseShortCodeFromQuery(url);
625
+ if (queryCode) {
626
+ console.log(`[Insert Affiliate] Found short code in query parameter: ${queryCode}`);
627
+ return queryCode;
628
+ }
629
+ // Fall back to path format (legacy: scheme://SHORTCODE)
572
630
  const match = url.match(/^[^:]+:\/\/(.+)$/);
573
631
  if (match) {
574
- const shortCode = match[1];
632
+ let shortCode = match[1];
575
633
  // Remove leading slash if present
576
- return shortCode.startsWith('/') ? shortCode.substring(1) : shortCode;
634
+ shortCode = shortCode.startsWith('/') ? shortCode.substring(1) : shortCode;
635
+ // If the path is 'insert-affiliate' (from new format without code param), return null
636
+ if (shortCode === 'insert-affiliate' || shortCode.startsWith('insert-affiliate?')) {
637
+ return null;
638
+ }
639
+ console.log(`[Insert Affiliate] Found short code in URL path (legacy format): ${shortCode}`);
640
+ return shortCode;
577
641
  }
578
642
  return null;
579
643
  }
@@ -593,26 +657,27 @@ const DeepLinkIapProvider = ({ children, }) => {
593
657
  const clearAsyncStorage = () => __awaiter(void 0, void 0, void 0, function* () {
594
658
  yield async_storage_1.default.clear();
595
659
  });
596
- // Helper function to get company code from state or storage
660
+ // Helper function to get company code from ref or storage (uses ref to avoid stale closures)
597
661
  const getActiveCompanyCode = () => __awaiter(void 0, void 0, void 0, function* () {
598
662
  verboseLog('Getting active company code...');
599
- let activeCompanyCode = companyCode;
600
- verboseLog(`Company code in React state: ${activeCompanyCode || 'empty'}`);
663
+ let activeCompanyCode = companyCodeRef.current;
664
+ verboseLog(`Company code in ref: ${activeCompanyCode || 'empty'}`);
601
665
  if (!activeCompanyCode || (activeCompanyCode.trim() === '' && activeCompanyCode !== null)) {
602
- verboseLog('Company code not in state, checking AsyncStorage...');
666
+ verboseLog('Company code not in ref, checking AsyncStorage...');
603
667
  activeCompanyCode = yield getValueFromAsync(ASYNC_KEYS.COMPANY_CODE);
604
668
  verboseLog(`Company code in AsyncStorage: ${activeCompanyCode || 'empty'}`);
605
669
  if (activeCompanyCode) {
606
- // Update state for future use
670
+ // Update ref and state for future use
671
+ companyCodeRef.current = activeCompanyCode;
607
672
  setCompanyCode(activeCompanyCode);
608
- verboseLog('Updated React state with company code from storage');
673
+ verboseLog('Updated ref and React state with company code from storage');
609
674
  }
610
675
  }
611
676
  return activeCompanyCode;
612
677
  });
613
- // Helper function for verbose logging
678
+ // Helper function for verbose logging (uses ref to avoid stale closures)
614
679
  const verboseLog = (message) => {
615
- if (verboseLogging) {
680
+ if (verboseLoggingRef.current) {
616
681
  console.log(`[Insert Affiliate] [VERBOSE] ${message}`);
617
682
  }
618
683
  };
@@ -630,6 +695,87 @@ const DeepLinkIapProvider = ({ children, }) => {
630
695
  break;
631
696
  }
632
697
  };
698
+ // Reports a new affiliate association to the backend for tracking.
699
+ // Only reports each unique affiliateIdentifier once to prevent duplicates.
700
+ const reportAffiliateAssociationIfNeeded = (affiliateIdentifier, source) => __awaiter(void 0, void 0, void 0, function* () {
701
+ try {
702
+ const activeCompanyCode = yield getActiveCompanyCode();
703
+ if (!activeCompanyCode) {
704
+ verboseLog('Cannot report affiliate association: no company code available');
705
+ return;
706
+ }
707
+ // Get the set of already-reported affiliate identifiers
708
+ const reportedAssociationsJson = yield async_storage_1.default.getItem(ASYNC_KEYS.REPORTED_AFFILIATE_ASSOCIATIONS);
709
+ const reportedAssociations = reportedAssociationsJson ? JSON.parse(reportedAssociationsJson) : [];
710
+ // Check if this affiliate identifier has already been reported
711
+ if (reportedAssociations.includes(affiliateIdentifier)) {
712
+ verboseLog(`Affiliate association already reported for: ${affiliateIdentifier}, skipping`);
713
+ return;
714
+ }
715
+ verboseLog(`Reporting new affiliate association: ${affiliateIdentifier} (source: ${source})`);
716
+ const response = yield fetch('https://api.insertaffiliate.com/V1/onboarding/affiliate-associated', {
717
+ method: 'POST',
718
+ headers: {
719
+ 'Content-Type': 'application/json',
720
+ },
721
+ body: JSON.stringify({
722
+ companyId: activeCompanyCode,
723
+ affiliateIdentifier: affiliateIdentifier,
724
+ source: source,
725
+ timestamp: new Date().toISOString(),
726
+ }),
727
+ });
728
+ if (response.ok) {
729
+ // Add to reported set and persist
730
+ reportedAssociations.push(affiliateIdentifier);
731
+ yield async_storage_1.default.setItem(ASYNC_KEYS.REPORTED_AFFILIATE_ASSOCIATIONS, JSON.stringify(reportedAssociations));
732
+ verboseLog(`Affiliate association reported successfully for: ${affiliateIdentifier}`);
733
+ }
734
+ else {
735
+ verboseLog(`Affiliate association report failed with status: ${response.status}`);
736
+ }
737
+ }
738
+ catch (error) {
739
+ // Silently fail - this is non-critical telemetry
740
+ verboseLog(`Affiliate association report error: ${error}`);
741
+ }
742
+ });
743
+ // Reports SDK initialization to the backend for onboarding verification.
744
+ // Only reports once per install to minimize server load.
745
+ const reportSdkInitIfNeeded = (companyCode, verboseLogging) => __awaiter(void 0, void 0, void 0, function* () {
746
+ try {
747
+ // Only report once per install
748
+ const alreadyReported = yield async_storage_1.default.getItem(ASYNC_KEYS.SDK_INIT_REPORTED);
749
+ if (alreadyReported === 'true') {
750
+ return;
751
+ }
752
+ if (verboseLogging) {
753
+ console.log('[Insert Affiliate] Reporting SDK initialization for onboarding verification...');
754
+ }
755
+ const response = yield fetch('https://api.insertaffiliate.com/V1/onboarding/sdk-init', {
756
+ method: 'POST',
757
+ headers: {
758
+ 'Content-Type': 'application/json',
759
+ },
760
+ body: JSON.stringify({ companyId: companyCode }),
761
+ });
762
+ if (response.ok) {
763
+ yield async_storage_1.default.setItem(ASYNC_KEYS.SDK_INIT_REPORTED, 'true');
764
+ if (verboseLogging) {
765
+ console.log('[Insert Affiliate] SDK initialization reported successfully');
766
+ }
767
+ }
768
+ else if (verboseLogging) {
769
+ console.log(`[Insert Affiliate] SDK initialization report failed with status: ${response.status}`);
770
+ }
771
+ }
772
+ catch (error) {
773
+ // Silently fail - this is non-critical telemetry
774
+ if (verboseLogging) {
775
+ console.log(`[Insert Affiliate] SDK initialization report error: ${error}`);
776
+ }
777
+ }
778
+ });
633
779
  // MARK: - Deep Linking Utilities
634
780
  // Retrieves and validates clipboard content for UUID format
635
781
  const getClipboardUUID = () => __awaiter(void 0, void 0, void 0, function* () {
@@ -995,7 +1141,7 @@ const DeepLinkIapProvider = ({ children, }) => {
995
1141
  if (matchFound && response.data.matched_affiliate_shortCode && response.data.matched_affiliate_shortCode.length > 0) {
996
1142
  const matchedShortCode = response.data.matched_affiliate_shortCode;
997
1143
  verboseLog(`Storing Matched short code from backend: ${matchedShortCode}`);
998
- yield storeInsertAffiliateIdentifier({ link: matchedShortCode });
1144
+ yield storeInsertAffiliateIdentifier({ link: matchedShortCode, source: 'clipboard_match' });
999
1145
  }
1000
1146
  }
1001
1147
  // Check for a successful response
@@ -1059,7 +1205,7 @@ const DeepLinkIapProvider = ({ children, }) => {
1059
1205
  return false;
1060
1206
  }
1061
1207
  });
1062
- const getAffiliateDetails = (affiliateCode) => __awaiter(void 0, void 0, void 0, function* () {
1208
+ const getAffiliateDetailsImpl = (affiliateCode) => __awaiter(void 0, void 0, void 0, function* () {
1063
1209
  try {
1064
1210
  const activeCompanyCode = yield getActiveCompanyCode();
1065
1211
  if (!activeCompanyCode) {
@@ -1098,27 +1244,25 @@ const DeepLinkIapProvider = ({ children, }) => {
1098
1244
  return null;
1099
1245
  }
1100
1246
  });
1101
- function setShortCode(shortCode) {
1102
- return __awaiter(this, void 0, void 0, function* () {
1103
- console.log('[Insert Affiliate] Setting short code.');
1104
- yield generateThenSetUserID();
1105
- // Validate it is a short code
1106
- const capitalisedShortCode = shortCode.toUpperCase();
1107
- isShortCode(capitalisedShortCode);
1108
- // Check if the affiliate exists before storing
1109
- const exists = yield checkAffiliateExists(capitalisedShortCode);
1110
- if (exists) {
1111
- // If affiliate exists, set the Insert Affiliate Identifier
1112
- yield storeInsertAffiliateIdentifier({ link: capitalisedShortCode });
1113
- console.log(`[Insert Affiliate] Short code ${capitalisedShortCode} validated and stored successfully.`);
1114
- return true;
1115
- }
1116
- else {
1117
- console.warn(`[Insert Affiliate] Short code ${capitalisedShortCode} does not exist. Not storing.`);
1118
- return false;
1119
- }
1120
- });
1121
- }
1247
+ const setShortCodeImpl = (shortCode) => __awaiter(void 0, void 0, void 0, function* () {
1248
+ console.log('[Insert Affiliate] Setting short code.');
1249
+ yield generateThenSetUserID();
1250
+ // Validate it is a short code
1251
+ const capitalisedShortCode = shortCode.toUpperCase();
1252
+ isShortCode(capitalisedShortCode);
1253
+ // Check if the affiliate exists before storing
1254
+ const exists = yield checkAffiliateExists(capitalisedShortCode);
1255
+ if (exists) {
1256
+ // If affiliate exists, set the Insert Affiliate Identifier
1257
+ yield storeInsertAffiliateIdentifier({ link: capitalisedShortCode, source: 'short_code_manual' });
1258
+ console.log(`[Insert Affiliate] Short code ${capitalisedShortCode} validated and stored successfully.`);
1259
+ return true;
1260
+ }
1261
+ else {
1262
+ console.warn(`[Insert Affiliate] Short code ${capitalisedShortCode} does not exist. Not storing.`);
1263
+ return false;
1264
+ }
1265
+ });
1122
1266
  function getOrCreateUserAccountToken() {
1123
1267
  return __awaiter(this, void 0, void 0, function* () {
1124
1268
  let userAccountToken = yield getValueFromAsync(ASYNC_KEYS.USER_ACCOUNT_TOKEN);
@@ -1130,9 +1274,9 @@ const DeepLinkIapProvider = ({ children, }) => {
1130
1274
  });
1131
1275
  }
1132
1276
  ;
1133
- const returnUserAccountTokenAndStoreExpectedTransaction = () => __awaiter(void 0, void 0, void 0, function* () {
1277
+ const returnUserAccountTokenAndStoreExpectedTransactionImpl = () => __awaiter(void 0, void 0, void 0, function* () {
1134
1278
  try {
1135
- const shortCode = yield returnInsertAffiliateIdentifier();
1279
+ const shortCode = yield returnInsertAffiliateIdentifierImpl();
1136
1280
  if (!shortCode) {
1137
1281
  console.log('[Insert Affiliate] No affiliate stored - not saving expected transaction.');
1138
1282
  return null;
@@ -1144,7 +1288,7 @@ const DeepLinkIapProvider = ({ children, }) => {
1144
1288
  return null;
1145
1289
  }
1146
1290
  else {
1147
- yield storeExpectedStoreTransaction(userAccountToken);
1291
+ yield storeExpectedStoreTransactionImpl(userAccountToken);
1148
1292
  return userAccountToken;
1149
1293
  }
1150
1294
  }
@@ -1164,12 +1308,12 @@ const DeepLinkIapProvider = ({ children, }) => {
1164
1308
  ;
1165
1309
  });
1166
1310
  // MARK: Return Insert Affiliate Identifier
1167
- const returnInsertAffiliateIdentifier = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (ignoreTimeout = false) {
1311
+ const returnInsertAffiliateIdentifierImpl = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (ignoreTimeout = false) {
1168
1312
  try {
1169
1313
  verboseLog(`Getting insert affiliate identifier (ignoreTimeout: ${ignoreTimeout})...`);
1170
1314
  // If timeout is enabled and we're not ignoring it, check validity first
1171
1315
  if (!ignoreTimeout && affiliateAttributionActiveTime) {
1172
- const isValid = yield isAffiliateAttributionValid();
1316
+ const isValid = yield isAffiliateAttributionValidImpl();
1173
1317
  if (!isValid) {
1174
1318
  verboseLog('Attribution has expired, returning null');
1175
1319
  return null;
@@ -1209,14 +1353,14 @@ const DeepLinkIapProvider = ({ children, }) => {
1209
1353
  });
1210
1354
  // MARK: Attribution Timeout Functions
1211
1355
  // Check if the current affiliate attribution is still valid based on timeout
1212
- const isAffiliateAttributionValid = () => __awaiter(void 0, void 0, void 0, function* () {
1356
+ const isAffiliateAttributionValidImpl = () => __awaiter(void 0, void 0, void 0, function* () {
1213
1357
  try {
1214
1358
  // If no timeout is set, attribution is always valid
1215
1359
  if (!affiliateAttributionActiveTime) {
1216
1360
  verboseLog('No attribution timeout set, attribution is valid');
1217
1361
  return true;
1218
1362
  }
1219
- const storedDate = yield getAffiliateStoredDate();
1363
+ const storedDate = yield getAffiliateStoredDateImpl();
1220
1364
  if (!storedDate) {
1221
1365
  verboseLog('No stored date found, attribution is invalid');
1222
1366
  return false;
@@ -1233,7 +1377,7 @@ const DeepLinkIapProvider = ({ children, }) => {
1233
1377
  }
1234
1378
  });
1235
1379
  // Get the date when the affiliate identifier was stored
1236
- const getAffiliateStoredDate = () => __awaiter(void 0, void 0, void 0, function* () {
1380
+ const getAffiliateStoredDateImpl = () => __awaiter(void 0, void 0, void 0, function* () {
1237
1381
  try {
1238
1382
  const storedDateString = yield getValueFromAsync(ASYNC_KEYS.AFFILIATE_STORED_DATE);
1239
1383
  if (!storedDateString) {
@@ -1250,87 +1394,84 @@ const DeepLinkIapProvider = ({ children, }) => {
1250
1394
  }
1251
1395
  });
1252
1396
  // MARK: Insert Affiliate Identifier
1253
- function setInsertAffiliateIdentifier(referringLink) {
1254
- return __awaiter(this, void 0, void 0, function* () {
1255
- console.log('[Insert Affiliate] Setting affiliate identifier.');
1256
- verboseLog(`Input referringLink: ${referringLink}`);
1257
- try {
1258
- verboseLog('Generating or retrieving user ID...');
1259
- const customerID = yield generateThenSetUserID();
1260
- console.log('[Insert Affiliate] Completed generateThenSetUserID within setInsertAffiliateIdentifier.');
1261
- verboseLog(`Customer ID: ${customerID}`);
1262
- if (!referringLink) {
1263
- console.warn('[Insert Affiliate] Referring link is invalid.');
1264
- verboseLog('Referring link is empty or invalid, storing as-is');
1265
- yield storeInsertAffiliateIdentifier({ link: referringLink });
1266
- return `${referringLink}-${customerID}`;
1267
- }
1268
- // Get company code from state or storage
1269
- verboseLog('Getting company code...');
1270
- const activeCompanyCode = yield getActiveCompanyCode();
1271
- verboseLog(`Active company code: ${activeCompanyCode || 'Not found'}`);
1272
- if (!activeCompanyCode) {
1273
- console.error('[Insert Affiliate] Company code is not set. Please initialize the SDK with a valid company code.');
1274
- verboseLog('Company code missing, cannot proceed with API call');
1275
- return;
1276
- }
1277
- // Check if referring link is already a short code, if so save it and stop here.
1278
- verboseLog('Checking if referring link is already a short code...');
1279
- if (isShortCode(referringLink)) {
1280
- console.log('[Insert Affiliate] Referring link is already a short code.');
1281
- verboseLog('Link is already a short code, storing directly');
1282
- yield storeInsertAffiliateIdentifier({ link: referringLink });
1283
- return `${referringLink}-${customerID}`;
1284
- }
1285
- verboseLog('Link is not a short code, will convert via API');
1286
- // If the code is not already a short code, encode it raedy to send to our endpoint to return the short code. Save it before making the call in case something goes wrong
1287
- // Encode the referring link
1288
- verboseLog('Encoding referring link for API call...');
1289
- const encodedAffiliateLink = encodeURIComponent(referringLink);
1290
- if (!encodedAffiliateLink) {
1291
- console.error('[Insert Affiliate] Failed to encode affiliate link.');
1292
- verboseLog('Failed to encode link, storing original');
1293
- yield storeInsertAffiliateIdentifier({ link: referringLink });
1294
- return `${referringLink}-${customerID}`;
1295
- }
1296
- // Create the request URL
1297
- const urlString = `https://api.insertaffiliate.com/V1/convert-deep-link-to-short-link?companyId=${activeCompanyCode}&deepLinkUrl=${encodedAffiliateLink}`;
1298
- console.log('[Insert Affiliate] urlString .', urlString);
1299
- verboseLog('Making API request to convert deep link to short code...');
1300
- const response = yield axios_1.default.get(urlString, {
1301
- headers: {
1302
- 'Content-Type': 'application/json',
1303
- },
1304
- });
1305
- verboseLog(`API response status: ${response.status}`);
1306
- // Call to the backend for the short code and save the resolse in valid
1307
- if (response.status === 200 && response.data.shortLink) {
1308
- const shortLink = response.data.shortLink;
1309
- console.log('[Insert Affiliate] Short link received:', shortLink);
1310
- verboseLog(`Successfully converted to short link: ${shortLink}`);
1311
- verboseLog('Storing short link to AsyncStorage...');
1312
- yield storeInsertAffiliateIdentifier({ link: shortLink });
1313
- verboseLog('Short link stored successfully');
1314
- return `${shortLink}-${customerID}`;
1315
- }
1316
- else {
1317
- console.warn('[Insert Affiliate] Unexpected response format.');
1318
- verboseLog(`Unexpected API response. Status: ${response.status}, Data: ${JSON.stringify(response.data)}`);
1319
- verboseLog('Storing original link as fallback');
1320
- yield storeInsertAffiliateIdentifier({ link: referringLink });
1321
- return `${referringLink}-${customerID}`;
1322
- }
1397
+ const setInsertAffiliateIdentifierImpl = (referringLink) => __awaiter(void 0, void 0, void 0, function* () {
1398
+ console.log('[Insert Affiliate] Setting affiliate identifier.');
1399
+ verboseLog(`Input referringLink: ${referringLink}`);
1400
+ try {
1401
+ verboseLog('Generating or retrieving user ID...');
1402
+ const customerID = yield generateThenSetUserID();
1403
+ console.log('[Insert Affiliate] Completed generateThenSetUserID within setInsertAffiliateIdentifier.');
1404
+ verboseLog(`Customer ID: ${customerID}`);
1405
+ if (!referringLink) {
1406
+ console.warn('[Insert Affiliate] Referring link is invalid.');
1407
+ verboseLog('Referring link is empty or invalid, storing as-is');
1408
+ yield storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
1409
+ return `${referringLink}-${customerID}`;
1410
+ }
1411
+ // Get company code from state or storage
1412
+ verboseLog('Getting company code...');
1413
+ const activeCompanyCode = yield getActiveCompanyCode();
1414
+ verboseLog(`Active company code: ${activeCompanyCode || 'Not found'}`);
1415
+ if (!activeCompanyCode) {
1416
+ console.error('[Insert Affiliate] Company code is not set. Please initialize the SDK with a valid company code.');
1417
+ verboseLog('Company code missing, cannot proceed with API call');
1418
+ return;
1323
1419
  }
1324
- catch (error) {
1325
- console.error('[Insert Affiliate] Error:', error);
1326
- verboseLog(`Error in setInsertAffiliateIdentifier: ${error}`);
1420
+ // Check if referring link is already a short code, if so save it and stop here.
1421
+ verboseLog('Checking if referring link is already a short code...');
1422
+ if (isShortCode(referringLink)) {
1423
+ console.log('[Insert Affiliate] Referring link is already a short code.');
1424
+ verboseLog('Link is already a short code, storing directly');
1425
+ yield storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
1426
+ return `${referringLink}-${customerID}`;
1427
+ }
1428
+ verboseLog('Link is not a short code, will convert via API');
1429
+ // If the code is not already a short code, encode it raedy to send to our endpoint to return the short code. Save it before making the call in case something goes wrong
1430
+ // Encode the referring link
1431
+ verboseLog('Encoding referring link for API call...');
1432
+ const encodedAffiliateLink = encodeURIComponent(referringLink);
1433
+ if (!encodedAffiliateLink) {
1434
+ console.error('[Insert Affiliate] Failed to encode affiliate link.');
1435
+ verboseLog('Failed to encode link, storing original');
1436
+ yield storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
1437
+ return `${referringLink}-${customerID}`;
1438
+ }
1439
+ // Create the request URL
1440
+ const urlString = `https://api.insertaffiliate.com/V1/convert-deep-link-to-short-link?companyId=${activeCompanyCode}&deepLinkUrl=${encodedAffiliateLink}`;
1441
+ console.log('[Insert Affiliate] urlString .', urlString);
1442
+ verboseLog('Making API request to convert deep link to short code...');
1443
+ const response = yield axios_1.default.get(urlString, {
1444
+ headers: {
1445
+ 'Content-Type': 'application/json',
1446
+ },
1447
+ });
1448
+ verboseLog(`API response status: ${response.status}`);
1449
+ // Call to the backend for the short code and save the resolse in valid
1450
+ if (response.status === 200 && response.data.shortLink) {
1451
+ const shortLink = response.data.shortLink;
1452
+ console.log('[Insert Affiliate] Short link received:', shortLink);
1453
+ verboseLog(`Successfully converted to short link: ${shortLink}`);
1454
+ verboseLog('Storing short link to AsyncStorage...');
1455
+ yield storeInsertAffiliateIdentifier({ link: shortLink, source: 'referring_link' });
1456
+ verboseLog('Short link stored successfully');
1457
+ return `${shortLink}-${customerID}`;
1327
1458
  }
1328
- });
1329
- }
1330
- ;
1459
+ else {
1460
+ console.warn('[Insert Affiliate] Unexpected response format.');
1461
+ verboseLog(`Unexpected API response. Status: ${response.status}, Data: ${JSON.stringify(response.data)}`);
1462
+ verboseLog('Storing original link as fallback');
1463
+ yield storeInsertAffiliateIdentifier({ link: referringLink, source: 'referring_link' });
1464
+ return `${referringLink}-${customerID}`;
1465
+ }
1466
+ }
1467
+ catch (error) {
1468
+ console.error('[Insert Affiliate] Error:', error);
1469
+ verboseLog(`Error in setInsertAffiliateIdentifier: ${error}`);
1470
+ }
1471
+ });
1331
1472
  function storeInsertAffiliateIdentifier(_a) {
1332
- return __awaiter(this, arguments, void 0, function* ({ link }) {
1333
- console.log(`[Insert Affiliate] Storing affiliate identifier: ${link}`);
1473
+ return __awaiter(this, arguments, void 0, function* ({ link, source }) {
1474
+ console.log(`[Insert Affiliate] Storing affiliate identifier: ${link} (source: ${source})`);
1334
1475
  // Check if we're trying to store the same link (prevent duplicate storage)
1335
1476
  const existingLink = yield getValueFromAsync(ASYNC_KEYS.REFERRER_LINK);
1336
1477
  if (existingLink === link) {
@@ -1351,13 +1492,18 @@ const DeepLinkIapProvider = ({ children, }) => {
1351
1492
  yield retrieveAndStoreOfferCode(link);
1352
1493
  // Trigger callback with the current affiliate identifier
1353
1494
  if (insertAffiliateIdentifierChangeCallbackRef.current) {
1354
- const currentIdentifier = yield returnInsertAffiliateIdentifier();
1495
+ const currentIdentifier = yield returnInsertAffiliateIdentifierImpl();
1355
1496
  verboseLog(`Triggering callback with identifier: ${currentIdentifier}`);
1356
1497
  insertAffiliateIdentifierChangeCallbackRef.current(currentIdentifier);
1357
1498
  }
1499
+ // Report this new affiliate association to the backend (fire and forget)
1500
+ const fullIdentifier = yield returnInsertAffiliateIdentifierImpl();
1501
+ if (fullIdentifier) {
1502
+ reportAffiliateAssociationIfNeeded(fullIdentifier, source);
1503
+ }
1358
1504
  });
1359
1505
  }
1360
- const validatePurchaseWithIapticAPI = (jsonIapPurchase, iapticAppId, iapticAppName, iapticPublicKey) => __awaiter(void 0, void 0, void 0, function* () {
1506
+ const validatePurchaseWithIapticAPIImpl = (jsonIapPurchase, iapticAppId, iapticAppName, iapticPublicKey) => __awaiter(void 0, void 0, void 0, function* () {
1361
1507
  var _a;
1362
1508
  try {
1363
1509
  // Check for E_IAP_NOT_AVAILABLE error in development environment
@@ -1396,7 +1542,7 @@ const DeepLinkIapProvider = ({ children, }) => {
1396
1542
  };
1397
1543
  }
1398
1544
  const requestBody = Object.assign(Object.assign({}, baseRequestBody), { transaction });
1399
- let insertAffiliateIdentifier = yield returnInsertAffiliateIdentifier();
1545
+ let insertAffiliateIdentifier = yield returnInsertAffiliateIdentifierImpl();
1400
1546
  if (insertAffiliateIdentifier) {
1401
1547
  requestBody.additionalData = {
1402
1548
  applicationUsername: `${insertAffiliateIdentifier}`,
@@ -1443,7 +1589,7 @@ const DeepLinkIapProvider = ({ children, }) => {
1443
1589
  return false;
1444
1590
  }
1445
1591
  });
1446
- const storeExpectedStoreTransaction = (purchaseToken) => __awaiter(void 0, void 0, void 0, function* () {
1592
+ const storeExpectedStoreTransactionImpl = (purchaseToken) => __awaiter(void 0, void 0, void 0, function* () {
1447
1593
  verboseLog(`Storing expected store transaction with token: ${purchaseToken}`);
1448
1594
  const activeCompanyCode = yield getActiveCompanyCode();
1449
1595
  if (!activeCompanyCode) {
@@ -1451,7 +1597,7 @@ const DeepLinkIapProvider = ({ children, }) => {
1451
1597
  verboseLog("Cannot store transaction: no company code available");
1452
1598
  return;
1453
1599
  }
1454
- const shortCode = yield returnInsertAffiliateIdentifier();
1600
+ const shortCode = yield returnInsertAffiliateIdentifierImpl();
1455
1601
  if (!shortCode) {
1456
1602
  console.error("[Insert Affiliate] No affiliate identifier found. Please set one before tracking events.");
1457
1603
  verboseLog("Cannot store transaction: no affiliate identifier available");
@@ -1492,7 +1638,7 @@ const DeepLinkIapProvider = ({ children, }) => {
1492
1638
  }
1493
1639
  });
1494
1640
  // MARK: Track Event
1495
- const trackEvent = (eventName) => __awaiter(void 0, void 0, void 0, function* () {
1641
+ const trackEventImpl = (eventName) => __awaiter(void 0, void 0, void 0, function* () {
1496
1642
  try {
1497
1643
  verboseLog(`Tracking event: ${eventName}`);
1498
1644
  const activeCompanyCode = yield getActiveCompanyCode();
@@ -1613,6 +1759,64 @@ const DeepLinkIapProvider = ({ children, }) => {
1613
1759
  // Remove special characters, keep only alphanumeric
1614
1760
  return removeSpecialCharacters(offerCode);
1615
1761
  };
1762
+ // ============================================================================
1763
+ // REF CALLBACK PATTERN: Update refs on every render for fresh closures
1764
+ // ============================================================================
1765
+ initializeImplRef.current = initializeImpl;
1766
+ setShortCodeImplRef.current = setShortCodeImpl;
1767
+ getAffiliateDetailsImplRef.current = getAffiliateDetailsImpl;
1768
+ returnInsertAffiliateIdentifierImplRef.current = returnInsertAffiliateIdentifierImpl;
1769
+ isAffiliateAttributionValidImplRef.current = isAffiliateAttributionValidImpl;
1770
+ getAffiliateStoredDateImplRef.current = getAffiliateStoredDateImpl;
1771
+ storeExpectedStoreTransactionImplRef.current = storeExpectedStoreTransactionImpl;
1772
+ returnUserAccountTokenAndStoreExpectedTransactionImplRef.current = returnUserAccountTokenAndStoreExpectedTransactionImpl;
1773
+ validatePurchaseWithIapticAPIImplRef.current = validatePurchaseWithIapticAPIImpl;
1774
+ trackEventImplRef.current = trackEventImpl;
1775
+ setInsertAffiliateIdentifierImplRef.current = setInsertAffiliateIdentifierImpl;
1776
+ handleInsertLinksImplRef.current = handleInsertLinksImpl;
1777
+ // ============================================================================
1778
+ // STABLE WRAPPERS: useCallback with [] deps that delegate to refs
1779
+ // These provide stable function references that always call current implementations
1780
+ // ============================================================================
1781
+ const initialize = (0, react_1.useCallback)((code, verboseLogging, insertLinksEnabled, insertLinksClipboardEnabled, affiliateAttributionActiveTime) => __awaiter(void 0, void 0, void 0, function* () {
1782
+ return initializeImplRef.current(code, verboseLogging, insertLinksEnabled, insertLinksClipboardEnabled, affiliateAttributionActiveTime);
1783
+ }), []);
1784
+ const setShortCode = (0, react_1.useCallback)((shortCode) => __awaiter(void 0, void 0, void 0, function* () {
1785
+ return setShortCodeImplRef.current(shortCode);
1786
+ }), []);
1787
+ const getAffiliateDetails = (0, react_1.useCallback)((affiliateCode) => __awaiter(void 0, void 0, void 0, function* () {
1788
+ return getAffiliateDetailsImplRef.current(affiliateCode);
1789
+ }), []);
1790
+ const returnInsertAffiliateIdentifier = (0, react_1.useCallback)((ignoreTimeout) => __awaiter(void 0, void 0, void 0, function* () {
1791
+ return returnInsertAffiliateIdentifierImplRef.current(ignoreTimeout);
1792
+ }), []);
1793
+ const isAffiliateAttributionValid = (0, react_1.useCallback)(() => __awaiter(void 0, void 0, void 0, function* () {
1794
+ return isAffiliateAttributionValidImplRef.current();
1795
+ }), []);
1796
+ const getAffiliateStoredDate = (0, react_1.useCallback)(() => __awaiter(void 0, void 0, void 0, function* () {
1797
+ return getAffiliateStoredDateImplRef.current();
1798
+ }), []);
1799
+ const storeExpectedStoreTransaction = (0, react_1.useCallback)((purchaseToken) => __awaiter(void 0, void 0, void 0, function* () {
1800
+ return storeExpectedStoreTransactionImplRef.current(purchaseToken);
1801
+ }), []);
1802
+ const returnUserAccountTokenAndStoreExpectedTransaction = (0, react_1.useCallback)(() => __awaiter(void 0, void 0, void 0, function* () {
1803
+ return returnUserAccountTokenAndStoreExpectedTransactionImplRef.current();
1804
+ }), []);
1805
+ const validatePurchaseWithIapticAPI = (0, react_1.useCallback)((jsonIapPurchase, iapticAppId, iapticAppName, iapticPublicKey) => __awaiter(void 0, void 0, void 0, function* () {
1806
+ return validatePurchaseWithIapticAPIImplRef.current(jsonIapPurchase, iapticAppId, iapticAppName, iapticPublicKey);
1807
+ }), []);
1808
+ const trackEvent = (0, react_1.useCallback)((eventName) => __awaiter(void 0, void 0, void 0, function* () {
1809
+ return trackEventImplRef.current(eventName);
1810
+ }), []);
1811
+ const setInsertAffiliateIdentifier = (0, react_1.useCallback)((referringLink) => __awaiter(void 0, void 0, void 0, function* () {
1812
+ return setInsertAffiliateIdentifierImplRef.current(referringLink);
1813
+ }), []);
1814
+ const setInsertAffiliateIdentifierChangeCallbackHandler = (0, react_1.useCallback)((callback) => {
1815
+ insertAffiliateIdentifierChangeCallbackRef.current = callback;
1816
+ }, []);
1817
+ const handleInsertLinks = (0, react_1.useCallback)((url) => __awaiter(void 0, void 0, void 0, function* () {
1818
+ return handleInsertLinksImplRef.current(url);
1819
+ }), []);
1616
1820
  return (react_1.default.createElement(exports.DeepLinkIapContext.Provider, { value: {
1617
1821
  referrerLink,
1618
1822
  userId,