insert-affiliate-js-sdk 1.2.0 → 1.3.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/README.md CHANGED
@@ -89,10 +89,23 @@ await InsertAffiliate.initialize('YOUR_COMPANY_CODE');
89
89
  <summary><strong>Advanced Initialization Options</strong> (click to expand)</summary>
90
90
 
91
91
  ```javascript
92
- // Enable verbose logging for debugging
93
- await InsertAffiliate.initialize('YOUR_COMPANY_CODE', true);
92
+ // Full initialization with all options
93
+ await InsertAffiliate.initialize(
94
+ 'YOUR_COMPANY_CODE', // Company code (required)
95
+ true, // Enable verbose logging (optional, default: false)
96
+ 86400000, // Attribution timeout in milliseconds (optional, e.g., 24 hours)
97
+ true // Prevent affiliate transfer (optional, default: false)
98
+ );
94
99
  ```
95
100
 
101
+ **Parameters:**
102
+ - `companyCode` (required): Your Insert Affiliate company code
103
+ - `verboseLogging` (optional): Enable detailed console logs for debugging
104
+ - `affiliateAttributionActiveTime` (optional): Time in milliseconds before attribution expires (e.g., `86400000` for 24 hours)
105
+ - `preventAffiliateTransfer` (optional): When `true`, prevents a new affiliate link from overwriting an existing affiliate attribution (defaults to `false`)
106
+ - Use this to ensure the first affiliate who acquired the user always gets credit
107
+ - New affiliate links will be silently ignored if the user already has an affiliate
108
+
96
109
  **Verbose logging shows:**
97
110
  - Initialization process and company code validation
98
111
  - Deep link processing and short code detection
@@ -125,17 +138,57 @@ import { InsertAffiliate } from 'insert-affiliate-js-sdk';
125
138
  import { Purchases } from '@revenuecat/purchases-capacitor';
126
139
 
127
140
  window.addEventListener('DOMContentLoaded', async () => {
128
- await InsertAffiliate.initialize('YOUR_COMPANY_CODE');
141
+ await InsertAffiliate.initialize(
142
+ 'YOUR_COMPANY_CODE',
143
+ false, // verbose logging
144
+ 86400000, // 24 hour attribution timeout (optional)
145
+ true // prevent affiliate transfer (optional)
146
+ );
129
147
  await Purchases.configure({ apiKey: 'YOUR_REVENUECAT_API_KEY' });
130
148
 
131
- const affiliateIdentifier = await InsertAffiliate.returnInsertAffiliateIdentifier();
149
+ // Set up callback for when affiliate identifier changes
150
+ // Note: Use preventAffiliateTransfer in initialize() to block affiliate changes in the SDK
151
+ InsertAffiliate.setInsertAffiliateIdentifierChangeCallback(async (identifier, offerCode) => {
152
+ if (!identifier) return;
132
153
 
133
- if (affiliateIdentifier) {
134
- await Purchases.setAttributes({ insert_affiliate: affiliateIdentifier });
135
- }
154
+ // Ensure RevenueCat subscriber exists before setting attributes
155
+ const customerInfo = await Purchases.getCustomerInfo();
156
+
157
+ // OPTIONAL: Prevent attribution for existing subscribers
158
+ // Uncomment to ensure affiliates only earn from users they actually brought:
159
+ // const hasActiveEntitlement = Object.keys(customerInfo.entitlements.active).length > 0;
160
+ // if (hasActiveEntitlement) return; // User already subscribed, don't attribute
161
+
162
+ // Get expiry timestamp for RevenueCat targeting
163
+ const expiryTimestamp = await InsertAffiliate.getAffiliateExpiryTimestamp();
164
+
165
+ // Set attributes for RevenueCat
166
+ const attributes = {
167
+ insert_affiliate: identifier,
168
+ insert_timedout: expiryTimestamp?.toString() || '',
169
+ };
170
+
171
+ // Add offer code for RevenueCat Targeting (if available)
172
+ if (offerCode) {
173
+ attributes.affiliateOfferCode = offerCode;
174
+ }
175
+
176
+ await Purchases.setAttributes(attributes);
177
+ await Purchases.syncAttributesAndOfferingsIfNeeded();
178
+ });
136
179
  });
