insert-affiliate-js-sdk 1.0.2 → 1.1.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 +10 -0
- package/CHANGELOG.md +60 -0
- package/README.md +600 -30
- package/dist/index.d.ts +35 -4
- package/dist/index.js +347 -37
- package/package.json +1 -1
- package/src/sdk/InsertAffiliate.ts +459 -37
|
@@ -19,98 +19,463 @@ interface ExpectedTransactionPayload {
|
|
|
19
19
|
storedDate: string;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export interface AffiliateDetails {
|
|
23
|
+
affiliateName: string;
|
|
24
|
+
affiliateShortCode: string;
|
|
25
|
+
deeplinkUrl: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type InsertAffiliateIdentifierChangeCallback = (identifier: string | null) => void;
|
|
29
|
+
|
|
22
30
|
export class InsertAffiliate {
|
|
23
31
|
private static isInitialized: boolean = false;
|
|
24
32
|
private static companyCode: string | null = null;
|
|
33
|
+
private static verboseLogging: boolean = false;
|
|
34
|
+
private static insertAffiliateIdentifierChangeCallback: InsertAffiliateIdentifierChangeCallback | null = null;
|
|
35
|
+
private static affiliateAttributionActiveTime: number | null = null; // in milliseconds
|
|
36
|
+
private static offerCode: string | null = null;
|
|
37
|
+
|
|
38
|
+
private static verboseLog(message: string): void {
|
|
39
|
+
if (this.verboseLogging) {
|
|
40
|
+
console.log(`[Insert Affiliate] [VERBOSE] ${message}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static async initialize(code: string | null, verboseLogging: boolean = false, affiliateAttributionActiveTime?: number): Promise<void> {
|
|
45
|
+
this.verboseLogging = verboseLogging;
|
|
46
|
+
|
|
47
|
+
if (verboseLogging) {
|
|
48
|
+
this.verboseLog('Starting SDK initialization...');
|
|
49
|
+
this.verboseLog(`Company code provided: ${code ? 'Yes' : 'No'}`);
|
|
50
|
+
this.verboseLog('Verbose logging enabled');
|
|
51
|
+
}
|
|
25
52
|
|
|
26
|
-
static async initialize(code: string | null): Promise<void> {
|
|
27
53
|
if (this.isInitialized) {
|
|
28
|
-
|
|
54
|
+
this.verboseLog('SDK already initialized, skipping');
|
|
29
55
|
return;
|
|
30
56
|
}
|
|
31
57
|
this.companyCode = code;
|
|
32
58
|
await saveValue('companyCode', code || '');
|
|
59
|
+
|
|
60
|
+
// Set attribution timeout if provided
|
|
61
|
+
if (affiliateAttributionActiveTime !== undefined) {
|
|
62
|
+
this.affiliateAttributionActiveTime = affiliateAttributionActiveTime;
|
|
63
|
+
await saveValue('affiliateAttributionActiveTime', affiliateAttributionActiveTime.toString());
|
|
64
|
+
this.verboseLog(`Attribution timeout set to: ${affiliateAttributionActiveTime}ms`);
|
|
65
|
+
}
|
|
66
|
+
|
|
33
67
|
this.isInitialized = true;
|
|
34
|
-
|
|
68
|
+
this.verboseLog(`SDK initialized ${code ? `with company code: ${code}` : 'without a company code.'}`);
|
|
69
|
+
this.verboseLog('Company code saved to storage');
|
|
70
|
+
this.verboseLog('SDK marked as initialized');
|
|
71
|
+
|
|
72
|
+
// Check for insertAffiliate URL parameter
|
|
73
|
+
await this.checkForInsertAffiliateParam();
|
|
35
74
|
}
|
|
36
75
|
|
|
37
|
-
static async
|
|
76
|
+
private static async checkForInsertAffiliateParam(): Promise<void> {
|
|
77
|
+
this.verboseLog('Checking for insertAffiliate URL parameter...');
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
// Only run in browser environments
|
|
81
|
+
if (typeof window === 'undefined' || !window.location) {
|
|
82
|
+
this.verboseLog('Not in browser environment, skipping URL parameter check');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.verboseLog(`Current URL: ${window.location.href}`);
|
|
87
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
88
|
+
const insertAffiliateParam = urlParams.get('insertAffiliate');
|
|
89
|
+
|
|
90
|
+
if (insertAffiliateParam) {
|
|
91
|
+
this.verboseLog(`Found insertAffiliate URL parameter: ${insertAffiliateParam}`);
|
|
92
|
+
await this.setShortCode(insertAffiliateParam);
|
|
93
|
+
this.verboseLog('Successfully processed insertAffiliate URL parameter');
|
|
94
|
+
} else {
|
|
95
|
+
this.verboseLog('No insertAffiliate URL parameter found');
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
this.verboseLog(`Error checking for insertAffiliate URL parameter: ${error}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static async returnInsertAffiliateIdentifier(ignoreTimeout: boolean = false): Promise<string | null> {
|
|
103
|
+
this.verboseLog('Getting insert affiliate identifier...');
|
|
38
104
|
const userId = await this.getOrCreateUserID();
|
|
39
105
|
const referrerLink = await getValue('referrerLink');
|
|
40
|
-
|
|
41
|
-
|
|
106
|
+
this.verboseLog(`User ID: ${userId || 'empty'}, Referrer link: ${referrerLink || 'empty'}`);
|
|
107
|
+
|
|
108
|
+
if (!referrerLink) {
|
|
109
|
+
this.verboseLog('No referrer link found, returning null');
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check attribution timeout unless explicitly ignored
|
|
114
|
+
if (!ignoreTimeout) {
|
|
115
|
+
const isValid = await this.isAffiliateAttributionValid();
|
|
116
|
+
if (!isValid) {
|
|
117
|
+
this.verboseLog('Attribution has expired, returning null');
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
this.verboseLog('Ignoring attribution timeout');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const identifier = `${referrerLink}-${userId}`;
|
|
125
|
+
this.verboseLog(`Returning affiliate identifier: ${identifier}`);
|
|
126
|
+
return identifier;
|
|
42
127
|
}
|
|
43
128
|
|
|
44
129
|
static async setInsertAffiliateIdentifier(referringLink: string): Promise<string | null> {
|
|
130
|
+
this.verboseLog(`Setting affiliate identifier. Input referringLink: ${referringLink}`);
|
|
131
|
+
|
|
45
132
|
const userId = await this.getOrCreateUserID();
|
|
46
|
-
|
|
133
|
+
this.verboseLog(`User ID: ${userId}`);
|
|
134
|
+
|
|
135
|
+
// Check if it's already a short code
|
|
136
|
+
const isShortCode = /^[a-zA-Z0-9]{3,25}$/.test(referringLink);
|
|
137
|
+
this.verboseLog(`Is short code: ${isShortCode}`);
|
|
138
|
+
|
|
139
|
+
const shortCode = isShortCode
|
|
47
140
|
? referringLink
|
|
48
141
|
: await this.fetchShortLink(referringLink);
|
|
49
142
|
|
|
50
|
-
if (!shortCode)
|
|
143
|
+
if (!shortCode) {
|
|
144
|
+
this.verboseLog('No short code found or generated, returning null');
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Check if the same short code is already stored
|
|
149
|
+
const existingShortCode = await getValue('referrerLink');
|
|
150
|
+
if (existingShortCode === shortCode) {
|
|
151
|
+
this.verboseLog(`Short code ${shortCode} is already set, not updating attribution date`);
|
|
152
|
+
const identifier = `${shortCode}-${userId}`;
|
|
153
|
+
this.verboseLog(`Returning existing identifier: ${identifier}`);
|
|
154
|
+
return identifier;
|
|
155
|
+
}
|
|
51
156
|
|
|
157
|
+
this.verboseLog(`Saving short code to storage: ${shortCode}`);
|
|
52
158
|
await saveValue('referrerLink', shortCode);
|
|
53
|
-
|
|
159
|
+
|
|
160
|
+
// Store the attribution date
|
|
161
|
+
const storedDate = new Date().toISOString();
|
|
162
|
+
await saveValue('affiliateStoredDate', storedDate);
|
|
163
|
+
this.verboseLog(`Short code saved successfully with stored date: ${storedDate}`);
|
|
164
|
+
|
|
165
|
+
const identifier = `${shortCode}-${userId}`;
|
|
166
|
+
this.verboseLog(`Returning identifier: ${identifier}`);
|
|
167
|
+
|
|
168
|
+
// Auto-fetch and store offer code (use just the short code, not the full identifier)
|
|
169
|
+
await this.fetchAndStoreOfferCode(shortCode);
|
|
170
|
+
|
|
171
|
+
// Trigger callback if one is registered
|
|
172
|
+
if (this.insertAffiliateIdentifierChangeCallback) {
|
|
173
|
+
this.verboseLog(`Triggering callback with identifier: ${identifier}`);
|
|
174
|
+
this.insertAffiliateIdentifierChangeCallback(identifier);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return identifier;
|
|
54
178
|
}
|
|
55
179
|
|
|
56
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Validates and sets a short code for affiliate tracking
|
|
182
|
+
* Validates the short code against the API before storing
|
|
183
|
+
* @param shortCode The short code to validate and set
|
|
184
|
+
* @returns true if the code exists and was successfully validated and stored, false otherwise
|
|
185
|
+
*/
|
|
186
|
+
static async setShortCode(shortCode: string): Promise<boolean> {
|
|
187
|
+
this.verboseLog(`Setting short code. Input: ${shortCode}`);
|
|
188
|
+
|
|
57
189
|
const valid = /^[a-zA-Z0-9]{3,25}$/.test(shortCode);
|
|
190
|
+
this.verboseLog(`Short code validation: ${valid ? 'Valid' : 'Invalid'}`);
|
|
191
|
+
|
|
58
192
|
if (!valid) {
|
|
59
|
-
|
|
60
|
-
return;
|
|
193
|
+
this.verboseLog('Invalid short code format, aborting');
|
|
194
|
+
return false;
|
|
61
195
|
}
|
|
196
|
+
|
|
197
|
+
// Validate that the short code exists in the system
|
|
198
|
+
const affiliateDetails = await this.getAffiliateDetails(shortCode);
|
|
199
|
+
if (!affiliateDetails) {
|
|
200
|
+
this.verboseLog(`Short code '${shortCode}' does not exist or validation failed`);
|
|
201
|
+
console.error(`[Insert Affiliate] Error: Short code '${shortCode}' does not exist or validation failed.`);
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.verboseLog(`Short code validated successfully for affiliate: ${affiliateDetails.affiliateName}`);
|
|
206
|
+
console.log(`[Insert Affiliate] Short code validated successfully for affiliate: ${affiliateDetails.affiliateName}`);
|
|
207
|
+
|
|
208
|
+
// If validation passes, set the Insert Affiliate Identifier
|
|
209
|
+
this.verboseLog('Calling setInsertAffiliateIdentifier with short code');
|
|
62
210
|
await this.setInsertAffiliateIdentifier(shortCode);
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
static setInsertAffiliateIdentifierChangeCallback(callback: InsertAffiliateIdentifierChangeCallback | null): void {
|
|
215
|
+
this.verboseLog(`Setting affiliate identifier change callback: ${callback ? 'callback provided' : 'callback cleared'}`);
|
|
216
|
+
this.insertAffiliateIdentifierChangeCallback = callback;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
static async isAffiliateAttributionValid(): Promise<boolean> {
|
|
220
|
+
this.verboseLog('Checking if affiliate attribution is valid...');
|
|
221
|
+
|
|
222
|
+
const storedDateStr = await getValue('affiliateStoredDate');
|
|
223
|
+
if (!storedDateStr) {
|
|
224
|
+
this.verboseLog('No stored date found, attribution invalid');
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Get timeout value from storage or class property
|
|
229
|
+
let timeoutMs = this.affiliateAttributionActiveTime;
|
|
230
|
+
if (timeoutMs === null) {
|
|
231
|
+
const storedTimeout = await getValue('affiliateAttributionActiveTime');
|
|
232
|
+
timeoutMs = storedTimeout ? parseInt(storedTimeout, 10) : null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// If no timeout is set, attribution never expires
|
|
236
|
+
if (timeoutMs === null) {
|
|
237
|
+
this.verboseLog('No attribution timeout configured, attribution is valid');
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const storedDate = new Date(storedDateStr);
|
|
242
|
+
const currentDate = new Date();
|
|
243
|
+
const elapsedMs = currentDate.getTime() - storedDate.getTime();
|
|
244
|
+
|
|
245
|
+
const isValid = elapsedMs <= timeoutMs;
|
|
246
|
+
this.verboseLog(`Attribution stored: ${storedDateStr}, elapsed: ${elapsedMs}ms, timeout: ${timeoutMs}ms, valid: ${isValid}`);
|
|
247
|
+
|
|
248
|
+
return isValid;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
static async getAffiliateStoredDate(): Promise<string | null> {
|
|
252
|
+
this.verboseLog('Getting affiliate stored date...');
|
|
253
|
+
const storedDate = await getValue('affiliateStoredDate');
|
|
254
|
+
this.verboseLog(`Stored date: ${storedDate || 'none'}`);
|
|
255
|
+
return storedDate;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Retrieve detailed information about an affiliate by their short code or deep link
|
|
260
|
+
* This method queries the API and does not store or set the affiliate identifier
|
|
261
|
+
* @param affiliateCode The short code or deep link to look up
|
|
262
|
+
* @returns AffiliateDetails if found, null otherwise
|
|
263
|
+
*/
|
|
264
|
+
static async getAffiliateDetails(affiliateCode: string): Promise<AffiliateDetails | null> {
|
|
265
|
+
this.verboseLog(`Getting affiliate details for: ${affiliateCode}`);
|
|
266
|
+
|
|
267
|
+
const companyCode = this.companyCode || await getValue('companyCode');
|
|
268
|
+
if (!companyCode) {
|
|
269
|
+
this.verboseLog('Cannot get affiliate details: no company code available');
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Strip UUID from code if present (e.g., "ABC123-uuid" becomes "ABC123")
|
|
274
|
+
const cleanCode = affiliateCode.includes('-') ? affiliateCode.split('-')[0] : affiliateCode;
|
|
275
|
+
this.verboseLog(`Clean code: ${cleanCode}`);
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
const url = 'https://api.insertaffiliate.com/V1/checkAffiliateExists';
|
|
279
|
+
const payload = {
|
|
280
|
+
companyId: companyCode,
|
|
281
|
+
affiliateCode: cleanCode,
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
this.verboseLog(`Making API call to: ${url}`);
|
|
285
|
+
this.verboseLog(`Payload: ${JSON.stringify(payload)}`);
|
|
286
|
+
|
|
287
|
+
const response = await fetch(url, {
|
|
288
|
+
method: 'POST',
|
|
289
|
+
headers: { 'Content-Type': 'application/json' },
|
|
290
|
+
body: JSON.stringify(payload),
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
this.verboseLog(`API response status: ${response.status}`);
|
|
294
|
+
|
|
295
|
+
if (!response.ok) {
|
|
296
|
+
this.verboseLog(`Failed to get affiliate details, status: ${response.status}`);
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const data = await response.json();
|
|
301
|
+
this.verboseLog(`API response data: ${JSON.stringify(data)}`);
|
|
302
|
+
|
|
303
|
+
if (data.exists && data.affiliate) {
|
|
304
|
+
const details: AffiliateDetails = {
|
|
305
|
+
affiliateName: data.affiliate.affiliateName,
|
|
306
|
+
affiliateShortCode: data.affiliate.affiliateShortCode,
|
|
307
|
+
deeplinkUrl: data.affiliate.deeplinkurl,
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
this.verboseLog(`Successfully retrieved affiliate details for: ${details.affiliateName}`);
|
|
311
|
+
return details;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
this.verboseLog('Affiliate does not exist');
|
|
315
|
+
return null;
|
|
316
|
+
} catch (error) {
|
|
317
|
+
this.verboseLog(`Error fetching affiliate details: ${error}`);
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
static async returnCompanyId(): Promise<string | null> {
|
|
323
|
+
this.verboseLog('Getting company ID...');
|
|
324
|
+
const companyCode = this.companyCode || await getValue('companyCode');
|
|
325
|
+
this.verboseLog(`Company ID: ${companyCode || 'none'}`);
|
|
326
|
+
return companyCode;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
static async getOfferCode(): Promise<string | null> {
|
|
330
|
+
this.verboseLog('Getting offer code...');
|
|
331
|
+
|
|
332
|
+
// Return cached offer code if available
|
|
333
|
+
if (this.offerCode) {
|
|
334
|
+
this.verboseLog(`Returning cached offer code: ${this.offerCode}`);
|
|
335
|
+
return this.offerCode;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Try to get from storage
|
|
339
|
+
const storedOfferCode = await getValue('offerCode');
|
|
340
|
+
if (storedOfferCode) {
|
|
341
|
+
this.verboseLog(`Returning stored offer code: ${storedOfferCode}`);
|
|
342
|
+
this.offerCode = storedOfferCode;
|
|
343
|
+
return storedOfferCode;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
this.verboseLog('No offer code found');
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private static async fetchAndStoreOfferCode(shortCode: string): Promise<void> {
|
|
351
|
+
this.verboseLog(`Fetching offer code for short code: ${shortCode}`);
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
// Get company code
|
|
355
|
+
const companyCode = this.companyCode || await getValue('companyCode');
|
|
356
|
+
if (!companyCode) {
|
|
357
|
+
this.verboseLog('Cannot fetch offer code: no company code available');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Use the more efficient endpoint with company code and just the short code
|
|
362
|
+
const encoded = encodeURIComponent(shortCode);
|
|
363
|
+
const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${companyCode}/${encoded}`;
|
|
364
|
+
this.verboseLog(`Making API call to: ${url}`);
|
|
365
|
+
|
|
366
|
+
const response = await fetch(url);
|
|
367
|
+
this.verboseLog(`API response status: ${response.status}`);
|
|
368
|
+
|
|
369
|
+
if (!response.ok) {
|
|
370
|
+
this.verboseLog(`Failed to fetch offer code, status: ${response.status}`);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const offerCode = (await response.text()).replace(/[^a-zA-Z0-9]/g, '');
|
|
375
|
+
this.verboseLog(`Received offer code: ${offerCode}`);
|
|
376
|
+
|
|
377
|
+
// Check for error codes
|
|
378
|
+
const errorCodes = [
|
|
379
|
+
'errorofferCodeNotFound',
|
|
380
|
+
'errorAffiliateoffercodenotfoundinanycompany',
|
|
381
|
+
'errorAffiliateoffercodenotfoundinanycompanyAffiliatelinkwas',
|
|
382
|
+
'Routenotfound'
|
|
383
|
+
];
|
|
384
|
+
|
|
385
|
+
if (errorCodes.includes(offerCode)) {
|
|
386
|
+
this.verboseLog('Offer code not found or invalid');
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Store offer code
|
|
391
|
+
this.offerCode = offerCode;
|
|
392
|
+
await saveValue('offerCode', offerCode);
|
|
393
|
+
this.verboseLog(`Offer code stored successfully: ${offerCode}`);
|
|
394
|
+
} catch (error) {
|
|
395
|
+
this.verboseLog(`Error fetching offer code: ${error}`);
|
|
396
|
+
}
|
|
63
397
|
}
|
|
64
398
|
|
|
65
399
|
static async trackEvent(eventName: string): Promise<void> {
|
|
400
|
+
this.verboseLog(`Tracking event: ${eventName}`);
|
|
401
|
+
|
|
66
402
|
const id = await this.returnInsertAffiliateIdentifier();
|
|
67
403
|
|
|
68
404
|
if (!id) {
|
|
69
|
-
|
|
405
|
+
this.verboseLog('Cannot track event: no affiliate identifier available');
|
|
70
406
|
return;
|
|
71
407
|
}
|
|
72
408
|
|
|
73
409
|
const companyCode = this.companyCode || await getValue('companyCode');
|
|
410
|
+
this.verboseLog(`Company code: ${companyCode || 'empty'}`);
|
|
74
411
|
|
|
75
|
-
if (!companyCode)
|
|
412
|
+
if (!companyCode) {
|
|
413
|
+
this.verboseLog('Cannot track event: no company code available');
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const payload = {
|
|
418
|
+
eventName,
|
|
419
|
+
deepLinkParam: id,
|
|
420
|
+
companyId: companyCode,
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
this.verboseLog(`Track event payload: ${JSON.stringify(payload)}`);
|
|
424
|
+
this.verboseLog('Making API call to track event...');
|
|
76
425
|
|
|
77
426
|
try {
|
|
78
|
-
await fetch('https://api.insertaffiliate.com/v1/trackEvent', {
|
|
427
|
+
const response = await fetch('https://api.insertaffiliate.com/v1/trackEvent', {
|
|
79
428
|
method: 'POST',
|
|
80
429
|
headers: { 'Content-Type': 'application/json' },
|
|
81
|
-
body: JSON.stringify(
|
|
82
|
-
{
|
|
83
|
-
eventName,
|
|
84
|
-
deepLinkParam: id,
|
|
85
|
-
companyId: companyCode,
|
|
86
|
-
}),
|
|
430
|
+
body: JSON.stringify(payload),
|
|
87
431
|
});
|
|
88
|
-
|
|
432
|
+
|
|
433
|
+
this.verboseLog(`Track event API response status: ${response.status}`);
|
|
434
|
+
|
|
435
|
+
if (response.ok) {
|
|
436
|
+
this.verboseLog(`Event tracked successfully: ${eventName}`);
|
|
437
|
+
} else {
|
|
438
|
+
this.verboseLog(`Failed to track event with status code: ${response.status}`);
|
|
439
|
+
}
|
|
89
440
|
} catch (err) {
|
|
90
|
-
|
|
441
|
+
this.verboseLog(`Network error tracking event: ${err}`);
|
|
91
442
|
}
|
|
92
443
|
}
|
|
93
444
|
|
|
94
445
|
static async returnUserAccountTokenAndStoreExpectedTransaction(): Promise<string | null> {
|
|
446
|
+
this.verboseLog('Getting user account token and storing expected transaction...');
|
|
447
|
+
|
|
95
448
|
const shortCode = await this.returnInsertAffiliateIdentifier();
|
|
96
|
-
if (!shortCode)
|
|
449
|
+
if (!shortCode) {
|
|
450
|
+
this.verboseLog('No affiliate identifier found, not saving expected transaction');
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
97
453
|
|
|
98
454
|
let token = await getValue('userAccountToken');
|
|
455
|
+
this.verboseLog(`Existing token: ${token || 'none'}`);
|
|
456
|
+
|
|
99
457
|
if (!token) {
|
|
458
|
+
this.verboseLog('Generating new user account token...');
|
|
100
459
|
token = generateUUID();
|
|
101
460
|
await saveValue('userAccountToken', token);
|
|
461
|
+
this.verboseLog(`Generated and saved new token: ${token}`);
|
|
102
462
|
}
|
|
103
463
|
|
|
464
|
+
this.verboseLog(`User account token: ${token}`);
|
|
104
465
|
await this.storeExpectedStoreTransaction(token);
|
|
105
466
|
return token;
|
|
106
467
|
}
|
|
107
468
|
|
|
108
469
|
static async storeExpectedStoreTransaction(userAccountToken: string): Promise<void> {
|
|
470
|
+
this.verboseLog(`Storing expected store transaction with token: ${userAccountToken}`);
|
|
471
|
+
|
|
109
472
|
const companyCode = this.companyCode || await getValue('companyCode');
|
|
110
473
|
const shortCode = await this.returnInsertAffiliateIdentifier();
|
|
111
474
|
|
|
475
|
+
this.verboseLog(`Company code: ${companyCode || 'empty'}, Short code: ${shortCode || 'empty'}`);
|
|
476
|
+
|
|
112
477
|
if (!companyCode || !shortCode) {
|
|
113
|
-
|
|
478
|
+
this.verboseLog('Cannot store transaction: missing company code or identifier');
|
|
114
479
|
return;
|
|
115
480
|
}
|
|
116
481
|
|
|
@@ -121,6 +486,9 @@ export class InsertAffiliate {
|
|
|
121
486
|
storedDate: new Date().toISOString(),
|
|
122
487
|
};
|
|
123
488
|
|
|
489
|
+
this.verboseLog(`Payload: ${JSON.stringify(payload)}`);
|
|
490
|
+
this.verboseLog('Making API call to store expected transaction...');
|
|
491
|
+
|
|
124
492
|
try {
|
|
125
493
|
const res = await fetch('https://api.insertaffiliate.com/v1/api/app-store-webhook/create-expected-transaction', {
|
|
126
494
|
method: 'POST',
|
|
@@ -128,13 +496,15 @@ export class InsertAffiliate {
|
|
|
128
496
|
body: JSON.stringify(payload),
|
|
129
497
|
});
|
|
130
498
|
|
|
499
|
+
this.verboseLog(`API response status: ${res.status}`);
|
|
500
|
+
|
|
131
501
|
if (res.status === 200) {
|
|
132
|
-
|
|
502
|
+
this.verboseLog('Expected transaction stored successfully on server');
|
|
133
503
|
} else {
|
|
134
|
-
|
|
504
|
+
this.verboseLog(`Failed to store transaction with status: ${res.status}`);
|
|
135
505
|
}
|
|
136
506
|
} catch (error) {
|
|
137
|
-
|
|
507
|
+
this.verboseLog(`Network error storing transaction: ${error}`);
|
|
138
508
|
}
|
|
139
509
|
}
|
|
140
510
|
|
|
@@ -145,7 +515,9 @@ export class InsertAffiliate {
|
|
|
145
515
|
iapticPublicKey: string
|
|
146
516
|
): Promise<boolean> {
|
|
147
517
|
try {
|
|
518
|
+
this.verboseLog('Starting Iaptic purchase validation...');
|
|
148
519
|
const isIOS = typeof window !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent);
|
|
520
|
+
this.verboseLog(`Platform detected: ${isIOS ? 'iOS' : 'Android'}`);
|
|
149
521
|
|
|
150
522
|
const baseRequest = {
|
|
151
523
|
id: iapticAppId,
|
|
@@ -155,12 +527,14 @@ export class InsertAffiliate {
|
|
|
155
527
|
let transaction: any;
|
|
156
528
|
|
|
157
529
|
if (isIOS) {
|
|
530
|
+
this.verboseLog('Creating iOS transaction payload');
|
|
158
531
|
transaction = {
|
|
159
532
|
id: iapticAppId,
|
|
160
533
|
type: 'ios-appstore',
|
|
161
534
|
appStoreReceipt: jsonIapPurchase.transactionReceipt,
|
|
162
535
|
};
|
|
163
536
|
} else {
|
|
537
|
+
this.verboseLog('Creating Android transaction payload');
|
|
164
538
|
const receiptJson: IapticAndroidReceipt = JSON.parse(atob(jsonIapPurchase.transactionReceipt));
|
|
165
539
|
transaction = {
|
|
166
540
|
id: receiptJson.orderId,
|
|
@@ -172,6 +546,7 @@ export class InsertAffiliate {
|
|
|
172
546
|
}
|
|
173
547
|
|
|
174
548
|
const insertAffiliateIdentifier = await this.returnInsertAffiliateIdentifier();
|
|
549
|
+
this.verboseLog(`Affiliate identifier: ${insertAffiliateIdentifier || 'none'}`);
|
|
175
550
|
|
|
176
551
|
const payload = {
|
|
177
552
|
...baseRequest,
|
|
@@ -181,6 +556,7 @@ export class InsertAffiliate {
|
|
|
181
556
|
: undefined,
|
|
182
557
|
};
|
|
183
558
|
|
|
559
|
+
this.verboseLog('Making API call to Iaptic validator...');
|
|
184
560
|
const response = await fetch('https://validator.iaptic.com/v1/validate', {
|
|
185
561
|
method: 'POST',
|
|
186
562
|
headers: {
|
|
@@ -190,19 +566,43 @@ export class InsertAffiliate {
|
|
|
190
566
|
body: JSON.stringify(payload),
|
|
191
567
|
});
|
|
192
568
|
|
|
193
|
-
|
|
569
|
+
this.verboseLog(`Iaptic validation response status: ${response.status}`);
|
|
570
|
+
|
|
571
|
+
if (response.status === 200) {
|
|
572
|
+
this.verboseLog('Purchase validated successfully');
|
|
573
|
+
return true;
|
|
574
|
+
} else {
|
|
575
|
+
this.verboseLog(`Validation failed with status: ${response.status}`);
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
194
578
|
} catch (error) {
|
|
195
|
-
|
|
579
|
+
this.verboseLog(`Error during purchase validation: ${error}`);
|
|
196
580
|
return false;
|
|
197
581
|
}
|
|
198
582
|
}
|
|
199
583
|
|
|
200
584
|
static async fetchAndConditionallyOpenUrl(affiliateLink: string, offerCodeUrlId: string): Promise<void> {
|
|
585
|
+
this.verboseLog('Fetching offer code and opening URL...');
|
|
201
586
|
const encoded = encodeURIComponent(affiliateLink);
|
|
587
|
+
this.verboseLog(`Encoded affiliate link: ${encoded}`);
|
|
202
588
|
|
|
203
589
|
try {
|
|
204
|
-
|
|
590
|
+
// Get company code
|
|
591
|
+
const companyCode = this.companyCode || await getValue('companyCode');
|
|
592
|
+
if (!companyCode) {
|
|
593
|
+
this.verboseLog('Cannot fetch offer code: no company code available');
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
this.verboseLog('Making API call to fetch offer code...');
|
|
598
|
+
const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${companyCode}/${encoded}`;
|
|
599
|
+
this.verboseLog(`API URL: ${url}`);
|
|
600
|
+
|
|
601
|
+
const res = await fetch(url);
|
|
602
|
+
this.verboseLog(`API response status: ${res.status}`);
|
|
603
|
+
|
|
205
604
|
const offerCode = (await res.text()).replace(/[^a-zA-Z0-9]/g, '');
|
|
605
|
+
this.verboseLog(`Received offer code: ${offerCode}`);
|
|
206
606
|
|
|
207
607
|
const errorCodes = [
|
|
208
608
|
'errorofferCodeNotFound',
|
|
@@ -212,37 +612,59 @@ export class InsertAffiliate {
|
|
|
212
612
|
];
|
|
213
613
|
|
|
214
614
|
if (errorCodes.includes(offerCode)) {
|
|
215
|
-
|
|
615
|
+
this.verboseLog('Offer code not found or invalid');
|
|
216
616
|
return;
|
|
217
617
|
}
|
|
218
618
|
|
|
219
619
|
const redeemUrl = `https://apps.apple.com/redeem?ctx=offercodes&id=${offerCodeUrlId}&code=${offerCode}`;
|
|
620
|
+
this.verboseLog(`Opening redeem URL: ${redeemUrl}`);
|
|
220
621
|
window.open(redeemUrl, '_blank');
|
|
622
|
+
this.verboseLog('Redeem URL opened successfully');
|
|
221
623
|
} catch (err) {
|
|
222
|
-
|
|
624
|
+
this.verboseLog(`Error fetching/opening offer code: ${err}`);
|
|
223
625
|
}
|
|
224
626
|
}
|
|
225
627
|
|
|
226
628
|
private static async getOrCreateUserID(): Promise<string> {
|
|
629
|
+
this.verboseLog('Getting or creating user ID...');
|
|
227
630
|
let id = await getValue('userId');
|
|
631
|
+
this.verboseLog(`Existing user ID: ${id || 'none'}`);
|
|
632
|
+
|
|
228
633
|
if (!id) {
|
|
634
|
+
this.verboseLog('Generating new user ID...');
|
|
229
635
|
id = generateShortDeviceID();
|
|
230
636
|
await saveValue('userId', id);
|
|
637
|
+
this.verboseLog(`Generated and saved new user ID: ${id}`);
|
|
231
638
|
}
|
|
639
|
+
|
|
232
640
|
return id;
|
|
233
641
|
}
|
|
234
642
|
|
|
235
643
|
private static async fetchShortLink(link: string): Promise<string | null> {
|
|
236
644
|
try {
|
|
645
|
+
this.verboseLog('Converting deep link to short link...');
|
|
237
646
|
const encoded = encodeURIComponent(link);
|
|
238
647
|
const companyCode = this.companyCode || await getValue('companyCode');
|
|
239
|
-
|
|
648
|
+
this.verboseLog(`Company code: ${companyCode || 'empty'}`);
|
|
649
|
+
|
|
650
|
+
if (!companyCode) {
|
|
651
|
+
this.verboseLog('No company code available, cannot convert link');
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const url = `https://api.insertaffiliate.com/V1/convert-deep-link-to-short-link?companyId=${companyCode}&deepLinkUrl=${encoded}`;
|
|
656
|
+
this.verboseLog(`Making API call to: ${url}`);
|
|
657
|
+
|
|
658
|
+
const res = await fetch(url);
|
|
659
|
+
this.verboseLog(`API response status: ${res.status}`);
|
|
240
660
|
|
|
241
|
-
const res = await fetch(`https://api.insertaffiliate.com/V1/convert-deep-link-to-short-link?companyId=${companyCode}&deepLinkUrl=${encoded}`);
|
|
242
661
|
const data = await res.json();
|
|
243
|
-
|
|
662
|
+
const shortLink = data?.shortLink || null;
|
|
663
|
+
this.verboseLog(`Short link received: ${shortLink || 'none'}`);
|
|
664
|
+
|
|
665
|
+
return shortLink;
|
|
244
666
|
} catch (err: any) {
|
|
245
|
-
|
|
667
|
+
this.verboseLog(`Error fetching short link: ${err?.message || err}`);
|
|
246
668
|
return null;
|
|
247
669
|
}
|
|
248
670
|
}
|