UNPKG

insert-affiliate-react-native-sdk

Version:

A package for connecting with the Insert Affiliate Platform to add app based affiliate marketing.

1,025 lines (1,024 loc) 88.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DeepLinkIapContext = void 0; const react_1 = __importStar(require("react")); const react_native_1 = require("react-native"); const axios_1 = __importDefault(require("axios")); const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage")); const clipboard_1 = __importDefault(require("@react-native-clipboard/clipboard")); const netinfo_1 = __importDefault(require("@react-native-community/netinfo")); const react_native_device_info_1 = __importDefault(require("react-native-device-info")); const react_native_play_install_referrer_1 = require("react-native-play-install-referrer"); // Development environment check for React Native const isDevelopmentEnvironment = typeof __DEV__ !== 'undefined' && __DEV__; const ASYNC_KEYS = { REFERRER_LINK: '@app_referrer_link', USER_PURCHASE: '@app_user_purchase', USER_ID: '@app_user_id', COMPANY_CODE: '@app_company_code', USER_ACCOUNT_TOKEN: '@app_user_account_token', IOS_OFFER_CODE: '@app_ios_offer_code', AFFILIATE_STORED_DATE: '@app_affiliate_stored_date', SDK_INIT_REPORTED: '@app_sdk_init_reported', REPORTED_AFFILIATE_ASSOCIATIONS: '@app_reported_affiliate_associations', }; // STARTING CONTEXT IMPLEMENTATION exports.DeepLinkIapContext = (0, react_1.createContext)({ referrerLink: '', userId: '', OfferCode: null, returnInsertAffiliateIdentifier: (ignoreTimeout) => __awaiter(void 0, void 0, void 0, function* () { return ''; }), isAffiliateAttributionValid: () => __awaiter(void 0, void 0, void 0, function* () { return false; }), getAffiliateStoredDate: () => __awaiter(void 0, void 0, void 0, function* () { return null; }), validatePurchaseWithIapticAPI: (jsonIapPurchase, iapticAppId, iapticAppName, iapticPublicKey) => __awaiter(void 0, void 0, void 0, function* () { return false; }), returnUserAccountTokenAndStoreExpectedTransaction: () => __awaiter(void 0, void 0, void 0, function* () { return ''; }), storeExpectedStoreTransaction: (purchaseToken) => __awaiter(void 0, void 0, void 0, function* () { }), trackEvent: (eventName) => __awaiter(void 0, void 0, void 0, function* () { }), setShortCode: (shortCode) => __awaiter(void 0, void 0, void 0, function* () { return false; }), getAffiliateDetails: (affiliateCode) => __awaiter(void 0, void 0, void 0, function* () { return null; }), setInsertAffiliateIdentifier: (referringLink) => __awaiter(void 0, void 0, void 0, function* () { }), setInsertAffiliateIdentifierChangeCallback: (callback) => { }, handleInsertLinks: (url) => __awaiter(void 0, void 0, void 0, function* () { return false; }), initialize: (code, verboseLogging, insertLinksEnabled, insertLinksClipboardEnabled, affiliateAttributionActiveTime) => __awaiter(void 0, void 0, void 0, function* () { }), isInitialized: false, }); const DeepLinkIapProvider = ({ children, }) => { const [referrerLink, setReferrerLink] = (0, react_1.useState)(''); const [userId, setUserId] = (0, react_1.useState)(''); const [companyCode, setCompanyCode] = (0, react_1.useState)(null); const [isInitialized, setIsInitialized] = (0, react_1.useState)(false); const [verboseLogging, setVerboseLogging] = (0, react_1.useState)(false); const [insertLinksEnabled, setInsertLinksEnabled] = (0, react_1.useState)(false); const [insertLinksClipboardEnabled, setInsertLinksClipboardEnabled] = (0, react_1.useState)(false); const [OfferCode, setOfferCode] = (0, react_1.useState)(null); const [affiliateAttributionActiveTime, setAffiliateAttributionActiveTime] = (0, react_1.useState)(null); const insertAffiliateIdentifierChangeCallbackRef = (0, react_1.useRef)(null); // MARK: Initialize the SDK const initialize = (companyCode_1, ...args_1) => __awaiter(void 0, [companyCode_1, ...args_1], void 0, function* (companyCode, verboseLogging = false, insertLinksEnabled = false, insertLinksClipboardEnabled = false, affiliateAttributionActiveTime) { setVerboseLogging(verboseLogging); setInsertLinksEnabled(insertLinksEnabled); setInsertLinksClipboardEnabled(insertLinksClipboardEnabled); if (affiliateAttributionActiveTime !== undefined) { setAffiliateAttributionActiveTime(affiliateAttributionActiveTime); } if (verboseLogging) { console.log('[Insert Affiliate] [VERBOSE] Starting SDK initialization...'); console.log('[Insert Affiliate] [VERBOSE] Company code provided:', companyCode ? 'Yes' : 'No'); console.log('[Insert Affiliate] [VERBOSE] Verbose logging enabled'); } if (isInitialized) { console.error('[Insert Affiliate] SDK is already initialized.'); return; } if (companyCode && companyCode.trim() !== '') { setCompanyCode(companyCode); yield saveValueInAsync(ASYNC_KEYS.COMPANY_CODE, companyCode); setIsInitialized(true); console.log(`[Insert Affiliate] SDK initialized with company code: ${companyCode}`); if (verboseLogging) { console.log('[Insert Affiliate] [VERBOSE] Company code saved to AsyncStorage'); console.log('[Insert Affiliate] [VERBOSE] SDK marked as initialized'); } // Report SDK initialization for onboarding verification (fire and forget) reportSdkInitIfNeeded(companyCode, verboseLogging); } else { console.warn('[Insert Affiliate] SDK initialized without a company code.'); setIsInitialized(true); if (verboseLogging) { console.log('[Insert Affiliate] [VERBOSE] No company code provided, SDK initialized in limited mode'); } } if (insertLinksEnabled && react_native_1.Platform.OS === 'ios') { try { const enhancedSystemInfo = yield getEnhancedSystemInfo(); yield sendSystemInfoToBackend(enhancedSystemInfo); } catch (error) { verboseLog(`Error sending system info for clipboard check: ${error}`); } } }); // EFFECT TO FETCH USER ID AND REF LINK // IF ALREADY EXISTS IN ASYNC STORAGE (0, react_1.useEffect)(() => { const fetchAsyncEssentials = () => __awaiter(void 0, void 0, void 0, function* () { try { verboseLog('Loading stored data from AsyncStorage...'); const uId = yield getValueFromAsync(ASYNC_KEYS.USER_ID); const refLink = yield getValueFromAsync(ASYNC_KEYS.REFERRER_LINK); const companyCodeFromStorage = yield getValueFromAsync(ASYNC_KEYS.COMPANY_CODE); const storedOfferCode = yield getValueFromAsync(ASYNC_KEYS.IOS_OFFER_CODE); verboseLog(`User ID found: ${uId ? 'Yes' : 'No'}`); verboseLog(`Referrer link found: ${refLink ? 'Yes' : 'No'}`); verboseLog(`Company code found: ${companyCodeFromStorage ? 'Yes' : 'No'}`); verboseLog(`iOS Offer Code found: ${storedOfferCode ? 'Yes' : 'No'}`); if (uId && refLink) { setUserId(uId); setReferrerLink(refLink); verboseLog('User ID and referrer link restored from storage'); } if (companyCodeFromStorage) { setCompanyCode(companyCodeFromStorage); verboseLog('Company code restored from storage'); } if (storedOfferCode) { setOfferCode(storedOfferCode); verboseLog('iOS Offer Code restored from storage'); } } catch (error) { errorLog(`ERROR ~ fetchAsyncEssentials: ${error}`); verboseLog(`Error loading from AsyncStorage: ${error}`); } }); fetchAsyncEssentials(); }, []); // Cleanup callback on unmount (0, react_1.useEffect)(() => { return () => { insertAffiliateIdentifierChangeCallbackRef.current = null; }; }, []); // Deep link event listeners - equivalent to iOS AppDelegate methods (0, react_1.useEffect)(() => { if (!isInitialized) return; // Handle app launch with URL (equivalent to didFinishLaunchingWithOptions) const handleInitialURL = () => __awaiter(void 0, void 0, void 0, function* () { try { const initialUrl = yield react_native_1.Linking.getInitialURL(); if (initialUrl) { verboseLog(`App launched with URL: ${initialUrl}`); const handled = yield handleDeepLink(initialUrl); if (handled) { verboseLog('URL was handled by Insert Affiliate SDK'); } else { verboseLog('URL was not handled by Insert Affiliate SDK'); } } } catch (error) { console.error('[Insert Affiliate] Error getting initial URL:', error); } }); // Handle URL opening while app is running (equivalent to open url) const handleUrlChange = (event) => __awaiter(void 0, void 0, void 0, function* () { try { verboseLog(`URL opened while app running: ${event.url}`); const handled = yield handleDeepLink(event.url); if (handled) { verboseLog('URL was handled by Insert Affiliate SDK'); } else { verboseLog('URL was not handled by Insert Affiliate SDK'); } } catch (error) { console.error('[Insert Affiliate] Error handling URL change:', error); } }); // Platform-specific deep link handler const handleDeepLink = (url) => __awaiter(void 0, void 0, void 0, function* () { try { verboseLog(`Platform detection: Platform.OS = ${react_native_1.Platform.OS}`); if (react_native_1.Platform.OS === 'ios') { verboseLog('Routing to iOS handler (handleInsertLinks)'); return yield handleInsertLinks(url); } else if (react_native_1.Platform.OS === 'android') { verboseLog('Routing to Android handler (handleInsertLinkAndroid)'); return yield handleInsertLinkAndroid(url); } verboseLog(`Unrecognized platform: ${react_native_1.Platform.OS}`); return false; } catch (error) { verboseLog(`Error handling deep link: ${error}`); return false; } }); // Set up listeners const urlListener = react_native_1.Linking.addEventListener('url', handleUrlChange); // Handle initial URL handleInitialURL(); // Cleanup return () => { urlListener === null || urlListener === void 0 ? void 0 : urlListener.remove(); }; }, [isInitialized]); // EFFECT TO HANDLE INSTALL REFERRER ON ANDROID (0, react_1.useEffect)(() => { if (react_native_1.Platform.OS === 'android' && isInitialized && insertLinksEnabled) { verboseLog('Install referrer effect - Platform.OS is android, isInitialized is true, and insertLinksEnabled is true'); // Ensure user ID is generated before processing install referrer const initializeAndCapture = () => __awaiter(void 0, void 0, void 0, function* () { yield generateThenSetUserID(); verboseLog('Install referrer effect - Generating user ID and capturing install referrer'); captureInstallReferrer(); }); initializeAndCapture(); } }, [isInitialized, insertLinksEnabled]); function generateThenSetUserID() { return __awaiter(this, void 0, void 0, function* () { verboseLog('Getting or generating user ID...'); let userId = yield getValueFromAsync(ASYNC_KEYS.USER_ID); if (!userId) { verboseLog('No existing user ID found, generating new one...'); userId = generateUserID(); setUserId(userId); yield saveValueInAsync(ASYNC_KEYS.USER_ID, userId); verboseLog(`Generated and saved new user ID: ${userId}`); } else { verboseLog(`Found existing user ID: ${userId}`); setUserId(userId); } return userId; }); } const generateUserID = () => { const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let uniqueId = ''; for (let i = 0; i < 6; i++) { const randomIndex = Math.floor(Math.random() * characters.length); uniqueId += characters[randomIndex]; } return uniqueId; }; const reset = () => { setCompanyCode(null); setIsInitialized(false); console.log('[Insert Affiliate] SDK has been reset.'); }; // MARK: Callback Management // Sets a callback that will be triggered whenever storeInsertAffiliateIdentifier is called // The callback receives the current affiliate identifier (returnInsertAffiliateIdentifier result) const setInsertAffiliateIdentifierChangeCallbackHandler = (callback) => { insertAffiliateIdentifierChangeCallbackRef.current = callback; }; // MARK: Deep Link Handling // Helper function to parse URLs in React Native compatible way const parseURL = (url) => { try { // Extract protocol const protocolMatch = url.match(/^([^:]+):/); const protocol = protocolMatch ? protocolMatch[1] + ':' : ''; // Extract hostname for https URLs let hostname = ''; if (protocol === 'https:' || protocol === 'http:') { const hostnameMatch = url.match(/^https?:\/\/([^\/]+)/); hostname = hostnameMatch ? hostnameMatch[1] : ''; } return { protocol, hostname, href: url }; } catch (error) { return { protocol: '', hostname: '', href: url }; } }; // Handles Android deep links with insertAffiliate parameter const handleInsertLinkAndroid = (url) => __awaiter(void 0, void 0, void 0, function* () { try { // Check if deep links are enabled if (!insertLinksEnabled) { verboseLog('Deep links are disabled, not handling Android URL'); return false; } verboseLog(`Processing Android deep link: ${url}`); if (!url || typeof url !== 'string') { verboseLog('Invalid URL provided to handleInsertLinkAndroid'); return false; } // Parse the URL to extract query parameters (React Native compatible) // URLSearchParams is not available in React Native, so parse manually let insertAffiliate = null; const queryIndex = url.indexOf('?'); if (queryIndex !== -1) { const queryString = url.substring(queryIndex + 1); const params = queryString.split('&'); for (const param of params) { const [key, value] = param.split('='); if (key === 'insertAffiliate' && value) { insertAffiliate = decodeURIComponent(value); break; } } } if (insertAffiliate && insertAffiliate.length > 0) { verboseLog(`Found insertAffiliate parameter: ${insertAffiliate}`); yield storeInsertAffiliateIdentifier({ link: insertAffiliate, source: 'deep_link_android' }); return true; } else { verboseLog('No insertAffiliate parameter found in Android deep link'); return false; } } catch (error) { verboseLog(`Error handling Android deep link: ${error}`); return false; } }); // MARK: Play Install Referrer /** * Captures install referrer data from Google Play Store * This method automatically extracts referral parameters and processes them */ const captureInstallReferrer = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (retryCount = 0) { try { // Check if deep links are enabled if (!insertLinksEnabled) { verboseLog('Deep links are disabled, not processing install referrer'); return false; } // Check if we're on Android if (react_native_1.Platform.OS !== 'android') { verboseLog('Install referrer is only available on Android'); return false; } verboseLog(`Starting install referrer capture... (attempt ${retryCount + 1})`); // Convert callback-based API to Promise with timeout const referrerData = yield new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Install referrer request timed out')); }, 10000); // 10 second timeout react_native_play_install_referrer_1.PlayInstallReferrer.getInstallReferrerInfo((info, error) => { clearTimeout(timeout); if (error) { reject(error); } else { resolve(info); } }); }); if (referrerData && referrerData.installReferrer) { verboseLog(`Raw install referrer data: ${referrerData.installReferrer}`); const success = yield processInstallReferrerData(referrerData.installReferrer); if (success) { verboseLog('Install referrer processed successfully'); return true; } else { verboseLog('No insertAffiliate parameter found in install referrer'); return false; } } else { verboseLog('No install referrer data found'); return false; } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); verboseLog(`Error capturing install referrer (attempt ${retryCount + 1}): ${errorMessage}`); // Check if this is a retryable error and we haven't exceeded max retries const isRetryableError = errorMessage.includes('SERVICE_UNAVAILABLE') || errorMessage.includes('DEVELOPER_ERROR') || errorMessage.includes('timed out') || errorMessage.includes('SERVICE_DISCONNECTED'); const maxRetries = 3; const retryDelay = Math.min(1000 * Math.pow(2, retryCount), 10000); // Exponential backoff, max 10s if (isRetryableError && retryCount < maxRetries) { verboseLog(`Retrying install referrer capture in ${retryDelay}ms...`); // Schedule retry setTimeout(() => { captureInstallReferrer(retryCount + 1); }, retryDelay); return false; } else { verboseLog(`Install referrer capture failed after ${retryCount + 1} attempts`); return false; } } }); /** * Processes the raw install referrer data and extracts insertAffiliate parameter * @param rawReferrer The raw referrer string from Play Store */ const processInstallReferrerData = (rawReferrer) => __awaiter(void 0, void 0, void 0, function* () { try { verboseLog('Processing install referrer data...'); if (!rawReferrer || rawReferrer.length === 0) { verboseLog('No referrer data provided'); return false; } verboseLog(`Raw referrer data: ${rawReferrer}`); // Parse the referrer string directly for insertAffiliate parameter let insertAffiliate = null; if (rawReferrer.includes('insertAffiliate=')) { const params = rawReferrer.split('&'); for (const param of params) { if (param.startsWith('insertAffiliate=')) { insertAffiliate = param.substring('insertAffiliate='.length); break; } } } verboseLog(`Extracted insertAffiliate parameter: ${insertAffiliate}`); // If we have insertAffiliate parameter, use it as the affiliate identifier if (insertAffiliate && insertAffiliate.length > 0) { verboseLog(`Found insertAffiliate parameter, setting as affiliate identifier: ${insertAffiliate}`); yield storeInsertAffiliateIdentifier({ link: insertAffiliate, source: 'install_referrer' }); return true; } else { verboseLog('No insertAffiliate parameter found in referrer data'); return false; } } catch (error) { verboseLog(`Error processing install referrer data: ${error}`); return false; } }); // Handles Insert Links deep linking - equivalent to iOS handleInsertLinks const handleInsertLinks = (url) => __awaiter(void 0, void 0, void 0, function* () { try { console.log(`[Insert Affiliate] Attempting to handle URL: ${url}`); if (!url || typeof url !== 'string') { console.log('[Insert Affiliate] Invalid URL provided to handleInsertLinks'); return false; } // Check if deep links are enabled synchronously if (!insertLinksEnabled) { console.log('[Insert Affiliate] Deep links are disabled, not handling URL'); return false; } const urlObj = parseURL(url); // Handle custom URL schemes (ia-companycode://shortcode) if (urlObj.protocol && urlObj.protocol.startsWith('ia-')) { return yield handleCustomURLScheme(url, urlObj.protocol); } // Handle universal links (https://insertaffiliate.link/V1/companycode/shortcode) // if (urlObj.protocol === 'https:' && urlObj.hostname?.includes('insertaffiliate.link')) { // return await handleUniversalLink(urlObj); // } return false; } catch (error) { console.error('[Insert Affiliate] Error handling Insert Link:', error); verboseLog(`Error in handleInsertLinks: ${error}`); return false; } }); // Handle custom URL schemes like ia-companycode://shortcode const handleCustomURLScheme = (url, protocol) => __awaiter(void 0, void 0, void 0, function* () { try { const scheme = protocol.replace(':', ''); if (!scheme.startsWith('ia-')) { return false; } // Extract company code from scheme (remove "ia-" prefix) const companyCode = scheme.substring(3); const shortCode = parseShortCodeFromURLString(url); if (!shortCode) { console.log(`[Insert Affiliate] Failed to parse short code from deep link: ${url}`); return false; } console.log(`[Insert Affiliate] Custom URL scheme detected - Company: ${companyCode}, Short code: ${shortCode}`); // Validate company code matches initialized one const activeCompanyCode = yield getActiveCompanyCode(); if (activeCompanyCode && companyCode.toLowerCase() !== activeCompanyCode.toLowerCase()) { console.log(`[Insert Affiliate] Warning: URL company code (${companyCode}) doesn't match initialized company code (${activeCompanyCode})`); } // If URL scheme is used, we can straight away store the short code as the referring link yield storeInsertAffiliateIdentifier({ link: shortCode, source: 'deep_link_ios' }); // Collect and send enhanced system info to backend try { const enhancedSystemInfo = yield getEnhancedSystemInfo(); yield sendSystemInfoToBackend(enhancedSystemInfo); } catch (error) { verboseLog(`Error sending system info for deep link: ${error}`); } return true; } catch (error) { console.error('[Insert Affiliate] Error handling custom URL scheme:', error); return false; } }); // Handle universal links like https://insertaffiliate.link/V1/companycode/shortcode // const handleUniversalLink = async (url: URL): Promise<boolean> => { // try { // const pathComponents = url.pathname.split('/').filter(segment => segment.length > 0); // // Expected format: /V1/companycode/shortcode // if (pathComponents.length < 3 || pathComponents[0] !== 'V1') { // console.log(`[Insert Affiliate] Invalid universal link format: ${url.href}`); // return false; // } // const companyCode = pathComponents[1]; // const shortCode = pathComponents[2]; // console.log(`[Insert Affiliate] Universal link detected - Company: ${companyCode}, Short code: ${shortCode}`); // // Validate company code matches initialized one // const activeCompanyCode = await getActiveCompanyCode(); // if (activeCompanyCode && companyCode.toLowerCase() !== activeCompanyCode.toLowerCase()) { // console.log(`[Insert Affiliate] Warning: URL company code (${companyCode}) doesn't match initialized company code (${activeCompanyCode})`); // } // // Process the affiliate attribution // await storeInsertAffiliateIdentifier({ link: shortCode }); // return true; // } catch (error) { // console.error('[Insert Affiliate] Error handling universal link:', error); // return false; // } // }; // Parse short code from URL const parseShortCodeFromURL = (url) => { try { // For custom schemes like ia-companycode://shortcode, everything after :// is the short code // Remove leading slash from pathname return url.pathname.startsWith('/') ? url.pathname.substring(1) : url.pathname; } catch (error) { verboseLog(`Error parsing short code from URL: ${error}`); return null; } }; const parseShortCodeFromURLString = (url) => { try { // For custom schemes like ia-companycode://shortcode, everything after :// is the short code const match = url.match(/^[^:]+:\/\/(.+)$/); if (match) { const shortCode = match[1]; // Remove leading slash if present return shortCode.startsWith('/') ? shortCode.substring(1) : shortCode; } return null; } catch (error) { verboseLog(`Error parsing short code from URL string: ${error}`); return null; } }; // Helper funciton Storage / Retrieval const saveValueInAsync = (key, value) => __awaiter(void 0, void 0, void 0, function* () { yield async_storage_1.default.setItem(key, value); }); const getValueFromAsync = (key) => __awaiter(void 0, void 0, void 0, function* () { const response = yield async_storage_1.default.getItem(key); return response; }); const clearAsyncStorage = () => __awaiter(void 0, void 0, void 0, function* () { yield async_storage_1.default.clear(); }); // Helper function to get company code from state or storage const getActiveCompanyCode = () => __awaiter(void 0, void 0, void 0, function* () { verboseLog('Getting active company code...'); let activeCompanyCode = companyCode; verboseLog(`Company code in React state: ${activeCompanyCode || 'empty'}`); if (!activeCompanyCode || (activeCompanyCode.trim() === '' && activeCompanyCode !== null)) { verboseLog('Company code not in state, checking AsyncStorage...'); activeCompanyCode = yield getValueFromAsync(ASYNC_KEYS.COMPANY_CODE); verboseLog(`Company code in AsyncStorage: ${activeCompanyCode || 'empty'}`); if (activeCompanyCode) { // Update state for future use setCompanyCode(activeCompanyCode); verboseLog('Updated React state with company code from storage'); } } return activeCompanyCode; }); // Helper function for verbose logging const verboseLog = (message) => { if (verboseLogging) { console.log(`[Insert Affiliate] [VERBOSE] ${message}`); } }; // Helper function to log errors const errorLog = (message, type) => { switch (type) { case 'error': console.error(`ENCOUNTER ERROR ~ ${message}`); break; case 'warn': console.warn(`ENCOUNTER WARNING ~ ${message}`); break; default: console.log(`LOGGING ~ ${message}`); break; } }; // Reports a new affiliate association to the backend for tracking. // Only reports each unique affiliateIdentifier once to prevent duplicates. const reportAffiliateAssociationIfNeeded = (affiliateIdentifier, source) => __awaiter(void 0, void 0, void 0, function* () { try { const activeCompanyCode = yield getActiveCompanyCode(); if (!activeCompanyCode) { verboseLog('Cannot report affiliate association: no company code available'); return; } // Get the set of already-reported affiliate identifiers const reportedAssociationsJson = yield async_storage_1.default.getItem(ASYNC_KEYS.REPORTED_AFFILIATE_ASSOCIATIONS); const reportedAssociations = reportedAssociationsJson ? JSON.parse(reportedAssociationsJson) : []; // Check if this affiliate identifier has already been reported if (reportedAssociations.includes(affiliateIdentifier)) { verboseLog(`Affiliate association already reported for: ${affiliateIdentifier}, skipping`); return; } verboseLog(`Reporting new affiliate association: ${affiliateIdentifier} (source: ${source})`); const response = yield fetch('https://api.insertaffiliate.com/V1/onboarding/affiliate-associated', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ companyId: activeCompanyCode, affiliateIdentifier: affiliateIdentifier, source: source, timestamp: new Date().toISOString(), }), }); if (response.ok) { // Add to reported set and persist reportedAssociations.push(affiliateIdentifier); yield async_storage_1.default.setItem(ASYNC_KEYS.REPORTED_AFFILIATE_ASSOCIATIONS, JSON.stringify(reportedAssociations)); verboseLog(`Affiliate association reported successfully for: ${affiliateIdentifier}`); } else { verboseLog(`Affiliate association report failed with status: ${response.status}`); } } catch (error) { // Silently fail - this is non-critical telemetry verboseLog(`Affiliate association report error: ${error}`); } }); // Reports SDK initialization to the backend for onboarding verification. // Only reports once per install to minimize server load. const reportSdkInitIfNeeded = (companyCode, verboseLogging) => __awaiter(void 0, void 0, void 0, function* () { try { // Only report once per install const alreadyReported = yield async_storage_1.default.getItem(ASYNC_KEYS.SDK_INIT_REPORTED); if (alreadyReported === 'true') { return; } if (verboseLogging) { console.log('[Insert Affiliate] Reporting SDK initialization for onboarding verification...'); } const response = yield fetch('https://api.insertaffiliate.com/V1/onboarding/sdk-init', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ companyId: companyCode }), }); if (response.ok) { yield async_storage_1.default.setItem(ASYNC_KEYS.SDK_INIT_REPORTED, 'true'); if (verboseLogging) { console.log('[Insert Affiliate] SDK initialization reported successfully'); } } else if (verboseLogging) { console.log(`[Insert Affiliate] SDK initialization report failed with status: ${response.status}`); } } catch (error) { // Silently fail - this is non-critical telemetry if (verboseLogging) { console.log(`[Insert Affiliate] SDK initialization report error: ${error}`); } } }); // MARK: - Deep Linking Utilities // Retrieves and validates clipboard content for UUID format const getClipboardUUID = () => __awaiter(void 0, void 0, void 0, function* () { // Check if clipboard access is enabled if (!insertLinksClipboardEnabled) { return null; } verboseLog('Getting clipboard UUID'); try { const clipboardString = yield clipboard_1.default.getString(); if (!clipboardString) { verboseLog('No clipboard string found or access denied'); return null; } const trimmedString = clipboardString.trim(); if (isValidUUID(trimmedString)) { verboseLog(`Valid clipboard UUID found: ${trimmedString}`); return trimmedString; } verboseLog(`Invalid clipboard UUID found: ${trimmedString}`); return null; } catch (error) { verboseLog(`Clipboard access error: ${error}`); return null; } }); // Validates if a string is a properly formatted UUID (36 characters) const isValidUUID = (string) => { if (string.length !== 36) return false; 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; return uuidRegex.test(string); }; // MARK: - System Info Collection // Gets network connection type and interface information const getNetworkInfo = () => __awaiter(void 0, void 0, void 0, function* () { try { const connectionInfo = { connectionType: 'unknown', interfaceTypes: [], isExpensive: false, isConstrained: false, status: 'disconnected', availableInterfaces: [] }; try { // Use NetInfo to get accurate network information const netInfo = yield netinfo_1.default.fetch(); connectionInfo.status = netInfo.isConnected ? 'connected' : 'disconnected'; connectionInfo.connectionType = netInfo.type || 'unknown'; connectionInfo.isExpensive = netInfo.isInternetReachable === false ? true : false; connectionInfo.isConstrained = false; // NetInfo doesn't provide this directly // Map NetInfo types to our interface format if (netInfo.type) { connectionInfo.interfaceTypes = [netInfo.type]; connectionInfo.availableInterfaces = [netInfo.type]; } // Additional details if available if (netInfo.details && 'isConnectionExpensive' in netInfo.details) { connectionInfo.isExpensive = netInfo.details.isConnectionExpensive || false; } } catch (error) { verboseLog(`Network info fetch failed: ${error}`); // Fallback to basic connectivity test try { const response = yield fetch('https://www.google.com/favicon.ico', { method: 'HEAD' }); if (response.ok) { connectionInfo.status = 'connected'; } } catch (fetchError) { verboseLog(`Fallback connectivity test failed: ${fetchError}`); } } return connectionInfo; } catch (error) { verboseLog(`Error getting network info: ${error}`); return { connectionType: 'unknown', interfaceTypes: [], isExpensive: false, isConstrained: false, status: 'disconnected', availableInterfaces: [] }; } }); const getNetworkPathInfo = () => __awaiter(void 0, void 0, void 0, function* () { try { const netInfo = yield netinfo_1.default.fetch(); // Default values - only set to true when proven let supportsIPv4 = false; let supportsIPv6 = false; let supportsDNS = false; let hasUnsatisfiedGateway = false; let gatewayCount = 0; let gateways = []; let interfaceDetails = []; if (netInfo.details && netInfo.isConnected) { supportsIPv4 = true; // IPv6 support based on interface type (following Swift logic) if (netInfo.type === 'wifi' || netInfo.type === 'cellular' || netInfo.type === 'ethernet') { supportsIPv6 = true; } else { supportsIPv6 = false; } supportsDNS = netInfo.isInternetReachable === true; // Get interface details from NetInfo if (netInfo.details && 'isConnectionExpensive' in netInfo.details) { // This is a cellular connection interfaceDetails.push({ name: 'cellular', index: 0, type: 'cellular' }); } else if (netInfo.type === 'wifi') { interfaceDetails.push({ name: 'en0', index: 0, type: 'wifi' }); } else if (netInfo.type === 'ethernet') { interfaceDetails.push({ name: 'en0', index: 0, type: 'wiredEthernet' }); } gatewayCount = interfaceDetails.length; hasUnsatisfiedGateway = gatewayCount === 0; // For React Native, we can't easily get actual gateway IPs // but we can indicate if we have network connectivity if (netInfo.isConnected) { gateways = ['default']; // Placeholder since we can't get actual gateway IPs } } // Fallback if NetInfo doesn't provide enough details if (interfaceDetails.length === 0) { interfaceDetails = [{ name: 'en0', index: 0, type: netInfo.type || 'unknown' }]; gatewayCount = 1; hasUnsatisfiedGateway = false; gateways = ['default']; } return { supportsIPv4, supportsIPv6, supportsDNS, hasUnsatisfiedGateway, gatewayCount, gateways, interfaceDetails }; } catch (error) { verboseLog(`Error getting network path info: ${error}`); // Fallback to basic defaults if NetInfo fails return { supportsIPv4: true, supportsIPv6: false, supportsDNS: true, hasUnsatisfiedGateway: false, gatewayCount: 1, gateways: ['default'], interfaceDetails: [{ name: 'en0', index: 0, type: 'unknown' }] }; } }); // Collects basic system information for deep linking (non-identifying data only) const getSystemInfo = () => __awaiter(void 0, void 0, void 0, function* () { const systemInfo = {}; try { systemInfo.systemName = yield react_native_device_info_1.default.getSystemName(); systemInfo.systemVersion = yield react_native_device_info_1.default.getSystemVersion(); systemInfo.model = yield react_native_device_info_1.default.getModel(); systemInfo.localizedModel = yield react_native_device_info_1.default.getModel(); systemInfo.isPhysicalDevice = !(yield react_native_device_info_1.default.isEmulator()); systemInfo.bundleId = yield react_native_device_info_1.default.getBundleId(); // Map device type to more readable format const deviceType = yield react_native_device_info_1.default.getDeviceType(); systemInfo.deviceType = deviceType === 'Handset' ? 'mobile' : deviceType; } catch (error) { verboseLog(`Error getting device info: ${error}`); // Fallback to basic platform detection systemInfo.systemName = 'iOS'; systemInfo.systemVersion = react_native_1.Platform.Version.toString(); systemInfo.model = 'iPhone'; systemInfo.localizedModel = systemInfo.model; systemInfo.isPhysicalDevice = true; // Assume physical device if we can't detect systemInfo.bundleId = 'null'; // Fallback if we can't get bundle ID systemInfo.deviceType = 'unknown'; } if (verboseLogging) { console.log('[Insert Affiliate] system info:', systemInfo); } return systemInfo; }); const getEnhancedSystemInfo = () => __awaiter(void 0, void 0, void 0, function* () { verboseLog('Collecting enhanced system information...'); let systemInfo = yield getSystemInfo(); verboseLog(`System info: ${JSON.stringify(systemInfo)}`); try { // Add timestamp const now = new Date(); systemInfo.requestTime = now.toISOString(); systemInfo.requestTimestamp = Math.floor(now.getTime()); // Add user agent style information const systemName = systemInfo.systemName; const systemVersion = systemInfo.systemVersion; const model = systemInfo.model; systemInfo.userAgent = `${model}; ${systemName} ${systemVersion}`; // Add screen dimensions and device pixel ratio (matching exact field names) const { width, height } = react_native_1.Dimensions.get('window'); const pixelRatio = react_native_1.PixelRatio.get(); systemInfo.screenWidth = Math.floor(width); systemInfo.screenHeight = Math.floor(height); systemInfo.screenAvailWidth = Math.floor(width); systemInfo.screenAvailHeight = Math.floor(height); systemInfo.devicePixelRatio = pixelRatio; systemInfo.screenColorDepth = 24; systemInfo.screenPixelDepth = 24; try { systemInfo.hardwareConcurrency = (yield react_native_device_info_1.default.getTotalMemory()) / (1024 * 1024 * 1024); // Convert to GB } catch (error) { systemInfo.hardwareConcurrency = 4; // Fallback assumption } systemInfo.maxTouchPoints = 5; // Default for mobile devices // Add screen dimensions (native mobile naming) systemInfo.screenInnerWidth = Math.floor(width); systemInfo.screenInnerHeight = Math.floor(height); systemInfo.screenOuterWidth = Math.floor(width); systemInfo.screenOuterHeight = Math.floor(height); // Add clipboard UUID if available const clipboardUUID = yield getClipboardUUID(); if (clipboardUUID) { systemInfo.clipboardID = clipboardUUID; verboseLog(`Found valid clipboard UUID: ${clipboardUUID}`); } else { if (insertLinksClipboardEnabled) { verboseLog('Clipboard UUID not available - it may require NSPasteboardGeneralUseDescription in Info.plist'); } else { verboseLog('Clipboard access is disabled - it may require NSPasteboardGeneralUseDescription in Info.plist'); } } // Add language information using Intl API try { // Get locale with region information const locale = Intl.DateTimeFormat().resolvedOptions().locale; const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; // Try to get more specific locale information let bestLocale = locale; // If the locale doesn't have region info, try to infer from timezone if (!locale.includes('-') && timeZone) { try { // Create a locale-specific date formatter to get region const regionLocale = new Intl.DateTimeFormat(undefined, { timeZone: timeZone }).resolvedOptions().locale; if (regionLocale && regionLocale.includes('-')) { bestLocale = regionLocale; } } catch (e) { // Fallback to original locale } } // Try navigator.language as fallback for better region detection if (!bestLocale.includes('-') && typeof navigator !== 'undefined' && navigator.language) { bestLocale = navigator.language; } const parts = bestLocale.split('-'); systemInfo.language = parts[0] || 'en'; systemInfo.country = parts[1] || null; // Set to null instead of defaulting to 'US' systemInfo.languages = [bestLocale, parts[0] || 'en']; } catch (error) {