insert-affiliate-react-native-sdk 1.6.2 → 1.6.5

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.
@@ -40,6 +40,10 @@ const react_1 = __importStar(require("react"));
40
40
  const react_native_1 = require("react-native");
41
41
  const axios_1 = __importDefault(require("axios"));
42
42
  const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
43
+ const clipboard_1 = __importDefault(require("@react-native-clipboard/clipboard"));
44
+ const netinfo_1 = __importDefault(require("@react-native-community/netinfo"));
45
+ const react_native_device_info_1 = __importDefault(require("react-native-device-info"));
46
+ const react_native_play_install_referrer_1 = require("react-native-play-install-referrer");
43
47
  const ASYNC_KEYS = {
44
48
  REFERRER_LINK: '@app_referrer_link',
45
49
  USER_PURCHASE: '@app_user_purchase',
@@ -60,7 +64,9 @@ exports.DeepLinkIapContext = (0, react_1.createContext)({
60
64
  trackEvent: (eventName) => __awaiter(void 0, void 0, void 0, function* () { }),
61
65
  setShortCode: (shortCode) => __awaiter(void 0, void 0, void 0, function* () { }),
62
66
  setInsertAffiliateIdentifier: (referringLink) => __awaiter(void 0, void 0, void 0, function* () { }),
63
- initialize: (code, verboseLogging) => __awaiter(void 0, void 0, void 0, function* () { }),
67
+ setInsertAffiliateIdentifierChangeCallback: (callback) => { },
68
+ handleInsertLinks: (url) => __awaiter(void 0, void 0, void 0, function* () { return false; }),
69
+ initialize: (code, verboseLogging, insertLinksEnabled, insertLinksClipboardEnabled) => __awaiter(void 0, void 0, void 0, function* () { }),
64
70
  isInitialized: false,
65
71
  });
66
72
  const DeepLinkIapProvider = ({ children, }) => {
@@ -69,10 +75,15 @@ const DeepLinkIapProvider = ({ children, }) => {
69
75
  const [companyCode, setCompanyCode] = (0, react_1.useState)(null);
70
76
  const [isInitialized, setIsInitialized] = (0, react_1.useState)(false);
71
77
  const [verboseLogging, setVerboseLogging] = (0, react_1.useState)(false);
78
+ const [insertLinksEnabled, setInsertLinksEnabled] = (0, react_1.useState)(false);
79
+ const [insertLinksClipboardEnabled, setInsertLinksClipboardEnabled] = (0, react_1.useState)(false);
72
80
  const [OfferCode, setOfferCode] = (0, react_1.useState)(null);
81
+ const insertAffiliateIdentifierChangeCallbackRef = (0, react_1.useRef)(null);
73
82
  // MARK: Initialize the SDK
74
- const initialize = (companyCode_1, ...args_1) => __awaiter(void 0, [companyCode_1, ...args_1], void 0, function* (companyCode, verboseLogging = false) {
83
+ const initialize = (companyCode_1, ...args_1) => __awaiter(void 0, [companyCode_1, ...args_1], void 0, function* (companyCode, verboseLogging = false, insertLinksEnabled = false, insertLinksClipboardEnabled = false) {
75
84
  setVerboseLogging(verboseLogging);
85
+ setInsertLinksEnabled(insertLinksEnabled);
86
+ setInsertLinksClipboardEnabled(insertLinksClipboardEnabled);
76
87
  if (verboseLogging) {
77
88
  console.log('[Insert Affiliate] [VERBOSE] Starting SDK initialization...');
78
89
  console.log('[Insert Affiliate] [VERBOSE] Company code provided:', companyCode ? 'Yes' : 'No');
@@ -99,6 +110,15 @@ const DeepLinkIapProvider = ({ children, }) => {
99
110
  console.log('[Insert Affiliate] [VERBOSE] No company code provided, SDK initialized in limited mode');
100
111
  }
101
112
  }
113
+ if (insertLinksEnabled && react_native_1.Platform.OS === 'ios') {
114
+ try {
115
+ const enhancedSystemInfo = yield getEnhancedSystemInfo();
116
+ yield sendSystemInfoToBackend(enhancedSystemInfo);
117
+ }
118
+ catch (error) {
119
+ verboseLog(`Error sending system info for clipboard check: ${error}`);
120
+ }
121
+ }
102
122
  });
103
123
  // EFFECT TO FETCH USER ID AND REF LINK
104
124
  // IF ALREADY EXISTS IN ASYNC STORAGE
@@ -135,6 +155,93 @@ const DeepLinkIapProvider = ({ children, }) => {
135
155
  });
136
156
  fetchAsyncEssentials();
137
157
  }, []);
158
+ // Cleanup callback on unmount
159
+ (0, react_1.useEffect)(() => {
160
+ return () => {
161
+ insertAffiliateIdentifierChangeCallbackRef.current = null;
162
+ };
163
+ }, []);
164
+ // Deep link event listeners - equivalent to iOS AppDelegate methods
165
+ (0, react_1.useEffect)(() => {
166
+ if (!isInitialized)
167
+ return;
168
+ // Handle app launch with URL (equivalent to didFinishLaunchingWithOptions)
169
+ const handleInitialURL = () => __awaiter(void 0, void 0, void 0, function* () {
170
+ try {
171
+ const initialUrl = yield react_native_1.Linking.getInitialURL();
172
+ if (initialUrl) {
173
+ verboseLog(`App launched with URL: ${initialUrl}`);
174
+ const handled = yield handleDeepLink(initialUrl);
175
+ if (handled) {
176
+ verboseLog('URL was handled by Insert Affiliate SDK');
177
+ }
178
+ else {
179
+ verboseLog('URL was not handled by Insert Affiliate SDK');
180
+ }
181
+ }
182
+ }
183
+ catch (error) {
184
+ console.error('[Insert Affiliate] Error getting initial URL:', error);
185
+ }
186
+ });
187
+ // Handle URL opening while app is running (equivalent to open url)
188
+ const handleUrlChange = (event) => __awaiter(void 0, void 0, void 0, function* () {
189
+ try {
190
+ verboseLog(`URL opened while app running: ${event.url}`);
191
+ const handled = yield handleDeepLink(event.url);
192
+ if (handled) {
193
+ verboseLog('URL was handled by Insert Affiliate SDK');
194
+ }
195
+ else {
196
+ verboseLog('URL was not handled by Insert Affiliate SDK');
197
+ }
198
+ }
199
+ catch (error) {
200
+ console.error('[Insert Affiliate] Error handling URL change:', error);
201
+ }
202
+ });
203
+ // Platform-specific deep link handler
204
+ const handleDeepLink = (url) => __awaiter(void 0, void 0, void 0, function* () {
205
+ try {
206
+ verboseLog(`Platform detection: Platform.OS = ${react_native_1.Platform.OS}`);
207
+ if (react_native_1.Platform.OS === 'ios') {
208
+ verboseLog('Routing to iOS handler (handleInsertLinks)');
209
+ return yield handleInsertLinks(url);
210
+ }
211
+ else if (react_native_1.Platform.OS === 'android') {
212
+ verboseLog('Routing to Android handler (handleInsertLinkAndroid)');
213
+ return yield handleInsertLinkAndroid(url);
214
+ }
215
+ verboseLog(`Unrecognized platform: ${react_native_1.Platform.OS}`);
216
+ return false;
217
+ }
218
+ catch (error) {
219
+ verboseLog(`Error handling deep link: ${error}`);
220
+ return false;
221
+ }
222
+ });
223
+ // Set up listeners
224
+ const urlListener = react_native_1.Linking.addEventListener('url', handleUrlChange);
225
+ // Handle initial URL
226
+ handleInitialURL();
227
+ // Cleanup
228
+ return () => {
229
+ urlListener === null || urlListener === void 0 ? void 0 : urlListener.remove();
230
+ };
231
+ }, [isInitialized]);
232
+ // EFFECT TO HANDLE INSTALL REFERRER ON ANDROID
233
+ (0, react_1.useEffect)(() => {
234
+ if (react_native_1.Platform.OS === 'android' && isInitialized && insertLinksEnabled) {
235
+ verboseLog('Install referrer effect - Platform.OS is android, isInitialized is true, and insertLinksEnabled is true');
236
+ // Ensure user ID is generated before processing install referrer
237
+ const initializeAndCapture = () => __awaiter(void 0, void 0, void 0, function* () {
238
+ yield generateThenSetUserID();
239
+ verboseLog('Install referrer effect - Generating user ID and capturing install referrer');
240
+ captureInstallReferrer();
241
+ });
242
+ initializeAndCapture();
243
+ }
244
+ }, [isInitialized, insertLinksEnabled]);
138
245
  function generateThenSetUserID() {
139
246
  return __awaiter(this, void 0, void 0, function* () {
140
247
  verboseLog('Getting or generating user ID...');
@@ -167,6 +274,304 @@ const DeepLinkIapProvider = ({ children, }) => {
167
274
  setIsInitialized(false);
168
275
  console.log('[Insert Affiliate] SDK has been reset.');
169
276
  };
277
+ // MARK: Callback Management
278
+ // Sets a callback that will be triggered whenever storeInsertAffiliateIdentifier is called
279
+ // The callback receives the current affiliate identifier (returnInsertAffiliateIdentifier result)
280
+ const setInsertAffiliateIdentifierChangeCallbackHandler = (callback) => {
281
+ insertAffiliateIdentifierChangeCallbackRef.current = callback;
282
+ };
283
+ // MARK: Deep Link Handling
284
+ // Helper function to parse URLs in React Native compatible way
285
+ const parseURL = (url) => {
286
+ try {
287
+ // Extract protocol
288
+ const protocolMatch = url.match(/^([^:]+):/);
289
+ const protocol = protocolMatch ? protocolMatch[1] + ':' : '';
290
+ // Extract hostname for https URLs
291
+ let hostname = '';
292
+ if (protocol === 'https:' || protocol === 'http:') {
293
+ const hostnameMatch = url.match(/^https?:\/\/([^\/]+)/);
294
+ hostname = hostnameMatch ? hostnameMatch[1] : '';
295
+ }
296
+ return {
297
+ protocol,
298
+ hostname,
299
+ href: url
300
+ };
301
+ }
302
+ catch (error) {
303
+ return {
304
+ protocol: '',
305
+ hostname: '',
306
+ href: url
307
+ };
308
+ }
309
+ };
310
+ // Handles Android deep links with insertAffiliate parameter
311
+ const handleInsertLinkAndroid = (url) => __awaiter(void 0, void 0, void 0, function* () {
312
+ try {
313
+ // Check if deep links are enabled
314
+ if (!insertLinksEnabled) {
315
+ verboseLog('Deep links are disabled, not handling Android URL');
316
+ return false;
317
+ }
318
+ verboseLog(`Processing Android deep link: ${url}`);
319
+ if (!url || typeof url !== 'string') {
320
+ verboseLog('Invalid URL provided to handleInsertLinkAndroid');
321
+ return false;
322
+ }
323
+ // Parse the URL to extract query parameters
324
+ const urlObj = new URL(url);
325
+ const insertAffiliate = urlObj.searchParams.get('insertAffiliate');
326
+ if (insertAffiliate && insertAffiliate.length > 0) {
327
+ verboseLog(`Found insertAffiliate parameter: ${insertAffiliate}`);
328
+ yield storeInsertAffiliateIdentifier({ link: insertAffiliate });
329
+ return true;
330
+ }
331
+ else {
332
+ verboseLog('No insertAffiliate parameter found in Android deep link');
333
+ return false;
334
+ }
335
+ }
336
+ catch (error) {
337
+ verboseLog(`Error handling Android deep link: ${error}`);
338
+ return false;
339
+ }
340
+ });
341
+ // MARK: Play Install Referrer
342
+ /**
343
+ * Captures install referrer data from Google Play Store
344
+ * This method automatically extracts referral parameters and processes them
345
+ */
346
+ const captureInstallReferrer = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (retryCount = 0) {
347
+ try {
348
+ // Check if deep links are enabled
349
+ if (!insertLinksEnabled) {
350
+ verboseLog('Deep links are disabled, not processing install referrer');
351
+ return false;
352
+ }
353
+ // Check if we're on Android
354
+ if (react_native_1.Platform.OS !== 'android') {
355
+ verboseLog('Install referrer is only available on Android');
356
+ return false;
357
+ }
358
+ verboseLog(`Starting install referrer capture... (attempt ${retryCount + 1})`);
359
+ // Convert callback-based API to Promise with timeout
360
+ const referrerData = yield new Promise((resolve, reject) => {
361
+ const timeout = setTimeout(() => {
362
+ reject(new Error('Install referrer request timed out'));
363
+ }, 10000); // 10 second timeout
364
+ react_native_play_install_referrer_1.PlayInstallReferrer.getInstallReferrerInfo((info, error) => {
365
+ clearTimeout(timeout);
366
+ if (error) {
367
+ reject(error);
368
+ }
369
+ else {
370
+ resolve(info);
371
+ }
372
+ });
373
+ });
374
+ if (referrerData && referrerData.installReferrer) {
375
+ verboseLog(`Raw install referrer data: ${referrerData.installReferrer}`);
376
+ const success = yield processInstallReferrerData(referrerData.installReferrer);
377
+ if (success) {
378
+ verboseLog('Install referrer processed successfully');
379
+ return true;
380
+ }
381
+ else {
382
+ verboseLog('No insertAffiliate parameter found in install referrer');
383
+ return false;
384
+ }
385
+ }
386
+ else {
387
+ verboseLog('No install referrer data found');
388
+ return false;
389
+ }
390
+ }
391
+ catch (error) {
392
+ const errorMessage = error instanceof Error ? error.message : String(error);
393
+ verboseLog(`Error capturing install referrer (attempt ${retryCount + 1}): ${errorMessage}`);
394
+ // Check if this is a retryable error and we haven't exceeded max retries
395
+ const isRetryableError = errorMessage.includes('SERVICE_UNAVAILABLE') ||
396
+ errorMessage.includes('DEVELOPER_ERROR') ||
397
+ errorMessage.includes('timed out') ||
398
+ errorMessage.includes('SERVICE_DISCONNECTED');
399
+ const maxRetries = 3;
400
+ const retryDelay = Math.min(1000 * Math.pow(2, retryCount), 10000); // Exponential backoff, max 10s
401
+ if (isRetryableError && retryCount < maxRetries) {
402
+ verboseLog(`Retrying install referrer capture in ${retryDelay}ms...`);
403
+ // Schedule retry
404
+ setTimeout(() => {
405
+ captureInstallReferrer(retryCount + 1);
406
+ }, retryDelay);
407
+ return false;
408
+ }
409
+ else {
410
+ verboseLog(`Install referrer capture failed after ${retryCount + 1} attempts`);
411
+ return false;
412
+ }
413
+ }
414
+ });
415
+ /**
416
+ * Processes the raw install referrer data and extracts insertAffiliate parameter
417
+ * @param rawReferrer The raw referrer string from Play Store
418
+ */
419
+ const processInstallReferrerData = (rawReferrer) => __awaiter(void 0, void 0, void 0, function* () {
420
+ try {
421
+ verboseLog('Processing install referrer data...');
422
+ if (!rawReferrer || rawReferrer.length === 0) {
423
+ verboseLog('No referrer data provided');
424
+ return false;
425
+ }
426
+ verboseLog(`Raw referrer data: ${rawReferrer}`);
427
+ // Parse the referrer string directly for insertAffiliate parameter
428
+ let insertAffiliate = null;
429
+ if (rawReferrer.includes('insertAffiliate=')) {
430
+ const params = rawReferrer.split('&');
431
+ for (const param of params) {
432
+ if (param.startsWith('insertAffiliate=')) {
433
+ insertAffiliate = param.substring('insertAffiliate='.length);
434
+ break;
435
+ }
436
+ }
437
+ }
438
+ verboseLog(`Extracted insertAffiliate parameter: ${insertAffiliate}`);
439
+ // If we have insertAffiliate parameter, use it as the affiliate identifier
440
+ if (insertAffiliate && insertAffiliate.length > 0) {
441
+ verboseLog(`Found insertAffiliate parameter, setting as affiliate identifier: ${insertAffiliate}`);
442
+ yield storeInsertAffiliateIdentifier({ link: insertAffiliate });
443
+ return true;
444
+ }
445
+ else {
446
+ verboseLog('No insertAffiliate parameter found in referrer data');
447
+ return false;
448
+ }
449
+ }
450
+ catch (error) {
451
+ verboseLog(`Error processing install referrer data: ${error}`);
452
+ return false;
453
+ }
454
+ });
455
+ // Handles Insert Links deep linking - equivalent to iOS handleInsertLinks
456
+ const handleInsertLinks = (url) => __awaiter(void 0, void 0, void 0, function* () {
457
+ try {
458
+ console.log(`[Insert Affiliate] Attempting to handle URL: ${url}`);
459
+ if (!url || typeof url !== 'string') {
460
+ console.log('[Insert Affiliate] Invalid URL provided to handleInsertLinks');
461
+ return false;
462
+ }
463
+ // Check if deep links are enabled synchronously
464
+ if (!insertLinksEnabled) {
465
+ console.log('[Insert Affiliate] Deep links are disabled, not handling URL');
466
+ return false;
467
+ }
468
+ const urlObj = parseURL(url);
469
+ // Handle custom URL schemes (ia-companycode://shortcode)
470
+ if (urlObj.protocol && urlObj.protocol.startsWith('ia-')) {
471
+ return yield handleCustomURLScheme(url, urlObj.protocol);
472
+ }
473
+ // Handle universal links (https://insertaffiliate.link/V1/companycode/shortcode)
474
+ // if (urlObj.protocol === 'https:' && urlObj.hostname?.includes('insertaffiliate.link')) {
475
+ // return await handleUniversalLink(urlObj);
476
+ // }
477
+ return false;
478
+ }
479
+ catch (error) {
480
+ console.error('[Insert Affiliate] Error handling Insert Link:', error);
481
+ verboseLog(`Error in handleInsertLinks: ${error}`);
482
+ return false;
483
+ }
484
+ });
485
+ // Handle custom URL schemes like ia-companycode://shortcode
486
+ const handleCustomURLScheme = (url, protocol) => __awaiter(void 0, void 0, void 0, function* () {
487
+ try {
488
+ const scheme = protocol.replace(':', '');
489
+ if (!scheme.startsWith('ia-')) {
490
+ return false;
491
+ }
492
+ // Extract company code from scheme (remove "ia-" prefix)
493
+ const companyCode = scheme.substring(3);
494
+ const shortCode = parseShortCodeFromURLString(url);
495
+ if (!shortCode) {
496
+ console.log(`[Insert Affiliate] Failed to parse short code from deep link: ${url}`);
497
+ return false;
498
+ }
499
+ console.log(`[Insert Affiliate] Custom URL scheme detected - Company: ${companyCode}, Short code: ${shortCode}`);
500
+ // Validate company code matches initialized one
501
+ const activeCompanyCode = yield getActiveCompanyCode();
502
+ if (activeCompanyCode && companyCode.toLowerCase() !== activeCompanyCode.toLowerCase()) {
503
+ console.log(`[Insert Affiliate] Warning: URL company code (${companyCode}) doesn't match initialized company code (${activeCompanyCode})`);
504
+ }
505
+ // If URL scheme is used, we can straight away store the short code as the referring link
506
+ yield storeInsertAffiliateIdentifier({ link: shortCode });
507
+ // Collect and send enhanced system info to backend
508
+ try {
509
+ const enhancedSystemInfo = yield getEnhancedSystemInfo();
510
+ yield sendSystemInfoToBackend(enhancedSystemInfo);
511
+ }
512
+ catch (error) {
513
+ verboseLog(`Error sending system info for deep link: ${error}`);
514
+ }
515
+ return true;
516
+ }
517
+ catch (error) {
518
+ console.error('[Insert Affiliate] Error handling custom URL scheme:', error);
519
+ return false;
520
+ }
521
+ });
522
+ // Handle universal links like https://insertaffiliate.link/V1/companycode/shortcode
523
+ // const handleUniversalLink = async (url: URL): Promise<boolean> => {
524
+ // try {
525
+ // const pathComponents = url.pathname.split('/').filter(segment => segment.length > 0);
526
+ // // Expected format: /V1/companycode/shortcode
527
+ // if (pathComponents.length < 3 || pathComponents[0] !== 'V1') {
528
+ // console.log(`[Insert Affiliate] Invalid universal link format: ${url.href}`);
529
+ // return false;
530
+ // }
531
+ // const companyCode = pathComponents[1];
532
+ // const shortCode = pathComponents[2];
533
+ // console.log(`[Insert Affiliate] Universal link detected - Company: ${companyCode}, Short code: ${shortCode}`);
534
+ // // Validate company code matches initialized one
535
+ // const activeCompanyCode = await getActiveCompanyCode();
536
+ // if (activeCompanyCode && companyCode.toLowerCase() !== activeCompanyCode.toLowerCase()) {
537
+ // console.log(`[Insert Affiliate] Warning: URL company code (${companyCode}) doesn't match initialized company code (${activeCompanyCode})`);
538
+ // }
539
+ // // Process the affiliate attribution
540
+ // await storeInsertAffiliateIdentifier({ link: shortCode });
541
+ // return true;
542
+ // } catch (error) {
543
+ // console.error('[Insert Affiliate] Error handling universal link:', error);
544
+ // return false;
545
+ // }
546
+ // };
547
+ // Parse short code from URL
548
+ const parseShortCodeFromURL = (url) => {
549
+ try {
550
+ // For custom schemes like ia-companycode://shortcode, everything after :// is the short code
551
+ // Remove leading slash from pathname
552
+ return url.pathname.startsWith('/') ? url.pathname.substring(1) : url.pathname;
553
+ }
554
+ catch (error) {
555
+ verboseLog(`Error parsing short code from URL: ${error}`);
556
+ return null;
557
+ }
558
+ };
559
+ const parseShortCodeFromURLString = (url) => {
560
+ try {
561
+ // For custom schemes like ia-companycode://shortcode, everything after :// is the short code
562
+ const match = url.match(/^[^:]+:\/\/(.+)$/);
563
+ if (match) {
564
+ const shortCode = match[1];
565
+ // Remove leading slash if present
566
+ return shortCode.startsWith('/') ? shortCode.substring(1) : shortCode;
567
+ }
568
+ return null;
569
+ }
570
+ catch (error) {
571
+ verboseLog(`Error parsing short code from URL string: ${error}`);
572
+ return null;
573
+ }
574
+ };
170
575
  // Helper funciton Storage / Retrieval
171
576
  const saveValueInAsync = (key, value) => __awaiter(void 0, void 0, void 0, function* () {
172
577
  yield async_storage_1.default.setItem(key, value);
@@ -215,6 +620,390 @@ const DeepLinkIapProvider = ({ children, }) => {
215
620
  break;
216
621
  }
217
622
  };
623
+ // MARK: - Deep Linking Utilities
624
+ // Retrieves and validates clipboard content for UUID format
625
+ const getClipboardUUID = () => __awaiter(void 0, void 0, void 0, function* () {
626
+ // Check if clipboard access is enabled
627
+ if (!insertLinksClipboardEnabled) {
628
+ return null;
629
+ }
630
+ verboseLog('Getting clipboard UUID');
631
+ try {
632
+ const clipboardString = yield clipboard_1.default.getString();
633
+ if (!clipboardString) {
634
+ verboseLog('No clipboard string found or access denied');
635
+ return null;
636
+ }
637
+ const trimmedString = clipboardString.trim();
638
+ if (isValidUUID(trimmedString)) {
639
+ verboseLog(`Valid clipboard UUID found: ${trimmedString}`);
640
+ return trimmedString;
641
+ }
642
+ verboseLog(`Invalid clipboard UUID found: ${trimmedString}`);
643
+ return null;
644
+ }
645
+ catch (error) {
646
+ verboseLog(`Clipboard access error: ${error}`);
647
+ return null;
648
+ }
649
+ });
650
+ // Validates if a string is a properly formatted UUID (36 characters)
651
+ const isValidUUID = (string) => {
652
+ if (string.length !== 36)
653
+ return false;
654
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
655
+ return uuidRegex.test(string);
656
+ };
657
+ // MARK: - System Info Collection
658
+ // Gets network connection type and interface information
659
+ const getNetworkInfo = () => __awaiter(void 0, void 0, void 0, function* () {
660
+ try {
661
+ const connectionInfo = {
662
+ connectionType: 'unknown',
663
+ interfaceTypes: [],
664
+ isExpensive: false,
665
+ isConstrained: false,
666
+ status: 'disconnected',
667
+ availableInterfaces: []
668
+ };
669
+ try {
670
+ // Use NetInfo to get accurate network information
671
+ const netInfo = yield netinfo_1.default.fetch();
672
+ connectionInfo.status = netInfo.isConnected ? 'connected' : 'disconnected';
673
+ connectionInfo.connectionType = netInfo.type || 'unknown';
674
+ connectionInfo.isExpensive = netInfo.isInternetReachable === false ? true : false;
675
+ connectionInfo.isConstrained = false; // NetInfo doesn't provide this directly
676
+ // Map NetInfo types to our interface format
677
+ if (netInfo.type) {
678
+ connectionInfo.interfaceTypes = [netInfo.type];
679
+ connectionInfo.availableInterfaces = [netInfo.type];
680
+ }
681
+ // Additional details if available
682
+ if (netInfo.details && 'isConnectionExpensive' in netInfo.details) {
683
+ connectionInfo.isExpensive = netInfo.details.isConnectionExpensive || false;
684
+ }
685
+ }
686
+ catch (error) {
687
+ verboseLog(`Network info fetch failed: ${error}`);
688
+ // Fallback to basic connectivity test
689
+ try {
690
+ const response = yield fetch('https://www.google.com/favicon.ico', {
691
+ method: 'HEAD'
692
+ });
693
+ if (response.ok) {
694
+ connectionInfo.status = 'connected';
695
+ }
696
+ }
697
+ catch (fetchError) {
698
+ verboseLog(`Fallback connectivity test failed: ${fetchError}`);
699
+ }
700
+ }
701
+ return connectionInfo;
702
+ }
703
+ catch (error) {
704
+ verboseLog(`Error getting network info: ${error}`);
705
+ return {
706
+ connectionType: 'unknown',
707
+ interfaceTypes: [],
708
+ isExpensive: false,
709
+ isConstrained: false,
710
+ status: 'disconnected',
711
+ availableInterfaces: []
712
+ };
713
+ }
714
+ });
715
+ const getNetworkPathInfo = () => __awaiter(void 0, void 0, void 0, function* () {
716
+ try {
717
+ const netInfo = yield netinfo_1.default.fetch();
718
+ // Default values - only set to true when proven
719
+ let supportsIPv4 = false;
720
+ let supportsIPv6 = false;
721
+ let supportsDNS = false;
722
+ let hasUnsatisfiedGateway = false;
723
+ let gatewayCount = 0;
724
+ let gateways = [];
725
+ let interfaceDetails = [];
726
+ if (netInfo.details && netInfo.isConnected) {
727
+ supportsIPv4 = true;
728
+ // IPv6 support based on interface type (following Swift logic)
729
+ if (netInfo.type === 'wifi' || netInfo.type === 'cellular' || netInfo.type === 'ethernet') {
730
+ supportsIPv6 = true;
731
+ }
732
+ else {
733
+ supportsIPv6 = false;
734
+ }
735
+ supportsDNS = netInfo.isInternetReachable === true;
736
+ // Get interface details from NetInfo
737
+ if (netInfo.details && 'isConnectionExpensive' in netInfo.details) {
738
+ // This is a cellular connection
739
+ interfaceDetails.push({
740
+ name: 'cellular',
741
+ index: 0,
742
+ type: 'cellular'
743
+ });
744
+ }
745
+ else if (netInfo.type === 'wifi') {
746
+ interfaceDetails.push({
747
+ name: 'en0',
748
+ index: 0,
749
+ type: 'wifi'
750
+ });
751
+ }
752
+ else if (netInfo.type === 'ethernet') {
753
+ interfaceDetails.push({
754
+ name: 'en0',
755
+ index: 0,
756
+ type: 'wiredEthernet'
757
+ });
758
+ }
759
+ gatewayCount = interfaceDetails.length;
760
+ hasUnsatisfiedGateway = gatewayCount === 0;
761
+ // For React Native, we can't easily get actual gateway IPs
762
+ // but we can indicate if we have network connectivity
763
+ if (netInfo.isConnected) {
764
+ gateways = ['default']; // Placeholder since we can't get actual gateway IPs
765
+ }
766
+ }
767
+ // Fallback if NetInfo doesn't provide enough details
768
+ if (interfaceDetails.length === 0) {
769
+ interfaceDetails = [{
770
+ name: 'en0',
771
+ index: 0,
772
+ type: netInfo.type || 'unknown'
773
+ }];
774
+ gatewayCount = 1;
775
+ hasUnsatisfiedGateway = false;
776
+ gateways = ['default'];
777
+ }
778
+ return {
779
+ supportsIPv4,
780
+ supportsIPv6,
781
+ supportsDNS,
782
+ hasUnsatisfiedGateway,
783
+ gatewayCount,
784
+ gateways,
785
+ interfaceDetails
786
+ };
787
+ }
788
+ catch (error) {
789
+ verboseLog(`Error getting network path info: ${error}`);
790
+ // Fallback to basic defaults if NetInfo fails
791
+ return {
792
+ supportsIPv4: true,
793
+ supportsIPv6: false,
794
+ supportsDNS: true,
795
+ hasUnsatisfiedGateway: false,
796
+ gatewayCount: 1,
797
+ gateways: ['default'],
798
+ interfaceDetails: [{
799
+ name: 'en0',
800
+ index: 0,
801
+ type: 'unknown'
802
+ }]
803
+ };
804
+ }
805
+ });
806
+ // Collects basic system information for deep linking (non-identifying data only)
807
+ const getSystemInfo = () => __awaiter(void 0, void 0, void 0, function* () {
808
+ const systemInfo = {};
809
+ try {
810
+ systemInfo.systemName = yield react_native_device_info_1.default.getSystemName();
811
+ systemInfo.systemVersion = yield react_native_device_info_1.default.getSystemVersion();
812
+ systemInfo.model = yield react_native_device_info_1.default.getModel();
813
+ systemInfo.localizedModel = yield react_native_device_info_1.default.getModel();
814
+ systemInfo.isPhysicalDevice = !(yield react_native_device_info_1.default.isEmulator());
815
+ systemInfo.bundleId = yield react_native_device_info_1.default.getBundleId();
816
+ // Map device type to more readable format
817
+ const deviceType = yield react_native_device_info_1.default.getDeviceType();
818
+ systemInfo.deviceType = deviceType === 'Handset' ? 'mobile' : deviceType;
819
+ }
820
+ catch (error) {
821
+ verboseLog(`Error getting device info: ${error}`);
822
+ // Fallback to basic platform detection
823
+ systemInfo.systemName = 'iOS';
824
+ systemInfo.systemVersion = react_native_1.Platform.Version.toString();
825
+ systemInfo.model = 'iPhone';
826
+ systemInfo.localizedModel = systemInfo.model;
827
+ systemInfo.isPhysicalDevice = true; // Assume physical device if we can't detect
828
+ systemInfo.bundleId = 'null'; // Fallback if we can't get bundle ID
829
+ systemInfo.deviceType = 'unknown';
830
+ }
831
+ if (verboseLogging) {
832
+ console.log('[Insert Affiliate] system info:', systemInfo);
833
+ }
834
+ return systemInfo;
835
+ });
836
+ const getEnhancedSystemInfo = () => __awaiter(void 0, void 0, void 0, function* () {
837
+ verboseLog('Collecting enhanced system information...');
838
+ let systemInfo = yield getSystemInfo();
839
+ verboseLog(`System info: ${JSON.stringify(systemInfo)}`);
840
+ try {
841
+ // Add timestamp
842
+ const now = new Date();
843
+ systemInfo.requestTime = now.toISOString();
844
+ systemInfo.requestTimestamp = Math.floor(now.getTime());
845
+ // Add user agent style information
846
+ const systemName = systemInfo.systemName;
847
+ const systemVersion = systemInfo.systemVersion;
848
+ const model = systemInfo.model;
849
+ systemInfo.userAgent = `${model}; ${systemName} ${systemVersion}`;
850
+ // Add screen dimensions and device pixel ratio (matching exact field names)
851
+ const { width, height } = react_native_1.Dimensions.get('window');
852
+ const pixelRatio = react_native_1.PixelRatio.get();
853
+ systemInfo.screenWidth = Math.floor(width);
854
+ systemInfo.screenHeight = Math.floor(height);
855
+ systemInfo.screenAvailWidth = Math.floor(width);
856
+ systemInfo.screenAvailHeight = Math.floor(height);
857
+ systemInfo.devicePixelRatio = pixelRatio;
858
+ systemInfo.screenColorDepth = 24;
859
+ systemInfo.screenPixelDepth = 24;
860
+ try {
861
+ systemInfo.hardwareConcurrency = (yield react_native_device_info_1.default.getTotalMemory()) / (1024 * 1024 * 1024); // Convert to GB
862
+ }
863
+ catch (error) {
864
+ systemInfo.hardwareConcurrency = 4; // Fallback assumption
865
+ }
866
+ systemInfo.maxTouchPoints = 5; // Default for mobile devices
867
+ // Add screen dimensions (native mobile naming)
868
+ systemInfo.screenInnerWidth = Math.floor(width);
869
+ systemInfo.screenInnerHeight = Math.floor(height);
870
+ systemInfo.screenOuterWidth = Math.floor(width);
871
+ systemInfo.screenOuterHeight = Math.floor(height);
872
+ // Add clipboard UUID if available
873
+ const clipboardUUID = yield getClipboardUUID();
874
+ if (clipboardUUID) {
875
+ systemInfo.clipboardID = clipboardUUID;
876
+ verboseLog(`Found valid clipboard UUID: ${clipboardUUID}`);
877
+ }
878
+ else {
879
+ if (insertLinksClipboardEnabled) {
880
+ verboseLog('Clipboard UUID not available - it may require NSPasteboardGeneralUseDescription in Info.plist');
881
+ }
882
+ else {
883
+ verboseLog('Clipboard access is disabled - it may require NSPasteboardGeneralUseDescription in Info.plist');
884
+ }
885
+ }
886
+ // Add language information using Intl API
887
+ try {
888
+ // Get locale with region information
889
+ const locale = Intl.DateTimeFormat().resolvedOptions().locale;
890
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
891
+ // Try to get more specific locale information
892
+ let bestLocale = locale;
893
+ // If the locale doesn't have region info, try to infer from timezone
894
+ if (!locale.includes('-') && timeZone) {
895
+ try {
896
+ // Create a locale-specific date formatter to get region
897
+ const regionLocale = new Intl.DateTimeFormat(undefined, {
898
+ timeZone: timeZone
899
+ }).resolvedOptions().locale;
900
+ if (regionLocale && regionLocale.includes('-')) {
901
+ bestLocale = regionLocale;
902
+ }
903
+ }
904
+ catch (e) {
905
+ // Fallback to original locale
906
+ }
907
+ }
908
+ // Try navigator.language as fallback for better region detection
909
+ if (!bestLocale.includes('-') && typeof navigator !== 'undefined' && navigator.language) {
910
+ bestLocale = navigator.language;
911
+ }
912
+ const parts = bestLocale.split('-');
913
+ systemInfo.language = parts[0] || 'en';
914
+ systemInfo.country = parts[1] || null; // Set to null instead of defaulting to 'US'
915
+ systemInfo.languages = [bestLocale, parts[0] || 'en'];
916
+ }
917
+ catch (error) {
918
+ verboseLog(`Error getting device locale: ${error}`);
919
+ // Fallback to defaults
920
+ systemInfo.language = 'en';
921
+ systemInfo.country = null; // Set to null instead of defaulting to 'US'
922
+ systemInfo.languages = ['en'];
923
+ }
924
+ // Add timezone info (matching exact field names)
925
+ const timezoneOffset = new Date().getTimezoneOffset();
926
+ systemInfo.timezoneOffset = -timezoneOffset;
927
+ systemInfo.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
928
+ // Add browser and platform info (matching exact field names)
929
+ systemInfo.browserVersion = systemInfo.systemVersion;
930
+ systemInfo.platform = systemInfo.systemName;
931
+ systemInfo.os = systemInfo.systemName;
932
+ systemInfo.osVersion = systemInfo.systemVersion;
933
+ // Add network connection info
934
+ verboseLog('Getting network info');
935
+ const networkInfo = yield getNetworkInfo();
936
+ const pathInfo = yield getNetworkPathInfo();
937
+ verboseLog(`Network info: ${JSON.stringify(networkInfo)}`);
938
+ verboseLog(`Network path info: ${JSON.stringify(pathInfo)}`);
939
+ systemInfo.networkInfo = networkInfo;
940
+ systemInfo.networkPath = pathInfo;
941
+ // Update connection info with real data
942
+ const connection = {};
943
+ connection.type = networkInfo.connectionType || 'unknown';
944
+ connection.isExpensive = networkInfo.isExpensive || false;
945
+ connection.isConstrained = networkInfo.isConstrained || false;
946
+ connection.status = networkInfo.status || 'unknown';
947
+ connection.interfaces = networkInfo.availableInterfaces || [];
948
+ connection.supportsIPv4 = pathInfo.supportsIPv4 || true;
949
+ connection.supportsIPv6 = pathInfo.supportsIPv6 || false;
950
+ connection.supportsDNS = pathInfo.supportsDNS || true;
951
+ // Keep legacy fields for compatibility
952
+ connection.downlink = networkInfo.connectionType === 'wifi' ? 100 : 10;
953
+ connection.effectiveType = networkInfo.connectionType === 'wifi' ? '4g' : '3g';
954
+ connection.rtt = networkInfo.connectionType === 'wifi' ? 20 : 100;
955
+ connection.saveData = networkInfo.isConstrained || false;
956
+ systemInfo.connection = connection;
957
+ verboseLog(`Enhanced system info collected: ${JSON.stringify(systemInfo)}`);
958
+ return systemInfo;
959
+ }
960
+ catch (error) {
961
+ verboseLog(`Error collecting enhanced system info: ${error}`);
962
+ return systemInfo;
963
+ }
964
+ });
965
+ // Sends enhanced system info to the backend API for deep link event tracking
966
+ const sendSystemInfoToBackend = (systemInfo) => __awaiter(void 0, void 0, void 0, function* () {
967
+ if (verboseLogging) {
968
+ console.log('[Insert Affiliate] Sending system info to backend...');
969
+ }
970
+ try {
971
+ const apiUrlString = 'https://insertaffiliate.link/V1/appDeepLinkEvents';
972
+ verboseLog(`Sending request to: ${apiUrlString}`);
973
+ const response = yield axios_1.default.post(apiUrlString, systemInfo, {
974
+ headers: {
975
+ 'Content-Type': 'application/json',
976
+ },
977
+ });
978
+ verboseLog(`System info response status: ${response.status}`);
979
+ if (response.data) {
980
+ verboseLog(`System info response: ${JSON.stringify(response.data)}`);
981
+ }
982
+ // Try to parse backend response and persist matched short code if present
983
+ if (response.data && typeof response.data === 'object') {
984
+ const matchFound = response.data.matchFound || false;
985
+ if (matchFound && response.data.matched_affiliate_shortCode && response.data.matched_affiliate_shortCode.length > 0) {
986
+ const matchedShortCode = response.data.matched_affiliate_shortCode;
987
+ verboseLog(`Storing Matched short code from backend: ${matchedShortCode}`);
988
+ yield storeInsertAffiliateIdentifier({ link: matchedShortCode });
989
+ }
990
+ }
991
+ // Check for a successful response
992
+ if (response.status >= 200 && response.status <= 299) {
993
+ verboseLog('System info sent successfully');
994
+ }
995
+ else {
996
+ verboseLog(`Failed to send system info with status code: ${response.status}`);
997
+ if (response.data) {
998
+ verboseLog(`Error response: ${JSON.stringify(response.data)}`);
999
+ }
1000
+ }
1001
+ }
1002
+ catch (error) {
1003
+ verboseLog(`Error sending system info: ${error}`);
1004
+ verboseLog(`Network error sending system info: ${error}`);
1005
+ }
1006
+ });
218
1007
  // MARK: Short Codes
219
1008
  const isShortCode = (referringLink) => {
220
1009
  // Short codes are 3-25 characters and can include underscores
@@ -282,8 +1071,14 @@ const DeepLinkIapProvider = ({ children, }) => {
282
1071
  verboseLog('React state empty, checking AsyncStorage...');
283
1072
  // Fallback to async storage if React state is empty
284
1073
  const storedLink = yield getValueFromAsync(ASYNC_KEYS.REFERRER_LINK);
285
- const storedUserId = yield getValueFromAsync(ASYNC_KEYS.USER_ID);
1074
+ let storedUserId = yield getValueFromAsync(ASYNC_KEYS.USER_ID);
286
1075
  verboseLog(`AsyncStorage - storedLink: ${storedLink || 'empty'}, storedUserId: ${storedUserId || 'empty'}`);
1076
+ // If we have a stored link but no user ID, generate one now
1077
+ if (storedLink && !storedUserId) {
1078
+ verboseLog('Found stored link but no user ID, generating user ID now...');
1079
+ storedUserId = yield generateThenSetUserID();
1080
+ verboseLog(`Generated user ID: ${storedUserId}`);
1081
+ }
287
1082
  if (storedLink && storedUserId) {
288
1083
  const identifier = `${storedLink}-${storedUserId}`;
289
1084
  verboseLog(`Found identifier in AsyncStorage: ${identifier}`);
@@ -387,6 +1182,12 @@ const DeepLinkIapProvider = ({ children, }) => {
387
1182
  // Automatically fetch and store offer code for any affiliate identifier
388
1183
  verboseLog('Attempting to fetch offer code for stored affiliate identifier...');
389
1184
  yield retrieveAndStoreOfferCode(link);
1185
+ // Trigger callback with the current affiliate identifier
1186
+ if (insertAffiliateIdentifierChangeCallbackRef.current) {
1187
+ const currentIdentifier = yield returnInsertAffiliateIdentifier();
1188
+ verboseLog(`Triggering callback with identifier: ${currentIdentifier}`);
1189
+ insertAffiliateIdentifierChangeCallbackRef.current(currentIdentifier);
1190
+ }
390
1191
  });
391
1192
  }
392
1193
  const validatePurchaseWithIapticAPI = (jsonIapPurchase, iapticAppId, iapticAppName, iapticPublicKey) => __awaiter(void 0, void 0, void 0, function* () {
@@ -548,9 +1349,18 @@ const DeepLinkIapProvider = ({ children, }) => {
548
1349
  verboseLog('Cannot fetch offer code: no company code available');
549
1350
  return null;
550
1351
  }
1352
+ let platformType = 'ios';
1353
+ // Check if its iOs or Android here
1354
+ if (react_native_1.Platform.OS !== 'ios') {
1355
+ verboseLog('Platform is not iOS, setting platform type to android');
1356
+ platformType = 'android';
1357
+ }
1358
+ else {
1359
+ verboseLog('Platform is iOS, setting platform type to ios');
1360
+ }
551
1361
  const encodedAffiliateLink = encodeURIComponent(affiliateLink);
552
- const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${activeCompanyCode}/${encodedAffiliateLink}`;
553
- verboseLog(`Fetching offer code from: ${url}`);
1362
+ const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${activeCompanyCode}/${encodedAffiliateLink}?platformType=${platformType}`;
1363
+ verboseLog(`Starting to fetch offer code from: ${url}`);
554
1364
  const response = yield axios_1.default.get(url);
555
1365
  if (response.status === 200) {
556
1366
  const offerCode = response.data;
@@ -603,8 +1413,8 @@ const DeepLinkIapProvider = ({ children, }) => {
603
1413
  }
604
1414
  });
605
1415
  const removeSpecialCharacters = (offerCode) => {
606
- // Remove special characters, keep only alphanumeric and underscores
607
- return offerCode.replace(/[^a-zA-Z0-9_]/g, '');
1416
+ // Remove special characters, keep only alphanumeric, underscores, and hyphens
1417
+ return offerCode.replace(/[^a-zA-Z0-9_-]/g, '');
608
1418
  };
609
1419
  const cleanOfferCode = (offerCode) => {
610
1420
  // Remove special characters, keep only alphanumeric
@@ -621,6 +1431,8 @@ const DeepLinkIapProvider = ({ children, }) => {
621
1431
  validatePurchaseWithIapticAPI,
622
1432
  trackEvent,
623
1433
  setInsertAffiliateIdentifier,
1434
+ setInsertAffiliateIdentifierChangeCallback: setInsertAffiliateIdentifierChangeCallbackHandler,
1435
+ handleInsertLinks,
624
1436
  initialize,
625
1437
  isInitialized,
626
1438
  } }, children));