insert-affiliate-js-sdk 1.0.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 ADDED
@@ -0,0 +1,208 @@
1
+ # Insert Affiliate JavaScript SDK
2
+
3
+ ## Overview
4
+
5
+ The **Insert Affiliate JavaScript SDK** brings affiliate tracking to web and hybrid applications, providing seamless integration with the [Insert Affiliate platform](https://insertaffiliate.com). It is fully compatible with Capacitor, making it a great choice for modern cross-platform apps that require affiliate attribution and purchase tracking support.
6
+
7
+ This SDK is ideal for developers who want to integrate affiliate marketing into their app's monetisation strategy and track purchases via partners like RevenueCat.
8
+
9
+ ### Features
10
+
11
+ - **Unique Device ID**: Creates a unique ID to anonymously associate purchases with users for tracking purposes.
12
+ - **Affiliate Identifier Management**: Set and retrieve the affiliate identifier based on user-specific links or short codes.
13
+ - **Short Code Support (Beta)**: Allow users to enter affiliate short codes for tracking.
14
+
15
+ ### Supported Platforms
16
+ - ✅ Capacitor (iOS / Android) – Fully tested
17
+ - ✅ Web Browsers – Tested in modern desktop and mobile browsers
18
+ - ⚠️ Other JavaScript Environments – May work, but not officially tested
19
+
20
+ ## Getting Started
21
+ To get started with the Insert Affiliate JavaScript SDK:
22
+
23
+ 1. [Install the SDK via NPM](#installation)
24
+ 2. [Initialise the SDK in your Main Javascript/Typescript File](#basic-usage)
25
+ 3. [Set up in-app purchases (Required)](#in-app-purchase-setup-required)
26
+ 4. [Set up deep linking (Required)](#deep-link-setup-required)
27
+ 5. [Use additional features like short codes and event tracking.](#additional-features)
28
+
29
+
30
+ ## Installation
31
+
32
+ Install the Insert Affiliate JavaScript SDK and required plugins:
33
+
34
+ ```bash
35
+ npm install insert-affiliate-js-sdk
36
+ ```
37
+
38
+ Then run
39
+ ```bash
40
+ npx cap sync
41
+ ```
42
+
43
+ ## Basic Usage
44
+ ### Import the SDKs
45
+
46
+ In your ```main.ts``` or ```main.js``` file:
47
+
48
+ ```javascript
49
+ import { InsertAffiliate } from 'insert-affiliate-js-sdk';
50
+ await InsertAffiliate.initialize("your_company_code");
51
+
52
+ ```
53
+ - Replace `{{ your_company_code }}` with the unique company code associated with your Insert Affiliate account. You can find this code in your dashboard under [Settings](http://app.insertaffiliate.com/settings).
54
+
55
+ ## In-App Purchase Setup [Required]
56
+ Insert Affiliate requires a Receipt Verification platform to validate in-app purchases. You must choose **one** of our supported partners:
57
+ - [RevenueCat](https://www.revenuecat.com/)
58
+
59
+ ### Option 1: RevenueCat Integration
60
+
61
+ #### Code Setup
62
+ 1. **Install RevenueCat SDK** - First, complete the set up of the relevant [RevenueCat SDK](https://www.revenuecat.com/docs/getting-started/installation) to set up in-app purchases and subscriptions.
63
+
64
+ 2. **Modify Initialisation Code** - Update the file where you initialise your deep linking (e.g., Branch.io) and RevenueCat to include a call to ```InsertAffiliate.returnInsertAffiliateIdentifier()```. This ensures that the Insert Affiliate identifier is passed to RevenueCat every time the app starts or a deep link is clicked.
65
+
66
+ 3. **Implementation Example**
67
+
68
+ ```javascript
69
+ import { InsertAffiliate } from 'insert-affiliate-js-sdk';
70
+ import { Purchases } from '@revenuecat/purchases-capacitor';
71
+
72
+ window.addEventListener('DOMContentLoaded', async () => {
73
+ await Purchases.configure({ apiKey: 'your_revcat_api_key' });
74
+
75
+ const affiliateIdentifier = await InsertAffiliate.returnInsertAffiliateIdentifier();
76
+
77
+ if (affiliateIdentifier) {
78
+ await Purchases.setAttributes({ insert_affiliate: affiliateIdentifier });
79
+ }
80
+ });
81
+ ```
82
+
83
+ #### Webhook Setup
84
+
85
+ Next, you must setup a webhook to allow us to communicate directly with RevenueCat to track affiliate purchases.
86
+
87
+ 1. Go to RevenueCat and [create a new webhook](https://www.revenuecat.com/docs/integrations/webhooks)
88
+
89
+ 2. Configure the webhook with these settings:
90
+ - Webhook URL: `https://api.insertaffiliate.com/v1/api/revenuecat-webhook`
91
+ - Authorization header: Use the value from your Insert Affiliate dashboard (you'll get this in step 4)
92
+ - Set "Event Type" to "All events"
93
+
94
+ 3. In your [Insert Affiliate dashboard settings](https://app.insertaffiliate.com/settings):
95
+ - Navigate to the verification settings
96
+ - Set the in-app purchase verification method to `RevenueCat`
97
+
98
+ 4. Back in your Insert Affiliate dashboard:
99
+ - Locate the `RevenueCat Webhook Authentication Header` value
100
+ - Copy this value
101
+ - Paste it as the Authorization header value in your RevenueCat webhook configuration
102
+
103
+
104
+ ## Deep Link Setup [Required]
105
+ Insert Affiliate requires a Deep Linking platform to create links for your affiliates. Our platform works with **any** deep linking provider, and you only need to follow these steps:
106
+ 1. **Create a deep link** in your chosen third-party platform and pass it to our dashboard when an affiliate signs up.
107
+ 2. **Handle deep link clicks** in your app by passing the clicked link:
108
+ ```javascript
109
+ InsertAffiliate.setInsertAffiliateIdentifier(data["~referring_link"]);
110
+ ```
111
+
112
+ ### Deep Linking with Branch.io
113
+ To set up deep linking with Branch.io, follow these steps:
114
+
115
+ 1. Create a deep link in Branch and pass it to our dashboard when an affiliate signs up.
116
+ - Example: [Create Affiliate](https://docs.insertaffiliate.com/create-affiliate).
117
+ 2. Modify Your Deep Link Handling
118
+ - After setting up your Branch integration, add the following code to initialise the Insert Affiliate SDK in your iOS app:
119
+
120
+
121
+ ```javascript
122
+ import { BranchDeepLinks, BranchInitEvent } from 'capacitor-branch-deep-links';
123
+ import { InsertAffiliate } from 'insert-affiliate-js-sdk';
124
+
125
+ let branchInitialised = false;
126
+
127
+ async function setUpBranchListener() {
128
+ if (branchInitialised) return;
129
+ branchInitialised = true;
130
+
131
+ try {
132
+ await BranchDeepLinks.addListener('init', async (event: BranchInitEvent) => {
133
+ const clicked = event?.referringParams?.['+clicked_branch_link'];
134
+ const referringLink = event?.referringParams?.['~referring_link'];
135
+
136
+ if (clicked && referringLink) {
137
+ await InsertAffiliate.setInsertAffiliateIdentifier(referringLink);
138
+ }
139
+ });
140
+
141
+ BranchDeepLinks.addListener('initError', (error: any) => {
142
+ console.error('Branch init error:', error);
143
+ });
144
+ } catch (err) {
145
+ console.error('Error setting up Branch listener:', err);
146
+ }
147
+ }
148
+
149
+ ```
150
+
151
+ ## Additional Features
152
+
153
+ ### 1. Event Tracking (Beta)
154
+
155
+ Insert Affiliate now includes a beta feature for event tracking. Use event tracking to log key user actions such as signups, purchases, or referrals. This is useful for:
156
+ - Understanding user behaviour.
157
+ - Measuring the effectiveness of marketing campaigns.
158
+ - Incentivising affiliates for designated actions being taken by the end users, rather than just in app purchases (i.e. pay an affilaite for each signup).
159
+
160
+ At this stage, we cannot guarantee that this feature is fully resistant to tampering or manipulation.
161
+
162
+ #### Using `trackEvent`
163
+
164
+ To track an event, use the `trackEvent` function. Make sure to set an affiliate identifier first; otherwise, event tracking won’t work. Here’s an example:
165
+
166
+ ```javascript
167
+ import { InsertAffiliate } from 'insert-affiliate-js-sdk';
168
+
169
+ async function trackSignupEvent() {
170
+ try {
171
+ await InsertAffiliate.trackEvent('your_event_name_here');
172
+ } catch (error) {
173
+ console.error('❌ Failed to track event:', error);
174
+ }
175
+ }
176
+ ```
177
+
178
+ ### 2. Short Codes (Beta)
179
+
180
+ ### What are Short Codes?
181
+
182
+ Short codes are unique, 10-character alphanumeric identifiers that affiliates can use to promote products or subscriptions. These codes are ideal for influencers or partners, making them easier to share than long URLs.
183
+
184
+ **Example Use Case**: An influencer promotes a subscription with the short code "JOIN123456" within their TikTok video's description. When users enter this code within your app during sign-up or before purchase, the app tracks the subscription back to the influencer for commission payouts.
185
+
186
+ For more information, visit the [Insert Affiliate Short Codes Documentation](https://docs.insertaffiliate.com/short-codes).
187
+
188
+ ### Setting a Short Code
189
+
190
+ Use the `setShortCode` method to associate a short code with an affiliate. This is ideal for scenarios where users enter the code via an input field, pop-up, or similar UI element.
191
+
192
+ Short codes must meet the following criteria:
193
+ - Exactly **10 characters long**.
194
+ - Contain only **letters and numbers** (alphanumeric characters).
195
+ - Replace {{ user_entered_short_code }} with the short code the user enters through your chosen input method, i.e. an input field / pop up element
196
+
197
+
198
+ #### Example Integration
199
+ Below is an example SwiftUI implementation where users can enter a short code, which will be validated and associated with the affiliate's account:
200
+
201
+ ```javascript
202
+ import { InsertAffiliate } from 'insert-affiliate-js-sdk';
203
+
204
+ // Example: user entered this in a form
205
+ const userEnteredCode = 'B3SC6VRRKQ';
206
+
207
+ InsertAffiliate.setShortCode(userEnteredCode);
208
+ ```
@@ -0,0 +1,22 @@
1
+ interface IapticIOSReceipt {
2
+ transactionReceipt: string;
3
+ }
4
+ declare class InsertAffiliate {
5
+ private static isInitialized;
6
+ private static companyCode;
7
+ static initialize(code: string | null): Promise<void>;
8
+ static returnInsertAffiliateIdentifier(): Promise<string | null>;
9
+ static setInsertAffiliateIdentifier(referringLink: string): Promise<string | null>;
10
+ static setShortCode(shortCode: string): Promise<void>;
11
+ static trackEvent(eventName: string): Promise<void>;
12
+ static returnUserAccountTokenAndStoreExpectedTransaction(): Promise<string | null>;
13
+ static storeExpectedStoreTransaction(userAccountToken: string): Promise<void>;
14
+ static validatePurchaseWithIapticAPI(jsonIapPurchase: IapticIOSReceipt | {
15
+ transactionReceipt: string;
16
+ }, iapticAppId: string, iapticAppName: string, iapticPublicKey: string): Promise<boolean>;
17
+ static fetchAndConditionallyOpenUrl(affiliateLink: string, offerCodeUrlId: string): Promise<void>;
18
+ private static getOrCreateUserID;
19
+ private static fetchShortLink;
20
+ }
21
+
22
+ export { InsertAffiliate };
package/dist/index.js ADDED
@@ -0,0 +1,256 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __defProps = Object.defineProperties;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
10
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
11
+ var __spreadValues = (a, b) => {
12
+ for (var prop in b || (b = {}))
13
+ if (__hasOwnProp.call(b, prop))
14
+ __defNormalProp(a, prop, b[prop]);
15
+ if (__getOwnPropSymbols)
16
+ for (var prop of __getOwnPropSymbols(b)) {
17
+ if (__propIsEnum.call(b, prop))
18
+ __defNormalProp(a, prop, b[prop]);
19
+ }
20
+ return a;
21
+ };
22
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
23
+ var __export = (target, all) => {
24
+ for (var name in all)
25
+ __defProp(target, name, { get: all[name], enumerable: true });
26
+ };
27
+ var __copyProps = (to, from, except, desc) => {
28
+ if (from && typeof from === "object" || typeof from === "function") {
29
+ for (let key of __getOwnPropNames(from))
30
+ if (!__hasOwnProp.call(to, key) && key !== except)
31
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
32
+ }
33
+ return to;
34
+ };
35
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
36
+
37
+ // src/index.ts
38
+ var index_exports = {};
39
+ __export(index_exports, {
40
+ InsertAffiliate: () => InsertAffiliate
41
+ });
42
+ module.exports = __toCommonJS(index_exports);
43
+
44
+ // src/utils/asyncStorage.ts
45
+ var saveValue = async (key, value) => {
46
+ localStorage.setItem(key, value);
47
+ };
48
+ var getValue = async (key) => {
49
+ return localStorage.getItem(key);
50
+ };
51
+
52
+ // src/utils/helpers.ts
53
+ var generateShortDeviceID = () => {
54
+ const hashed = Math.abs(generateUUID().hashCode()) % 16777215;
55
+ return hashed.toString(16).padStart(6, "0").toUpperCase();
56
+ };
57
+ var generateUUID = () => {
58
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
59
+ const r = Math.floor(Math.random() * 16);
60
+ const v = c === "x" ? r : r & 3 | 8;
61
+ return v.toString(16);
62
+ });
63
+ };
64
+ if (!String.prototype.hasOwnProperty("hashCode")) {
65
+ String.prototype.hashCode = function() {
66
+ let hash = 0;
67
+ for (let i = 0; i < this.length; i++) {
68
+ const chr = this.charCodeAt(i);
69
+ hash = (hash << 5) - hash + chr;
70
+ hash |= 0;
71
+ }
72
+ return hash;
73
+ };
74
+ }
75
+
76
+ // src/sdk/InsertAffiliate.ts
77
+ var InsertAffiliate = class {
78
+ static async initialize(code) {
79
+ if (this.isInitialized) {
80
+ console.warn("[Insert Affiliate] SDK already initialized.");
81
+ return;
82
+ }
83
+ this.companyCode = code;
84
+ await saveValue("companyCode", code || "");
85
+ this.isInitialized = true;
86
+ console.log(`[Insert Affiliate] SDK initialized ${code ? `with company code: ${code}` : "without a company code."}`);
87
+ }
88
+ static async returnInsertAffiliateIdentifier() {
89
+ const userId = await this.getOrCreateUserID();
90
+ const referrerLink = await getValue("referrerLink");
91
+ if (!referrerLink) return null;
92
+ return `${referrerLink}-${userId}`;
93
+ }
94
+ static async setInsertAffiliateIdentifier(referringLink) {
95
+ const userId = await this.getOrCreateUserID();
96
+ const shortCode = /^[a-zA-Z0-9]{10}$/.test(referringLink) ? referringLink : await this.fetchShortLink(referringLink);
97
+ if (!shortCode) return null;
98
+ await saveValue("referrerLink", shortCode);
99
+ return `${shortCode}-${userId}`;
100
+ }
101
+ static async setShortCode(shortCode) {
102
+ const valid = /^[a-zA-Z0-9]{10}$/.test(shortCode);
103
+ if (!valid) {
104
+ console.warn("[Insert Affiliate] Invalid short code.");
105
+ return;
106
+ }
107
+ await this.setInsertAffiliateIdentifier(shortCode);
108
+ }
109
+ static async trackEvent(eventName) {
110
+ const id = await this.returnInsertAffiliateIdentifier();
111
+ if (!id) {
112
+ console.warn("[Insert Affiliate] No affiliate identifier found.");
113
+ return;
114
+ }
115
+ try {
116
+ await fetch("https://api.insertaffiliate.com/v1/trackEvent", {
117
+ method: "POST",
118
+ headers: { "Content-Type": "application/json" },
119
+ body: JSON.stringify({ eventName, deepLinkParam: id })
120
+ });
121
+ console.log("[Insert Affiliate] Event tracked:", eventName);
122
+ } catch (err) {
123
+ console.error("[Insert Affiliate] Failed to track event:", err);
124
+ }
125
+ }
126
+ static async returnUserAccountTokenAndStoreExpectedTransaction() {
127
+ const shortCode = await this.returnInsertAffiliateIdentifier();
128
+ if (!shortCode) return null;
129
+ let token = await getValue("userAccountToken");
130
+ if (!token) {
131
+ token = generateUUID();
132
+ await saveValue("userAccountToken", token);
133
+ }
134
+ await this.storeExpectedStoreTransaction(token);
135
+ return token;
136
+ }
137
+ static async storeExpectedStoreTransaction(userAccountToken) {
138
+ const companyCode = this.companyCode || await getValue("companyCode");
139
+ const shortCode = await this.returnInsertAffiliateIdentifier();
140
+ if (!companyCode || !shortCode) {
141
+ console.error("[Insert Affiliate] Missing company code or identifier.");
142
+ return;
143
+ }
144
+ const payload = {
145
+ UUID: userAccountToken,
146
+ companyCode,
147
+ shortCode,
148
+ storedDate: (/* @__PURE__ */ new Date()).toISOString()
149
+ };
150
+ try {
151
+ const res = await fetch("https://api.insertaffiliate.com/v1/api/app-store-webhook/create-expected-transaction", {
152
+ method: "POST",
153
+ headers: { "Content-Type": "application/json" },
154
+ body: JSON.stringify(payload)
155
+ });
156
+ if (res.status === 200) {
157
+ console.log("[Insert Affiliate] Stored expected transaction");
158
+ } else {
159
+ console.warn("[Insert Affiliate] Failed storing transaction:", res.status);
160
+ }
161
+ } catch (error) {
162
+ console.error("[Insert Affiliate] Error storing transaction:", error);
163
+ }
164
+ }
165
+ static async validatePurchaseWithIapticAPI(jsonIapPurchase, iapticAppId, iapticAppName, iapticPublicKey) {
166
+ try {
167
+ const isIOS = typeof window !== "undefined" && /iPad|iPhone|iPod/.test(navigator.userAgent);
168
+ const baseRequest = {
169
+ id: iapticAppId,
170
+ type: "application"
171
+ };
172
+ let transaction;
173
+ if (isIOS) {
174
+ transaction = {
175
+ id: iapticAppId,
176
+ type: "ios-appstore",
177
+ appStoreReceipt: jsonIapPurchase.transactionReceipt
178
+ };
179
+ } else {
180
+ const receiptJson = JSON.parse(atob(jsonIapPurchase.transactionReceipt));
181
+ transaction = {
182
+ id: receiptJson.orderId,
183
+ type: "android-playstore",
184
+ purchaseToken: receiptJson.purchaseToken,
185
+ receipt: jsonIapPurchase.transactionReceipt,
186
+ signature: receiptJson.signature
187
+ };
188
+ }
189
+ const insertAffiliateIdentifier = await this.returnInsertAffiliateIdentifier();
190
+ const payload = __spreadProps(__spreadValues({}, baseRequest), {
191
+ transaction,
192
+ additionalData: insertAffiliateIdentifier ? { applicationUsername: insertAffiliateIdentifier } : void 0
193
+ });
194
+ const response = await fetch("https://validator.iaptic.com/v1/validate", {
195
+ method: "POST",
196
+ headers: {
197
+ "Content-Type": "application/json",
198
+ Authorization: `Basic ${btoa(`${iapticAppName}:${iapticPublicKey}`)}`
199
+ },
200
+ body: JSON.stringify(payload)
201
+ });
202
+ return response.status === 200;
203
+ } catch (error) {
204
+ console.error("[Insert Affiliate] Purchase validation failed:", error);
205
+ return false;
206
+ }
207
+ }
208
+ static async fetchAndConditionallyOpenUrl(affiliateLink, offerCodeUrlId) {
209
+ const encoded = encodeURIComponent(affiliateLink);
210
+ try {
211
+ const res = await fetch(`https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${encoded}`);
212
+ const offerCode = (await res.text()).replace(/[^a-zA-Z0-9]/g, "");
213
+ const errorCodes = [
214
+ "errorofferCodeNotFound",
215
+ "errorAffiliateoffercodenotfoundinanycompany",
216
+ "errorAffiliateoffercodenotfoundinanycompanyAffiliatelinkwas",
217
+ "Routenotfound"
218
+ ];
219
+ if (errorCodes.includes(offerCode)) {
220
+ console.warn("[Insert Affiliate] Offer Code Not Found");
221
+ return;
222
+ }
223
+ const redeemUrl = `https://apps.apple.com/redeem?ctx=offercodes&id=${offerCodeUrlId}&code=${offerCode}`;
224
+ window.open(redeemUrl, "_blank");
225
+ } catch (err) {
226
+ console.error("[Insert Affiliate] Error fetching/opening offer code:", err);
227
+ }
228
+ }
229
+ static async getOrCreateUserID() {
230
+ let id = await getValue("userId");
231
+ if (!id) {
232
+ id = generateShortDeviceID();
233
+ await saveValue("userId", id);
234
+ }
235
+ return id;
236
+ }
237
+ static async fetchShortLink(link) {
238
+ try {
239
+ const encoded = encodeURIComponent(link);
240
+ const companyCode = this.companyCode || await getValue("companyCode");
241
+ if (!companyCode) return null;
242
+ const res = await fetch(`https://api.insertaffiliate.com/V1/convert-deep-link-to-short-link?companyId=${companyCode}&deepLinkUrl=${encoded}`);
243
+ const data = await res.json();
244
+ return (data == null ? void 0 : data.shortLink) || null;
245
+ } catch (err) {
246
+ console.error("[Insert Affiliate] Failed to fetch short link:", (err == null ? void 0 : err.message) || err);
247
+ return null;
248
+ }
249
+ }
250
+ };
251
+ InsertAffiliate.isInitialized = false;
252
+ InsertAffiliate.companyCode = null;
253
+ // Annotate the CommonJS export names for ESM import in node:
254
+ 0 && (module.exports = {
255
+ InsertAffiliate
256
+ });
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "insert-affiliate-js-sdk",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "scripts": {
14
+ "build": "tsup",
15
+ "dev": "tsup --watch",
16
+ "prepare": "npm run build"
17
+ },
18
+ "keywords": [],
19
+ "author": "",
20
+ "license": "ISC",
21
+ "devDependencies": {
22
+ "tsup": "^8.4.0",
23
+ "typescript": "^5.8.2"
24
+ }
25
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './sdk/InsertAffiliate';
@@ -0,0 +1,239 @@
1
+ // src/sdk/InsertAffiliate.ts
2
+ import { getValue, saveValue } from '../utils/asyncStorage';
3
+ import { generateUUID, generateShortDeviceID } from '../utils/helpers';
4
+
5
+ interface IapticAndroidReceipt {
6
+ orderId: string;
7
+ purchaseToken: string;
8
+ signature: string;
9
+ }
10
+
11
+ interface IapticIOSReceipt {
12
+ transactionReceipt: string;
13
+ }
14
+
15
+ interface ExpectedTransactionPayload {
16
+ UUID: string;
17
+ companyCode: string;
18
+ shortCode: string;
19
+ storedDate: string;
20
+ }
21
+
22
+ export class InsertAffiliate {
23
+ private static isInitialized: boolean = false;
24
+ private static companyCode: string | null = null;
25
+
26
+ static async initialize(code: string | null): Promise<void> {
27
+ if (this.isInitialized) {
28
+ console.warn('[Insert Affiliate] SDK already initialized.');
29
+ return;
30
+ }
31
+ this.companyCode = code;
32
+ await saveValue('companyCode', code || '');
33
+ this.isInitialized = true;
34
+ console.log(`[Insert Affiliate] SDK initialized ${code ? `with company code: ${code}` : 'without a company code.'}`);
35
+ }
36
+
37
+ static async returnInsertAffiliateIdentifier(): Promise<string | null> {
38
+ const userId = await this.getOrCreateUserID();
39
+ const referrerLink = await getValue('referrerLink');
40
+ if (!referrerLink) return null;
41
+ return `${referrerLink}-${userId}`;
42
+ }
43
+
44
+ static async setInsertAffiliateIdentifier(referringLink: string): Promise<string | null> {
45
+ const userId = await this.getOrCreateUserID();
46
+ const shortCode = /^[a-zA-Z0-9]{10}$/.test(referringLink)
47
+ ? referringLink
48
+ : await this.fetchShortLink(referringLink);
49
+
50
+ if (!shortCode) return null;
51
+
52
+ await saveValue('referrerLink', shortCode);
53
+ return `${shortCode}-${userId}`;
54
+ }
55
+
56
+ static async setShortCode(shortCode: string): Promise<void> {
57
+ const valid = /^[a-zA-Z0-9]{10}$/.test(shortCode);
58
+ if (!valid) {
59
+ console.warn('[Insert Affiliate] Invalid short code.');
60
+ return;
61
+ }
62
+ await this.setInsertAffiliateIdentifier(shortCode);
63
+ }
64
+
65
+ static async trackEvent(eventName: string): Promise<void> {
66
+ const id = await this.returnInsertAffiliateIdentifier();
67
+ if (!id) {
68
+ console.warn('[Insert Affiliate] No affiliate identifier found.');
69
+ return;
70
+ }
71
+
72
+ try {
73
+ await fetch('https://api.insertaffiliate.com/v1/trackEvent', {
74
+ method: 'POST',
75
+ headers: { 'Content-Type': 'application/json' },
76
+ body: JSON.stringify({ eventName, deepLinkParam: id }),
77
+ });
78
+ console.log('[Insert Affiliate] Event tracked:', eventName);
79
+ } catch (err) {
80
+ console.error('[Insert Affiliate] Failed to track event:', err);
81
+ }
82
+ }
83
+
84
+ static async returnUserAccountTokenAndStoreExpectedTransaction(): Promise<string | null> {
85
+ const shortCode = await this.returnInsertAffiliateIdentifier();
86
+ if (!shortCode) return null;
87
+
88
+ let token = await getValue('userAccountToken');
89
+ if (!token) {
90
+ token = generateUUID();
91
+ await saveValue('userAccountToken', token);
92
+ }
93
+
94
+ await this.storeExpectedStoreTransaction(token);
95
+ return token;
96
+ }
97
+
98
+ static async storeExpectedStoreTransaction(userAccountToken: string): Promise<void> {
99
+ const companyCode = this.companyCode || await getValue('companyCode');
100
+ const shortCode = await this.returnInsertAffiliateIdentifier();
101
+
102
+ if (!companyCode || !shortCode) {
103
+ console.error('[Insert Affiliate] Missing company code or identifier.');
104
+ return;
105
+ }
106
+
107
+ const payload: ExpectedTransactionPayload = {
108
+ UUID: userAccountToken,
109
+ companyCode,
110
+ shortCode,
111
+ storedDate: new Date().toISOString(),
112
+ };
113
+
114
+ try {
115
+ const res = await fetch('https://api.insertaffiliate.com/v1/api/app-store-webhook/create-expected-transaction', {
116
+ method: 'POST',
117
+ headers: { 'Content-Type': 'application/json' },
118
+ body: JSON.stringify(payload),
119
+ });
120
+
121
+ if (res.status === 200) {
122
+ console.log('[Insert Affiliate] Stored expected transaction');
123
+ } else {
124
+ console.warn('[Insert Affiliate] Failed storing transaction:', res.status);
125
+ }
126
+ } catch (error) {
127
+ console.error('[Insert Affiliate] Error storing transaction:', error);
128
+ }
129
+ }
130
+
131
+ static async validatePurchaseWithIapticAPI(
132
+ jsonIapPurchase: IapticIOSReceipt | { transactionReceipt: string },
133
+ iapticAppId: string,
134
+ iapticAppName: string,
135
+ iapticPublicKey: string
136
+ ): Promise<boolean> {
137
+ try {
138
+ const isIOS = typeof window !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent);
139
+
140
+ const baseRequest = {
141
+ id: iapticAppId,
142
+ type: 'application',
143
+ };
144
+
145
+ let transaction: any;
146
+
147
+ if (isIOS) {
148
+ transaction = {
149
+ id: iapticAppId,
150
+ type: 'ios-appstore',
151
+ appStoreReceipt: jsonIapPurchase.transactionReceipt,
152
+ };
153
+ } else {
154
+ const receiptJson: IapticAndroidReceipt = JSON.parse(atob(jsonIapPurchase.transactionReceipt));
155
+ transaction = {
156
+ id: receiptJson.orderId,
157
+ type: 'android-playstore',
158
+ purchaseToken: receiptJson.purchaseToken,
159
+ receipt: jsonIapPurchase.transactionReceipt,
160
+ signature: receiptJson.signature,
161
+ };
162
+ }
163
+
164
+ const insertAffiliateIdentifier = await this.returnInsertAffiliateIdentifier();
165
+
166
+ const payload = {
167
+ ...baseRequest,
168
+ transaction,
169
+ additionalData: insertAffiliateIdentifier
170
+ ? { applicationUsername: insertAffiliateIdentifier }
171
+ : undefined,
172
+ };
173
+
174
+ const response = await fetch('https://validator.iaptic.com/v1/validate', {
175
+ method: 'POST',
176
+ headers: {
177
+ 'Content-Type': 'application/json',
178
+ Authorization: `Basic ${btoa(`${iapticAppName}:${iapticPublicKey}`)}`
179
+ },
180
+ body: JSON.stringify(payload),
181
+ });
182
+
183
+ return response.status === 200;
184
+ } catch (error) {
185
+ console.error('[Insert Affiliate] Purchase validation failed:', error);
186
+ return false;
187
+ }
188
+ }
189
+
190
+ static async fetchAndConditionallyOpenUrl(affiliateLink: string, offerCodeUrlId: string): Promise<void> {
191
+ const encoded = encodeURIComponent(affiliateLink);
192
+
193
+ try {
194
+ const res = await fetch(`https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${encoded}`);
195
+ const offerCode = (await res.text()).replace(/[^a-zA-Z0-9]/g, '');
196
+
197
+ const errorCodes = [
198
+ 'errorofferCodeNotFound',
199
+ 'errorAffiliateoffercodenotfoundinanycompany',
200
+ 'errorAffiliateoffercodenotfoundinanycompanyAffiliatelinkwas',
201
+ 'Routenotfound'
202
+ ];
203
+
204
+ if (errorCodes.includes(offerCode)) {
205
+ console.warn('[Insert Affiliate] Offer Code Not Found');
206
+ return;
207
+ }
208
+
209
+ const redeemUrl = `https://apps.apple.com/redeem?ctx=offercodes&id=${offerCodeUrlId}&code=${offerCode}`;
210
+ window.open(redeemUrl, '_blank');
211
+ } catch (err) {
212
+ console.error('[Insert Affiliate] Error fetching/opening offer code:', err);
213
+ }
214
+ }
215
+
216
+ private static async getOrCreateUserID(): Promise<string> {
217
+ let id = await getValue('userId');
218
+ if (!id) {
219
+ id = generateShortDeviceID();
220
+ await saveValue('userId', id);
221
+ }
222
+ return id;
223
+ }
224
+
225
+ private static async fetchShortLink(link: string): Promise<string | null> {
226
+ try {
227
+ const encoded = encodeURIComponent(link);
228
+ const companyCode = this.companyCode || await getValue('companyCode');
229
+ if (!companyCode) return null;
230
+
231
+ const res = await fetch(`https://api.insertaffiliate.com/V1/convert-deep-link-to-short-link?companyId=${companyCode}&deepLinkUrl=${encoded}`);
232
+ const data = await res.json();
233
+ return data?.shortLink || null;
234
+ } catch (err: any) {
235
+ console.error('[Insert Affiliate] Failed to fetch short link:', err?.message || err);
236
+ return null;
237
+ }
238
+ }
239
+ }
@@ -0,0 +1,9 @@
1
+ // src/utils/asyncStorage.ts
2
+ export const saveValue = async (key: string, value: string): Promise<void> => {
3
+ localStorage.setItem(key, value);
4
+ };
5
+
6
+ export const getValue = async (key: string): Promise<string | null> => {
7
+ return localStorage.getItem(key);
8
+ };
9
+
@@ -0,0 +1,32 @@
1
+ // src/utils/helpers.ts
2
+ export const generateShortDeviceID = (): string => {
3
+ const hashed = Math.abs(generateUUID().hashCode()) % 0xffffff;
4
+ return hashed.toString(16).padStart(6, '0').toUpperCase();
5
+ };
6
+
7
+ export const generateUUID = (): string => {
8
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c: string): string => {
9
+ const r = Math.floor(Math.random() * 16);
10
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
11
+ return v.toString(16);
12
+ });
13
+ };
14
+
15
+ // Ensure we only patch String.prototype once
16
+ if (!String.prototype.hasOwnProperty('hashCode')) {
17
+ String.prototype.hashCode = function (): number {
18
+ let hash = 0;
19
+ for (let i = 0; i < this.length; i++) {
20
+ const chr = this.charCodeAt(i);
21
+ hash = (hash << 5) - hash + chr;
22
+ hash |= 0; // Convert to 32bit integer
23
+ }
24
+ return hash;
25
+ };
26
+ }
27
+
28
+ declare global {
29
+ interface String {
30
+ hashCode(): number;
31
+ }
32
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Node",
6
+ "declaration": true,
7
+ "outDir": "dist",
8
+ "esModuleInterop": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "strict": true,
11
+ "skipLibCheck": true
12
+ },
13
+ "include": ["src"]
14
+ }
package/tsup.config.js ADDED
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from 'tsup'
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['cjs'], // <- Use CJS for compatibility
6
+ dts: true,
7
+ clean: true,
8
+ outDir: 'dist',
9
+ })
package/uploadNotes.md ADDED
@@ -0,0 +1,49 @@
1
+ # 📦 Uploading `insert-affiliate-js-sdk` to npm
2
+
3
+ This guide walks you through the steps to build, version, tag, and publish the SDK to npm.
4
+
5
+ ---
6
+
7
+ ## ✅ 1. Build the SDK
8
+
9
+ Run the build command using `tsup` to compile TypeScript into JavaScript:
10
+
11
+ ```bash
12
+ npm run build
13
+ ```
14
+
15
+ This outputs compiled files to the dist/ folder, including:
16
+
17
+ - CommonJS + ESM builds
18
+ - Type definitions (.d.ts)
19
+
20
+
21
+ ## 🔢 2. Bump the Version
22
+ Update the version field in package.json based on changes:
23
+
24
+ Change Type Version Format
25
+ - Bug fix: 1.0.0 → 1.0.1
26
+ - New feature: 1.0.0 → 1.1.0
27
+ - Breaking change: 1.0.0 → 2.0.0
28
+
29
+ ## 🏷 3. Commit and Tag the Release
30
+ ```bash
31
+ git add .
32
+ git commit -m "Release v1.0.1"
33
+
34
+ # Tag it
35
+ git tag v1.0.1
36
+
37
+ # Push both the commit and the tag
38
+ git push origin main --tags
39
+ ```
40
+
41
+ ## 🔐 4. Login to npm (if you haven’t)
42
+ ```bash
43
+ npm login
44
+ ```
45
+
46
+ ## 🚀 5. Publish to npm
47
+ ```bash
48
+ npm publish --access public
49
+ ```