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