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
JavaScript
"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) {