gomarketme-react-native 2.0.0 → 4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gomarketme-react-native",
3
- "version": "2.0.0",
3
+ "version": "4.0.0",
4
4
  "description": "Affiliate Marketing for React Native-Based iOS and Android Apps.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,16 +18,17 @@
18
18
  "author": "GoMarketMe",
19
19
  "license": "MIT",
20
20
  "dependencies": {
21
- "@react-native-async-storage/async-storage": "^1.15.6",
22
- "axios": "^0.21.1",
23
- "get-user-locale": "^2.3.2",
24
- "react-native-device-info": "^14.0.0",
25
- "react-native-iap": "^12.16.0"
21
+ "@react-native-async-storage/async-storage": "^2.2.0",
22
+ "axios": "^1.13.2",
23
+ "get-user-locale": "^3.0.0",
24
+ "react-native-device-info": "^14.1.1",
25
+ "react-native-iap": "^14.4.38",
26
+ "react-native-nitro-modules": "^0.31.4"
26
27
  },
27
28
  "devDependencies": {
28
- "@types/node": "^15.12.5",
29
- "@types/react-native": "^0.64.12",
30
- "typescript": "^4.3.5"
29
+ "@types/node": "^24.10.0",
30
+ "@types/react-native": "^0.73.0",
31
+ "typescript": "^5.9.3"
31
32
  },
