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 +92 -10
- package/dist/index.d.ts +11 -3
- package/dist/index.js +47 -9
- package/package.json +1 -1
- package/src/sdk/InsertAffiliate.ts +66 -12
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
|
-
//
|
|
93
|
-
await InsertAffiliate.initialize(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
134
|
-
await Purchases.
|
|
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
|
|
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
|
@@ -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(
|
|
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<
|
|
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
|
|