gomarketme-react-native-expo
Version:
Affiliate Marketing for React Native Expo-Based iOS and Android Apps.
447 lines (402 loc) • 14.2 kB
text/typescript
import { Platform, Dimensions, PixelRatio } from 'react-native';
import axios from 'axios';
import * as RNIap from 'react-native-iap';
import * as Application from 'expo-application';
import * as Device from 'expo-device';
import * as Localization from 'expo-localization';
export class GoMarketMeAffiliateMarketingData {
campaign: Campaign;
affiliate: Affiliate;
saleDistribution: SaleDistribution;
affiliateCampaignCode: string;
deviceId: string;
offerCode?: string;
constructor(
campaign: Campaign,
affiliate: Affiliate,
saleDistribution: SaleDistribution,
affiliateCampaignCode: string,
deviceId: string,
offerCode?: string
) {
this.campaign = campaign;
this.affiliate = affiliate;
this.saleDistribution = saleDistribution;
this.affiliateCampaignCode = affiliateCampaignCode;
this.deviceId = deviceId;
this.offerCode = offerCode;
}
static fromJson(json: Record<string, any>): GoMarketMeAffiliateMarketingData | null {
if (Object.keys(json).length === 0) {
return null;
}
return new GoMarketMeAffiliateMarketingData(
Campaign.fromJson(json.campaign),
Affiliate.fromJson(json.affiliate),
SaleDistribution.fromJson(json.sale_distribution),
json.affiliate_campaign_code || '',
json.device_id || '',
json.offer_code
);
}
}
export class Campaign {
id: string;
name: string;
status: string;
type: string;
publicLinkUrl?: string;
constructor(id: string, name: string, status: string, type: string, publicLinkUrl?: string) {
this.id = id;
this.name = name;
this.status = status;
this.type = type;
this.publicLinkUrl = publicLinkUrl;
}
static fromJson(json: Record<string, any>): Campaign {
return new Campaign(
json.id || '',
json.name || '',
json.status || '',
json.type || '',
json.public_link_url
);
}
}
export class Affiliate {
id: string;
firstName: string;
lastName: string;
countryCode: string;
instagramAccount: string;
tiktokAccount: string;
xAccount: string;
constructor(
id: string,
firstName: string,
lastName: string,
countryCode: string,
instagramAccount: string,
tiktokAccount: string,
xAccount: string
) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.countryCode = countryCode;
this.instagramAccount = instagramAccount;
this.tiktokAccount = tiktokAccount;
this.xAccount = xAccount;
}
static fromJson(json: Record<string, any>): Affiliate {
return new Affiliate(
json.id || '',
json.first_name || '',
json.last_name || '',
json.country_code || '',
json.instagram_account || '',
json.tiktok_account || '',
json.x_account || ''
);
}
}
export class SaleDistribution {
platformPercentage: string;
affiliatePercentage: string;
constructor(platformPercentage: string, affiliatePercentage: string) {
this.platformPercentage = platformPercentage;
this.affiliatePercentage = affiliatePercentage;
}
static fromJson(json: Record<string, any>): SaleDistribution {
return new SaleDistribution(
json.platform_percentage || '',
json.affiliate_percentage || ''
);
}
}
class GoMarketMe {
private static instance: GoMarketMe;
private sdkType = 'ReactNativeExpo';
private sdkVersion = '2.0.1';
private sdkInitializedKey = 'GOMARKETME_SDK_INITIALIZED';
private sdkInitializationUrl = 'https://4v9008q1a5.execute-api.us-west-2.amazonaws.com/prod/v1/sdk-initialization';
private systemInfoUrl = 'https://4v9008q1a5.execute-api.us-west-2.amazonaws.com/prod/v1/mobile/system-info';
private eventUrl = 'https://4v9008q1a5.execute-api.us-west-2.amazonaws.com/prod/v1/event';
private _affiliateCampaignCode = '';
private _deviceId = '';
private _packageName = ''
public affiliateMarketingData?: GoMarketMeAffiliateMarketingData | null;
private constructor() { }
public static getInstance(): GoMarketMe {
if (!GoMarketMe.instance) {
GoMarketMe.instance = new GoMarketMe();
}
return GoMarketMe.instance;
}
public async initialize(apiKey: string): Promise<void> {
try {
const isSDKInitialized = await this._isSDKInitialized();
if (!isSDKInitialized) {
await this._postSDKInitialization(apiKey);
}
this._packageName = Application.applicationId ?? '';
const systemInfo = await this._getSystemInfo();
this.affiliateMarketingData = await this._postSystemInfo(systemInfo, apiKey);
const currPurchases = await RNIap.getPurchaseHistory();
await this._fetchConsolidatedPurchases(currPurchases, apiKey)
await this._addListener(apiKey);
} catch (e) {
console.log('Error initializing GoMarketMe:', e);
}
}
private async _addListener(apiKey: string): Promise<void> {
try {
RNIap.initConnection().then(result => {
RNIap.purchaseUpdatedListener(async (purchase: RNIap.Purchase) => {
await this._fetchConsolidatedPurchases([purchase], apiKey);
});
RNIap.purchaseErrorListener((error) => {
console.log('Purchase error:', error);
});
});
} catch (e) {
console.log('Error setting up IAP listeners:', e);
}
}
private async _getSystemInfo(): Promise<any> {
const deviceData = Platform.select({
ios: await this._readIosDeviceInfo(),
android: await this._readAndroidDeviceInfo(),
});
this._deviceId = deviceData['deviceId'];
const devicePixelRatio = PixelRatio.get();
const dimension = Dimensions.get('window');
const windowData = {
devicePixelRatio: devicePixelRatio,
width: dimension.width * devicePixelRatio,
height: dimension.height * devicePixelRatio,
};
return {
device_info: deviceData,
window_info: windowData,
time_zone: this._getTimeZone(),
language_code: this._getLanguageCode(),
};
}
private async _postSDKInitialization(apiKey: string): Promise<void> {
try {
const response = await axios.post(this.sdkInitializationUrl, {}, {
headers: {
'Content-Type': 'application/json',
'x-api-key': apiKey,
},
});
if (response.status === 200) {
await this._markSDKAsInitialized();
} else {
console.log('Failed to mark SDK as Initialized. Status code:', response.status);
}
} catch (e) {
console.log('Error sending SDK information to server:', e);
}
}
private async _postSystemInfo(data: any, apiKey: string): Promise<GoMarketMeAffiliateMarketingData | null> {
let output: GoMarketMeAffiliateMarketingData | null = null;
try {
data['sdk_type'] = this.sdkType;
data['sdk_version'] = this.sdkVersion;
data['package_name'] = this._packageName;
const response = await axios.post(this.systemInfoUrl, data, {
headers: {
'Content-Type': 'application/json',
'x-api-key': apiKey,
},
});
if (response.status === 200) {
output = GoMarketMeAffiliateMarketingData.fromJson(response.data);
if (output != null) {
this._affiliateCampaignCode = output.affiliateCampaignCode;
}
} else {
console.log('Failed to send system info. Status code:', response.status);
}
} catch (e) {
console.log('Error sending system info to server:', e);
}
return output;
}
private _generateAndroidId = () => {
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const getRandomString = (length: number) => {
return Array.from({ length }, () =>
characters[Math.floor(Math.random() * characters.length)]
).join('');
};
const part1 = getRandomString(4);
const part2 = getRandomString(6);
const part3 = getRandomString(3);
return `${part1}.${part2}.${part3}`;
};
private async _readAndroidDeviceInfo(): Promise<any> {
let androidId = Platform.OS === 'android' ? Application.getAndroidId() : '';
let deviceId = this._generateAndroidId();
let systemName = Device.osName;
let systemVersion = Device.osVersion;
let brand = Device.brand;
let model = Device.modelName;
let manufacturer = Device.manufacturer;
let isEmulator = !Device.isDevice;
return {
deviceId: androidId,
_deviceId: deviceId,
_uniqueId: deviceId,
systemName: systemName,
systemVersion: systemVersion,
brand: brand,
model: model,
manufacturer: manufacturer,
isEmulator: isEmulator
};
}
private async _readIosDeviceInfo(): Promise<any> {
let deviceId = Platform.OS === 'ios' ? await Application.getIosIdForVendorAsync() : '';
let systemName = Device.osName;
let systemVersion = Device.osVersion;
let brand = Device.brand;
let model = Device.modelName;
let manufacturer = Device.manufacturer;
let isEmulator = !Device.isDevice;
return {
deviceId: deviceId,
_deviceId: deviceId,
systemName: systemName,
systemVersion: systemVersion,
brand: brand,
model: model,
manufacturer: manufacturer,
isEmulator: isEmulator
};
}
private _getTimeZone = (): string => {
return Intl.DateTimeFormat().resolvedOptions().timeZone;
};
private _getLanguageCode(): string {
return Localization.getLocales()[0].languageTag;
}
private async _fetchConsolidatedPurchases(purchaseDetailsList: RNIap.Purchase[], apiKey: string): Promise<void> {
for (const purchase of purchaseDetailsList) {
if (purchase.transactionReceipt) {
var data = this._serializePurchaseDetails(purchase);
data['products'] = []
if (data.productID != '') {
const products = await RNIap.getProducts({ skus: [data.productID] })
if (products.length > 0) {
for (const product0 of products) {
data['products'].push(this._serializeProductDetails(product0))
}
}
else {
const products = await RNIap.getSubscriptions({ skus: [data.productID] });
if (products.length > 0) {
for (const product0 of products) {
data['products'].push(this._serializeSubscriptionDetails(product0))
}
}
}
}
await this._sendEventToServer(JSON.stringify(data), 'purchase', apiKey);
}
}
}
private async _sendEventToServer(body: string, eventType: string, apiKey: string): Promise<void> {
try {
const response = await axios.post(this.eventUrl, body, {
headers: {
'Content-Type': 'application/json',
'x-affiliate-campaign-code': this._affiliateCampaignCode,
'x-device-id': this._deviceId,
'x-event-type': eventType,
'x-product-type': Platform.OS,
'x-source-name': Platform.OS === 'android' ? 'google_play' : 'app_store',
'x-api-key': apiKey,
},
});
if (response.status === 200) {
console.log(`${eventType} sent successfully`);
} else {
console.log(`Failed to send ${eventType}. Status code:`, response.status);
}
} catch (e) {
console.log(`Error sending ${eventType} to server:`, e);
}
}
private _serializePurchaseDetails(purchase: RNIap.Purchase): any {
return {
packageName: this._packageName,
productID: purchase.productId,
purchaseID: purchase.transactionId || '',
transactionDate: purchase.transactionDate || '',
status: Platform.select({
ios: (purchase as any).transactionStateIOS, // Removed non-existent properties
android: (purchase as any).purchaseStateAndroid,
}),
verificationData: {
localVerificationData: purchase.transactionReceipt,
serverVerificationData: purchase.transactionReceipt,
source: Platform.OS === 'android' ? 'google_play' : 'app_store',
},
pendingCompletePurchase: '',
error: '',
hashCode: '',
_purchase: purchase
};
}
private _serializeProductDetails(product: RNIap.Product): any {
return {
packageName: this._packageName,
productID: product.productId,
productTitle: product.title,
productDescription: product.description,
productPrice: product.localizedPrice,
productRawPrice: product.price,
productCurrencySymbol: product.localizedPrice.replace(product.price, ''),
productCurrencyCode: product.currency,
hashCode: '',
_product: product
};
}
private _serializeSubscriptionDetails(subscription: RNIap.Subscription): any {
let output: any = {
productID: subscription.productId,
productTitle: subscription.title,
productDescription: subscription.description,
hashCode: '',
};
if (Platform.OS === 'ios') {
const subscriptionIOS = subscription as RNIap.SubscriptionIOS;
output.productPrice = subscriptionIOS.localizedPrice;
output.productRawPrice = subscriptionIOS.price;
output.productCurrencyCode = subscriptionIOS.currency;
output._subscription = subscriptionIOS;
} else if (Platform.OS === 'android') {
const subscriptionAndroid = subscription as RNIap.SubscriptionAndroid;
if (subscriptionAndroid.subscriptionOfferDetails?.length) {
const offerDetails = subscriptionAndroid.subscriptionOfferDetails[0];
const priceAmountMicros = parseInt(offerDetails.pricingPhases.pricingPhaseList[0].priceAmountMicros, 10) || 0;
output.productPrice = offerDetails.pricingPhases.pricingPhaseList[0].formattedPrice;
output.productRawPrice = String(priceAmountMicros / 1_000_000);
output.productCurrencyCode = offerDetails.pricingPhases.pricingPhaseList[0].priceCurrencyCode;
}
output._subscription = subscriptionAndroid;
}
return output;
}
private async _markSDKAsInitialized(): Promise<boolean> {
return true;
}
private async _isSDKInitialized(): Promise<boolean> {
return false;
}
}
export default GoMarketMe.getInstance();