insert-affiliate-react-native-sdk 1.6.5 → 1.8.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/dist/DeepLinkIapProvider.d.ts +4 -2
- package/dist/DeepLinkIapProvider.js +112 -6
- package/dist/useDeepLinkIapProvider.d.ts +4 -2
- package/dist/useDeepLinkIapProvider.js +3 -1
- package/package.json +1 -1
- package/readme.md +83 -1
- package/src/DeepLinkIapProvider.tsx +132 -8
- package/src/useDeepLinkIapProvider.tsx +4 -0
|
@@ -10,7 +10,9 @@ type T_DEEPLINK_IAP_CONTEXT = {
|
|
|
10
10
|
referrerLink: string;
|
|
11
11
|
userId: string;
|
|
12
12
|
OfferCode: string | null;
|
|
13
|
-
returnInsertAffiliateIdentifier: () => Promise<string | null>;
|
|
13
|
+
returnInsertAffiliateIdentifier: (ignoreTimeout?: boolean) => Promise<string | null>;
|
|
14
|
+
isAffiliateAttributionValid: () => Promise<boolean>;
|
|
15
|
+
getAffiliateStoredDate: () => Promise<Date | null>;
|
|
14
16
|
validatePurchaseWithIapticAPI: (jsonIapPurchase: CustomPurchase, iapticAppId: string, iapticAppName: string, iapticPublicKey: string) => Promise<boolean>;
|
|
15
17
|
returnUserAccountTokenAndStoreExpectedTransaction: () => Promise<string | null>;
|
|
16
18
|
storeExpectedStoreTransaction: (purchaseToken: string) => Promise<void>;
|
|
@@ -19,7 +21,7 @@ type T_DEEPLINK_IAP_CONTEXT = {
|
|
|
19
21
|
setInsertAffiliateIdentifier: (referringLink: string) => Promise<void | string>;
|
|
20
22
|
setInsertAffiliateIdentifierChangeCallback: (callback: InsertAffiliateIdentifierChangeCallback | null) => void;
|
|
21
23
|
handleInsertLinks: (url: string) => Promise<boolean>;
|
|
22
|
-
initialize: (code: string | null, verboseLogging?: boolean, insertLinksEnabled?: boolean, insertLinksClipboardEnabled?: boolean) => Promise<void>;
|
|
24
|
+
initialize: (code: string | null, verboseLogging?: boolean, insertLinksEnabled?: boolean, insertLinksClipboardEnabled?: boolean, affiliateAttributionActiveTime?: number) => Promise<void>;
|
|
23
25
|
isInitialized: boolean;
|
|
24
26
|
};
|
|
25
27
|
export declare const DeepLinkIapContext: React.Context<T_DEEPLINK_IAP_CONTEXT>;
|
|
@@ -44,6 +44,8 @@ const clipboard_1 = __importDefault(require("@react-native-clipboard/clipboard")
|
|
|
44
44
|
const netinfo_1 = __importDefault(require("@react-native-community/netinfo"));
|
|
45
45
|
const react_native_device_info_1 = __importDefault(require("react-native-device-info"));
|
|
46
46
|
const react_native_play_install_referrer_1 = require("react-native-play-install-referrer");
|
|
47
|
+
// Development environment check for React Native
|
|
48
|
+
const isDevelopmentEnvironment = typeof __DEV__ !== 'undefined' && __DEV__;
|
|
47
49
|
const ASYNC_KEYS = {
|
|
48
50
|
REFERRER_LINK: '@app_referrer_link',
|
|
49
51
|
USER_PURCHASE: '@app_user_purchase',
|
|
@@ -51,13 +53,16 @@ const ASYNC_KEYS = {
|
|
|
51
53
|
COMPANY_CODE: '@app_company_code',
|
|
52
54
|
USER_ACCOUNT_TOKEN: '@app_user_account_token',
|
|
53
55
|
IOS_OFFER_CODE: '@app_ios_offer_code',
|
|
56
|
+
AFFILIATE_STORED_DATE: '@app_affiliate_stored_date',
|
|
54
57
|
};
|
|
55
58
|
// STARTING CONTEXT IMPLEMENTATION
|
|
56
59
|
exports.DeepLinkIapContext = (0, react_1.createContext)({
|
|
57
60
|
referrerLink: '',
|
|
58
61
|
userId: '',
|
|
59
62
|
OfferCode: null,
|
|
60
|
-
returnInsertAffiliateIdentifier: () => __awaiter(void 0, void 0, void 0, function* () { return ''; }),
|
|
63
|
+
returnInsertAffiliateIdentifier: (ignoreTimeout) => __awaiter(void 0, void 0, void 0, function* () { return ''; }),
|
|
64
|
+
isAffiliateAttributionValid: () => __awaiter(void 0, void 0, void 0, function* () { return false; }),
|
|
65
|
+
getAffiliateStoredDate: () => __awaiter(void 0, void 0, void 0, function* () { return null; }),
|
|
61
66
|
validatePurchaseWithIapticAPI: (jsonIapPurchase, iapticAppId, iapticAppName, iapticPublicKey) => __awaiter(void 0, void 0, void 0, function* () { return false; }),
|
|
62
67
|
returnUserAccountTokenAndStoreExpectedTransaction: () => __awaiter(void 0, void 0, void 0, function* () { return ''; }),
|
|
63
68
|
storeExpectedStoreTransaction: (purchaseToken) => __awaiter(void 0, void 0, void 0, function* () { }),
|
|
@@ -66,7 +71,7 @@ exports.DeepLinkIapContext = (0, react_1.createContext)({
|
|
|
66
71
|
setInsertAffiliateIdentifier: (referringLink) => __awaiter(void 0, void 0, void 0, function* () { }),
|
|
67
72
|
setInsertAffiliateIdentifierChangeCallback: (callback) => { },
|
|
68
73
|
handleInsertLinks: (url) => __awaiter(void 0, void 0, void 0, function* () { return false; }),
|
|
69
|
-
initialize: (code, verboseLogging, insertLinksEnabled, insertLinksClipboardEnabled) => __awaiter(void 0, void 0, void 0, function* () { }),
|
|
74
|
+
initialize: (code, verboseLogging, insertLinksEnabled, insertLinksClipboardEnabled, affiliateAttributionActiveTime) => __awaiter(void 0, void 0, void 0, function* () { }),
|
|
70
75
|
isInitialized: false,
|
|
71
76
|
});
|
|
72
77
|
const DeepLinkIapProvider = ({ children, }) => {
|
|
@@ -78,12 +83,16 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
78
83
|
const [insertLinksEnabled, setInsertLinksEnabled] = (0, react_1.useState)(false);
|
|
79
84
|
const [insertLinksClipboardEnabled, setInsertLinksClipboardEnabled] = (0, react_1.useState)(false);
|
|
80
85
|
const [OfferCode, setOfferCode] = (0, react_1.useState)(null);
|
|
86
|
+
const [affiliateAttributionActiveTime, setAffiliateAttributionActiveTime] = (0, react_1.useState)(null);
|
|
81
87
|
const insertAffiliateIdentifierChangeCallbackRef = (0, react_1.useRef)(null);
|
|
82
88
|
// MARK: Initialize the SDK
|
|
83
|
-
const initialize = (companyCode_1, ...args_1) => __awaiter(void 0, [companyCode_1, ...args_1], void 0, function* (companyCode, verboseLogging = false, insertLinksEnabled = false, insertLinksClipboardEnabled = false) {
|
|
89
|
+
const initialize = (companyCode_1, ...args_1) => __awaiter(void 0, [companyCode_1, ...args_1], void 0, function* (companyCode, verboseLogging = false, insertLinksEnabled = false, insertLinksClipboardEnabled = false, affiliateAttributionActiveTime) {
|
|
84
90
|
setVerboseLogging(verboseLogging);
|
|
85
91
|
setInsertLinksEnabled(insertLinksEnabled);
|
|
86
92
|
setInsertLinksClipboardEnabled(insertLinksClipboardEnabled);
|
|
93
|
+
if (affiliateAttributionActiveTime !== undefined) {
|
|
94
|
+
setAffiliateAttributionActiveTime(affiliateAttributionActiveTime);
|
|
95
|
+
}
|
|
87
96
|
if (verboseLogging) {
|
|
88
97
|
console.log('[Insert Affiliate] [VERBOSE] Starting SDK initialization...');
|
|
89
98
|
console.log('[Insert Affiliate] [VERBOSE] Company code provided:', companyCode ? 'Yes' : 'No');
|
|
@@ -1051,16 +1060,33 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
1051
1060
|
}
|
|
1052
1061
|
}
|
|
1053
1062
|
catch (error) {
|
|
1063
|
+
// Handle E_IAP_NOT_AVAILABLE error gracefully
|
|
1064
|
+
if ((error === null || error === void 0 ? void 0 : error.code) === 'E_IAP_NOT_AVAILABLE' ||
|
|
1065
|
+
(error instanceof Error && error.message.includes('E_IAP_NOT_AVAILABLE'))) {
|
|
1066
|
+
if (isDevelopmentEnvironment) {
|
|
1067
|
+
console.warn('[Insert Affiliate] IAP not available in development environment. Cannot store expected transaction.');
|
|
1068
|
+
verboseLog('E_IAP_NOT_AVAILABLE error in returnUserAccountTokenAndStoreExpectedTransaction - gracefully handling in development');
|
|
1069
|
+
}
|
|
1070
|
+
return null; // Return null but don't crash
|
|
1071
|
+
}
|
|
1054
1072
|
console.error('[Insert Affiliate] Error in returnUserAccountTokenAndStoreExpectedTransaction:', error);
|
|
1055
1073
|
return null;
|
|
1056
1074
|
}
|
|
1057
1075
|
;
|
|
1058
1076
|
});
|
|
1059
1077
|
// MARK: Return Insert Affiliate Identifier
|
|
1060
|
-
|
|
1061
|
-
const returnInsertAffiliateIdentifier = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
1078
|
+
const returnInsertAffiliateIdentifier = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (ignoreTimeout = false) {
|
|
1062
1079
|
try {
|
|
1063
|
-
verboseLog(
|
|
1080
|
+
verboseLog(`Getting insert affiliate identifier (ignoreTimeout: ${ignoreTimeout})...`);
|
|
1081
|
+
// If timeout is enabled and we're not ignoring it, check validity first
|
|
1082
|
+
if (!ignoreTimeout && affiliateAttributionActiveTime) {
|
|
1083
|
+
const isValid = yield isAffiliateAttributionValid();
|
|
1084
|
+
if (!isValid) {
|
|
1085
|
+
verboseLog('Attribution has expired, returning null');
|
|
1086
|
+
return null;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
// Now get the actual identifier
|
|
1064
1090
|
verboseLog(`React state - referrerLink: ${referrerLink || 'empty'}, userId: ${userId || 'empty'}`);
|
|
1065
1091
|
// Try React state first
|
|
1066
1092
|
if (referrerLink && userId) {
|
|
@@ -1092,6 +1118,48 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
1092
1118
|
return null;
|
|
1093
1119
|
}
|
|
1094
1120
|
});
|
|
1121
|
+
// MARK: Attribution Timeout Functions
|
|
1122
|
+
// Check if the current affiliate attribution is still valid based on timeout
|
|
1123
|
+
const isAffiliateAttributionValid = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
1124
|
+
try {
|
|
1125
|
+
// If no timeout is set, attribution is always valid
|
|
1126
|
+
if (!affiliateAttributionActiveTime) {
|
|
1127
|
+
verboseLog('No attribution timeout set, attribution is valid');
|
|
1128
|
+
return true;
|
|
1129
|
+
}
|
|
1130
|
+
const storedDate = yield getAffiliateStoredDate();
|
|
1131
|
+
if (!storedDate) {
|
|
1132
|
+
verboseLog('No stored date found, attribution is invalid');
|
|
1133
|
+
return false;
|
|
1134
|
+
}
|
|
1135
|
+
const now = new Date();
|
|
1136
|
+
const timeDifferenceSeconds = Math.floor((now.getTime() - storedDate.getTime()) / 1000);
|
|
1137
|
+
const isValid = timeDifferenceSeconds <= affiliateAttributionActiveTime;
|
|
1138
|
+
verboseLog(`Attribution timeout check: stored=${storedDate.toISOString()}, now=${now.toISOString()}, diff=${timeDifferenceSeconds}s, timeout=${affiliateAttributionActiveTime}s, valid=${isValid}`);
|
|
1139
|
+
return isValid;
|
|
1140
|
+
}
|
|
1141
|
+
catch (error) {
|
|
1142
|
+
verboseLog(`Error checking attribution validity: ${error}`);
|
|
1143
|
+
return false;
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1146
|
+
// Get the date when the affiliate identifier was stored
|
|
1147
|
+
const getAffiliateStoredDate = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
1148
|
+
try {
|
|
1149
|
+
const storedDateString = yield getValueFromAsync(ASYNC_KEYS.AFFILIATE_STORED_DATE);
|
|
1150
|
+
if (!storedDateString) {
|
|
1151
|
+
verboseLog('No affiliate stored date found');
|
|
1152
|
+
return null;
|
|
1153
|
+
}
|
|
1154
|
+
const storedDate = new Date(storedDateString);
|
|
1155
|
+
verboseLog(`Retrieved affiliate stored date: ${storedDate.toISOString()}`);
|
|
1156
|
+
return storedDate;
|
|
1157
|
+
}
|
|
1158
|
+
catch (error) {
|
|
1159
|
+
verboseLog(`Error getting affiliate stored date: ${error}`);
|
|
1160
|
+
return null;
|
|
1161
|
+
}
|
|
1162
|
+
});
|
|
1095
1163
|
// MARK: Insert Affiliate Identifier
|
|
1096
1164
|
function setInsertAffiliateIdentifier(referringLink) {
|
|
1097
1165
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -1174,10 +1242,20 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
1174
1242
|
function storeInsertAffiliateIdentifier(_a) {
|
|
1175
1243
|
return __awaiter(this, arguments, void 0, function* ({ link }) {
|
|
1176
1244
|
console.log(`[Insert Affiliate] Storing affiliate identifier: ${link}`);
|
|
1245
|
+
// Check if we're trying to store the same link (prevent duplicate storage)
|
|
1246
|
+
const existingLink = yield getValueFromAsync(ASYNC_KEYS.REFERRER_LINK);
|
|
1247
|
+
if (existingLink === link) {
|
|
1248
|
+
verboseLog(`Link ${link} is already stored, skipping duplicate storage`);
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1177
1251
|
verboseLog(`Updating React state with referrer link: ${link}`);
|
|
1178
1252
|
setReferrerLink(link);
|
|
1179
1253
|
verboseLog(`Saving referrer link to AsyncStorage...`);
|
|
1180
1254
|
yield saveValueInAsync(ASYNC_KEYS.REFERRER_LINK, link);
|
|
1255
|
+
// Store the current date/time when the affiliate identifier is stored
|
|
1256
|
+
const currentDate = new Date().toISOString();
|
|
1257
|
+
verboseLog(`Saving affiliate stored date: ${currentDate}`);
|
|
1258
|
+
yield saveValueInAsync(ASYNC_KEYS.AFFILIATE_STORED_DATE, currentDate);
|
|
1181
1259
|
verboseLog(`Referrer link saved to AsyncStorage successfully`);
|
|
1182
1260
|
// Automatically fetch and store offer code for any affiliate identifier
|
|
1183
1261
|
verboseLog('Attempting to fetch offer code for stored affiliate identifier...');
|
|
@@ -1191,7 +1269,21 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
1191
1269
|
});
|
|
1192
1270
|
}
|
|
1193
1271
|
const validatePurchaseWithIapticAPI = (jsonIapPurchase, iapticAppId, iapticAppName, iapticPublicKey) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1272
|
+
var _a;
|
|
1194
1273
|
try {
|
|
1274
|
+
// Check for E_IAP_NOT_AVAILABLE error in development environment
|
|
1275
|
+
if (((_a = jsonIapPurchase === null || jsonIapPurchase === void 0 ? void 0 : jsonIapPurchase.error) === null || _a === void 0 ? void 0 : _a.code) === 'E_IAP_NOT_AVAILABLE' ||
|
|
1276
|
+
(jsonIapPurchase === null || jsonIapPurchase === void 0 ? void 0 : jsonIapPurchase.code) === 'E_IAP_NOT_AVAILABLE' ||
|
|
1277
|
+
(typeof jsonIapPurchase === 'string' && jsonIapPurchase.includes('E_IAP_NOT_AVAILABLE'))) {
|
|
1278
|
+
if (isDevelopmentEnvironment) {
|
|
1279
|
+
console.warn('[Insert Affiliate] IAP not available in development environment. This is expected behavior.');
|
|
1280
|
+
verboseLog('E_IAP_NOT_AVAILABLE error detected in development - gracefully handling');
|
|
1281
|
+
}
|
|
1282
|
+
else {
|
|
1283
|
+
console.error('[Insert Affiliate] IAP not available in production environment. Please check your IAP configuration.');
|
|
1284
|
+
}
|
|
1285
|
+
return false; // Return false but don't crash
|
|
1286
|
+
}
|
|
1195
1287
|
const baseRequestBody = {
|
|
1196
1288
|
id: iapticAppId,
|
|
1197
1289
|
type: 'application',
|
|
@@ -1241,6 +1333,18 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
1241
1333
|
}
|
|
1242
1334
|
}
|
|
1243
1335
|
catch (error) {
|
|
1336
|
+
// Handle E_IAP_NOT_AVAILABLE error gracefully
|
|
1337
|
+
if ((error === null || error === void 0 ? void 0 : error.code) === 'E_IAP_NOT_AVAILABLE' ||
|
|
1338
|
+
(error instanceof Error && error.message.includes('E_IAP_NOT_AVAILABLE'))) {
|
|
1339
|
+
if (isDevelopmentEnvironment) {
|
|
1340
|
+
console.warn('[Insert Affiliate] IAP not available in development environment. SDK will continue without purchase validation.');
|
|
1341
|
+
verboseLog('E_IAP_NOT_AVAILABLE error caught in validatePurchaseWithIapticAPI - gracefully handling in development');
|
|
1342
|
+
}
|
|
1343
|
+
else {
|
|
1344
|
+
console.error('[Insert Affiliate] IAP not available in production environment. Please check your IAP configuration.');
|
|
1345
|
+
}
|
|
1346
|
+
return false; // Return false but don't crash
|
|
1347
|
+
}
|
|
1244
1348
|
if (error instanceof Error) {
|
|
1245
1349
|
console.error(`validatePurchaseWithIapticAPI Error: ${error.message}`);
|
|
1246
1350
|
}
|
|
@@ -1426,6 +1530,8 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
1426
1530
|
OfferCode,
|
|
1427
1531
|
setShortCode,
|
|
1428
1532
|
returnInsertAffiliateIdentifier,
|
|
1533
|
+
isAffiliateAttributionValid,
|
|
1534
|
+
getAffiliateStoredDate,
|
|
1429
1535
|
storeExpectedStoreTransaction,
|
|
1430
1536
|
returnUserAccountTokenAndStoreExpectedTransaction,
|
|
1431
1537
|
validatePurchaseWithIapticAPI,
|
|
@@ -6,13 +6,15 @@ declare const useDeepLinkIapProvider: () => {
|
|
|
6
6
|
}, iapticAppId: string, iapticAppName: string, iapticPublicKey: string) => Promise<boolean>;
|
|
7
7
|
storeExpectedStoreTransaction: (purchaseToken: string) => Promise<void>;
|
|
8
8
|
returnUserAccountTokenAndStoreExpectedTransaction: () => Promise<string | null>;
|
|
9
|
-
returnInsertAffiliateIdentifier: () => Promise<string | null>;
|
|
9
|
+
returnInsertAffiliateIdentifier: (ignoreTimeout?: boolean) => Promise<string | null>;
|
|
10
|
+
isAffiliateAttributionValid: () => Promise<boolean>;
|
|
11
|
+
getAffiliateStoredDate: () => Promise<Date | null>;
|
|
10
12
|
trackEvent: (eventName: string) => Promise<void>;
|
|
11
13
|
setShortCode: (shortCode: string) => Promise<void>;
|
|
12
14
|
setInsertAffiliateIdentifier: (referringLink: string) => Promise<void | string>;
|
|
13
15
|
setInsertAffiliateIdentifierChangeCallback: (callback: import("./DeepLinkIapProvider").InsertAffiliateIdentifierChangeCallback | null) => void;
|
|
14
16
|
handleInsertLinks: (url: string) => Promise<boolean>;
|
|
15
|
-
initialize: (code: string | null, verboseLogging?: boolean, insertLinksEnabled?: boolean, insertLinksClipboardEnabled?: boolean) => Promise<void>;
|
|
17
|
+
initialize: (code: string | null, verboseLogging?: boolean, insertLinksEnabled?: boolean, insertLinksClipboardEnabled?: boolean, affiliateAttributionActiveTime?: number) => Promise<void>;
|
|
16
18
|
isInitialized: boolean;
|
|
17
19
|
OfferCode: string | null;
|
|
18
20
|
};
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const react_1 = require("react");
|
|
4
4
|
const DeepLinkIapProvider_1 = require("./DeepLinkIapProvider");
|
|
5
5
|
const useDeepLinkIapProvider = () => {
|
|
6
|
-
const { referrerLink, userId, validatePurchaseWithIapticAPI, storeExpectedStoreTransaction, returnUserAccountTokenAndStoreExpectedTransaction, returnInsertAffiliateIdentifier, trackEvent, setShortCode, setInsertAffiliateIdentifier, setInsertAffiliateIdentifierChangeCallback, handleInsertLinks, initialize, isInitialized, OfferCode, } = (0, react_1.useContext)(DeepLinkIapProvider_1.DeepLinkIapContext);
|
|
6
|
+
const { referrerLink, userId, validatePurchaseWithIapticAPI, storeExpectedStoreTransaction, returnUserAccountTokenAndStoreExpectedTransaction, returnInsertAffiliateIdentifier, isAffiliateAttributionValid, getAffiliateStoredDate, trackEvent, setShortCode, setInsertAffiliateIdentifier, setInsertAffiliateIdentifierChangeCallback, handleInsertLinks, initialize, isInitialized, OfferCode, } = (0, react_1.useContext)(DeepLinkIapProvider_1.DeepLinkIapContext);
|
|
7
7
|
return {
|
|
8
8
|
referrerLink,
|
|
9
9
|
userId,
|
|
@@ -11,6 +11,8 @@ const useDeepLinkIapProvider = () => {
|
|
|
11
11
|
storeExpectedStoreTransaction,
|
|
12
12
|
returnUserAccountTokenAndStoreExpectedTransaction,
|
|
13
13
|
returnInsertAffiliateIdentifier,
|
|
14
|
+
isAffiliateAttributionValid,
|
|
15
|
+
getAffiliateStoredDate,
|
|
14
16
|
trackEvent,
|
|
15
17
|
setShortCode,
|
|
16
18
|
setInsertAffiliateIdentifier,
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -172,7 +172,8 @@ const Child = () => {
|
|
|
172
172
|
"{{ your-company-code }}",
|
|
173
173
|
false, // Enable for debugging
|
|
174
174
|
true, // Enables Insert Links
|
|
175
|
-
true
|
|
175
|
+
true, // Enable Insert Links Clipboard access to avoid permission prompt
|
|
176
|
+
604800 // Optional: Attribution timeout in seconds (7 days)
|
|
176
177
|
);
|
|
177
178
|
}
|
|
178
179
|
}, [initialize, isInitialized]);
|
|
@@ -1227,3 +1228,84 @@ Short codes must meet the following criteria:
|
|
|
1227
1228
|
onPress={() => setShortCode('JOIN_123')}
|
|
1228
1229
|
/>
|
|
1229
1230
|
```
|
|
1231
|
+
|
|
1232
|
+
### Attribution Timeout
|
|
1233
|
+
|
|
1234
|
+
You can configure how long an affiliate link attribution remains active after being clicked. This allows you to control the attribution window for commissions.
|
|
1235
|
+
|
|
1236
|
+
#### Basic Usage
|
|
1237
|
+
|
|
1238
|
+
When initializing the SDK, you can specify the attribution timeout in seconds:
|
|
1239
|
+
|
|
1240
|
+
```javascript
|
|
1241
|
+
const Child = () => {
|
|
1242
|
+
const { initialize, isInitialized } = useDeepLinkIapProvider();
|
|
1243
|
+
|
|
1244
|
+
useEffect(() => {
|
|
1245
|
+
if (!isInitialized) {
|
|
1246
|
+
// Set attribution timeout to 7 days (7 * 24 * 60 * 60 = 604800 seconds)
|
|
1247
|
+
initialize(
|
|
1248
|
+
"{{ your-company-code }}",
|
|
1249
|
+
false, // verbose logging
|
|
1250
|
+
false, // insert links enabled
|
|
1251
|
+
false, // insert links clipboard enabled
|
|
1252
|
+
604800 // attribution timeout in seconds
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
}, [initialize, isInitialized]);
|
|
1256
|
+
}
|
|
1257
|
+
```
|
|
1258
|
+
|
|
1259
|
+
**When to use `affiliateAttributionActiveTime`:**
|
|
1260
|
+
- Set to a number in seconds to define how long affiliate attributions remain active
|
|
1261
|
+
- Set to `null` or omit to disable attribution timeout (attribution never expires)
|
|
1262
|
+
- Common values: 86400 (1 day), 604800 (7 days), 2592000 (30 days)
|
|
1263
|
+
|
|
1264
|
+
#### Common Timeout Values
|
|
1265
|
+
|
|
1266
|
+
```javascript
|
|
1267
|
+
// 1 day
|
|
1268
|
+
initialize("your-company-code", false, false, false, 86400);
|
|
1269
|
+
|
|
1270
|
+
// 7 days (default for many platforms)
|
|
1271
|
+
initialize("your-company-code", false, false, false, 604800);
|
|
1272
|
+
|
|
1273
|
+
// 30 days
|
|
1274
|
+
initialize("your-company-code", false, false, false, 2592000);
|
|
1275
|
+
|
|
1276
|
+
// No timeout (attribution never expires)
|
|
1277
|
+
initialize("your-company-code", false, false, false); // or pass null/undefined
|
|
1278
|
+
```
|
|
1279
|
+
|
|
1280
|
+
#### Advanced Usage
|
|
1281
|
+
|
|
1282
|
+
The SDK provides methods to work with attribution timeouts:
|
|
1283
|
+
|
|
1284
|
+
```javascript
|
|
1285
|
+
const {
|
|
1286
|
+
returnInsertAffiliateIdentifier,
|
|
1287
|
+
isAffiliateAttributionValid,
|
|
1288
|
+
getAffiliateStoredDate
|
|
1289
|
+
} = useDeepLinkIapProvider();
|
|
1290
|
+
|
|
1291
|
+
// Get affiliate identifier (respects timeout)
|
|
1292
|
+
const identifier = await returnInsertAffiliateIdentifier();
|
|
1293
|
+
|
|
1294
|
+
// Get affiliate identifier ignoring timeout
|
|
1295
|
+
const rawIdentifier = await returnInsertAffiliateIdentifier(true);
|
|
1296
|
+
|
|
1297
|
+
// Check if attribution is still valid
|
|
1298
|
+
const isValid = await isAffiliateAttributionValid();
|
|
1299
|
+
|
|
1300
|
+
// Get the date when affiliate was first stored
|
|
1301
|
+
const storedDate = await getAffiliateStoredDate();
|
|
1302
|
+
```
|
|
1303
|
+
|
|
1304
|
+
#### How It Works
|
|
1305
|
+
|
|
1306
|
+
1. **Attribution Storage**: When an affiliate link is clicked and processed, the SDK stores both the affiliate identifier and the current timestamp
|
|
1307
|
+
2. **Timeout Check**: When `returnInsertAffiliateIdentifier()` is called, the SDK checks if the stored attribution is still within the timeout window
|
|
1308
|
+
3. **Expired Attribution**: If the attribution has expired, the method returns `null` instead of the affiliate identifier
|
|
1309
|
+
4. **Bypass Option**: You can bypass the timeout check by passing `true` to `returnInsertAffiliateIdentifier(true)`
|
|
1310
|
+
|
|
1311
|
+
This ensures that affiliates are only credited for purchases made within the specified attribution window, providing fair and accurate commission tracking.
|
|
@@ -7,6 +7,9 @@ import NetInfo from '@react-native-community/netinfo';
|
|
|
7
7
|
import DeviceInfo from 'react-native-device-info';
|
|
8
8
|
import { PlayInstallReferrer, PlayInstallReferrerInfo } from 'react-native-play-install-referrer';
|
|
9
9
|
|
|
10
|
+
// Development environment check for React Native
|
|
11
|
+
const isDevelopmentEnvironment = typeof __DEV__ !== 'undefined' && __DEV__;
|
|
12
|
+
|
|
10
13
|
// TYPES USED IN THIS PROVIDER
|
|
11
14
|
type T_DEEPLINK_IAP_PROVIDER = {
|
|
12
15
|
children: React.ReactNode;
|
|
@@ -22,7 +25,9 @@ type T_DEEPLINK_IAP_CONTEXT = {
|
|
|
22
25
|
referrerLink: string;
|
|
23
26
|
userId: string;
|
|
24
27
|
OfferCode: string | null;
|
|
25
|
-
returnInsertAffiliateIdentifier: () => Promise<string | null>;
|
|
28
|
+
returnInsertAffiliateIdentifier: (ignoreTimeout?: boolean) => Promise<string | null>;
|
|
29
|
+
isAffiliateAttributionValid: () => Promise<boolean>;
|
|
30
|
+
getAffiliateStoredDate: () => Promise<Date | null>;
|
|
26
31
|
validatePurchaseWithIapticAPI: (
|
|
27
32
|
jsonIapPurchase: CustomPurchase,
|
|
28
33
|
iapticAppId: string,
|
|
@@ -40,7 +45,7 @@ type T_DEEPLINK_IAP_CONTEXT = {
|
|
|
40
45
|
) => Promise<void | string>;
|
|
41
46
|
setInsertAffiliateIdentifierChangeCallback: (callback: InsertAffiliateIdentifierChangeCallback | null) => void;
|
|
42
47
|
handleInsertLinks: (url: string) => Promise<boolean>;
|
|
43
|
-
initialize: (code: string | null, verboseLogging?: boolean, insertLinksEnabled?: boolean, insertLinksClipboardEnabled?: boolean) => Promise<void>;
|
|
48
|
+
initialize: (code: string | null, verboseLogging?: boolean, insertLinksEnabled?: boolean, insertLinksClipboardEnabled?: boolean, affiliateAttributionActiveTime?: number) => Promise<void>;
|
|
44
49
|
isInitialized: boolean;
|
|
45
50
|
};
|
|
46
51
|
|
|
@@ -67,6 +72,7 @@ const ASYNC_KEYS = {
|
|
|
67
72
|
COMPANY_CODE: '@app_company_code',
|
|
68
73
|
USER_ACCOUNT_TOKEN: '@app_user_account_token',
|
|
69
74
|
IOS_OFFER_CODE: '@app_ios_offer_code',
|
|
75
|
+
AFFILIATE_STORED_DATE: '@app_affiliate_stored_date',
|
|
70
76
|
};
|
|
71
77
|
|
|
72
78
|
// STARTING CONTEXT IMPLEMENTATION
|
|
@@ -74,7 +80,9 @@ export const DeepLinkIapContext = createContext<T_DEEPLINK_IAP_CONTEXT>({
|
|
|
74
80
|
referrerLink: '',
|
|
75
81
|
userId: '',
|
|
76
82
|
OfferCode: null,
|
|
77
|
-
returnInsertAffiliateIdentifier: async () => '',
|
|
83
|
+
returnInsertAffiliateIdentifier: async (ignoreTimeout?: boolean) => '',
|
|
84
|
+
isAffiliateAttributionValid: async () => false,
|
|
85
|
+
getAffiliateStoredDate: async () => null,
|
|
78
86
|
validatePurchaseWithIapticAPI: async (
|
|
79
87
|
jsonIapPurchase: CustomPurchase,
|
|
80
88
|
iapticAppId: string,
|
|
@@ -88,7 +96,7 @@ export const DeepLinkIapContext = createContext<T_DEEPLINK_IAP_CONTEXT>({
|
|
|
88
96
|
setInsertAffiliateIdentifier: async (referringLink: string) => {},
|
|
89
97
|
setInsertAffiliateIdentifierChangeCallback: (callback: InsertAffiliateIdentifierChangeCallback | null) => {},
|
|
90
98
|
handleInsertLinks: async (url: string) => false,
|
|
91
|
-
initialize: async (code: string | null, verboseLogging?: boolean, insertLinksEnabled?: boolean, insertLinksClipboardEnabled?: boolean) => {},
|
|
99
|
+
initialize: async (code: string | null, verboseLogging?: boolean, insertLinksEnabled?: boolean, insertLinksClipboardEnabled?: boolean, affiliateAttributionActiveTime?: number) => {},
|
|
92
100
|
isInitialized: false,
|
|
93
101
|
});
|
|
94
102
|
|
|
@@ -103,13 +111,17 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
103
111
|
const [insertLinksEnabled, setInsertLinksEnabled] = useState<boolean>(false);
|
|
104
112
|
const [insertLinksClipboardEnabled, setInsertLinksClipboardEnabled] = useState<boolean>(false);
|
|
105
113
|
const [OfferCode, setOfferCode] = useState<string | null>(null);
|
|
114
|
+
const [affiliateAttributionActiveTime, setAffiliateAttributionActiveTime] = useState<number | null>(null);
|
|
106
115
|
const insertAffiliateIdentifierChangeCallbackRef = useRef<InsertAffiliateIdentifierChangeCallback | null>(null);
|
|
107
116
|
|
|
108
117
|
// MARK: Initialize the SDK
|
|
109
|
-
const initialize = async (companyCode: string | null, verboseLogging: boolean = false, insertLinksEnabled: boolean = false, insertLinksClipboardEnabled: boolean = false): Promise<void> => {
|
|
118
|
+
const initialize = async (companyCode: string | null, verboseLogging: boolean = false, insertLinksEnabled: boolean = false, insertLinksClipboardEnabled: boolean = false, affiliateAttributionActiveTime?: number): Promise<void> => {
|
|
110
119
|
setVerboseLogging(verboseLogging);
|
|
111
120
|
setInsertLinksEnabled(insertLinksEnabled);
|
|
112
121
|
setInsertLinksClipboardEnabled(insertLinksClipboardEnabled);
|
|
122
|
+
if (affiliateAttributionActiveTime !== undefined) {
|
|
123
|
+
setAffiliateAttributionActiveTime(affiliateAttributionActiveTime);
|
|
124
|
+
}
|
|
113
125
|
|
|
114
126
|
if (verboseLogging) {
|
|
115
127
|
console.log('[Insert Affiliate] [VERBOSE] Starting SDK initialization...');
|
|
@@ -1204,16 +1216,37 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1204
1216
|
return userAccountToken;
|
|
1205
1217
|
}
|
|
1206
1218
|
} catch (error) {
|
|
1219
|
+
// Handle E_IAP_NOT_AVAILABLE error gracefully
|
|
1220
|
+
if ((error as any)?.code === 'E_IAP_NOT_AVAILABLE' ||
|
|
1221
|
+
(error instanceof Error && error.message.includes('E_IAP_NOT_AVAILABLE'))) {
|
|
1222
|
+
|
|
1223
|
+
if (isDevelopmentEnvironment) {
|
|
1224
|
+
console.warn('[Insert Affiliate] IAP not available in development environment. Cannot store expected transaction.');
|
|
1225
|
+
verboseLog('E_IAP_NOT_AVAILABLE error in returnUserAccountTokenAndStoreExpectedTransaction - gracefully handling in development');
|
|
1226
|
+
}
|
|
1227
|
+
return null; // Return null but don't crash
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1207
1230
|
console.error('[Insert Affiliate] Error in returnUserAccountTokenAndStoreExpectedTransaction:', error);
|
|
1208
1231
|
return null;
|
|
1209
1232
|
};
|
|
1210
1233
|
};
|
|
1211
1234
|
|
|
1212
1235
|
// MARK: Return Insert Affiliate Identifier
|
|
1213
|
-
|
|
1214
|
-
const returnInsertAffiliateIdentifier = async (): Promise<string | null> => {
|
|
1236
|
+
const returnInsertAffiliateIdentifier = async (ignoreTimeout: boolean = false): Promise<string | null> => {
|
|
1215
1237
|
try {
|
|
1216
|
-
verboseLog(
|
|
1238
|
+
verboseLog(`Getting insert affiliate identifier (ignoreTimeout: ${ignoreTimeout})...`);
|
|
1239
|
+
|
|
1240
|
+
// If timeout is enabled and we're not ignoring it, check validity first
|
|
1241
|
+
if (!ignoreTimeout && affiliateAttributionActiveTime) {
|
|
1242
|
+
const isValid = await isAffiliateAttributionValid();
|
|
1243
|
+
if (!isValid) {
|
|
1244
|
+
verboseLog('Attribution has expired, returning null');
|
|
1245
|
+
return null;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// Now get the actual identifier
|
|
1217
1250
|
verboseLog(`React state - referrerLink: ${referrerLink || 'empty'}, userId: ${userId || 'empty'}`);
|
|
1218
1251
|
|
|
1219
1252
|
// Try React state first
|
|
@@ -1252,6 +1285,54 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1252
1285
|
}
|
|
1253
1286
|
};
|
|
1254
1287
|
|
|
1288
|
+
// MARK: Attribution Timeout Functions
|
|
1289
|
+
|
|
1290
|
+
// Check if the current affiliate attribution is still valid based on timeout
|
|
1291
|
+
const isAffiliateAttributionValid = async (): Promise<boolean> => {
|
|
1292
|
+
try {
|
|
1293
|
+
// If no timeout is set, attribution is always valid
|
|
1294
|
+
if (!affiliateAttributionActiveTime) {
|
|
1295
|
+
verboseLog('No attribution timeout set, attribution is valid');
|
|
1296
|
+
return true;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
const storedDate = await getAffiliateStoredDate();
|
|
1300
|
+
if (!storedDate) {
|
|
1301
|
+
verboseLog('No stored date found, attribution is invalid');
|
|
1302
|
+
return false;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
const now = new Date();
|
|
1306
|
+
const timeDifferenceSeconds = Math.floor((now.getTime() - storedDate.getTime()) / 1000);
|
|
1307
|
+
const isValid = timeDifferenceSeconds <= affiliateAttributionActiveTime;
|
|
1308
|
+
|
|
1309
|
+
verboseLog(`Attribution timeout check: stored=${storedDate.toISOString()}, now=${now.toISOString()}, diff=${timeDifferenceSeconds}s, timeout=${affiliateAttributionActiveTime}s, valid=${isValid}`);
|
|
1310
|
+
|
|
1311
|
+
return isValid;
|
|
1312
|
+
} catch (error) {
|
|
1313
|
+
verboseLog(`Error checking attribution validity: ${error}`);
|
|
1314
|
+
return false;
|
|
1315
|
+
}
|
|
1316
|
+
};
|
|
1317
|
+
|
|
1318
|
+
// Get the date when the affiliate identifier was stored
|
|
1319
|
+
const getAffiliateStoredDate = async (): Promise<Date | null> => {
|
|
1320
|
+
try {
|
|
1321
|
+
const storedDateString = await getValueFromAsync(ASYNC_KEYS.AFFILIATE_STORED_DATE);
|
|
1322
|
+
if (!storedDateString) {
|
|
1323
|
+
verboseLog('No affiliate stored date found');
|
|
1324
|
+
return null;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
const storedDate = new Date(storedDateString);
|
|
1328
|
+
verboseLog(`Retrieved affiliate stored date: ${storedDate.toISOString()}`);
|
|
1329
|
+
return storedDate;
|
|
1330
|
+
} catch (error) {
|
|
1331
|
+
verboseLog(`Error getting affiliate stored date: ${error}`);
|
|
1332
|
+
return null;
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
|
|
1255
1336
|
// MARK: Insert Affiliate Identifier
|
|
1256
1337
|
|
|
1257
1338
|
async function setInsertAffiliateIdentifier(
|
|
@@ -1349,10 +1430,24 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1349
1430
|
|
|
1350
1431
|
async function storeInsertAffiliateIdentifier({ link }: { link: string }) {
|
|
1351
1432
|
console.log(`[Insert Affiliate] Storing affiliate identifier: ${link}`);
|
|
1433
|
+
|
|
1434
|
+
// Check if we're trying to store the same link (prevent duplicate storage)
|
|
1435
|
+
const existingLink = await getValueFromAsync(ASYNC_KEYS.REFERRER_LINK);
|
|
1436
|
+
if (existingLink === link) {
|
|
1437
|
+
verboseLog(`Link ${link} is already stored, skipping duplicate storage`);
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1352
1441
|
verboseLog(`Updating React state with referrer link: ${link}`);
|
|
1353
1442
|
setReferrerLink(link);
|
|
1354
1443
|
verboseLog(`Saving referrer link to AsyncStorage...`);
|
|
1355
1444
|
await saveValueInAsync(ASYNC_KEYS.REFERRER_LINK, link);
|
|
1445
|
+
|
|
1446
|
+
// Store the current date/time when the affiliate identifier is stored
|
|
1447
|
+
const currentDate = new Date().toISOString();
|
|
1448
|
+
verboseLog(`Saving affiliate stored date: ${currentDate}`);
|
|
1449
|
+
await saveValueInAsync(ASYNC_KEYS.AFFILIATE_STORED_DATE, currentDate);
|
|
1450
|
+
|
|
1356
1451
|
verboseLog(`Referrer link saved to AsyncStorage successfully`);
|
|
1357
1452
|
|
|
1358
1453
|
// Automatically fetch and store offer code for any affiliate identifier
|
|
@@ -1374,6 +1469,20 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1374
1469
|
iapticPublicKey: string
|
|
1375
1470
|
): Promise<boolean> => {
|
|
1376
1471
|
try {
|
|
1472
|
+
// Check for E_IAP_NOT_AVAILABLE error in development environment
|
|
1473
|
+
if ((jsonIapPurchase as any)?.error?.code === 'E_IAP_NOT_AVAILABLE' ||
|
|
1474
|
+
(jsonIapPurchase as any)?.code === 'E_IAP_NOT_AVAILABLE' ||
|
|
1475
|
+
(typeof jsonIapPurchase === 'string' && (jsonIapPurchase as string).includes('E_IAP_NOT_AVAILABLE'))) {
|
|
1476
|
+
|
|
1477
|
+
if (isDevelopmentEnvironment) {
|
|
1478
|
+
console.warn('[Insert Affiliate] IAP not available in development environment. This is expected behavior.');
|
|
1479
|
+
verboseLog('E_IAP_NOT_AVAILABLE error detected in development - gracefully handling');
|
|
1480
|
+
} else {
|
|
1481
|
+
console.error('[Insert Affiliate] IAP not available in production environment. Please check your IAP configuration.');
|
|
1482
|
+
}
|
|
1483
|
+
return false; // Return false but don't crash
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1377
1486
|
const baseRequestBody: RequestBody = {
|
|
1378
1487
|
id: iapticAppId,
|
|
1379
1488
|
type: 'application',
|
|
@@ -1432,6 +1541,19 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1432
1541
|
return false;
|
|
1433
1542
|
}
|
|
1434
1543
|
} catch (error) {
|
|
1544
|
+
// Handle E_IAP_NOT_AVAILABLE error gracefully
|
|
1545
|
+
if ((error as any)?.code === 'E_IAP_NOT_AVAILABLE' ||
|
|
1546
|
+
(error instanceof Error && error.message.includes('E_IAP_NOT_AVAILABLE'))) {
|
|
1547
|
+
|
|
1548
|
+
if (isDevelopmentEnvironment) {
|
|
1549
|
+
console.warn('[Insert Affiliate] IAP not available in development environment. SDK will continue without purchase validation.');
|
|
1550
|
+
verboseLog('E_IAP_NOT_AVAILABLE error caught in validatePurchaseWithIapticAPI - gracefully handling in development');
|
|
1551
|
+
} else {
|
|
1552
|
+
console.error('[Insert Affiliate] IAP not available in production environment. Please check your IAP configuration.');
|
|
1553
|
+
}
|
|
1554
|
+
return false; // Return false but don't crash
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1435
1557
|
if (error instanceof Error) {
|
|
1436
1558
|
console.error(`validatePurchaseWithIapticAPI Error: ${error.message}`);
|
|
1437
1559
|
} else {
|
|
@@ -1658,6 +1780,8 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
1658
1780
|
OfferCode,
|
|
1659
1781
|
setShortCode,
|
|
1660
1782
|
returnInsertAffiliateIdentifier,
|
|
1783
|
+
isAffiliateAttributionValid,
|
|
1784
|
+
getAffiliateStoredDate,
|
|
1661
1785
|
storeExpectedStoreTransaction,
|
|
1662
1786
|
returnUserAccountTokenAndStoreExpectedTransaction,
|
|
1663
1787
|
validatePurchaseWithIapticAPI,
|
|
@@ -9,6 +9,8 @@ const useDeepLinkIapProvider = () => {
|
|
|
9
9
|
storeExpectedStoreTransaction,
|
|
10
10
|
returnUserAccountTokenAndStoreExpectedTransaction,
|
|
11
11
|
returnInsertAffiliateIdentifier,
|
|
12
|
+
isAffiliateAttributionValid,
|
|
13
|
+
getAffiliateStoredDate,
|
|
12
14
|
trackEvent,
|
|
13
15
|
setShortCode,
|
|
14
16
|
setInsertAffiliateIdentifier,
|
|
@@ -26,6 +28,8 @@ const useDeepLinkIapProvider = () => {
|
|
|
26
28
|
storeExpectedStoreTransaction,
|
|
27
29
|
returnUserAccountTokenAndStoreExpectedTransaction,
|
|
28
30
|
returnInsertAffiliateIdentifier,
|
|
31
|
+
isAffiliateAttributionValid,
|
|
32
|
+
getAffiliateStoredDate,
|
|
29
33
|
trackEvent,
|
|
30
34
|
setShortCode,
|
|
31
35
|
setInsertAffiliateIdentifier,
|