137
180
  ```
138
181
 
182
+ **Using RevenueCat Targeting (Recommended)**
183
+
184
+ RevenueCat Targeting automatically shows different offerings based on the `affiliateOfferCode` attribute. Simply display `offerings.current`:
185
+
186
+ ```javascript
187
+ const offerings = await Purchases.getOfferings();
188
+ const currentOffering = offerings.current;
189
+ // RevenueCat targeting automatically shows the correct offering based on affiliateOfferCode
190
+ ```
191
+
139
192
  **Step 2: Webhook Setup**
140
193
 
141
194
  1. In RevenueCat, [create a new webhook](https://www.revenuecat.com/docs/integrations/webhooks)
@@ -386,15 +439,16 @@ Learn more: [Short Codes Documentation](https://docs.insertaffiliate.com/short-c
386
439
  Get notified when the affiliate identifier changes:
387
440
 
388
441
  ```javascript
389
- InsertAffiliate.setInsertAffiliateIdentifierChangeCallback((identifier) => {
442
+ InsertAffiliate.setInsertAffiliateIdentifierChangeCallback((identifier, offerCode) => {
390
443
  if (identifier) {
391
444
  console.log('Affiliate changed:', identifier);
445
+ console.log('Offer code:', offerCode || 'none');
392
446
 
393
447
  // Update UI
394
448
  document.getElementById('affiliate-banner').style.display = 'block';
395
449
 
396
450
  // Track in analytics
397
- analytics.track('affiliate_link_clicked', { identifier });
451
+ analytics.track('affiliate_link_clicked', { identifier, offerCode });
398
452
  }
399
453
  });
400
454
 
@@ -402,8 +456,33 @@ InsertAffiliate.setInsertAffiliateIdentifierChangeCallback((identifier) => {
402
456
  InsertAffiliate.setInsertAffiliateIdentifierChangeCallback(null);
403
457
  ```
404
458
 
459
+ **Callback Parameters:**
460
+ - `identifier` (string | null): The full affiliate identifier (shortCode-userId)
461
+ - `offerCode` (string | null): The offer code associated with this affiliate (if any)
462
+
405
463
  </details>
406
464
 
465
+ ### Prevent Affiliate Transfer
466
+
467
+ By default, clicking a new affiliate link will overwrite any existing attribution. Enable `preventAffiliateTransfer` to lock the first affiliate:
468
+
469
+ ```javascript
470
+ await InsertAffiliate.initialize(
471
+ "YOUR_COMPANY_CODE",
472
+ false, // verboseLogging
473
+ 604800, // 7-day attribution timeout
474
+ true // preventAffiliateTransfer - locks first affiliate
475
+ );
476
+ ```
477
+
478
+ **How it works:**
479
+ - When enabled, once a user is attributed to an affiliate, that attribution is locked
480
+ - New affiliate links will not overwrite the existing attribution
481
+ - The callback still fires with the existing affiliate data (not the new one)
482
+ - Useful for preventing "affiliate stealing" where users click competitor links
483
+
484
+ Learn more: [Prevent Affiliate Transfer Documentation](https://docs.insertaffiliate.com/prevent-affiliate-transfer)
485
+
407
486
  ---
408
487
 
409
488
  ## 📖 API Reference
@@ -412,7 +491,7 @@ InsertAffiliate.setInsertAffiliateIdentifierChangeCallback(null);
412
491
 
413
492
  | Method | Description | Returns |
414
493
  |--------|-------------|---------|
415
- | `initialize(companyCode, verbose?)` | Initialize the SDK | `Promise<void>` |
494
+ | `initialize(companyCode, verbose?, timeout?, preventTransfer?)` | Initialize the SDK | `Promise<void>` |
416
495
  | `returnInsertAffiliateIdentifier(ignoreTimeout?)` | Get current affiliate identifier | `Promise<string \| null>` |
417
496
  | `returnCompanyId()` | Get company ID | `Promise<string \| null>` |
418
497
  | `setInsertAffiliateIdentifier(link)` | Set affiliate from deep link | `Promise<string \| null>` |
@@ -420,6 +499,9 @@ InsertAffiliate.setInsertAffiliateIdentifierChangeCallback(null);
420
499
  | `getAffiliateDetails(code)` | Get affiliate info without storing | `Promise<AffiliateDetails \| null>` |
421
500
  | `trackEvent(eventName)` | Track custom event | `Promise<void>` |
422
501
  | `getOfferCode()` | Get offer code modifier | `Promise<string \| null>` |
502
+ | `getAffiliateExpiryTimestamp()` | Get Unix timestamp (ms) when attribution expires | `Promise<number \| null>` |
503
+ | `getAffiliateStoredDate()` | Get ISO date string when affiliate was stored | `Promise<string \| null>` |
504
+ | `isAffiliateAttributionValid()` | Check if attribution is still valid | `Promise<boolean>` |
423
505
  | `setInsertAffiliateIdentifierChangeCallback(fn)` | Set change callback | `void` |
424
506
 
425
507
  <details>
package/dist/index.d.ts CHANGED
@@ -6,16 +6,17 @@ interface AffiliateDetails {
6
6
  affiliateShortCode: string;
7
7
  deeplinkUrl: string;
8
8
  }
9
- type InsertAffiliateIdentifierChangeCallback = (identifier: string | null) => void;
9
+ type InsertAffiliateIdentifierChangeCallback = (identifier: string | null, offerCode: string | null) => void;
10
10
  declare class InsertAffiliate {
11
11
  private static isInitialized;
12
12
  private static companyCode;
13
13
  private static verboseLogging;
14
14
  private static insertAffiliateIdentifierChangeCallback;
15
15
  private static affiliateAttributionActiveTime;
16
+ private static preventAffiliateTransfer;
16
17
  private static offerCode;
17
18
  private static verboseLog;
18
- static initialize(code: string | null, verboseLogging?: boolean, affiliateAttributionActiveTime?: number): Promise<void>;
19
+ static initialize(code: string | null, verboseLogging?: boolean, affiliateAttributionActiveTime?: number, preventAffiliateTransfer?: boolean): Promise<void>;
19
20
  private static checkForInsertAffiliateParam;
20
21
  static returnInsertAffiliateIdentifier(ignoreTimeout?: boolean): Promise<string | null>;
21
22
  static setInsertAffiliateIdentifier(referringLink: string): Promise<string | null>;
@@ -29,13 +30,20 @@ declare class InsertAffiliate {
29
30
  static setInsertAffiliateIdentifierChangeCallback(callback: InsertAffiliateIdentifierChangeCallback | null): void;
30
31
  static isAffiliateAttributionValid(): Promise<boolean>;
31
32
  static getAffiliateStoredDate(): Promise<string | null>;
33
+ /**
34
+ * Get the Unix timestamp (in milliseconds) when the affiliate attribution will expire
35
+ * @returns The expiry timestamp in milliseconds, or null if no attribution or no timeout configured
36
+ */
37
+ static getAffiliateExpiryTimestamp(): Promise<number | null>;
32
38
  /**
33
39
  * Retrieve detailed information about an affiliate by their short code or deep link
34
40
  * This method queries the API and does not store or set the affiliate identifier
35
41
  * @param affiliateCode The short code or deep link to look up
36
42
  * @returns AffiliateDetails if found, null otherwise
37
43
  */
38
- static getAffiliateDetails(affiliateCode: string): Promise<AffiliateDetails | null>;
44
+ static getAffiliateDetails(affiliateCode: string, options?: {
45
+ trackUsage?: boolean;
46
+ }): Promise<AffiliateDetails | null>;
39
47
  static returnCompanyId(): Promise<string | null>;
40
48
  /**
41
49
  * Get the offer code for the current affiliate
package/dist/index.js CHANGED
@@ -80,12 +80,14 @@ var InsertAffiliate = class {
80
80
  console.log(`[Insert Affiliate] [VERBOSE] ${message}`);
81
81
  }
82
82
  }
83
- static async initialize(code, verboseLogging = false, affiliateAttributionActiveTime) {
83
+ static async initialize(code, verboseLogging = false, affiliateAttributionActiveTime, preventAffiliateTransfer = false) {
84
84
  this.verboseLogging = verboseLogging;
85
+ this.preventAffiliateTransfer = preventAffiliateTransfer;
85
86
  if (verboseLogging) {
86
87
  this.verboseLog("Starting SDK initialization...");
87
88
  this.verboseLog(`Company code provided: ${code ? "Yes" : "No"}`);
88
89
  this.verboseLog("Verbose logging enabled");
90
+ this.verboseLog(`Prevent affiliate transfer: ${preventAffiliateTransfer}`);
89
91
  }
90
92
  if (this.isInitialized) {
91
93
  this.verboseLog("SDK already initialized, skipping");
@@ -165,6 +167,11 @@ var InsertAffiliate = class {
165
167
  this.verboseLog(`Returning existing identifier: ${identifier2}`);
166
168
  return identifier2;
167
169
  }
170
+ if (this.preventAffiliateTransfer && existingShortCode && existingShortCode !== shortCode) {
171
+ this.verboseLog(`Transfer blocked: existing affiliate "${existingShortCode}" protected from being replaced by "${shortCode}"`);
172
+ const identifier2 = `${existingShortCode}-${userId}`;
173
+ return identifier2;
174
+ }
168
175
  this.verboseLog(`Saving short code to storage: ${shortCode}`);
169
176
  await saveValue("referrerLink", shortCode);
170
177
  const storedDate = (/* @__PURE__ */ new Date()).toISOString();
@@ -172,10 +179,10 @@ var InsertAffiliate = class {
172
179
  this.verboseLog(`Short code saved successfully with stored date: ${storedDate}`);
173
180
  const identifier = `${shortCode}-${userId}`;
174
181
  this.verboseLog(`Returning identifier: ${identifier}`);
175
- await this.fetchAndStoreOfferCode(shortCode);
182
+ const offerCode = await this.fetchAndStoreOfferCode(shortCode);
176
183
  if (this.insertAffiliateIdentifierChangeCallback) {
177
- this.verboseLog(`Triggering callback with identifier: ${identifier}`);
178
- this.insertAffiliateIdentifierChangeCallback(identifier);
184
+ this.verboseLog(`Triggering callback with identifier: ${identifier}, offerCode: ${offerCode || "none"}`);
185
+ this.insertAffiliateIdentifierChangeCallback(identifier, offerCode);
179
186
  }
180
187
  return identifier;
181
188
  }
@@ -193,7 +200,7 @@ var InsertAffiliate = class {
193
200
  this.verboseLog("Invalid short code format, aborting");
194
201
  return false;
195
202
  }
196
- const affiliateDetails = await this.getAffiliateDetails(shortCode);
203
+ const affiliateDetails = await this.getAffiliateDetails(shortCode, { trackUsage: true });
197
204
  if (!affiliateDetails) {
198
205
  this.verboseLog(`Short code '${shortCode}' does not exist or validation failed`);
199
206
  console.error(`[Insert Affiliate] Error: Short code '${shortCode}' does not exist or validation failed.`);
@@ -238,13 +245,38 @@ var InsertAffiliate = class {
238
245
  this.verboseLog(`Stored date: ${storedDate || "none"}`);
239
246
  return storedDate;
240
247
  }
248
+ /**
249
+ * Get the Unix timestamp (in milliseconds) when the affiliate attribution will expire
250
+ * @returns The expiry timestamp in milliseconds, or null if no attribution or no timeout configured
251
+ */
252
+ static async getAffiliateExpiryTimestamp() {
253
+ this.verboseLog("Getting affiliate expiry timestamp...");
254
+ const storedDateStr = await getValue("affiliateStoredDate");
255
+ if (!storedDateStr) {
256
+ this.verboseLog("No stored date found, returning null");
257
+ return null;
258
+ }
259
+ let timeoutMs = this.affiliateAttributionActiveTime;
260
+ if (timeoutMs === null) {
261
+ const storedTimeout = await getValue("affiliateAttributionActiveTime");
262
+ timeoutMs = storedTimeout ? parseInt(storedTimeout, 10) : null;
263
+ }
264
+ if (timeoutMs === null) {
265
+ this.verboseLog("No attribution timeout configured, returning null");
266
+ return null;
267
+ }
268
+ const storedDate = new Date(storedDateStr);
269
+ const expiryTimestamp = storedDate.getTime() + timeoutMs;
270
+ this.verboseLog(`Expiry timestamp: ${expiryTimestamp} (stored: ${storedDateStr}, timeout: ${timeoutMs}ms)`);
271
+ return expiryTimestamp;
272
+ }
241
273
  /**
242
274
  * Retrieve detailed information about an affiliate by their short code or deep link
243
275
  * This method queries the API and does not store or set the affiliate identifier
244
276
  * @param affiliateCode The short code or deep link to look up
245
277
  * @returns AffiliateDetails if found, null otherwise
246
278
  */
247
- static async getAffiliateDetails(affiliateCode) {
279
+ static async getAffiliateDetails(affiliateCode, options) {
248
280
  this.verboseLog(`Getting affiliate details for: ${affiliateCode}`);
249
281
  const companyCode = this.companyCode || await getValue("companyCode");
250
282
  if (!companyCode) {
@@ -259,6 +291,9 @@ var InsertAffiliate = class {
259
291
  companyId: companyCode,
260
292
  affiliateCode: cleanCode
261
293
  };
294
+ if (options == null ? void 0 : options.trackUsage) {
295
+ payload.trackUsage = true;
296
+ }
262
297
  this.verboseLog(`Making API call to: ${url}`);
263
298
  this.verboseLog(`Payload: ${JSON.stringify(payload)}`);
264
299
  const response = await fetch(url, {
@@ -385,7 +420,7 @@ var InsertAffiliate = class {
385
420
  const companyCode = this.companyCode || await getValue("companyCode");
386
421
  if (!companyCode) {
387
422
  this.verboseLog("Cannot fetch offer code: no company code available");
388
- return;
423
+ return null;
389
424
  }
390
425
  const encoded = encodeURIComponent(shortCode);
391
426
  const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${companyCode}/${encoded}`;
@@ -394,7 +429,7 @@ var InsertAffiliate = class {
394
429
  this.verboseLog(`API response status: ${response.status}`);
395
430
  if (!response.ok) {
396
431
  this.verboseLog(`Failed to fetch offer code, status: ${response.status}`);
397
- return;
432
+ return null;
398
433
  }
399
434
  const offerCode = (await response.text()).replace(/[^a-zA-Z0-9]/g, "");
400
435
  this.verboseLog(`Received offer code: ${offerCode}`);
@@ -406,13 +441,15 @@ var InsertAffiliate = class {
406
441
  ];
407
442
  if (errorCodes.includes(offerCode)) {
408
443
  this.verboseLog("Offer code not found or invalid");
409
- return;
444
+ return null;
410
445
  }
411
446
  this.offerCode = offerCode;
412
447
  await saveValue("offerCode", offerCode);
413
448
  this.verboseLog(`Offer code stored successfully: ${offerCode}`);
449
+ return offerCode;
414
450
  } catch (error) {
415
451
  this.verboseLog(`Error fetching offer code: ${error}`);
452
+ return null;
416
453
  }
417
454
  }
418
455
  static async trackEvent(eventName) {
@@ -636,6 +673,7 @@ InsertAffiliate.verboseLogging = false;
636
673
  InsertAffiliate.insertAffiliateIdentifierChangeCallback = null;
637
674
  InsertAffiliate.affiliateAttributionActiveTime = null;
638
675
  // in milliseconds
676
+ InsertAffiliate.preventAffiliateTransfer = false;
639
677
  InsertAffiliate.offerCode = null;
640
678
  // Annotate the CommonJS export names for ESM import in node:
641
679
  0 && (module.exports = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "insert-affiliate-js-sdk",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -25,7 +25,7 @@ export interface AffiliateDetails {
25
25
  deeplinkUrl: string;
26
26
  }
27
27
 
28
- export type InsertAffiliateIdentifierChangeCallback = (identifier: string | null) => void;
28
+ export type InsertAffiliateIdentifierChangeCallback = (identifier: string | null, offerCode: string | null) => void;
29
29
 
30
30
  export class InsertAffiliate {
31
31
  private static isInitialized: boolean = false;
@@ -33,6 +33,7 @@ export class InsertAffiliate {
33
33
  private static verboseLogging: boolean = false;
34
34
  private static insertAffiliateIdentifierChangeCallback: InsertAffiliateIdentifierChangeCallback | null = null;
35
35
  private static affiliateAttributionActiveTime: number | null = null; // in milliseconds
36
+ private static preventAffiliateTransfer: boolean = false;
36
37
  private static offerCode: string | null = null;
37
38
 
38
39
  private static verboseLog(message: string): void {
@@ -41,13 +42,20 @@ export class InsertAffiliate {
41
42
  }
42
43
  }
43
44
 
44
- static async initialize(code: string | null, verboseLogging: boolean = false, affiliateAttributionActiveTime?: number): Promise<void> {
45
+ static async initialize(
46
+ code: string | null,
47
+ verboseLogging: boolean = false,
48
+ affiliateAttributionActiveTime?: number,
49
+ preventAffiliateTransfer: boolean = false
50
+ ): Promise<void> {
45
51
  this.verboseLogging = verboseLogging;
52
+ this.preventAffiliateTransfer = preventAffiliateTransfer;
46
53
 
47
54
  if (verboseLogging) {
48
55
  this.verboseLog('Starting SDK initialization...');
49
56
  this.verboseLog(`Company code provided: ${code ? 'Yes' : 'No'}`);
50
57
  this.verboseLog('Verbose logging enabled');
58
+ this.verboseLog(`Prevent affiliate transfer: ${preventAffiliateTransfer}`);
51
59
  }
52
60
 
53
61
  if (this.isInitialized) {
@@ -154,6 +162,13 @@ export class InsertAffiliate {
154
162
  return identifier;
155
163
  }
156
164
 
165
+ // Check if transfer is blocked
166
+ if (this.preventAffiliateTransfer && existingShortCode && existingShortCode !== shortCode) {
167
+ this.verboseLog(`Transfer blocked: existing affiliate "${existingShortCode}" protected from being replaced by "${shortCode}"`);
168
+ const identifier = `${existingShortCode}-${userId}`;
169
+ return identifier;
170
+ }
171
+
157
172
  this.verboseLog(`Saving short code to storage: ${shortCode}`);
158
173
  await saveValue('referrerLink', shortCode);
159
174
 
@@ -166,12 +181,12 @@ export class InsertAffiliate {
166
181
  this.verboseLog(`Returning identifier: ${identifier}`);
167
182
 
168
183
  // Auto-fetch and store offer code (use just the short code, not the full identifier)
169
- await this.fetchAndStoreOfferCode(shortCode);
184
+ const offerCode = await this.fetchAndStoreOfferCode(shortCode);
170
185
 
171
186
  // Trigger callback if one is registered
172
187
  if (this.insertAffiliateIdentifierChangeCallback) {
173
- this.verboseLog(`Triggering callback with identifier: ${identifier}`);
174
- this.insertAffiliateIdentifierChangeCallback(identifier);
188
+ this.verboseLog(`Triggering callback with identifier: ${identifier}, offerCode: ${offerCode || 'none'}`);
189
+ this.insertAffiliateIdentifierChangeCallback(identifier, offerCode);
175
190
  }
176
191
 
177
192
  return identifier;
@@ -195,7 +210,7 @@ export class InsertAffiliate {
195
210
  }
196
211
 
197
212
  // Validate that the short code exists in the system
198
- const affiliateDetails = await this.getAffiliateDetails(shortCode);
213
+ const affiliateDetails = await this.getAffiliateDetails(shortCode, { trackUsage: true });
199
214
  if (!affiliateDetails) {
200
215
  this.verboseLog(`Short code '${shortCode}' does not exist or validation failed`);
201
216
  console.error(`[Insert Affiliate] Error: Short code '${shortCode}' does not exist or validation failed.`);
@@ -255,13 +270,46 @@ export class InsertAffiliate {
255
270
  return storedDate;
256
271
  }
257
272
 
273
+ /**
274
+ * Get the Unix timestamp (in milliseconds) when the affiliate attribution will expire
275
+ * @returns The expiry timestamp in milliseconds, or null if no attribution or no timeout configured
276
+ */
277
+ static async getAffiliateExpiryTimestamp(): Promise<number | null> {
278
+ this.verboseLog('Getting affiliate expiry timestamp...');
279
+
280
+ const storedDateStr = await getValue('affiliateStoredDate');
281
+ if (!storedDateStr) {
282
+ this.verboseLog('No stored date found, returning null');
283
+ return null;
284
+ }
285
+
286
+ // Get timeout value from storage or class property
287
+ let timeoutMs = this.affiliateAttributionActiveTime;
288
+ if (timeoutMs === null) {
289
+ const storedTimeout = await getValue('affiliateAttributionActiveTime');
290
+ timeoutMs = storedTimeout ? parseInt(storedTimeout, 10) : null;
291
+ }
292
+
293
+ // If no timeout is set, return null (attribution never expires)
294
+ if (timeoutMs === null) {
295
+ this.verboseLog('No attribution timeout configured, returning null');
296
+ return null;
297
+ }
298
+
299
+ const storedDate = new Date(storedDateStr);
300
+ const expiryTimestamp = storedDate.getTime() + timeoutMs;
301
+ this.verboseLog(`Expiry timestamp: ${expiryTimestamp} (stored: ${storedDateStr}, timeout: ${timeoutMs}ms)`);
302
+
303
+ return expiryTimestamp;
304
+ }
305
+
258
306
  /**
259
307
  * Retrieve detailed information about an affiliate by their short code or deep link
260
308
  * This method queries the API and does not store or set the affiliate identifier
261
309
  * @param affiliateCode The short code or deep link to look up
262
310
  * @returns AffiliateDetails if found, null otherwise
263
311
  */
264
- static async getAffiliateDetails(affiliateCode: string): Promise<AffiliateDetails | null> {
312
+ static async getAffiliateDetails(affiliateCode: string, options?: { trackUsage?: boolean }): Promise<AffiliateDetails | null> {
265
313
  this.verboseLog(`Getting affiliate details for: ${affiliateCode}`);
266
314
 
267
315
  const companyCode = this.companyCode || await getValue('companyCode');
@@ -276,11 +324,15 @@ export class InsertAffiliate {
276
324
 
277
325
  try {
278
326
  const url = 'https://api.insertaffiliate.com/V1/checkAffiliateExists';
279
- const payload = {
327
+ const payload: Record<string, any> = {
280
328
  companyId: companyCode,
281
329
  affiliateCode: cleanCode,
282
330
  };
283
331
 
332
+ if (options?.trackUsage) {
333
+ payload.trackUsage = true;
334
+ }
335
+
284
336
  this.verboseLog(`Making API call to: ${url}`);
285
337
  this.verboseLog(`Payload: ${JSON.stringify(payload)}`);
286
338
 
@@ -431,7 +483,7 @@ export class InsertAffiliate {
431
483
  }
432
484
  }
433
485
 
434
- private static async fetchAndStoreOfferCode(shortCode: string): Promise<void> {
486
+ private static async fetchAndStoreOfferCode(shortCode: string): Promise<string | null> {
435
487
  this.verboseLog(`Fetching offer code for short code: ${shortCode}`);
436
488
 
437
489
  try {
@@ -439,7 +491,7 @@ export class InsertAffiliate {
439
491
  const companyCode = this.companyCode || await getValue('companyCode');
440
492
  if (!companyCode) {
441
493
  this.verboseLog('Cannot fetch offer code: no company code available');
442
- return;
494
+ return null;
443
495
  }
444
496
 
445
497
  // Use the more efficient endpoint with company code and just the short code
@@ -452,7 +504,7 @@ export class InsertAffiliate {
452
504
 
453
505
  if (!response.ok) {
454
506
  this.verboseLog(`Failed to fetch offer code, status: ${response.status}`);
455
- return;
507
+ return null;
456
508
  }
457
509
 
458
510
  const offerCode = (await response.text()).replace(/[^a-zA-Z0-9]/g, '');
@@ -468,15 +520,17 @@ export class InsertAffiliate {
468
520
 
469
521
  if (errorCodes.includes(offerCode)) {
470
522
  this.verboseLog('Offer code not found or invalid');
471
- return;
523
+ return null;
472
524
  }
473
525
 
474
526
  // Store offer code
475
527
  this.offerCode = offerCode;
476
528
  await saveValue('offerCode', offerCode);
477
529
  this.verboseLog(`Offer code stored successfully: ${offerCode}`);
530
+ return offerCode;
478
531
  } catch (error) {
479
532
  this.verboseLog(`Error fetching offer code: ${error}`);
533
+ return null;
480
534
  }
481
535
  }
482
536