32
33
  "peerDependencies": {
33
34
  "react": "*",
package/src/index.tsx CHANGED
@@ -1,9 +1,20 @@
1
1
  import { Platform, Dimensions, PixelRatio } from 'react-native';
2
2
  import DeviceInfo from 'react-native-device-info';
3
- import getUserLocale from 'get-user-locale'
3
+ import getUserLocale from 'get-user-locale';
4
4
  import AsyncStorage from '@react-native-async-storage/async-storage';
5
- import * as RNIap from 'react-native-iap';
6
5
  import axios from 'axios';
6
+
7
+ import {
8
+ type Purchase,
9
+ type PurchaseError,
10
+ purchaseUpdatedListener,
11
+ purchaseErrorListener,
12
+ fetchProducts,
13
+ ProductCommon,
14
+ PurchaseCommon,
15
+ getAvailablePurchases,
16
+ } from 'react-native-iap';
17
+
7
18
  export class GoMarketMeAffiliateMarketingData {
8
19
  campaign: Campaign;
9
20
  affiliate: Affiliate;
@@ -29,11 +40,7 @@ export class GoMarketMeAffiliateMarketingData {
29
40
  }
30
41
 
31
42
  static fromJson(json: Record<string, any>): GoMarketMeAffiliateMarketingData | null {
32
- // Check if the json is an empty object
33
- if (Object.keys(json).length === 0) {
34
- return null; // Return null or undefined, depending on your preference
35
- }
36
-
43
+ if (!json || Object.keys(json).length === 0) return null;
37
44
  return new GoMarketMeAffiliateMarketingData(
38
45
  Campaign.fromJson(json.campaign),
39
46
  Affiliate.fromJson(json.affiliate),
@@ -62,11 +69,11 @@ export class Campaign {
62
69
 
63
70
  static fromJson(json: Record<string, any>): Campaign {
64
71
  return new Campaign(
65
- json.id || '',
66
- json.name || '',
67
- json.status || '',
68
- json.type || '',
69
- json.public_link_url
72
+ json?.id || '',
73
+ json?.name || '',
74
+ json?.status || '',
75
+ json?.type || '',
76
+ json?.public_link_url
70
77
  );
71
78
  }
72
79
  }
@@ -100,13 +107,13 @@ export class Affiliate {
100
107
 
101
108
  static fromJson(json: Record<string, any>): Affiliate {
102
109
  return new Affiliate(
103
- json.id || '',
104
- json.first_name || '',
105
- json.last_name || '',
106
- json.country_code || '',
107
- json.instagram_account || '',
108
- json.tiktok_account || '',
109
- json.x_account || ''
110
+ json?.id || '',
111
+ json?.first_name || '',
112
+ json?.last_name || '',
113
+ json?.country_code || '',
114
+ json?.instagram_account || '',
115
+ json?.tiktok_account || '',
116
+ json?.x_account || ''
110
117
  );
111
118
  }
112
119
  }
@@ -121,220 +128,183 @@ export class SaleDistribution {
121
128
  }
122
129
 
123
130
  static fromJson(json: Record<string, any>): SaleDistribution {
124
- return new SaleDistribution(
125
- json.platform_percentage || '',
126
- json.affiliate_percentage || ''
127
- );
131
+ return new SaleDistribution(json?.platform_percentage || '', json?.affiliate_percentage || '');
128
132
  }
129
133
  }
130
134
 
131
135
  class GoMarketMe {
132
136
  private static instance: GoMarketMe;
133
137
  private sdkType = 'ReactNative';
134
- private sdkVersion = '2.0.0';
138
+ private sdkVersion = '4.0.0';
135
139
  private sdkInitializedKey = 'GOMARKETME_SDK_INITIALIZED';
136
- private sdkInitializationUrl = 'https://4v9008q1a5.execute-api.us-west-2.amazonaws.com/prod/v1/sdk-initialization';
137
- private systemInfoUrl = 'https://4v9008q1a5.execute-api.us-west-2.amazonaws.com/prod/v1/mobile/system-info';
140
+ private sdkInitializationUrl =
141
+ 'https://4v9008q1a5.execute-api.us-west-2.amazonaws.com/prod/v1/sdk-initialization';
142
+ private systemInfoUrl =
143
+ 'https://4v9008q1a5.execute-api.us-west-2.amazonaws.com/prod/v1/mobile/system-info';
138
144
  private eventUrl = 'https://4v9008q1a5.execute-api.us-west-2.amazonaws.com/prod/v1/event';
139
145
  private _affiliateCampaignCode = '';
140
146
  private _deviceId = '';
141
- private _packageName = ''
147
+ private _packageName = '';
148
+ private _purchaseUpdateUnsub?: ReturnType<typeof purchaseUpdatedListener>;
149
+ private _purchaseErrorUnsub?: ReturnType<typeof purchaseErrorListener>;
142
150
  public affiliateMarketingData?: GoMarketMeAffiliateMarketingData | null;
143
151
 
144
152
  private constructor() { }
145
153
 
146
154
  public static getInstance(): GoMarketMe {
147
- if (!GoMarketMe.instance) {
148
- GoMarketMe.instance = new GoMarketMe();
149
- }
155
+ if (!GoMarketMe.instance) GoMarketMe.instance = new GoMarketMe();
150
156
  return GoMarketMe.instance;
151
157
  }
152
158
 
153
159
  public async initialize(apiKey: string): Promise<void> {
154
160
  try {
155
161
  const isSDKInitialized = await this._isSDKInitialized();
156
- if (!isSDKInitialized) {
157
- await this._postSDKInitialization(apiKey);
158
- }
162
+ if (!isSDKInitialized) await this._postSDKInitialization(apiKey);
159
163
  this._packageName = DeviceInfo.getBundleId();
160
164
  const systemInfo = await this._getSystemInfo();
161
165
  this.affiliateMarketingData = await this._postSystemInfo(systemInfo, apiKey);
162
- await this._addListener(apiKey);
166
+ await this._addListeners(apiKey);
167
+ this._fetchExistingPurchases(apiKey).catch(e => {
168
+ console.log('Error in background purchase fetch:', e);
169
+ });
163
170
  } catch (e) {
164
171
  console.log('Error initializing GoMarketMe:', e);
165
172
  }
166
173
  }
167
174
 
168
- private async _addListener(apiKey: string): Promise<void> {
175
+ public removeListeners(): void {
169
176
  try {
170
- RNIap.purchaseUpdatedListener(async (purchase: RNIap.Purchase) => {
171
- await this._fetchConsolidatedPurchases([purchase], apiKey);
177
+ this._purchaseUpdateUnsub?.remove?.();
178
+ this._purchaseErrorUnsub?.remove?.();
179
+ } catch (e) {
180
+ console.log('Error removing listeners:', e);
181
+ }
182
+ }
183
+
184
+ private async _addListeners(apiKey: string): Promise<void> {
185
+ if (!this._purchaseUpdateUnsub) {
186
+ this._purchaseUpdateUnsub = purchaseUpdatedListener(async (purchase: Purchase) => {
187
+ try {
188
+ await this._fetchConsolidatedPurchases([purchase], apiKey);
189
+ } catch (e) {
190
+ console.log('purchaseUpdated error:', e);
191
+ }
172
192
  });
193
+ }
173
194
 
174
- RNIap.purchaseErrorListener((error) => {
175
- console.log('Purchase error:', error);
195
+ if (!this._purchaseErrorUnsub) {
196
+ this._purchaseErrorUnsub = purchaseErrorListener((error: PurchaseError) => {
197
+ console.log('purchaseErrorListener:', error);
176
198
  });
177
- } catch (e) {
178
- console.log('Error setting up IAP listeners:', e);
179
199
  }
180
200
  }
181
201
 
182
202
  private async _getSystemInfo(): Promise<any> {
183
- const deviceData = Platform.select({
184
- ios: await this._readIosDeviceInfo(),
185
- android: await this._readAndroidDeviceInfo(),
186
- });
203
+ const deviceData =
204
+ Platform.OS === 'ios'
205
+ ? await this._readIosDeviceInfo()
206
+ : await this._readAndroidDeviceInfo();
187
207
 
188
- this._deviceId = deviceData['deviceId'];
208
+ this._deviceId = deviceData['deviceId'] || '';
189
209
 
190
210
  const devicePixelRatio = PixelRatio.get();
191
211
  const dimension = Dimensions.get('window');
192
212
 
193
- const windowData = {
194
- devicePixelRatio: devicePixelRatio,
195
- width: dimension.width * devicePixelRatio,
196
- height: dimension.height * devicePixelRatio,
197
- };
198
-
199
213
  return {
200
214
  device_info: deviceData,
201
- window_info: windowData,
202
- time_zone: this._getTimeZone(),
203
- language_code: this._getLanguageCode(),
215
+ window_info: {
216
+ devicePixelRatio,
217
+ width: dimension.width * devicePixelRatio,
218
+ height: dimension.height * devicePixelRatio,
219
+ },
220
+ time_zone: Intl.DateTimeFormat().resolvedOptions().timeZone,
221
+ language_code: getUserLocale(),
222
+ sdk_type: this.sdkType,
223
+ sdk_version: this.sdkVersion,
224
+ package_name: this._packageName,
225
+ is_production: await this._isProduction()
204
226
  };
205
227
  }
206
228
 
207
229
  private async _postSDKInitialization(apiKey: string): Promise<void> {
208
230
  try {
209
- const response = await axios.post(this.sdkInitializationUrl, {}, {
210
- headers: {
211
- 'Content-Type': 'application/json',
212
- 'x-api-key': apiKey,
213
- },
231
+ const res = await axios.post(this.sdkInitializationUrl, {}, {
232
+ headers: { 'x-api-key': apiKey },
214
233
  });
215
- if (response.status === 200) {
216
- await this._markSDKAsInitialized();
217
- } else {
218
- console.log('Failed to mark SDK as Initialized. Status code:', response.status);
219
- }
234
+ if (res.status === 200) await this._markSDKAsInitialized();
220
235
  } catch (e) {
221
- console.log('Error sending SDK information to server:', e);
236
+ console.log('Error posting SDK initialization:', e);
222
237
  }
223
238
  }
224
239
 
225
- private async _postSystemInfo(data: any, apiKey: string): Promise<GoMarketMeAffiliateMarketingData | null> {
226
- let output: GoMarketMeAffiliateMarketingData | null = null;
240
+ private async _postSystemInfo(data: any, apiKey: string) {
227
241
  try {
228
- data['sdk_type'] = this.sdkType;
229
- data['sdk_version'] = this.sdkVersion;
230
- data['package_name'] = this._packageName;
231
- const response = await axios.post(this.systemInfoUrl, data, {
232
- headers: {
233
- 'Content-Type': 'application/json',
234
- 'x-api-key': apiKey,
235
- },
242
+ const res = await axios.post(this.systemInfoUrl, data, {
243
+ headers: { 'x-api-key': apiKey },
236
244
  });
237
- if (response.status === 200) {
238
- output = GoMarketMeAffiliateMarketingData.fromJson(response.data);
239
- if (output != null) {
240
- this._affiliateCampaignCode = output.affiliateCampaignCode;
241
- }
242
- } else {
243
- console.log('Failed to send system info. Status code:', response.status);
245
+ if (res.status === 200) {
246
+ const obj = GoMarketMeAffiliateMarketingData.fromJson(res.data);
247
+ if (obj) this._affiliateCampaignCode = obj.affiliateCampaignCode;
248
+ return obj;
244
249
  }
245
250
  } catch (e) {
246
- console.log('Error sending system info to server:', e);
251
+ console.log('Error posting system info:', e);
247
252
  }
248
- return output;
253
+ return null;
249
254
  }
250
255
 
251
- private async _readAndroidDeviceInfo(): Promise<any> {
252
-
253
- let androidId = await DeviceInfo.getAndroidId();
254
- let uniqueId = await DeviceInfo.getUniqueId();
255
- let deviceId = await DeviceInfo.getDeviceId(); // model
256
- let systemName = await DeviceInfo.getSystemName();
257
- let systemVersion = await DeviceInfo.getSystemVersion()
258
- let brand = await DeviceInfo.getBrand();
259
- let model = await DeviceInfo.getModel();
260
- let manufacturer = await DeviceInfo.getManufacturer();
261
- let isEmulator = await DeviceInfo.isEmulator();
262
-
256
+ private async _readAndroidDeviceInfo() {
263
257
  return {
264
- deviceId: androidId,
265
- _deviceId: deviceId,
266
- _uniqueId: uniqueId,
267
- systemName: systemName,
268
- systemVersion: systemVersion,
269
- brand: brand,
270
- model: model,
271
- manufacturer: manufacturer,
272
- isEmulator: isEmulator
258
+ deviceId: await DeviceInfo.getAndroidId(),
259
+ brand: await DeviceInfo.getBrand(),
260
+ model: await DeviceInfo.getModel(),
261
+ manufacturer: await DeviceInfo.getManufacturer(),
262
+ systemName: await DeviceInfo.getSystemName(),
263
+ systemVersion: await DeviceInfo.getSystemVersion(),
273
264
  };
274
265
  }
275
266
 
276
- private async _readIosDeviceInfo(): Promise<any> {
277
-
278
- let uniqueId = await DeviceInfo.getUniqueId();
279
- let deviceId = await DeviceInfo.getDeviceId(); // model
280
- let systemName = await DeviceInfo.getSystemName();
281
- let systemVersion = await DeviceInfo.getSystemVersion()
282
- let brand = await DeviceInfo.getBrand();
283
- let model = await DeviceInfo.getModel();
284
- let manufacturer = await DeviceInfo.getManufacturer();
285
- let isEmulator = await DeviceInfo.isEmulator();
286
-
267
+ private async _readIosDeviceInfo() {
287
268
  return {
288
- deviceId: uniqueId,
289
- _deviceId: deviceId,
290
- systemName: systemName,
291
- systemVersion: systemVersion,
292
- brand: brand,
293
- model: model,
294
- manufacturer: manufacturer,
295
- isEmulator: isEmulator
269
+ deviceId: await DeviceInfo.getUniqueId(),
270
+ brand: await DeviceInfo.getBrand(),
271
+ model: await DeviceInfo.getModel(),
272
+ manufacturer: await DeviceInfo.getManufacturer(),
273
+ systemName: await DeviceInfo.getSystemName(),
274
+ systemVersion: await DeviceInfo.getSystemVersion(),
296
275
  };
297
276
  }
298
277
 
299
- private _getTimeZone = (): string => {
300
- return Intl.DateTimeFormat().resolvedOptions().timeZone;
301
- };
302
-
303
- private _getLanguageCode(): string {
304
- return getUserLocale();
305
- }
306
-
307
- private async _fetchConsolidatedPurchases(purchaseDetailsList: RNIap.Purchase[], apiKey: string): Promise<void> {
308
- for (const purchase of purchaseDetailsList) {
309
- if (purchase.transactionReceipt) {
310
- var data = this._serializePurchaseDetails(purchase);
311
- data['products'] = []
312
- if (data.productID != '') {
313
- const products = await RNIap.getProducts({ skus: [data.productID] })
314
- if (products.length > 0) {
315
- for (const product0 of products) {
316
- data['products'].push(this._serializeProductDetails(product0))
317
- }
318
- }
319
- else {
320
- const products = await RNIap.getSubscriptions({ skus: [data.productID] });
321
- if (products.length > 0) {
322
- for (const product0 of products) {
323
- data['products'].push(this._serializeSubscriptionDetails(product0))
324
- }
278
+ private async _fetchConsolidatedPurchases(purchases: Purchase[], apiKey: string) {
279
+ for (const purchase of purchases) {
280
+ const receipt = purchase.purchaseToken;
281
+ if (!receipt) continue;
282
+
283
+ const data: any = this._serializePurchaseDetails(purchase);
284
+ data.products = [];
285
+
286
+ const pid = data.productID || '';
287
+ if (pid) {
288
+ try {
289
+ const products = await fetchProducts({ skus: [pid] });
290
+ if (products && products.length) {
291
+ for (const p of products) {
292
+ data.products.push(this._serializeProductDetails(p));
325
293
  }
326
294
  }
295
+ } catch (e) {
296
+ console.warn('Error fetching products:', e);
327
297
  }
328
- await this._sendEventToServer(JSON.stringify(data), 'purchase', apiKey);
329
298
  }
299
+
300
+ await this._sendEventToServer(JSON.stringify(data), 'purchase_product', apiKey);
330
301
  }
331
302
  }
332
303
 
333
- private async _sendEventToServer(body: string, eventType: string, apiKey: string): Promise<void> {
304
+ private async _sendEventToServer(body: string, eventType: string, apiKey: string) {
334
305
  try {
335
- const response = await axios.post(this.eventUrl, body, {
306
+ await axios.post(this.eventUrl, body, {
336
307
  headers: {
337
- 'Content-Type': 'application/json',
338
308
  'x-affiliate-campaign-code': this._affiliateCampaignCode,
339
309
  'x-device-id': this._deviceId,
340
310
  'x-event-type': eventType,
@@ -343,99 +313,78 @@ class GoMarketMe {
343
313
  'x-api-key': apiKey,
344
314
  },
345
315
  });
346
- if (response.status === 200) {
347
- console.log(`${eventType} sent successfully`);
348
- } else {
349
- console.log(`Failed to send ${eventType}. Status code:`, response.status);
350
- }
316
+ console.log(`${eventType} sent successfully`);
351
317
  } catch (e) {
352
- console.log(`Error sending ${eventType} to server:`, e);
318
+ console.log(`Error sending ${eventType}:`, e);
353
319
  }
354
320
  }
355
321
 
356
- private _serializePurchaseDetails(purchase: RNIap.Purchase): any {
322
+ private _serializePurchaseDetails(purchase: PurchaseCommon) {
357
323
  return {
358
324
  packageName: this._packageName,
359
325
  productID: purchase.productId,
360
- purchaseID: purchase.transactionId || '',
361
- transactionDate: purchase.transactionDate || '',
362
- status: Platform.select({
363
- ios: (purchase as any).transactionStateIOS, // Removed non-existent properties
364
- android: (purchase as any).purchaseStateAndroid,
365
- }),
326
+ purchaseID: purchase.id,
327
+ transactionDate: purchase.transactionDate,
366
328
  verificationData: {
367
- localVerificationData: purchase.transactionReceipt,
368
- serverVerificationData: purchase.transactionReceipt,
329
+ localVerificationData: purchase.purchaseToken,
369
330
  source: Platform.OS === 'android' ? 'google_play' : 'app_store',
370
331
  },
371
- pendingCompletePurchase: '',
372
- error: {},
373
- hashCode: '',
374
- _purchase: purchase
375
332
  };
376
333
  }
377
334
 
378
- private _serializeProductDetails(product: RNIap.Product): any {
335
+ private _serializeProductDetails(product: ProductCommon) {
379
336
  return {
380
337
  packageName: this._packageName,
381
- productID: product.productId,
338
+ productID: product.id,
382
339
  productTitle: product.title,
383
340
  productDescription: product.description,
384
- productPrice: product.localizedPrice,
385
- productRawPrice: product.price,
386
- productCurrencySymbol: product.localizedPrice.replace('product.price', ''),
341
+ productPrice: product.displayPrice,
342
+ productRawPrice: product.price || '',
387
343
  productCurrencyCode: product.currency,
388
- hashCode: '',
389
- _product: product
390
344
  };
391
345
  }
392
346
 
393
- private _serializeSubscriptionDetails(subscription: RNIap.Subscription): any {
394
- let output: any = {
395
- productID: subscription.productId,
396
- productTitle: subscription.title,
397
- productDescription: subscription.description,
398
- hashCode: '',
399
- };
400
-
401
- if (Platform.OS === 'ios') {
402
- const subscriptionIOS = subscription as RNIap.SubscriptionIOS;
403
- output.productPrice = subscriptionIOS.localizedPrice;
404
- output.productRawPrice = subscriptionIOS.price;
405
- output.productCurrencyCode = subscriptionIOS.currency;
406
- output._subscription = subscriptionIOS;
407
- } else if (Platform.OS === 'android') {
408
- const subscriptionAndroid = subscription as RNIap.SubscriptionAndroid;
409
- if (subscriptionAndroid.subscriptionOfferDetails?.length) {
410
- const offerDetails = subscriptionAndroid.subscriptionOfferDetails[0];
411
- const priceAmountMicros = parseInt(offerDetails.pricingPhases.pricingPhaseList[0].priceAmountMicros, 10) || 0;
412
- output.productPrice = offerDetails.pricingPhases.pricingPhaseList[0].formattedPrice;
413
- output.productRawPrice = String(priceAmountMicros / 1_000_000);
414
- output.productCurrencyCode = offerDetails.pricingPhases.pricingPhaseList[0].priceCurrencyCode;
415
- }
416
- output._subscription = subscriptionAndroid;
417
- }
347
+ private async _markSDKAsInitialized() {
348
+ await AsyncStorage.setItem(this.sdkInitializedKey, 'true');
349
+ }
418
350
 
419
- return output;
351
+ private async _isSDKInitialized() {
352
+ const val = await AsyncStorage.getItem(this.sdkInitializedKey);
353
+ return val === 'true';
420
354
  }
421
355
 
422
- private async _markSDKAsInitialized(): Promise<boolean> {
356
+ private async _isProduction(): Promise<boolean> {
357
+ if (__DEV__) return false;
358
+
423
359
  try {
424
- await AsyncStorage.setItem(this.sdkInitializedKey, 'true');
425
- return true;
426
- } catch (e) {
427
- console.log('Failed to save SDK initialization:', e);
360
+ const installer = await DeviceInfo.getInstallerPackageName();
361
+
362
+ if (
363
+ installer === 'com.android.vending' ||
364
+ installer === 'com.amazon.venezia'
365
+ ) {
366
+ return true;
367
+ }
368
+
369
+ if (installer == null && !__DEV__) {
370
+ return true;
371
+ }
372
+
373
+ return false;
374
+ } catch (error) {
375
+ console.warn('Error determining production status:', error);
428
376
  return false;
429
377
  }
430
378
  }
431
379
 
432
- private async _isSDKInitialized(): Promise<boolean> {
380
+ private async _fetchExistingPurchases(apiKey: string): Promise<void> {
433
381
  try {
434
- const value = await AsyncStorage.getItem(this.sdkInitializedKey);
435
- return value === 'true';
382
+ const purchases = await getAvailablePurchases();
383
+ if (purchases && purchases.length > 0) {
384
+ await this._fetchConsolidatedPurchases(purchases, apiKey);
385
+ }
436
386
  } catch (e) {
437
- console.log('Failed to load SDK initialization:', e);
438
- return false;
387
+ console.log('Error fetching existing purchases on app open:', e);
439
388
  }
440
389
  }
441
390
  }