insert-affiliate-js-sdk 1.3.0 → 1.4.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/CHANGELOG.md CHANGED
@@ -5,6 +5,13 @@ All notable changes to the Insert Affiliate JavaScript SDK will be documented in
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.3.1] - 2026-03-29
9
+
10
+ ### Fixed
11
+ - **Offer code sanitization** - Fixed offer codes with dashes/underscores being stripped (e.g. `pro-v3-ext` was incorrectly becoming `prov3ext`)
12
+ - Now checks for API error responses before cleaning the offer code
13
+ - Sanitization regex updated to preserve dashes and underscores
14
+
8
15
  ## [1.1.0] - 2025-11-23
9
16
 
10
17
  ### Added
package/dist/index.d.ts CHANGED
@@ -6,6 +6,11 @@ interface AffiliateDetails {
6
6
  affiliateShortCode: string;
7
7
  deeplinkUrl: string;
8
8
  }
9
+ type AffiliateLookupStatus = 'found' | 'not_found' | 'lookup_failed' | 'not_configured';
10
+ interface AffiliateLookupResult {
11
+ status: AffiliateLookupStatus;
12
+ details: AffiliateDetails | null;
13
+ }
9
14
  type InsertAffiliateIdentifierChangeCallback = (identifier: string | null, offerCode: string | null) => void;
10
15
  declare class InsertAffiliate {
11
16
  private static isInitialized;
@@ -24,9 +29,13 @@ declare class InsertAffiliate {
24
29
  * Validates and sets a short code for affiliate tracking
25
30
  * Validates the short code against the API before storing
26
31
  * @param shortCode The short code to validate and set
32
+ * @param options.onLookupFailed called when the lookup itself couldn't be completed
33
+ * (not just an invalid code) — use it to offer a retry instead of proceeding unattributed.
27
34
  * @returns true if the code exists and was successfully validated and stored, false otherwise
28
35
  */
29
- static setShortCode(shortCode: string): Promise<boolean>;
36
+ static setShortCode(shortCode: string, options?: {
37
+ onLookupFailed?: () => void;
38
+ }): Promise<boolean>;
30
39
  static setInsertAffiliateIdentifierChangeCallback(callback: InsertAffiliateIdentifierChangeCallback | null): void;
31
40
  static isAffiliateAttributionValid(): Promise<boolean>;
32
41
  static getAffiliateStoredDate(): Promise<string | null>;
@@ -36,8 +45,21 @@ declare class InsertAffiliate {
36
45
  */
37
46
  static getAffiliateExpiryTimestamp(): Promise<number | null>;
38
47
  /**
39
- * Retrieve detailed information about an affiliate by their short code or deep link
40
- * This method queries the API and does not store or set the affiliate identifier
48
+ * Retrieve detailed information about an affiliate by their short code or deep link,
49
+ * distinguishing "no affiliate matches this code" from "couldn't check" (backend
50
+ * outage, timeout, rate limit). This method queries the API and does not store or set
51
+ * the affiliate identifier.
52
+ * @param affiliateCode The short code or deep link to look up
53
+ * @returns an AffiliateLookupResult with a status of 'found', 'not_found', 'lookup_failed', or 'not_configured'
54
+ */
55
+ static getAffiliateLookupResult(affiliateCode: string, options?: {
56
+ trackUsage?: boolean;
57
+ }): Promise<AffiliateLookupResult>;
58
+ /**
59
+ * Retrieve detailed information about an affiliate by their short code or deep link.
60
+ * Kept for backward compatibility: collapses 'not_found' and 'lookup_failed' into the
61
+ * same null result, exactly as before. Use getAffiliateLookupResult if you need to tell
62
+ * an invalid code apart from a backend outage.
41
63
  * @param affiliateCode The short code or deep link to look up
42
64
  * @returns AffiliateDetails if found, null otherwise
43
65
  */
@@ -76,4 +98,4 @@ declare class InsertAffiliate {
76
98
  private static fetchShortLink;
77
99
  }
78
100
 
79
- export { type AffiliateDetails, InsertAffiliate, type InsertAffiliateIdentifierChangeCallback };
101
+ export { type AffiliateDetails, type AffiliateLookupResult, type AffiliateLookupStatus, InsertAffiliate, type InsertAffiliateIdentifierChangeCallback };
package/dist/index.js CHANGED
@@ -190,9 +190,12 @@ var InsertAffiliate = class {
190
190
  * Validates and sets a short code for affiliate tracking
191
191
  * Validates the short code against the API before storing
192
192
  * @param shortCode The short code to validate and set
193
+ * @param options.onLookupFailed called when the lookup itself couldn't be completed
194
+ * (not just an invalid code) — use it to offer a retry instead of proceeding unattributed.
193
195
  * @returns true if the code exists and was successfully validated and stored, false otherwise
194
196
  */
195
- static async setShortCode(shortCode) {
197
+ static async setShortCode(shortCode, options) {
198
+ var _a;
196
199
  this.verboseLog(`Setting short code. Input: ${shortCode}`);
197
200
  const valid = /^[a-zA-Z0-9]{3,25}$/.test(shortCode);
198
201
  this.verboseLog(`Short code validation: ${valid ? "Valid" : "Invalid"}`);
@@ -200,12 +203,16 @@ var InsertAffiliate = class {
200
203
  this.verboseLog("Invalid short code format, aborting");
201
204
  return false;
202
205
  }
203
- const affiliateDetails = await this.getAffiliateDetails(shortCode, { trackUsage: true });
204
- if (!affiliateDetails) {
205
- this.verboseLog(`Short code '${shortCode}' does not exist or validation failed`);
206
+ const lookup = await this.getAffiliateLookupResult(shortCode, { trackUsage: true });
207
+ if (lookup.status !== "found" || !lookup.details) {
208
+ this.verboseLog(`Short code '${shortCode}' does not exist or validation failed (status: ${lookup.status})`);
206
209
  console.error(`[Insert Affiliate] Error: Short code '${shortCode}' does not exist or validation failed.`);
210
+ if (lookup.status === "lookup_failed") {
211
+ (_a = options == null ? void 0 : options.onLookupFailed) == null ? void 0 : _a.call(options);
212
+ }
207
213
  return false;
208
214
  }
215
+ const affiliateDetails = lookup.details;
209
216
  this.verboseLog(`Short code validated successfully for affiliate: ${affiliateDetails.affiliateName}`);
210
217
  console.log(`[Insert Affiliate] Short code validated successfully for affiliate: ${affiliateDetails.affiliateName}`);
211
218
  this.verboseLog("Calling setInsertAffiliateIdentifier with short code");
@@ -271,17 +278,19 @@ var InsertAffiliate = class {
271
278
  return expiryTimestamp;
272
279
  }
273
280
  /**
274
- * Retrieve detailed information about an affiliate by their short code or deep link
275
- * This method queries the API and does not store or set the affiliate identifier
281
+ * Retrieve detailed information about an affiliate by their short code or deep link,
282
+ * distinguishing "no affiliate matches this code" from "couldn't check" (backend
283
+ * outage, timeout, rate limit). This method queries the API and does not store or set
284
+ * the affiliate identifier.
276
285
  * @param affiliateCode The short code or deep link to look up
277
- * @returns AffiliateDetails if found, null otherwise
286
+ * @returns an AffiliateLookupResult with a status of 'found', 'not_found', 'lookup_failed', or 'not_configured'
278
287
  */
279
- static async getAffiliateDetails(affiliateCode, options) {
288
+ static async getAffiliateLookupResult(affiliateCode, options) {
280
289
  this.verboseLog(`Getting affiliate details for: ${affiliateCode}`);
281
290
  const companyCode = this.companyCode || await getValue("companyCode");
282
291
  if (!companyCode) {
283
292
  this.verboseLog("Cannot get affiliate details: no company code available");
284
- return null;
293
+ return { status: "not_configured", details: null };
285
294
  }
286
295
  const cleanCode = affiliateCode.includes("-") ? affiliateCode.split("-")[0] : affiliateCode;
287
296
  this.verboseLog(`Clean code: ${cleanCode}`);
@@ -304,26 +313,42 @@ var InsertAffiliate = class {
304
313
  this.verboseLog(`API response status: ${response.status}`);
305
314
  if (!response.ok) {
306
315
  this.verboseLog(`Failed to get affiliate details, status: ${response.status}`);
307
- return null;
316
+ return { status: "lookup_failed", details: null };
308
317
  }
309
318
  const data = await response.json();
310
319
  this.verboseLog(`API response data: ${JSON.stringify(data)}`);
311
- if (data.exists && data.affiliate) {
320
+ if (data.exists) {
321
+ if (!data.affiliate) {
322
+ this.verboseLog("Affiliate exists but response is missing affiliate details");
323
+ return { status: "lookup_failed", details: null };
324
+ }
312
325
  const details = {
313
326
  affiliateName: data.affiliate.affiliateName,
314
327
  affiliateShortCode: data.affiliate.affiliateShortCode,
315
328
  deeplinkUrl: data.affiliate.deeplinkurl
316
329
  };
317
330
  this.verboseLog(`Successfully retrieved affiliate details for: ${details.affiliateName}`);
318
- return details;
331
+ return { status: "found", details };
319
332
  }
320
333
  this.verboseLog("Affiliate does not exist");
321
- return null;
334
+ return { status: "not_found", details: null };
322
335
  } catch (error) {
323
336
  this.verboseLog(`Error fetching affiliate details: ${error}`);
324
- return null;
337
+ return { status: "lookup_failed", details: null };
325
338
  }
326
339
  }
340
+ /**
341
+ * Retrieve detailed information about an affiliate by their short code or deep link.
342
+ * Kept for backward compatibility: collapses 'not_found' and 'lookup_failed' into the
343
+ * same null result, exactly as before. Use getAffiliateLookupResult if you need to tell
344
+ * an invalid code apart from a backend outage.
345
+ * @param affiliateCode The short code or deep link to look up
346
+ * @returns AffiliateDetails if found, null otherwise
347
+ */
348
+ static async getAffiliateDetails(affiliateCode, options) {
349
+ const result = await this.getAffiliateLookupResult(affiliateCode, options);
350
+ return result.details;
351
+ }
327
352
  static async returnCompanyId() {
328
353
  this.verboseLog("Getting company ID...");
329
354
  const companyCode = this.companyCode || await getValue("companyCode");
@@ -390,18 +415,13 @@ var InsertAffiliate = class {
390
415
  this.verboseLog(`Failed to fetch offer code, status: ${response.status}`);
391
416
  return null;
392
417
  }
393
- const offerCode = (await response.text()).replace(/[^a-zA-Z0-9]/g, "");
394
- this.verboseLog(`Received offer code: ${offerCode}`);
395
- const errorCodes = [
396
- "errorofferCodeNotFound",
397
- "errorAffiliateoffercodenotfoundinanycompany",
398
- "errorAffiliateoffercodenotfoundinanycompanyAffiliatelinkwas",
399
- "Routenotfound"
400
- ];
401
- if (errorCodes.includes(offerCode)) {
418
+ const rawOfferCode = await response.text();
419
+ if (rawOfferCode.includes("errorofferCodeNotFound") || rawOfferCode.includes("errorAffiliateoffercodenotfoundinanycompany") || rawOfferCode.includes("errorAffiliateoffercodenotfoundinanycompanyAffiliatelinkwas") || rawOfferCode.includes("Routenotfound")) {
402
420
  this.verboseLog("Offer code not found or invalid");
403
421
  return null;
404
422
  }
423
+ const offerCode = rawOfferCode.replace(/[^a-zA-Z0-9_-]/g, "");
424
+ this.verboseLog(`Received offer code: ${offerCode}`);
405
425
  const storageKey = platformType === "stripe" ? "offerCode" : `offerCode_${platformType}`;
406
426
  await saveValue(storageKey, offerCode);
407
427
  if (platformType === "stripe") {
@@ -431,18 +451,13 @@ var InsertAffiliate = class {
431
451
  this.verboseLog(`Failed to fetch offer code, status: ${response.status}`);
432
452
  return null;
433
453
  }
434
- const offerCode = (await response.text()).replace(/[^a-zA-Z0-9]/g, "");
435
- this.verboseLog(`Received offer code: ${offerCode}`);
436
- const errorCodes = [
437
- "errorofferCodeNotFound",
438
- "errorAffiliateoffercodenotfoundinanycompany",
439
- "errorAffiliateoffercodenotfoundinanycompanyAffiliatelinkwas",
440
- "Routenotfound"
441
- ];
442
- if (errorCodes.includes(offerCode)) {
454
+ const rawOfferCode = await response.text();
455
+ if (rawOfferCode.includes("errorofferCodeNotFound") || rawOfferCode.includes("errorAffiliateoffercodenotfoundinanycompany") || rawOfferCode.includes("errorAffiliateoffercodenotfoundinanycompanyAffiliatelinkwas") || rawOfferCode.includes("Routenotfound")) {
443
456
  this.verboseLog("Offer code not found or invalid");
444
457
  return null;
445
458
  }
459
+ const offerCode = rawOfferCode.replace(/[^a-zA-Z0-9_-]/g, "");
460
+ this.verboseLog(`Received offer code: ${offerCode}`);
446
461
  this.offerCode = offerCode;
447
462
  await saveValue("offerCode", offerCode);
448
463
  this.verboseLog(`Offer code stored successfully: ${offerCode}`);
@@ -611,18 +626,13 @@ var InsertAffiliate = class {
611
626
  this.verboseLog(`API URL: ${url}`);
612
627
  const res = await fetch(url);
613
628
  this.verboseLog(`API response status: ${res.status}`);
614
- const offerCode = (await res.text()).replace(/[^a-zA-Z0-9]/g, "");
615
- this.verboseLog(`Received offer code: ${offerCode}`);
616
- const errorCodes = [
617
- "errorofferCodeNotFound",
618
- "errorAffiliateoffercodenotfoundinanycompany",
619
- "errorAffiliateoffercodenotfoundinanycompanyAffiliatelinkwas",
620
- "Routenotfound"
621
- ];
622
- if (errorCodes.includes(offerCode)) {
629
+ const rawOfferCode = await res.text();
630
+ if (rawOfferCode.includes("errorofferCodeNotFound") || rawOfferCode.includes("errorAffiliateoffercodenotfoundinanycompany") || rawOfferCode.includes("errorAffiliateoffercodenotfoundinanycompanyAffiliatelinkwas") || rawOfferCode.includes("Routenotfound")) {
623
631
  this.verboseLog("Offer code not found or invalid");
624
632
  return;
625
633
  }
634
+ const offerCode = rawOfferCode.replace(/[^a-zA-Z0-9_-]/g, "");
635
+ this.verboseLog(`Received offer code: ${offerCode}`);
626
636
  const redeemUrl = `https://apps.apple.com/redeem?ctx=offercodes&id=${offerCodeUrlId}&code=${offerCode}`;
627
637
  this.verboseLog(`Opening redeem URL: ${redeemUrl}`);
628
638
  window.open(redeemUrl, "_blank");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "insert-affiliate-js-sdk",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,6 +1,6 @@
1
1
  // src/sdk/InsertAffiliate.ts
2
2
  import { getValue, saveValue } from '../utils/asyncStorage';
3
- import { generateUUID, generateShortDeviceID } from '../utils/helpers';
3
+ import { generateShortDeviceID, generateUUID } from '../utils/helpers';
4
4
 
5
5
  interface IapticAndroidReceipt {
6
6
  orderId: string;
@@ -25,6 +25,15 @@ export interface AffiliateDetails {
25
25
  deeplinkUrl: string;
26
26
  }
27
27
 
28
+ // 'lookup_failed' (outage, timeout) may be worth retrying. 'not_configured' (no company
29
+ // code set) never will be — it's always a bug in the calling app, not the code or backend.
30
+ export type AffiliateLookupStatus = 'found' | 'not_found' | 'lookup_failed' | 'not_configured';
31
+
32
+ export interface AffiliateLookupResult {
33
+ status: AffiliateLookupStatus;
34
+ details: AffiliateDetails | null;
35
+ }
36
+
28
37
  export type InsertAffiliateIdentifierChangeCallback = (identifier: string | null, offerCode: string | null) => void;
29
38
 
30
39
  export class InsertAffiliate {
@@ -196,9 +205,11 @@ export class InsertAffiliate {
196
205
  * Validates and sets a short code for affiliate tracking
197
206
  * Validates the short code against the API before storing
198
207
  * @param shortCode The short code to validate and set
208
+ * @param options.onLookupFailed called when the lookup itself couldn't be completed
209
+ * (not just an invalid code) — use it to offer a retry instead of proceeding unattributed.
199
210
  * @returns true if the code exists and was successfully validated and stored, false otherwise
200
211
  */
201
- static async setShortCode(shortCode: string): Promise<boolean> {
212
+ static async setShortCode(shortCode: string, options?: { onLookupFailed?: () => void }): Promise<boolean> {
202
213
  this.verboseLog(`Setting short code. Input: ${shortCode}`);
203
214
 
204
215
  const valid = /^[a-zA-Z0-9]{3,25}$/.test(shortCode);
@@ -210,12 +221,16 @@ export class InsertAffiliate {
210
221
  }
211
222
 
212
223
  // Validate that the short code exists in the system
213
- const affiliateDetails = await this.getAffiliateDetails(shortCode, { trackUsage: true });
214
- if (!affiliateDetails) {
215
- this.verboseLog(`Short code '${shortCode}' does not exist or validation failed`);
224
+ const lookup = await this.getAffiliateLookupResult(shortCode, { trackUsage: true });
225
+ if (lookup.status !== 'found' || !lookup.details) {
226
+ this.verboseLog(`Short code '${shortCode}' does not exist or validation failed (status: ${lookup.status})`);
216
227
  console.error(`[Insert Affiliate] Error: Short code '${shortCode}' does not exist or validation failed.`);
228
+ if (lookup.status === 'lookup_failed') {
229
+ options?.onLookupFailed?.();
230
+ }
217
231
  return false;
218
232
  }
233
+ const affiliateDetails = lookup.details;
219
234
 
220
235
  this.verboseLog(`Short code validated successfully for affiliate: ${affiliateDetails.affiliateName}`);
221
236
  console.log(`[Insert Affiliate] Short code validated successfully for affiliate: ${affiliateDetails.affiliateName}`);
@@ -304,18 +319,20 @@ export class InsertAffiliate {
304
319
  }
305
320
 
306
321
  /**
307
- * Retrieve detailed information about an affiliate by their short code or deep link
308
- * This method queries the API and does not store or set the affiliate identifier
322
+ * Retrieve detailed information about an affiliate by their short code or deep link,
323
+ * distinguishing "no affiliate matches this code" from "couldn't check" (backend
324
+ * outage, timeout, rate limit). This method queries the API and does not store or set
325
+ * the affiliate identifier.
309
326
  * @param affiliateCode The short code or deep link to look up
310
- * @returns AffiliateDetails if found, null otherwise
327
+ * @returns an AffiliateLookupResult with a status of 'found', 'not_found', 'lookup_failed', or 'not_configured'
311
328
  */
312
- static async getAffiliateDetails(affiliateCode: string, options?: { trackUsage?: boolean }): Promise<AffiliateDetails | null> {
329
+ static async getAffiliateLookupResult(affiliateCode: string, options?: { trackUsage?: boolean }): Promise<AffiliateLookupResult> {
313
330
  this.verboseLog(`Getting affiliate details for: ${affiliateCode}`);
314
331
 
315
332
  const companyCode = this.companyCode || await getValue('companyCode');
316
333
  if (!companyCode) {
317
334
  this.verboseLog('Cannot get affiliate details: no company code available');
318
- return null;
335
+ return { status: 'not_configured', details: null };
319
336
  }
320
337
 
321
338
  // Strip UUID from code if present (e.g., "ABC123-uuid" becomes "ABC123")
@@ -346,13 +363,19 @@ export class InsertAffiliate {
346
363
 
347
364
  if (!response.ok) {
348
365
  this.verboseLog(`Failed to get affiliate details, status: ${response.status}`);
349
- return null;
366
+ return { status: 'lookup_failed', details: null };
350
367
  }
351
368
 
352
369
  const data = await response.json();
353
370
  this.verboseLog(`API response data: ${JSON.stringify(data)}`);
354
371
 
355
- if (data.exists && data.affiliate) {
372
+ if (data.exists) {
373
+ if (!data.affiliate) {
374
+ // Malformed response, not a real not-found.
375
+ this.verboseLog('Affiliate exists but response is missing affiliate details');
376
+ return { status: 'lookup_failed', details: null };
377
+ }
378
+
356
379
  const details: AffiliateDetails = {
357
380
  affiliateName: data.affiliate.affiliateName,
358
381
  affiliateShortCode: data.affiliate.affiliateShortCode,
@@ -360,17 +383,30 @@ export class InsertAffiliate {
360
383
  };
361
384
 
362
385
  this.verboseLog(`Successfully retrieved affiliate details for: ${details.affiliateName}`);
363
- return details;
386
+ return { status: 'found', details };
364
387
  }
365
388
 
366
389
  this.verboseLog('Affiliate does not exist');
367
- return null;
390
+ return { status: 'not_found', details: null };
368
391
  } catch (error) {
369
392
  this.verboseLog(`Error fetching affiliate details: ${error}`);
370
- return null;
393
+ return { status: 'lookup_failed', details: null };
371
394
  }
372
395
  }
373
396
 
397
+ /**
398
+ * Retrieve detailed information about an affiliate by their short code or deep link.
399
+ * Kept for backward compatibility: collapses 'not_found' and 'lookup_failed' into the
400
+ * same null result, exactly as before. Use getAffiliateLookupResult if you need to tell
401
+ * an invalid code apart from a backend outage.
402
+ * @param affiliateCode The short code or deep link to look up
403
+ * @returns AffiliateDetails if found, null otherwise
404
+ */
405
+ static async getAffiliateDetails(affiliateCode: string, options?: { trackUsage?: boolean }): Promise<AffiliateDetails | null> {
406
+ const result = await this.getAffiliateLookupResult(affiliateCode, options);
407
+ return result.details;
408
+ }
409
+
374
410
  static async returnCompanyId(): Promise<string | null> {
375
411
  this.verboseLog('Getting company ID...');
376
412
  const companyCode = this.companyCode || await getValue('companyCode');
@@ -453,21 +489,23 @@ export class InsertAffiliate {
453
489
  return null;
454
490
  }
455
491
 
456
- const offerCode = (await response.text()).replace(/[^a-zA-Z0-9]/g, '');
457
- this.verboseLog(`Received offer code: ${offerCode}`);
458
-
459
- const errorCodes = [
460
- 'errorofferCodeNotFound',
461
- 'errorAffiliateoffercodenotfoundinanycompany',
462
- 'errorAffiliateoffercodenotfoundinanycompanyAffiliatelinkwas',
463
- 'Routenotfound'
464
- ];
492
+ const rawOfferCode = await response.text();
465
493
 
466
- if (errorCodes.includes(offerCode)) {
494
+ // Check for specific error strings from API before cleaning
495
+ if (
496
+ rawOfferCode.includes('errorofferCodeNotFound') ||
497
+ rawOfferCode.includes('errorAffiliateoffercodenotfoundinanycompany') ||
498
+ rawOfferCode.includes('errorAffiliateoffercodenotfoundinanycompanyAffiliatelinkwas') ||
499
+ rawOfferCode.includes('Routenotfound')
500
+ ) {
467
501
  this.verboseLog('Offer code not found or invalid');
468
502
  return null;
469
503
  }
470
504
 
505
+ // Remove special characters, keep only alphanumeric, underscores, and dashes
506
+ const offerCode = rawOfferCode.replace(/[^a-zA-Z0-9_-]/g, '');
507
+ this.verboseLog(`Received offer code: ${offerCode}`);
508
+
471
509
  // Store offer code with platform-specific key
472
510
  const storageKey = platformType === 'stripe' ? 'offerCode' : `offerCode_${platformType}`;
473
511
  await saveValue(storageKey, offerCode);
@@ -507,22 +545,23 @@ export class InsertAffiliate {
507
545
  return null;
508
546
  }
509
547
 
510
- const offerCode = (await response.text()).replace(/[^a-zA-Z0-9]/g, '');
511
- this.verboseLog(`Received offer code: ${offerCode}`);
548
+ const rawOfferCode = await response.text();
512
549
 
513
- // Check for error codes
514
- const errorCodes = [
515
- 'errorofferCodeNotFound',
516
- 'errorAffiliateoffercodenotfoundinanycompany',
517
- 'errorAffiliateoffercodenotfoundinanycompanyAffiliatelinkwas',
518
- 'Routenotfound'
519
- ];
520
-
521
- if (errorCodes.includes(offerCode)) {
550
+ // Check for specific error strings from API before cleaning
551
+ if (
552
+ rawOfferCode.includes('errorofferCodeNotFound') ||
553
+ rawOfferCode.includes('errorAffiliateoffercodenotfoundinanycompany') ||
554
+ rawOfferCode.includes('errorAffiliateoffercodenotfoundinanycompanyAffiliatelinkwas') ||
555
+ rawOfferCode.includes('Routenotfound')
556
+ ) {
522
557
  this.verboseLog('Offer code not found or invalid');
523
558
  return null;
524
559
  }
525
560
 
561
+ // Remove special characters, keep only alphanumeric, underscores, and dashes
562
+ const offerCode = rawOfferCode.replace(/[^a-zA-Z0-9_-]/g, '');
563
+ this.verboseLog(`Received offer code: ${offerCode}`);
564
+
526
565
  // Store offer code
527
566
  this.offerCode = offerCode;
528
567
  await saveValue('offerCode', offerCode);
@@ -739,21 +778,23 @@ export class InsertAffiliate {
739
778
  const res = await fetch(url);
740
779
  this.verboseLog(`API response status: ${res.status}`);
741
780
 
742
- const offerCode = (await res.text()).replace(/[^a-zA-Z0-9]/g, '');
743
- this.verboseLog(`Received offer code: ${offerCode}`);
781
+ const rawOfferCode = await res.text();
744
782
 
745
- const errorCodes = [
746
- 'errorofferCodeNotFound',
747
- 'errorAffiliateoffercodenotfoundinanycompany',
748
- 'errorAffiliateoffercodenotfoundinanycompanyAffiliatelinkwas',
749
- 'Routenotfound'
750
- ];
751
-
752
- if (errorCodes.includes(offerCode)) {
783
+ // Check for specific error strings from API before cleaning
784
+ if (
785
+ rawOfferCode.includes('errorofferCodeNotFound') ||
786
+ rawOfferCode.includes('errorAffiliateoffercodenotfoundinanycompany') ||
787
+ rawOfferCode.includes('errorAffiliateoffercodenotfoundinanycompanyAffiliatelinkwas') ||
788
+ rawOfferCode.includes('Routenotfound')
789
+ ) {
753
790
  this.verboseLog('Offer code not found or invalid');
754
791
  return;
755
792
  }
756
793
 
794
+ // Remove special characters, keep only alphanumeric, underscores, and dashes
795
+ const offerCode = rawOfferCode.replace(/[^a-zA-Z0-9_-]/g, '');
796
+ this.verboseLog(`Received offer code: ${offerCode}`);
797
+
757
798
  const redeemUrl = `https://apps.apple.com/redeem?ctx=offercodes&id=${offerCodeUrlId}&code=${offerCode}`;
758
799
  this.verboseLog(`Opening redeem URL: ${redeemUrl}`);
759
800
  window.open(redeemUrl, '_blank');
@@ -1,10 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "WebFetch(domain:github.com)",
5
- "WebFetch(domain:www.revenuecat.com)"
6
- ],
7
- "deny": [],
8
- "ask": []
9
- }
10
- }