From 6ab92152d52c7b645f3bbf7b269285af854341da Mon Sep 17 00:00:00 2001 From: Kamo Spertsyan Date: Mon, 5 Dec 2022 21:54:24 +0300 Subject: [PATCH 1/7] Native Android part of new major release. (#194) * Native modules updated. * Native modules updated. --- android/build.gradle | 7 +- .../com/reactlibrary/AutomationsModule.java | 61 ++++++++++- .../com/reactlibrary/EntitiesConverter.java | 9 +- .../com/reactlibrary/QonversionModule.java | 100 ++++-------------- .../com/reactlibrary/QonversionPackage.java | 11 +- .../com/reactlibrary/QonversionSDKInfo.java | 15 --- 6 files changed, 95 insertions(+), 108 deletions(-) delete mode 100644 android/src/main/java/com/reactlibrary/QonversionSDKInfo.java diff --git a/android/build.gradle b/android/build.gradle index 396f21b..c0248ca 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -27,11 +27,10 @@ buildscript { // ref: https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:build_script_external_dependencies repositories { google() - jcenter() } if (project == rootProject) { dependencies { - classpath 'com.android.tools.build:gradle:3.4.1' + classpath 'com.android.tools.build:gradle:3.4.3' } } } @@ -61,14 +60,12 @@ repositories { url "$rootDir/../node_modules/jsc-android/dist" } google() - jcenter() } dependencies { //noinspection GradleDynamicVersion implementation 'com.facebook.react:react-native:+' // From node_modules - implementation "com.google.firebase:firebase-messaging:21.0.1" // Used for Qonversion.handleNotification compilation - implementation "io.qonversion.sandwich:sandwich:0.2.0" + implementation "io.qonversion.sandwich:sandwich_local:1.0.0-RC3" } afterEvaluate { project -> diff --git a/android/src/main/java/com/reactlibrary/AutomationsModule.java b/android/src/main/java/com/reactlibrary/AutomationsModule.java index 1873e67..72e02f4 100644 --- a/android/src/main/java/com/reactlibrary/AutomationsModule.java +++ b/android/src/main/java/com/reactlibrary/AutomationsModule.java @@ -3,12 +3,16 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; import com.facebook.react.modules.core.DeviceEventManagerModule; +import org.json.JSONException; + import java.util.Map; import io.qonversion.sandwich.AutomationsEventListener; @@ -33,14 +37,69 @@ public void initialize() { eventEmitter = getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class); } + @NonNull @Override public String getName() { return "RNAutomations"; } + @ReactMethod + void initializeSdk() { + automationsSandwich.initialize(); + } + @ReactMethod void subscribe() { - automationsSandwich.subscribe(this); + automationsSandwich.setDelegate(this); + } + + @ReactMethod + public void setNotificationsToken(String token) { + automationsSandwich.setNotificationToken(token); + } + + @ReactMethod + public void getNotificationCustomPayload(final ReadableMap notificationData, final Promise promise) { + if (notificationData == null) { + promise.resolve(null); + return; + } + + final Map dataMap; + try { + dataMap = EntitiesConverter.convertReadableMapToHashMap(notificationData); + } catch (JSONException e) { + promise.reject(e); + return; + } + + final Map payload = automationsSandwich.getNotificationCustomPayload(dataMap); + if (payload == null) { + promise.resolve(null); + } else { + final WritableMap convertedPayload = EntitiesConverter.convertMapToWritableMap(payload); + + promise.resolve(convertedPayload); + } + } + + @ReactMethod + public void handleNotification(final ReadableMap notificationData, final Promise promise) { + if (notificationData == null) { + promise.resolve(false); + return; + } + + final Map dataMap; + try { + dataMap = EntitiesConverter.convertReadableMapToHashMap(notificationData); + } catch (JSONException e) { + promise.resolve(false); + return; + } + + final boolean isQonversionNotification = automationsSandwich.handleNotification(dataMap); + promise.resolve(isQonversionNotification); } @Override diff --git a/android/src/main/java/com/reactlibrary/EntitiesConverter.java b/android/src/main/java/com/reactlibrary/EntitiesConverter.java index a6f2aab..0947e84 100644 --- a/android/src/main/java/com/reactlibrary/EntitiesConverter.java +++ b/android/src/main/java/com/reactlibrary/EntitiesConverter.java @@ -17,6 +17,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; public class EntitiesConverter { @@ -27,7 +28,7 @@ static HashMap convertReadableMapToHashMap(ReadableMap readableM } static Map toMap(JSONObject jsonobj) throws JSONException { - Map map = new HashMap(); + Map map = new HashMap<>(); Iterator keys = jsonobj.keys(); while(keys.hasNext()) { String key = keys.next(); @@ -42,7 +43,7 @@ static Map toMap(JSONObject jsonobj) throws JSONException { } public static List toList(JSONArray array) throws JSONException { - List list = new ArrayList(); + List list = new ArrayList<>(); for(int i = 0; i < array.length(); i++) { Object value = array.get(i); if (value instanceof JSONArray) { @@ -74,10 +75,10 @@ static JSONObject convertMapToJson(ReadableMap readableMap) throws JSONException object.put(key, readableMap.getString(key)); break; case Map: - object.put(key, convertMapToJson(readableMap.getMap(key))); + object.put(key, convertMapToJson(Objects.requireNonNull(readableMap.getMap(key)))); break; case Array: - object.put(key, convertArrayToJson(readableMap.getArray(key))); + object.put(key, convertArrayToJson(Objects.requireNonNull(readableMap.getArray(key)))); break; } } diff --git a/android/src/main/java/com/reactlibrary/QonversionModule.java b/android/src/main/java/com/reactlibrary/QonversionModule.java index ee7dfd3..a830cb9 100644 --- a/android/src/main/java/com/reactlibrary/QonversionModule.java +++ b/android/src/main/java/com/reactlibrary/QonversionModule.java @@ -12,11 +12,9 @@ import java.util.HashMap; import java.util.Map; import java.util.List; -import android.app.Activity; import android.app.Application; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import io.qonversion.sandwich.ActivityProvider; import io.qonversion.sandwich.PurchaseResultListener; import io.qonversion.sandwich.QonversionEventsListener; import io.qonversion.sandwich.QonversionSandwich; @@ -27,7 +25,7 @@ public class QonversionModule extends ReactContextBaseJavaModule implements Qonv private final QonversionSandwich qonversionSandwich; - private static final String EVENT_PERMISSIONS_UPDATED = "permissions_updated"; + private static final String EVENT_ENTITLEMENTS_UPDATED = "entitlements_updated"; private static final String ERROR_CODE_PURCHASE_CANCELLED_BY_USER = "PURCHASE_CANCELLED_BY_USER"; @@ -38,13 +36,7 @@ public QonversionModule(ReactApplicationContext reactContext) { qonversionSandwich = new QonversionSandwich( (Application) reactContext.getApplicationContext(), - new ActivityProvider() { - @Nullable - @Override - public Activity getCurrentActivity() { - return QonversionModule.this.getCurrentActivity(); - } - }, + QonversionModule.this::getCurrentActivity, this ); } @@ -56,6 +48,7 @@ public void initialize() { eventEmitter = getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class); } + @NonNull @Override public String getName() { return "RNQonversion"; @@ -67,8 +60,19 @@ public void storeSDKInfo(String source, String sdkVersion) { } @ReactMethod - public void launch(String key, Boolean observeMode, final Promise promise) { - qonversionSandwich.launch(key, observeMode, getResultListener(promise)); + public void initializeSdk( + String projectKey, + String launchModeKey, + @Nullable String environmentKey, + @Nullable String entitlementsCacheLifetimeKey + ) { + qonversionSandwich.initialize( + getReactApplicationContext(), + projectKey, + launchModeKey, + environmentKey, + entitlementsCacheLifetimeKey + ); } @ReactMethod @@ -139,8 +143,8 @@ public void addAttributionData(ReadableMap map, String provider) { } @ReactMethod - public void checkPermissions(final Promise promise) { - qonversionSandwich.checkPermissions(getResultListener(promise)); + public void checkEntitlements(final Promise promise) { + qonversionSandwich.checkEntitlements(getResultListener(promise)); } @ReactMethod @@ -159,11 +163,6 @@ public void checkTrialIntroEligibilityForProductIds(ReadableArray ids, final Pro qonversionSandwich.checkTrialIntroEligibility(idList, getResultListener(promise)); } - @ReactMethod - public void experiments(final Promise promise) { - qonversionSandwich.experiments(getResultListener(promise)); - } - @ReactMethod public void restore(final Promise promise) { qonversionSandwich.restore(getResultListener(promise)); @@ -174,11 +173,6 @@ public void syncPurchases() { qonversionSandwich.syncPurchases(); } - @ReactMethod - public void setDebugMode() { - qonversionSandwich.setDebugMode(); - } - @ReactMethod public void identify(String userID) { qonversionSandwich.identify(userID); @@ -189,65 +183,11 @@ public void logout() { qonversionSandwich.logout(); } - @ReactMethod - public void setPermissionsCacheLifetime(String lifetime) { - qonversionSandwich.setPermissionsCacheLifetime(lifetime); - } - - @ReactMethod - public void setNotificationsToken(String token) { - qonversionSandwich.setNotificationToken(token); - } - - @ReactMethod - public void getNotificationCustomPayload(final ReadableMap notificationData, final Promise promise) { - if (notificationData == null) { - promise.resolve(null); - return; - } - - final Map dataMap; - try { - dataMap = EntitiesConverter.convertReadableMapToHashMap(notificationData); - } catch (JSONException e) { - promise.reject(e); - return; - } - - Map payload = qonversionSandwich.getNotificationCustomPayload(dataMap); - if (payload == null) { - promise.resolve(null); - } else { - final WritableMap convertedPayload = EntitiesConverter.convertMapToWritableMap(payload); - - promise.resolve(convertedPayload); - } - } - - @ReactMethod - public void handleNotification(final ReadableMap notificationData, final Promise promise) { - if (notificationData == null) { - promise.resolve(false); - return; - } - - final Map dataMap; - try { - dataMap = EntitiesConverter.convertReadableMapToHashMap(notificationData); - } catch (JSONException e) { - promise.resolve(false); - return; - } - - boolean isQonversionNotification = qonversionSandwich.handleNotification(dataMap); - promise.resolve(isQonversionNotification); - } - @Override - public void onPermissionsUpdate(@NonNull Map map) { + public void onEntitlementsUpdated(@NonNull Map map) { final WritableMap payload = EntitiesConverter.convertMapToWritableMap(map); if (eventEmitter != null) { - eventEmitter.emit(EVENT_PERMISSIONS_UPDATED, payload); + eventEmitter.emit(EVENT_ENTITLEMENTS_UPDATED, payload); } } diff --git a/android/src/main/java/com/reactlibrary/QonversionPackage.java b/android/src/main/java/com/reactlibrary/QonversionPackage.java index 83a02e2..1829294 100644 --- a/android/src/main/java/com/reactlibrary/QonversionPackage.java +++ b/android/src/main/java/com/reactlibrary/QonversionPackage.java @@ -1,5 +1,7 @@ package com.reactlibrary; +import androidx.annotation.NonNull; + import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -10,16 +12,19 @@ import com.facebook.react.uimanager.ViewManager; public class QonversionPackage implements ReactPackage { + @NonNull @Override - public List createNativeModules(ReactApplicationContext reactContext) { - return Arrays.asList( + public List createNativeModules(@NonNull ReactApplicationContext reactContext) { + return Arrays.asList( new QonversionModule(reactContext), new AutomationsModule(reactContext) ); } + @SuppressWarnings("rawtypes") + @NonNull @Override - public List createViewManagers(ReactApplicationContext reactContext) { + public List createViewManagers(@NonNull ReactApplicationContext reactContext) { return Collections.emptyList(); } } diff --git a/android/src/main/java/com/reactlibrary/QonversionSDKInfo.java b/android/src/main/java/com/reactlibrary/QonversionSDKInfo.java deleted file mode 100644 index 6ab9098..0000000 --- a/android/src/main/java/com/reactlibrary/QonversionSDKInfo.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.reactlibrary; - -public class QonversionSDKInfo { - public String sourceKey; - public String source; - public String sdkVersionKey; - public String sdkVersion; - - public QonversionSDKInfo(String sourceKey, String source, String sdkVersionKey, String sdkVersion){ - this.sourceKey = sourceKey; - this.source = source; - this.sdkVersionKey = sdkVersionKey; - this.sdkVersion = sdkVersion; - } -} From 30e9c7c28d931f70c06f07a6f08b3a507fe697f2 Mon Sep 17 00:00:00 2001 From: Kamo Spertsyan Date: Tue, 6 Dec 2022 15:33:28 +0300 Subject: [PATCH 2/7] Cross-platform part of new major release (#195) * Native modules updated. * Native modules updated. * RN part refactoring * RN example refactoring with lib fixes * Testing fixes * Enum values rewritten from ints to strings * Renew state renamed * Comment fix * Comment fix * Comment fix * Enum renamings * Enum renamings * identity nullability fix * Comment fix * Comment fix * Comment fix * Comment fix --- android/build.gradle | 6 +- .../com/reactlibrary/QonversionModule.java | 5 + example/App.js | 517 ++++++++--------- example/android/app/build.gradle | 9 +- example/android/build.gradle | 4 +- example/notificationsManager.js | 15 +- example/package.json | 2 +- example/yarn.lock | 157 +++--- src/Automations.ts | 45 ++ src/AutomationsApi.ts | 39 ++ src/Qonversion.ts | 40 ++ src/QonversionApi.ts | 238 ++++++++ src/QonversionConfig.ts | 26 + src/QonversionConfigBuilder.ts | 74 +++ src/classes/ExperimentGroup.ts | 11 - src/classes/ExperimentInfo.ts | 13 - src/classes/LaunchResult.ts | 26 - src/classes/Permission.ts | 31 -- src/classes/Qonversion.ts | 518 ------------------ src/classes/UpdatedPurchasesDelegate.ts | 10 - src/{classes => dto}/ActionResult.ts | 2 +- src/{classes => dto}/AutomationsDelegate.ts | 2 +- src/{classes => dto}/AutomationsEvent.ts | 2 +- src/dto/Entitlement.ts | 31 ++ src/dto/EntitlementsUpdateListener.ts | 10 + src/{classes => dto}/IntroEligibility.ts | 2 +- src/{classes => dto}/Offering.ts | 2 +- src/{classes => dto}/Offerings.ts | 0 src/{classes => dto}/Product.ts | 2 +- .../PromoPurchasesListener.ts} | 8 +- src/{classes => dto}/QonversionError.ts | 0 src/dto/User.ts | 11 + src/{ => dto}/enums.ts | 66 +-- .../storeProducts/SKProduct.ts | 0 .../storeProducts/SKProductDiscount.ts | 2 +- .../storeProducts/SKSubscriptionPeriod.ts | 2 +- .../storeProducts/SkuDetails.ts | 0 src/index.ts | 38 +- .../AutomationsInternal.ts} | 39 +- src/{classes => internal}/Mapper.ts | 172 ++---- src/internal/QonversionInternal.ts | 256 +++++++++ src/internal/utils.ts | 13 + src/utils.ts | 44 -- 43 files changed, 1327 insertions(+), 1163 deletions(-) create mode 100644 src/Automations.ts create mode 100644 src/AutomationsApi.ts create mode 100644 src/Qonversion.ts create mode 100644 src/QonversionApi.ts create mode 100644 src/QonversionConfig.ts create mode 100644 src/QonversionConfigBuilder.ts delete mode 100644 src/classes/ExperimentGroup.ts delete mode 100644 src/classes/ExperimentInfo.ts delete mode 100644 src/classes/LaunchResult.ts delete mode 100644 src/classes/Permission.ts delete mode 100644 src/classes/Qonversion.ts delete mode 100644 src/classes/UpdatedPurchasesDelegate.ts rename src/{classes => dto}/ActionResult.ts (91%) rename src/{classes => dto}/AutomationsDelegate.ts (89%) rename src/{classes => dto}/AutomationsEvent.ts (82%) create mode 100644 src/dto/Entitlement.ts create mode 100644 src/dto/EntitlementsUpdateListener.ts rename src/{classes => dto}/IntroEligibility.ts (78%) rename src/{classes => dto}/Offering.ts (91%) rename src/{classes => dto}/Offerings.ts (100%) rename src/{classes => dto}/Product.ts (99%) rename src/{classes/PromoPurchasesDelegate.ts => dto/PromoPurchasesListener.ts} (64%) rename src/{classes => dto}/QonversionError.ts (100%) create mode 100644 src/dto/User.ts rename src/{ => dto}/enums.ts (75%) rename src/{classes => dto}/storeProducts/SKProduct.ts (100%) rename src/{classes => dto}/storeProducts/SKProductDiscount.ts (97%) rename src/{classes => dto}/storeProducts/SKSubscriptionPeriod.ts (84%) rename src/{classes => dto}/storeProducts/SkuDetails.ts (100%) rename src/{classes/Automations.ts => internal/AutomationsInternal.ts} (67%) rename src/{classes => internal}/Mapper.ts (75%) create mode 100644 src/internal/QonversionInternal.ts create mode 100644 src/internal/utils.ts delete mode 100644 src/utils.ts diff --git a/android/build.gradle b/android/build.gradle index c0248ca..a32eebc 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -10,9 +10,9 @@ // original location: // - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/app/build.gradle -def DEFAULT_COMPILE_SDK_VERSION = 28 +def DEFAULT_COMPILE_SDK_VERSION = 33 def DEFAULT_MIN_SDK_VERSION = 16 -def DEFAULT_TARGET_SDK_VERSION = 28 +def DEFAULT_TARGET_SDK_VERSION = 33 def safeExtGet(prop, fallback) { rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback @@ -65,7 +65,7 @@ repositories { dependencies { //noinspection GradleDynamicVersion implementation 'com.facebook.react:react-native:+' // From node_modules - implementation "io.qonversion.sandwich:sandwich_local:1.0.0-RC3" + implementation "io.qonversion.sandwich:sandwich_local:1.0.0-RC5" } afterEvaluate { project -> diff --git a/android/src/main/java/com/reactlibrary/QonversionModule.java b/android/src/main/java/com/reactlibrary/QonversionModule.java index a830cb9..cb31561 100644 --- a/android/src/main/java/com/reactlibrary/QonversionModule.java +++ b/android/src/main/java/com/reactlibrary/QonversionModule.java @@ -183,6 +183,11 @@ public void logout() { qonversionSandwich.logout(); } + @ReactMethod + public void userInfo(final Promise promise) { + qonversionSandwich.userInfo(getResultListener(promise)); + } + @Override public void onEntitlementsUpdated(@NonNull Map map) { final WritableMap payload = EntitiesConverter.convertMapToWritableMap(map); diff --git a/example/App.js b/example/App.js index ac7fa14..9dcfe05 100644 --- a/example/App.js +++ b/example/App.js @@ -1,4 +1,4 @@ -/* eslint-disable prettier/prettier */ +/* eslint-disable prettier/prettier,react-native/no-inline-styles */ /** * Sample React Native App * https://github.com/facebook/react-native @@ -9,243 +9,260 @@ import React, {Component} from 'react'; import {Image, TouchableOpacity, StyleSheet, Text, View, SafeAreaView, ActivityIndicator, Alert} from 'react-native'; -import Qonversion, {Product, Permission} from 'react-native-qonversion'; +import Qonversion, { + Product, + QonversionConfigBuilder, + LaunchMode, + Environment, + Entitlement, + EntitlementsCacheLifetime, + Automations, +} from 'react-native-qonversion'; import NotificationsManager from './notificationsManager'; NotificationsManager.init(); type StateType = { - inAppButtonTitle: string; - subscriptionButtonTitle: string; - loading: boolean; - checkPermissionsHidden: boolean; + inAppButtonTitle: string; + subscriptionButtonTitle: string; + loading: boolean; + checkEntitlementsHidden: boolean; }; const prettyDuration = { - 'WEEKLY': 'weekly', - 'MONTHLY': 'monthly', - '3_MONTHS': '3 months', - '6_MONTHS': '6 months', - 'ANNUAL': 'annual', - 'LIFETIME': 'lifetime', + 'WEEKLY': 'weekly', + 'MONTHLY': 'monthly', + '3_MONTHS': '3 months', + '6_MONTHS': '6 months', + 'ANNUAL': 'annual', + 'LIFETIME': 'lifetime', }; export class QonversionSample extends React.PureComponent<{}, StateType> { - constructor(props) { - super(props); + constructor(props) { + super(props); - this.state = { - inAppButtonTitle: "Loading...", - subscriptionButtonTitle: "Loading...", - loading: true, - checkPermissionsHidden: true, - }; + this.state = { + inAppButtonTitle: 'Loading...', + subscriptionButtonTitle: 'Loading...', + loading: true, + checkEntitlementsHidden: true, + }; - // eslint-disable-next-line consistent-this - const outerClassRef = this; // necessary for anonymous classes to access this. - Qonversion.launchWithKey('PV77YHL7qnGvsdmpTs7gimsxUvY-Znl2'); - Qonversion.setUpdatedPurchasesDelegate({ - onPermissionsUpdated(permissions) { - console.log('Permissions updated!', permissions); - outerClassRef.handlePermissions(permissions); - }, - }); - Qonversion.setPromoPurchasesDelegate({ - onPromoPurchaseReceived: async (productId, promoPurchaseExecutor) => { - try { - const permissions = await promoPurchaseExecutor(productId); - console.log('Promo purchase completed. Permissions: ', permissions); - outerClassRef.handlePermissions(permissions); - } catch (e) { - console.log('Promo purchase failed.'); - } - }, - }); - Qonversion.checkPermissions().then(permissions => { - this.handlePermissions(permissions); - }); + // eslint-disable-next-line consistent-this + const outerClassRef = this; // necessary for anonymous classes to access this. + const config = new QonversionConfigBuilder( + 'PV77YHL7qnGvsdmpTs7gimsxUvY-Znl2', + LaunchMode.SUBSCRIPTION_MANAGEMENT + ) + .setEnvironment(Environment.SANDBOX) + .setEntitlementsCacheLifetime(EntitlementsCacheLifetime.MONTH) + .setEntitlementsUpdateListener({ + onEntitlementsUpdated(entitlements) { + console.log('Entitlements updated!', entitlements); + outerClassRef.handleEntitlements(entitlements); + }, + }) + .build(); + Qonversion.initialize(config); + Automations.initialize(); + Qonversion.getSharedInstance().setPromoPurchasesDelegate({ + onPromoPurchaseReceived: async (productId, promoPurchaseExecutor) => { + try { + const entitlements = await promoPurchaseExecutor(productId); + console.log('Promo purchase completed. Entitlements: ', entitlements); + outerClassRef.handleEntitlements(entitlements); + } catch (e) { + console.log('Promo purchase failed.'); + } + }, + }); + Qonversion.getSharedInstance().checkEntitlements().then(entitlements => { + this.handleEntitlements(entitlements); + }); + } + + handleEntitlements(entitlements) { + let checkActiveEntitlementsButtonHidden = this.state.checkEntitlementsHidden; + if (entitlements.size > 0) { + const entitlementsValues = Array.from(entitlements.values()); + checkActiveEntitlementsButtonHidden = !entitlementsValues.some(item => item.isActive === true); } + Qonversion.getSharedInstance().products().then(products => { + let inAppTitle = this.state.inAppButtonTitle; + let subscriptionButtonTitle = this.state.subscriptionButtonTitle; - handlePermissions(permissions) { - let checkActivePermissionsButtonHidden = this.state.checkPermissionsHidden; - if (permissions.size > 0) { - const permissionsValues = Array.from(permissions.values()); - checkActivePermissionsButtonHidden = !permissionsValues.some(item => item.isActive === true); + const inApp: Product = products.get('in_app'); + if (inApp) { + inAppTitle = 'Buy for ' + inApp.prettyPrice; + const entitlement = entitlements.get('Test Entitlement'); + if (entitlement) { + inAppTitle = entitlement.isActive ? 'Purchased' : inAppTitle; } - Qonversion.products().then(products => { - let inAppTitle = this.state.inAppButtonTitle; - let subscriptionButtonTitle = this.state.subscriptionButtonTitle; + } - const inApp: Product = products.get('in_app'); - if (inApp) { - inAppTitle = 'Buy for ' + inApp.prettyPrice; - const permission = permissions.get('Test Permission'); - if (permission) { - inAppTitle = permission.isActive ? 'Purchased' : inAppTitle; - } - } - - const main: Product = products.get('main'); - if (main) { - subscriptionButtonTitle = 'Subscribe for ' + main.prettyPrice + ' / ' + prettyDuration[main.duration]; - const permission = permissions.get('plus'); - if (permission) { - subscriptionButtonTitle = permission.isActive ? 'Purchased' : subscriptionButtonTitle; - } - } + const main: Product = products.get('main'); + if (main) { + subscriptionButtonTitle = 'Subscribe for ' + main.prettyPrice + ' / ' + prettyDuration[main.duration]; + const entitlement = entitlements.get('plus'); + if (entitlement) { + subscriptionButtonTitle = entitlement.isActive ? 'Purchased' : subscriptionButtonTitle; + } + } - this.setState({ - loading: false, - inAppButtonTitle: inAppTitle, - subscriptionButtonTitle: subscriptionButtonTitle, - checkPermissionsHidden: checkActivePermissionsButtonHidden, - }); - }); - } + this.setState({ + loading: false, + inAppButtonTitle: inAppTitle, + subscriptionButtonTitle: subscriptionButtonTitle, + checkEntitlementsHidden: checkActiveEntitlementsButtonHidden, + }); + }); + } render() { return ( - + - {'Build in-app\nsubscriptions'} + {'Build in-app\nsubscriptions'} - - {'without server code'} - - {this.state.loading && - - } - - Start with 3 days free trial - { - this.setState({loading: true}); - Qonversion.purchase('main').then(() => { - this.setState({loading: false, subscriptionButtonTitle: 'Purchased'}); - }).catch(error => { - this.setState({loading: false}); - - if (!error.userCanceled) { - Alert.alert( - 'Error', - error.message, - [ - { text: 'OK' }, - ], - { cancelable: true } - ); - } - }); - }} - > - {this.state.subscriptionButtonTitle} - - { - this.setState({loading: true}); - Qonversion.purchase('in_app').then(() => { - this.setState({loading: false, inAppButtonTitle: 'Purchased'}); - }).catch(error => { - this.setState({loading: false}); + + {'without server code'} + + {this.state.loading && + + } + + Start with 3 + days free trial + { + this.setState({loading: true}); + Qonversion.getSharedInstance().purchase('main').then(() => { + this.setState({loading: false, subscriptionButtonTitle: 'Purchased'}); + }).catch(error => { + this.setState({loading: false}); - if (!error.userCanceled) { - Alert.alert( - 'Error', - error.message, - [ - { text: 'OK' }, - ], - { cancelable: true } - ); - } - }); - }} - > - {this.state.inAppButtonTitle} - - { - this.setState({loading: true}); - Qonversion.restore().then(permissions => { - this.setState({loading: false}); + if (!error.userCanceled) { + Alert.alert( + 'Error', + error.message, + [ + {text: 'OK'}, + ], + {cancelable: true} + ); + } + }); + }} + > + {this.state.subscriptionButtonTitle} + + { + this.setState({loading: true}); + Qonversion.getSharedInstance().purchase('in_app').then(() => { + this.setState({loading: false, inAppButtonTitle: 'Purchased'}); + }).catch(error => { + this.setState({loading: false}); - let checkActivePermissionsButtonHidden = this.state.checkPermissionsHidden; - let inAppTitle = this.state.inAppButtonTitle; - let subscriptionButtonTitle = this.state.subscriptionButtonTitle; - if (permissions.size > 0) { - const permissionsValues = Array.from(permissions.values()); - checkActivePermissionsButtonHidden = permissionsValues.some(item => item.isActive === true); + if (!error.userCanceled) { + Alert.alert( + 'Error', + error.message, + [ + {text: 'OK'}, + ], + {cancelable: true} + ); + } + }); + }} + > + {this.state.inAppButtonTitle} + + { + this.setState({loading: true}); + Qonversion.getSharedInstance().restore().then(entitlements => { + this.setState({loading: false}); - const standartPermission = permissions.get('Test Permission'); - if (standartPermission && standartPermission.isActive) { - inAppTitle = 'Restored'; - } + let checkActiveEntitlementsButtonHidden = this.state.checkEntitlementsHidden; + let inAppTitle = this.state.inAppButtonTitle; + let subscriptionButtonTitle = this.state.subscriptionButtonTitle; + if (entitlements.size > 0) { + const entitlementsValues = Array.from(entitlements.values()); + checkActiveEntitlementsButtonHidden = entitlementsValues.some(item => item.isActive === true); - const plusPermission = permissions.get('plus'); - if (plusPermission && plusPermission.isActive) { - subscriptionButtonTitle = 'Restored'; - } - } else { - Alert.alert( - 'Error', - 'No purchases to restore', - [ - { text: 'OK' } - ], - { cancelable: true } - ); - } + const standartEntitlement = entitlements.get('Test Entitlement'); + if (standartEntitlement && standartEntitlement.isActive) { + inAppTitle = 'Restored'; + } - this.setState({loading: false, checkPermissionsButtonHidden: checkActivePermissionsButtonHidden, inAppButtonTitle: inAppTitle, - subscriptionButtonTitle: subscriptionButtonTitle}); + const plusEntitlement = entitlements.get('plus'); + if (plusEntitlement && plusEntitlement.isActive) { + subscriptionButtonTitle = 'Restored'; + } + } else { + Alert.alert( + 'Error', + 'No purchases to restore', + [ + {text: 'OK'}, + ], + {cancelable: true} + ); + } - // if let permission: Qonversion.Permission = self.permissions["plus"], permission.isActive { - // self.mainProductSubscriptionButton.setTitle("Restored", for: .normal) - // } - }); - }} - > - Restore purchases - - {!this.state.checkPermissionsHidden && { - Qonversion.checkPermissions().then(permissions => { - let message = ''; - const permissionsValues = Array.from(permissions.values()); - permissionsValues.map((permission: Permission) => { - if (permission.isActive) { - message = message + 'permissionID: ' + permission.permissionID + '\n' + 'productID: ' + permission.productID + '\n' + 'renewState: ' + permission.renewState + '\n\n'; - } - }); - Alert.alert( - 'Active permissions', - message, - [ - { text: 'OK' } - ], - { cancelable: true } - ); - }); - }} - > - Check active permissions - - } - + this.setState({ + loading: false, + checkEntitlementsButtonHidden: checkActiveEntitlementsButtonHidden, + inAppButtonTitle: inAppTitle, + subscriptionButtonTitle: subscriptionButtonTitle, + }); + }); + }} + > + Restore purchases + + {!this.state.checkEntitlementsHidden && { + Qonversion.getSharedInstance().checkEntitlements().then(entitlements => { + let message = ''; + const entitlementsValues = Array.from(entitlements.values()); + entitlementsValues.map((entitlement: Entitlement) => { + if (entitlement.isActive) { + message = message + 'entitlementID: ' + entitlement.id + '\n' + 'productID: ' + entitlement.productId + '\n' + 'renewState: ' + entitlement.renewState + '\n\n'; + } + }); + Alert.alert( + 'Active entitlements', + message, + [ + {text: 'OK'}, + ], + {cancelable: true} + ); + }); + }} + > + Check active entitlements + + } + ); } @@ -253,54 +270,54 @@ export class QonversionSample extends React.PureComponent<{}, StateType> { const styles = StyleSheet.create({ - additionalButtonsText: { - flex: 1, - textAlign:'center', - }, - additionalButton: { - flexDirection: 'row', - height: 40, - marginHorizontal:32, - alignItems: 'center', - }, - subscriptionButton: { - flexDirection: 'row', - marginHorizontal:32, - height: 54, - marginTop:10, - backgroundColor:'#3E5AE1', - borderRadius:20, - borderWidth: 1, - borderColor: '#3E5AE1', - alignItems: 'center', - }, - inAppButton: { - flexDirection: 'row', - marginHorizontal:32, - height: 54, - marginTop:10, - borderRadius:20, - borderWidth: 1, - borderColor: '#3E5AE1', - alignItems: 'center', - }, - inAppButtonTitle: { - flex: 1, - textAlign:'center', - color: '#3E5AE1', - }, - buttonTitle: { - flex: 1, - color:'#fff', - textAlign:'center', - }, + additionalButtonsText: { + flex: 1, + textAlign: 'center', + }, + additionalButton: { + flexDirection: 'row', + height: 40, + marginHorizontal: 32, + alignItems: 'center', + }, + subscriptionButton: { + flexDirection: 'row', + marginHorizontal: 32, + height: 54, + marginTop: 10, + backgroundColor: '#3E5AE1', + borderRadius: 20, + borderWidth: 1, + borderColor: '#3E5AE1', + alignItems: 'center', + }, + inAppButton: { + flexDirection: 'row', + marginHorizontal: 32, + height: 54, + marginTop: 10, + borderRadius: 20, + borderWidth: 1, + borderColor: '#3E5AE1', + alignItems: 'center', + }, + inAppButtonTitle: { + flex: 1, + textAlign: 'center', + color: '#3E5AE1', + }, + buttonTitle: { + flex: 1, + color: '#fff', + textAlign: 'center', + }, }); export default class App extends Component { render() { return ( <> - + ); diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 4bb1be2..9286e39 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -78,7 +78,8 @@ import com.android.build.OutputFile */ project.ext.react = [ - enableHermes: false, // clean and rebuild if changing + enableHermes: true, // clean and rebuild if changing + entryFile: "index.js", ] apply from: "../../node_modules/react-native/react.gradle" @@ -211,9 +212,9 @@ dependencies { } if (enableHermes) { - def hermesPath = "../../node_modules/hermes-engine/android/"; - debugImplementation files(hermesPath + "hermes-debug.aar") - releaseImplementation files(hermesPath + "hermes-release.aar") + implementation("com.facebook.react:hermes-engine:+") { + exclude group:'com.facebook.fbjni' + } } else { implementation jscFlavor } diff --git a/example/android/build.gradle b/example/android/build.gradle index 353c2b7..94ccc20 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -4,8 +4,8 @@ buildscript { ext { buildToolsVersion = "28.0.3" minSdkVersion = 16 - compileSdkVersion = 31 - targetSdkVersion = 31 + compileSdkVersion = 33 + targetSdkVersion = 33 } repositories { google() diff --git a/example/notificationsManager.js b/example/notificationsManager.js index 007f2ee..1a5c2b3 100644 --- a/example/notificationsManager.js +++ b/example/notificationsManager.js @@ -1,17 +1,17 @@ import PushNotificationIOS from '@react-native-community/push-notification-ios'; import PushNotification from 'react-native-push-notification'; -import Qonversion from 'react-native-qonversion'; +import {Automations} from 'react-native-qonversion'; import {Platform} from 'react-native'; const NotificationsManager = { init: () => { PushNotification.configure({ - onRegister: function(token) { + onRegister: function (token) { console.log('Device token:', token); - Qonversion.setNotificationsToken(token.token); + Automations.getSharedInstance().setNotificationsToken(token.token); }, - onNotification: async function(notification) { + onNotification: async function (notification) { console.log('Notification:', notification); if (notification.userInteraction) { @@ -21,9 +21,10 @@ const NotificationsManager = { } else if (Platform.OS === 'android') { notificationData = JSON.parse(notification.data.notificationData); } - const isQonversionNotification = await Qonversion.handleNotification( - notificationData, - ); + const isQonversionNotification = + await Automations.getSharedInstance().handleNotification( + notificationData, + ); console.log( 'Is notification handled by Qonversion: ', isQonversionNotification, diff --git a/example/package.json b/example/package.json index bfbb606..8f89573 100644 --- a/example/package.json +++ b/example/package.json @@ -17,7 +17,7 @@ "@react-native-community/push-notification-ios": "^1.10.1", "babel-preset-react-native": "2.1.0", "react": "18.1.0", - "react-native": "0.70.3", + "react-native": "0.70.5", "react-native-push-notification": "^8.1.1", "react-native-qonversion": "file:../" }, diff --git a/example/yarn.lock b/example/yarn.lock index e083aa8..5086301 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -1312,12 +1312,12 @@ slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/create-cache-key-function@^27.0.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-27.5.1.tgz#7448fae15602ea95c828f5eceed35c202a820b31" - integrity sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ== +"@jest/create-cache-key-function@^29.0.3": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-29.3.1.tgz#3a0970ea595ab3d9507244edbcef14d6b016cdc9" + integrity sha512-4i+E+E40gK13K78ffD/8cy4lSSqeWwyXeTZoq16tndiCP12hC8uQsPJdIu5C6Kf22fD8UbBk71so7s/6VwpUOQ== dependencies: - "@jest/types" "^27.5.1" + "@jest/types" "^29.3.1" "@jest/environment@^26.6.2": version "26.6.2" @@ -1382,6 +1382,13 @@ optionalDependencies: node-notifier "^8.0.0" +"@jest/schemas@^29.0.0": + version "29.0.0" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.0.0.tgz#5f47f5994dd4ef067fb7b4188ceac45f77fe952a" + integrity sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA== + dependencies: + "@sinclair/typebox" "^0.24.1" + "@jest/source-map@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" @@ -1455,6 +1462,18 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" +"@jest/types@^29.3.1": + version "29.3.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.3.1.tgz#7c5a80777cb13e703aeec6788d044150341147e3" + integrity sha512-d0S0jmmTpjnhCmNpApgX3jrUZgZ22ivKJRvL2lli5hpCRoNnp1f85r2/wpKfXuYu8E7Jjh1hGfhPyup1NM5AmA== + dependencies: + "@jest/schemas" "^29.0.0" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -1509,7 +1528,7 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@react-native-community/cli-clean@^9.1.0": +"@react-native-community/cli-clean@^9.2.1": version "9.2.1" resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-9.2.1.tgz#198c5dd39c432efb5374582073065ff75d67d018" integrity sha512-dyNWFrqRe31UEvNO+OFWmQ4hmqA07bR9Ief/6NnGwx67IO9q83D5PEAf/o96ML6jhSbDwCmpPKhPwwBbsyM3mQ== @@ -1519,7 +1538,7 @@ execa "^1.0.0" prompts "^2.4.0" -"@react-native-community/cli-config@^9.1.0", "@react-native-community/cli-config@^9.2.1": +"@react-native-community/cli-config@^9.2.1": version "9.2.1" resolved "https://registry.yarnpkg.com/@react-native-community/cli-config/-/cli-config-9.2.1.tgz#54eb026d53621ccf3a9df8b189ac24f6e56b8750" integrity sha512-gHJlBBXUgDN9vrr3aWkRqnYrPXZLztBDQoY97Mm5Yo6MidsEpYo2JIP6FH4N/N2p1TdjxJL4EFtdd/mBpiR2MQ== @@ -1537,13 +1556,13 @@ dependencies: serve-static "^1.13.1" -"@react-native-community/cli-doctor@^9.1.2": - version "9.2.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-doctor/-/cli-doctor-9.2.1.tgz#04859a93f0ea87d78cc7050362b6ce2b1c54fd36" - integrity sha512-RpUax0pkKumXJ5hcRG0Qd+oYWsA2RFeMWKY+Npg8q05Cwd1rqDQfWGprkHC576vz26+FPuvwEagoAf6fR2bvJA== +"@react-native-community/cli-doctor@^9.2.1": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-doctor/-/cli-doctor-9.3.0.tgz#8817a3fd564453467def5b5bc8aecdc4205eff50" + integrity sha512-/fiuG2eDGC2/OrXMOWI5ifq4X1gdYTQhvW2m0TT5Lk1LuFiZsbTCp1lR+XILKekuTvmYNjEGdVpeDpdIWlXdEA== dependencies: "@react-native-community/cli-config" "^9.2.1" - "@react-native-community/cli-platform-ios" "^9.2.1" + "@react-native-community/cli-platform-ios" "^9.3.0" "@react-native-community/cli-tools" "^9.2.1" chalk "^4.1.2" command-exists "^1.2.8" @@ -1559,23 +1578,23 @@ sudo-prompt "^9.0.0" wcwidth "^1.0.1" -"@react-native-community/cli-hermes@^9.1.0": - version "9.2.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-hermes/-/cli-hermes-9.2.1.tgz#c4aeadc4aa2b55cd0dd931a1a1c1909fd426f31a" - integrity sha512-723/NMb7egXzJrbWT1uEkN2hOpw+OOtWTG2zKJ3j7KKgUd8u/pP+/z5jO8xVrq+eYJEMjDK0FBEo1Xj7maR4Sw== +"@react-native-community/cli-hermes@^9.2.1": + version "9.3.1" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-hermes/-/cli-hermes-9.3.1.tgz#569d27c1effd684ba451ad4614e29a99228cec49" + integrity sha512-Mq4PK8m5YqIdaVq5IdRfp4qK09aVO+aiCtd6vjzjNUgk1+1X5cgUqV6L65h4N+TFJYJHcp2AnB+ik1FAYXvYPQ== dependencies: - "@react-native-community/cli-platform-android" "^9.2.1" + "@react-native-community/cli-platform-android" "^9.3.1" "@react-native-community/cli-tools" "^9.2.1" chalk "^4.1.2" hermes-profile-transformer "^0.0.6" ip "^1.1.5" -"@react-native-community/cli-platform-android@9.1.0": - version "9.1.0" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-android/-/cli-platform-android-9.1.0.tgz#3f80c202196c3874b86395b7f3f5fc13093d2d9e" - integrity sha512-OZ/Krq0wH6T7LuAvwFdJYe47RrHG8IOcoab47H4QQdYGTmJgTS3SlVkr6gn79pZyBGyp7xVizD30QJrIIyDjnw== +"@react-native-community/cli-platform-android@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-android/-/cli-platform-android-9.2.1.tgz#cd73cb6bbaeb478cafbed10bd12dfc01b484d488" + integrity sha512-VamCZ8nido3Q3Orhj6pBIx48itORNPLJ7iTfy3nucD1qISEDih3DOzCaQCtmqdEBgUkNkNl0O+cKgq5A3th3Zg== dependencies: - "@react-native-community/cli-tools" "^9.1.0" + "@react-native-community/cli-tools" "^9.2.1" chalk "^4.1.2" execa "^1.0.0" fs-extra "^8.1.0" @@ -1583,10 +1602,10 @@ logkitty "^0.7.1" slash "^3.0.0" -"@react-native-community/cli-platform-android@^9.2.1": - version "9.2.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-android/-/cli-platform-android-9.2.1.tgz#cd73cb6bbaeb478cafbed10bd12dfc01b484d488" - integrity sha512-VamCZ8nido3Q3Orhj6pBIx48itORNPLJ7iTfy3nucD1qISEDih3DOzCaQCtmqdEBgUkNkNl0O+cKgq5A3th3Zg== +"@react-native-community/cli-platform-android@^9.3.1": + version "9.3.1" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-android/-/cli-platform-android-9.3.1.tgz#378cd72249653cc74672094400657139f21bafb8" + integrity sha512-m0bQ6Twewl7OEZoVf79I2GZmsDqh+Gh0bxfxWgwxobsKDxLx8/RNItAo1lVtTCgzuCR75cX4EEO8idIF9jYhew== dependencies: "@react-native-community/cli-tools" "^9.2.1" chalk "^4.1.2" @@ -1596,21 +1615,21 @@ logkitty "^0.7.1" slash "^3.0.0" -"@react-native-community/cli-platform-ios@9.1.2": - version "9.1.2" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-9.1.2.tgz#fd59b2aadee8d54317f204b3c640767dca5e6225" - integrity sha512-XpgA9ymm76yjvtYPByqFF1LP7eM/lH5K3zpkZkV9MJLStOIo3mrzN2ywRDZ/xVOheh5LafS4YMmrjIajf11oIQ== +"@react-native-community/cli-platform-ios@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-9.2.1.tgz#d90740472216ffae5527dfc5f49063ede18a621f" + integrity sha512-dEgvkI6CFgPk3vs8IOR0toKVUjIFwe4AsXFvWWJL5qhrIzW9E5Owi0zPkSvzXsMlfYMbVX0COfVIK539ZxguSg== dependencies: - "@react-native-community/cli-tools" "^9.1.0" + "@react-native-community/cli-tools" "^9.2.1" chalk "^4.1.2" execa "^1.0.0" glob "^7.1.3" ora "^5.4.1" -"@react-native-community/cli-platform-ios@^9.2.1": - version "9.2.1" - resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-9.2.1.tgz#d90740472216ffae5527dfc5f49063ede18a621f" - integrity sha512-dEgvkI6CFgPk3vs8IOR0toKVUjIFwe4AsXFvWWJL5qhrIzW9E5Owi0zPkSvzXsMlfYMbVX0COfVIK539ZxguSg== +"@react-native-community/cli-platform-ios@^9.3.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@react-native-community/cli-platform-ios/-/cli-platform-ios-9.3.0.tgz#45abde2a395fddd7cf71e8b746c1dc1ee2260f9a" + integrity sha512-nihTX53BhF2Q8p4B67oG3RGe1XwggoGBrMb6vXdcu2aN0WeXJOXdBLgR900DAA1O8g7oy1Sudu6we+JsVTKnjw== dependencies: "@react-native-community/cli-tools" "^9.2.1" chalk "^4.1.2" @@ -1618,7 +1637,7 @@ glob "^7.1.3" ora "^5.4.1" -"@react-native-community/cli-plugin-metro@^9.1.3": +"@react-native-community/cli-plugin-metro@^9.2.1": version "9.2.1" resolved "https://registry.yarnpkg.com/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-9.2.1.tgz#0ec207e78338e0cc0a3cbe1b43059c24afc66158" integrity sha512-byBGBH6jDfUvcHGFA45W/sDwMlliv7flJ8Ns9foCh3VsIeYYPoDjjK7SawE9cPqRdMAD4SY7EVwqJnOtRbwLiQ== @@ -1634,7 +1653,7 @@ metro-runtime "0.72.3" readline "^1.3.0" -"@react-native-community/cli-server-api@^9.1.0", "@react-native-community/cli-server-api@^9.2.1": +"@react-native-community/cli-server-api@^9.2.1": version "9.2.1" resolved "https://registry.yarnpkg.com/@react-native-community/cli-server-api/-/cli-server-api-9.2.1.tgz#41ac5916b21d324bccef447f75600c03b2f54fbe" integrity sha512-EI+9MUxEbWBQhWw2PkhejXfkcRqPl+58+whlXJvKHiiUd7oVbewFs0uLW0yZffUutt4FGx6Uh88JWEgwOzAdkw== @@ -1649,7 +1668,7 @@ serve-static "^1.13.1" ws "^7.5.1" -"@react-native-community/cli-tools@^9.1.0", "@react-native-community/cli-tools@^9.2.1": +"@react-native-community/cli-tools@^9.2.1": version "9.2.1" resolved "https://registry.yarnpkg.com/@react-native-community/cli-tools/-/cli-tools-9.2.1.tgz#c332324b1ea99f9efdc3643649bce968aa98191c" integrity sha512-bHmL/wrKmBphz25eMtoJQgwwmeCylbPxqFJnFSbkqJPXQz3ManQ6q/gVVMqFyz7D3v+riaus/VXz3sEDa97uiQ== @@ -1671,19 +1690,19 @@ dependencies: joi "^17.2.1" -"@react-native-community/cli@9.1.3": - version "9.1.3" - resolved "https://registry.yarnpkg.com/@react-native-community/cli/-/cli-9.1.3.tgz#abe5e406214edb2ca828c51fbdaed7baf776298c" - integrity sha512-dfiBxNvqSwxkMduH0eAEJNS+LBbwxm1rOlTO7bN+FZvUwZNCCgIYrixfRo+ifqDUv8N5AbpQB5umnLbs0AjPaA== +"@react-native-community/cli@9.2.1": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@react-native-community/cli/-/cli-9.2.1.tgz#15cc32531fc323d4232d57b1f2d7c571816305ac" + integrity sha512-feMYS5WXXKF4TSWnCXozHxtWq36smyhGaENXlkiRESfYZ1mnCUlPfOanNCAvNvBqdyh9d4o0HxhYKX1g9l6DCQ== dependencies: - "@react-native-community/cli-clean" "^9.1.0" - "@react-native-community/cli-config" "^9.1.0" + "@react-native-community/cli-clean" "^9.2.1" + "@react-native-community/cli-config" "^9.2.1" "@react-native-community/cli-debugger-ui" "^9.0.0" - "@react-native-community/cli-doctor" "^9.1.2" - "@react-native-community/cli-hermes" "^9.1.0" - "@react-native-community/cli-plugin-metro" "^9.1.3" - "@react-native-community/cli-server-api" "^9.1.0" - "@react-native-community/cli-tools" "^9.1.0" + "@react-native-community/cli-doctor" "^9.2.1" + "@react-native-community/cli-hermes" "^9.2.1" + "@react-native-community/cli-plugin-metro" "^9.2.1" + "@react-native-community/cli-server-api" "^9.2.1" + "@react-native-community/cli-tools" "^9.2.1" "@react-native-community/cli-types" "^9.1.0" chalk "^4.1.2" commander "^9.4.0" @@ -1757,6 +1776,11 @@ resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== +"@sinclair/typebox@^0.24.1": + version "0.24.51" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" + integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -1896,6 +1920,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^17.0.8": + version "17.0.13" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.13.tgz#34cced675ca1b1d51fcf4d34c3c6f0fa142a5c76" + integrity sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg== + dependencies: + "@types/yargs-parser" "*" + "@typescript-eslint/eslint-plugin@^3.1.0": version "3.10.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.10.1.tgz#7e061338a1383f59edc204c605899f93dc2e2c8f" @@ -6545,10 +6576,10 @@ react-is@^18.1.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -react-native-codegen@^0.70.5: - version "0.70.5" - resolved "https://registry.yarnpkg.com/react-native-codegen/-/react-native-codegen-0.70.5.tgz#afcfec394fc9357d870452eace5c7550e6ac403f" - integrity sha512-vXqgCWWIWlzsCtwD6hbmwmCleGNJYm+n4xO9VMfzzlF3xt9gjC7/USSMTf/YZlCK/hDwQU412QrNS6A9OH+mag== +react-native-codegen@^0.70.6: + version "0.70.6" + resolved "https://registry.yarnpkg.com/react-native-codegen/-/react-native-codegen-0.70.6.tgz#2ce17d1faad02ad4562345f8ee7cbe6397eda5cb" + integrity sha512-kdwIhH2hi+cFnG5Nb8Ji2JwmcCxnaOOo9440ov7XDzSvGfmUStnCzl+MCW8jLjqHcE4icT7N9y+xx4f50vfBTw== dependencies: "@babel/parser" "^7.14.0" flow-parser "^0.121.0" @@ -6566,17 +6597,17 @@ react-native-push-notification@^8.1.1: integrity sha512-XpBtG/w+a6WXTxu6l1dNYyTiHnbgnvjoc3KxPTxYkaIABRmvuJZkFxqruyGvfCw7ELAlZEAJO+dthdTabCe1XA== "react-native-qonversion@file:..": - version "3.6.1" + version "3.6.2" -react-native@0.70.3: - version "0.70.3" - resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.70.3.tgz#a5315ce14dd3f51f9ef97827af5b0a470ade1d9c" - integrity sha512-EiJxOonyvpSWa3Eik7a6YAD6QU8lK2W9M/DDdYlpWmIlGLCd5110rIpEZjxttsyrohxlRJAYRgJ9Tx2mMXqtfw== +react-native@0.70.5: + version "0.70.5" + resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.70.5.tgz#f60540b21d338891086e0a834e331c124dd1f55c" + integrity sha512-5NZM80LC3L+TIgQX/09yiyy48S73wMgpIgN5cCv3XTMR394+KpDI3rBZGH4aIgWWuwijz31YYVF5504+9n2Zfw== dependencies: - "@jest/create-cache-key-function" "^27.0.1" - "@react-native-community/cli" "9.1.3" - "@react-native-community/cli-platform-android" "9.1.0" - "@react-native-community/cli-platform-ios" "9.1.2" + "@jest/create-cache-key-function" "^29.0.3" + "@react-native-community/cli" "9.2.1" + "@react-native-community/cli-platform-android" "9.2.1" + "@react-native-community/cli-platform-ios" "9.2.1" "@react-native/assets" "1.0.0" "@react-native/normalize-color" "2.0.0" "@react-native/polyfills" "2.0.0" @@ -6595,7 +6626,7 @@ react-native@0.70.3: pretty-format "^26.5.2" promise "^8.0.3" react-devtools-core "4.24.0" - react-native-codegen "^0.70.5" + react-native-codegen "^0.70.6" react-native-gradle-plugin "^0.70.3" react-refresh "^0.4.0" react-shallow-renderer "^16.15.0" diff --git a/src/Automations.ts b/src/Automations.ts new file mode 100644 index 0000000..6770422 --- /dev/null +++ b/src/Automations.ts @@ -0,0 +1,45 @@ +import AutomationsApi from './AutomationsApi'; +import Qonversion from './Qonversion'; +import AutomationsInternal from './internal/AutomationsInternal'; + +export default class Automations { + private constructor() {} + + private static backingInstance: AutomationsApi | undefined; + + /** + * Use this variable to get a current initialized instance of the Qonversion Automations. + * Please, use the property only after calling {@link Automations.initialize}. + * Otherwise, trying to access the variable will cause an error. + * + * @return Current initialized instance of the Qonversion Automations. + * @throws error if the instance has not been initialized. + */ + static getSharedInstance(): AutomationsApi { + if (!this.backingInstance) { + throw "Automations have not been initialized. You should call " + + "the initialize method before accessing the shared instance of Automations." + } + + return this.backingInstance; + } + + /** + * An entry point to use Qonversion Automations. Call to initialize Automations. + * Make sure you have initialized {@link Qonversion} first. + * + * @return Initialized instance of the Qonversion Automations. + * @throws error if {@link Qonversion} has not been initialized. + */ + static initialize(): AutomationsApi { + try { + Qonversion.getSharedInstance(); + } catch (e) { + throw "Qonversion has not been initialized. " + + "Automations initialization should be called after Qonversion is initialized." + } + + this.backingInstance = new AutomationsInternal(); + return this.backingInstance; + } +} diff --git a/src/AutomationsApi.ts b/src/AutomationsApi.ts new file mode 100644 index 0000000..31fa548 --- /dev/null +++ b/src/AutomationsApi.ts @@ -0,0 +1,39 @@ +import {AutomationsDelegate} from './dto/AutomationsDelegate'; + +interface AutomationsApi { + /** + * The Automations delegate is responsible for handling in-app screens and actions when push notification is received. + * Make sure the method is called before {@link AutomationsApi.handleNotification}. + * + * @param delegate the delegate to be notified about Automations events. + */ + setDelegate(delegate: AutomationsDelegate): void; + + /** + * Set push token to Qonversion to enable Qonversion push notifications + * + * @param token Firebase device token on Android. APNs device token on iOS. + */ + setNotificationsToken(token: string): void; + + /** + * Call to handle push notifications sent by Qonversion Automations. + * + * @param notificationData notification payload data + * @return true when a push notification was received from Qonversion. Otherwise, returns false, so you need to handle a notification yourself + * + * @see [Firebase RemoteMessage data](https://pub.dev/documentation/firebase_messaging_platform_interface/latest/firebase_messaging_platform_interface/RemoteMessage/data.html) + * @see [APNs notification data](https://developer.apple.com/documentation/usernotifications/unnotificationcontent/1649869-userinfo) + */ + handleNotification(notificationData: Map): Promise; + + /** + * Get parsed custom payload, which you added to the notification in the dashboard + * + * @param notificationData notification payload data + * @return a map with custom payload from the notification or null if it's not provided. + */ + getNotificationCustomPayload(notificationData: Map): Promise | null>; +} + +export default AutomationsApi; diff --git a/src/Qonversion.ts b/src/Qonversion.ts new file mode 100644 index 0000000..0241284 --- /dev/null +++ b/src/Qonversion.ts @@ -0,0 +1,40 @@ +import QonversionConfig from './QonversionConfig'; +import QonversionApi from './QonversionApi'; +import QonversionInternal from './internal/QonversionInternal'; + +export default class Qonversion { + private constructor() {} + + private static backingInstance: QonversionApi | undefined; + + /** + * Use this variable to get a current initialized instance of the Qonversion SDK. + * Please, use the property only after calling {@link Qonversion.initialize}. + * Otherwise, trying to access the variable will cause an exception. + * + * @return Current initialized instance of the Qonversion SDK. + * @throws error if the instance has not been initialized + */ + static getSharedInstance(): QonversionApi { + if (!this.backingInstance) { + throw "Qonversion has not been initialized. You should call " + + "the initialize method before accessing the shared instance of Qonversion." + } + + return this.backingInstance; + } + + /** + * An entry point to use Qonversion SDK. Call to initialize Qonversion SDK with required and extra configs. + * The function is the best way to set additional configs you need to use Qonversion SDK. + * You still have an option to set a part of additional configs later via calling separated setters. + * + * @param config a config that contains key SDK settings. + * Call {@link QonversionConfigBuilder.build} to configure and create a QonversionConfig instance. + * @return Initialized instance of the Qonversion SDK. + */ + static initialize(config: QonversionConfig): QonversionApi { + this.backingInstance = new QonversionInternal(config); + return this.backingInstance; + } +} diff --git a/src/QonversionApi.ts b/src/QonversionApi.ts new file mode 100644 index 0000000..1302693 --- /dev/null +++ b/src/QonversionApi.ts @@ -0,0 +1,238 @@ +import Entitlement from './dto/Entitlement'; +import Product from './dto/Product'; +import {UserProperty, ProrationMode, AttributionProvider} from './dto/enums'; +import Offerings from './dto/Offerings'; +import IntroEligibility from './dto/IntroEligibility'; +import User from './dto/User'; +import {EntitlementsUpdateListener} from './dto/EntitlementsUpdateListener'; +import {PromoPurchasesListener} from './dto/PromoPurchasesListener'; + +interface QonversionApi { + + /** + * Make a purchase and validate it through server-to-server using Qonversion's Backend + * + * @param productId Qonversion product identifier for purchase + * @returns the promise with the user entitlements including the ones obtained by the purchase + */ + purchase(productId: string): Promise>; + + /** + * Make a purchase and validate it through server-to-server using Qonversion's Backend + * + * @param product - Qonversion's {@link Product} object + * @returns the promise with the user entitlements including the ones obtained by the purchase + */ + purchaseProduct(product: Product): Promise>; + + /** + * Android only. Returns `null` if called on iOS. + * + * Update (upgrade/downgrade) subscription on Google Play Store and validate it through server-to-server using Qonversion's Backend + * + * @param productId Qonversion product identifier for purchase + * @param oldProductId Qonversion product identifier from which the upgrade/downgrade will be initialized + * @param prorationMode proration mode + * @returns the promise with the user entitlements including updated ones. + * + * @see [Google Play Documentation](https://developer.android.com/google/play/billing/subscriptions#upgrade-downgrade) + * for more details. + * @see [Proration mode](https://developer.android.com/google/play/billing/subscriptions#proration) + * @see [Product Center](https://qonversion.io/docs/product-center) + */ + updatePurchase( + productId: string, + oldProductId: string, + prorationMode: ProrationMode | undefined + ): Promise | null>; + + /** + * Android only. Returns `null` if called on iOS. + * + * Update (upgrade/downgrade) subscription on Google Play Store and validate it through server-to-server using Qonversion's Backend + * + * @param product Qonversion product for purchase + * @param oldProductId Qonversion product identifier from which the upgrade/downgrade will be initialized + * @param prorationMode proration mode + * @returns the promise with the user entitlements including updated ones + * + * @see [Google Play Documentation](https://developer.android.com/google/play/billing/subscriptions#upgrade-downgrade) + * for more details. + * @see [Proration mode](https://developer.android.com/google/play/billing/subscriptions#proration) + * @see [Product Center](https://qonversion.io/docs/product-center) + */ + updatePurchaseWithProduct( + product: Product, + oldProductId: String, + prorationMode: ProrationMode | undefined + ): Promise | null>; + + /** + * Returns Qonversion products in association with Apple and Google Play Store Products. + * + * @returns the promise with Qonversion products + * + * @see [Product Center](https://qonversion.io/docs/product-center) + */ + products(): Promise>; + + /** + * Return Qonversion Offerings Object. + * + * An offering is a group of products that you can offer to a user on a given paywall based on your business logic. + * For example, you can offer one set of products on a paywall immediately after onboarding and another + * set of products with discounts later on if a user has not converted. + * Offerings allow changing the products offered remotely without releasing app updates. + * + * @returns the promise with Qonversion offerings + * + * @see [Offerings](https://qonversion.io/docs/offerings) for more details + * @see [Product Center](https://qonversion.io/docs/product-center) for more details + */ + offerings(): Promise; + + /** + * You can check if a user is eligible for an introductory offer, including a free trial. + * You can show only a regular price for users who are not eligible for an introductory offer. + * + * @param ids products identifiers that must be checked + * @returns the promise with eligibility map + */ + checkTrialIntroEligibility(ids: string[]): Promise>; + + /** + * You need to call the checkEntitlements method to check if a user has the required entitlement. + * + * This method will check the user receipt and will return the current entitlements. + * + * @returns the promise with the entitlements + * + * If Apple or Google servers are not responding at the time of the request, Qonversion provides the latest + * entitlements' data from its database. + */ + checkEntitlements(): Promise>; + + /** + * Restores users purchases in your app, to maintain access to purchased content. + * Users sometimes need to restore purchased content, such as when they upgrade to a new phone. + * + * @returns the promise with the user entitlements + */ + restore(): Promise>; + + /** + * Android only. Does nothing if called on iOS. + * + * This method will send all purchases to the Qonversion backend. Call this every time when purchase is handled + * by your own implementation. + * + * **Warning!** + * + * This method works for Android only. + * It should only be called if you're using Qonversion SDK in observer mode. + * + * @see [Observer mode for Android SDK](https://documentation.qonversion.io/docs/observer-mode#android-sdk) + */ + syncPurchases(): void; + + /** + * Call this function to link a user to his unique ID in your system and share purchase data. + * + * @param userID unique user ID in your system + */ + identify(userID: string): void + + /** + * Call this function to unlink a user from his unique ID in your system and his purchase data. + */ + logout(): void; + + /** + * This method returns information about the current Qonversion user. + */ + userInfo(): Promise; + + /** + * Sends your attribution {@link data} to the {@link provider}. + * + * @param data an object containing your attribution data + * @param provider the provider to which the data will be sent + */ + attribution(data: Object, provider: AttributionProvider): void; + + /** + * Sets Qonversion reserved user {@link property}, like email or one-signal id + * + * User properties are attributes you can set on a user level. + * You can send user properties to third party platforms as well as use them in Qonversion for customer segmentation + * and analytics. + * + * @param property defined enum key that will be transformed to string. + * @param value property value. + * + * @see [documentation](https://documentation.qonversion.io/docs/user-properties) + */ + setProperty(property: UserProperty, value: string): void; + + /** + * Adds custom user {@link property}. + * + * User properties are attributes you can set on a user level. + * You can send user properties to third party platforms as well as use them in Qonversion for customer segmentation + * and analytics. + * + * @param property custom user property key. + * @param value property value. + * + * @see [documentation](https://documentation.qonversion.io/docs/user-properties) + */ + setUserProperty(property: string, value: string): void; + + /** + * Provide a listener to be notified about asynchronous user entitlements updates. + * + * Make sure you provide this listener for being up-to-date with the user entitlements. + * Else you can lose some important updates. Also, please, consider that this listener + * should live for the whole lifetime of the application. + * + * You may set entitlements listener both *after* Qonversion SDK initializing + * with {@link QonversionApi.setEntitlementsUpdateListener} and *while* Qonversion initializing + * with {@link Qonversion.initialize}. + * + * @param listener listener to be called when entitlements update + */ + setEntitlementsUpdateListener(listener: EntitlementsUpdateListener): void; + + /** + * iOS only. Does nothing if called on Android. + * + * On iOS 14.5+, after requesting the app tracking permission using ATT, you need to notify Qonversion if tracking + * is allowed and IDFA is available. + */ + setAdvertisingID(): void; + + /** + * iOS only. Does nothing if called on Android. + * + * Enable attribution collection from Apple Search Ads. False by default. + */ + setAppleSearchAdsAttributionEnabled(enabled: boolean): void; + + /** + * iOS only. Does nothing if called on Android. + * + * Set the delegate to handle promo purchases. + * The delegate is called when a promo purchase from the App Store happens. + * @param delegate delegate to be called when event happens. + */ + setPromoPurchasesDelegate(delegate: PromoPurchasesListener): void; + + /** + * iOS only. Does nothing if called on Android. + * + * On iOS 14.0+ shows up a sheet for users to redeem App Store offer codes. + */ + presentCodeRedemptionSheet(): void; +} + +export default QonversionApi; diff --git a/src/QonversionConfig.ts b/src/QonversionConfig.ts new file mode 100644 index 0000000..1b488ff --- /dev/null +++ b/src/QonversionConfig.ts @@ -0,0 +1,26 @@ +import {EntitlementsCacheLifetime, Environment, LaunchMode} from './dto/enums'; +import {EntitlementsUpdateListener} from './dto/EntitlementsUpdateListener'; + +class QonversionConfig { + readonly projectKey: string; + readonly launchMode: LaunchMode; + readonly environment: Environment; + readonly entitlementsCacheLifetime: EntitlementsCacheLifetime; + readonly entitlementsUpdateListener: EntitlementsUpdateListener | undefined; + + constructor( + projectKey: string, + launchMode: LaunchMode, + environment: Environment = Environment.PRODUCTION, + entitlementsCacheLifetime: EntitlementsCacheLifetime = EntitlementsCacheLifetime.MONTH, + entitlementsUpdateListener: EntitlementsUpdateListener | undefined = undefined + ) { + this.projectKey = projectKey; + this.launchMode = launchMode; + this.environment = environment; + this.entitlementsCacheLifetime = entitlementsCacheLifetime; + this.entitlementsUpdateListener = entitlementsUpdateListener; + } +} + +export default QonversionConfig; diff --git a/src/QonversionConfigBuilder.ts b/src/QonversionConfigBuilder.ts new file mode 100644 index 0000000..cc553e2 --- /dev/null +++ b/src/QonversionConfigBuilder.ts @@ -0,0 +1,74 @@ +import {EntitlementsCacheLifetime, Environment, LaunchMode} from './dto/enums'; +import {EntitlementsUpdateListener} from './dto/EntitlementsUpdateListener'; +import QonversionConfig from './QonversionConfig'; + +class QonversionConfigBuilder { + private readonly projectKey: string; + private readonly launchMode: LaunchMode; + + constructor(projectKey: string, launchMode: LaunchMode) { + this.projectKey = projectKey; + this.launchMode = launchMode; + } + + private environment: Environment = Environment.PRODUCTION; + private entitlementsCacheLifetime: EntitlementsCacheLifetime = EntitlementsCacheLifetime.MONTH; + private entitlementsUpdateListener: EntitlementsUpdateListener | undefined = undefined; + + /** + * Set current application {@link Environment}. Used to distinguish sandbox and production users. + * + * @param environment current environment. + * @return builder instance for chain calls. + */ + setEnvironment(environment: Environment): QonversionConfigBuilder { + this.environment = environment; + return this; + } + + /** + * Entitlements cache is used when there are problems with the Qonversion API + * or internet connection. If so, Qonversion will return the last successfully loaded + * entitlements. The current method allows you to configure how long that cache may be used. + * The default value is {@link EntitlementsCacheLifetime.MONTH}. + * + * @param lifetime desired entitlements cache lifetime duration + * @return builder instance for chain calls. + */ + setEntitlementsCacheLifetime(lifetime: EntitlementsCacheLifetime): QonversionConfigBuilder { + this.entitlementsCacheLifetime = lifetime; + return this; + } + + /** + * Provide a listener to be notified about asynchronous user entitlements updates. + * + * Make sure you provide this listener for being up-to-date with the user entitlements. + * Else you can lose some important updates. Also, please, consider that this listener + * should live for the whole lifetime of the application. + * + * @param entitlementsUpdateListener listener to be called when entitlements update. + * @return builder instance for chain calls. + */ + setEntitlementsUpdateListener(entitlementsUpdateListener: EntitlementsUpdateListener): QonversionConfigBuilder { + this.entitlementsUpdateListener = entitlementsUpdateListener; + return this; + } + + /** + * Generate {@link QonversionConfig} instance with all the provided configurations. + * + * @return the complete {@link QonversionConfig} instance. + */ + build(): QonversionConfig { + return new QonversionConfig( + this.projectKey, + this.launchMode, + this.environment, + this.entitlementsCacheLifetime, + this.entitlementsUpdateListener + ) + } +} + +export default QonversionConfigBuilder; diff --git a/src/classes/ExperimentGroup.ts b/src/classes/ExperimentGroup.ts deleted file mode 100644 index a8c9d8a..0000000 --- a/src/classes/ExperimentGroup.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ExperimentGroupType } from "../enums"; - -class ExperimentGroup { - type: ExperimentGroupType; - - constructor(type: ExperimentGroupType) { - this.type = type; - } -} - -export default ExperimentGroup; diff --git a/src/classes/ExperimentInfo.ts b/src/classes/ExperimentInfo.ts deleted file mode 100644 index deaf54b..0000000 --- a/src/classes/ExperimentInfo.ts +++ /dev/null @@ -1,13 +0,0 @@ -import ExperimentGroup from "./ExperimentGroup"; - -class ExperimentInfo { - identifier: string; - group: ExperimentGroup; - - constructor(identifier: string, group: ExperimentGroup) { - this.identifier = identifier; - this.group = group; - } -} - -export default ExperimentInfo; diff --git a/src/classes/LaunchResult.ts b/src/classes/LaunchResult.ts deleted file mode 100644 index 0e5d02f..0000000 --- a/src/classes/LaunchResult.ts +++ /dev/null @@ -1,26 +0,0 @@ -import Permission from "./Permission"; -import Product from "./Product"; - -class LaunchResult { - uid: string; - timestamp: number; - products: Map; - permissions: Map; - userProducts: Map; - - constructor( - uid: string, - timestamp: number, - products: Map, - permissions: Map, - userProducts: Map - ) { - this.uid = uid; - this.timestamp = timestamp; - this.products = products; - this.permissions = permissions; - this.userProducts = userProducts; - } -} - -export default LaunchResult; diff --git a/src/classes/Permission.ts b/src/classes/Permission.ts deleted file mode 100644 index d79efec..0000000 --- a/src/classes/Permission.ts +++ /dev/null @@ -1,31 +0,0 @@ -import {PermissionSource, RenewState} from "../enums"; - -class Permission { - permissionID: string; - productID: string; - isActive: boolean; - renewState: RenewState; - source: PermissionSource; - startedDate: number; - expirationDate?: number; - - constructor( - permissionID: string, - productID: string, - isActive: boolean, - renewState: RenewState, - source: PermissionSource, - startedDate: number, - expirationDate: number | undefined, - ) { - this.permissionID = permissionID; - this.productID = productID; - this.isActive = isActive; - this.renewState = renewState; - this.source = source; - this.startedDate = startedDate; - this.expirationDate = expirationDate; - } -} - -export default Permission; diff --git a/src/classes/Qonversion.ts b/src/classes/Qonversion.ts deleted file mode 100644 index 66c90cb..0000000 --- a/src/classes/Qonversion.ts +++ /dev/null @@ -1,518 +0,0 @@ -import {NativeEventEmitter, NativeModules} from "react-native"; -import {PermissionsCacheLifetime, Property, ProrationMode, Provider} from "../enums"; -import ExperimentInfo from "./ExperimentInfo"; -import IntroEligibility from "./IntroEligibility"; -import LaunchResult from "./LaunchResult"; -import Mapper from "./Mapper"; -import Offerings from "./Offerings"; -import Permission from "./Permission"; -import Product from "./Product"; -import { - convertPropertyToNativeKey, - convertProviderToNativeKey, - DefinedNativeErrorCodes, - isAndroid, - isIos -} from "../utils"; -import {UpdatedPurchasesDelegate} from './UpdatedPurchasesDelegate'; -import {PromoPurchasesDelegate} from './PromoPurchasesDelegate'; - -const {RNQonversion} = NativeModules; - -const sdkVersion = "3.6.2"; - -const EVENT_PERMISSIONS_UPDATED = "permissions_updated"; -const EVENT_PROMO_PURCHASE_RECEIVED = "promo_purchase_received"; - -export default class Qonversion { - /** - * Initializes Qonversion SDK with the given API ${@link key}. - * You can get one in your account on https://dash.qonversion.io. - * - * @param key project key to setup the SDK. - * @param observerMode set true if you are using observer mode only. - * @returns the promise with the launch result. - * - * @see [Observer mode](https://documentation.qonversion.io/docs/how-qonversion-works) - * @see [Installing the Android SDK](https://qonversion.io/docs/google) - */ - static async launchWithKey( - key: string, - observerMode: boolean = false - ): Promise { - RNQonversion.storeSDKInfo("rn", sdkVersion); - const response = await RNQonversion.launch(key, observerMode); - const launchResult = Mapper.convertLaunchResult(response); - - return launchResult; - } - - /** - * Call this function to link a user to his unique ID in your system and share purchase data. - * - * @param userID unique user ID in your system. - */ - static identify(userID: string) { - RNQonversion.identify(userID); - } - - /** - * Call this function to unlink a user from his unique ID in your system and his purchase data. - */ - static logout() { - RNQonversion.logout(); - } - - /** - * Sets user property for pre-defined case {@link property}. - * - * User properties are attributes you can set on a user level. - * You can send user properties to third party platforms as well as use them in Qonversion for customer segmentation - * and analytics. - * - * @param property defined enum key that will be transformed to string. - * @param value property value. - * - * @see [documentation](https://documentation.qonversion.io/docs/user-properties) - */ - static setProperty(property: Property, value: string) { - const key = convertPropertyToNativeKey(property) - - if (key) { - RNQonversion.setDefinedProperty(key, value); - } - } - - /** - * Adds custom user {@link property}. - * - * User properties are attributes you can set on a user level. - * You can send user properties to third party platforms as well as use them in Qonversion for customer segmentation - * and analytics. - * - * @param property custom user property key. - * @param value property value. - * - * @see [documentation](https://documentation.qonversion.io/docs/user-properties) - */ - static setUserProperty(property: string, value: string) { - RNQonversion.setCustomProperty(property, value); - } - - /** - * Qonversion SDK provides an asynchronous method to set your side User ID that can be used to match users in - * third-party integrations. - * - * @param userId your database user ID. - * - * @see [documentation](https://documentation.qonversion.io/docs/user-identifiers) - * - * @deprecated Will be removed in a future major release. Use {@link setProperty} with {@link Property.CUSTOM_USER_ID} - * instead - */ - static setUserId(userId: string) { - this.setProperty(Property.CUSTOM_USER_ID, userId); - } - - /** - * Sends your attribution {@link data} to the {@link provider}. - * - * @param data an object containing your attribution data. - * @param provider the provider to which the data will be sent. - */ - static addAttributionData(data: Object, provider: Provider) { - const key = convertProviderToNativeKey(provider); - - if (key) { - RNQonversion.addAttributionData(data, key); - } - } - - /** - * You need to call the checkPermissions method at the start of your app to check if a user has the required - * permission. - * - * This method will check the user receipt and will return the current permissions. - * - * @returns the promise with the permissions. - * - * If Apple or Google servers are not responding at the time of the request, Qonversion provides the latest - * permissions data from its database. - */ - static async checkPermissions(): Promise> { - const permissions = await RNQonversion.checkPermissions(); - const mappedPermissions: Map< - string, - Permission - > = Mapper.convertPermissions(permissions); - - return mappedPermissions; - } - - /** - * Starts a process of purchasing a product with the specified identifier. - * - * @throws exception in case of error in purchase flow. - * @param productId Qonversion product identifier for purchase. - * @returns the promise with the user permissions including the ones obtained by the purchase. - */ - static async purchase(productId: string): Promise> { - return this.purchaseProxy(productId); - } - - /** - * Starts a process of purchasing product with Qonversion's {@link Product} object. - * - * @param product - Qonversion's {@link Product} object - * @returns { Promise> } A map of available permissions - */ - static async purchaseProduct(product: Product): Promise> { - return this.purchaseProxy(product.qonversionID, product.offeringId); - } - - private static async purchaseProxy(productId: string, offeringId: string | null = null): Promise> { - try { - const purchasePromise = !!offeringId ? - RNQonversion.purchaseProduct(productId, offeringId) - : - RNQonversion.purchase(productId); - - const permissions = await purchasePromise; - - // noinspection UnnecessaryLocalVariableJS - const mappedPermissions = Mapper.convertPermissions(permissions); - - return mappedPermissions; - } catch (e) { - e.userCanceled = e.code === DefinedNativeErrorCodes.PURCHASE_CANCELLED_BY_USER; - throw e; - } - } - - /** - * Android only. Returns `null` if called on iOS. - * - * Upgrading, downgrading, or changing a subscription on Google Play Store requires calling updatePurchase() function. - * - * @param productId Qonversion product identifier for purchase. - * @param oldProductId Qonversion product identifier from which the upgrade/downgrade will be initialized. - * @param prorationMode proration mode. - * @returns the promise with the user permissions including updated ones. - * - * @see [Google Play Documentation](https://developer.android.com/google/play/billing/subscriptions#upgrade-downgrade) - * for more details. - * @see [Proration mode](https://developer.android.com/google/play/billing/subscriptions#proration) - * @see [Product Center](https://qonversion.io/docs/product-center) - */ - static async updatePurchase( - productId: string, - oldProductId: string, - prorationMode: ProrationMode | null = null - ): Promise | null> { - if (!isAndroid()) { - return null; - } - - try { - let permissions; - if (prorationMode == null) { - permissions = await RNQonversion.updatePurchase(productId, oldProductId); - } else { - permissions = await RNQonversion.updatePurchaseWithProrationMode( - productId, - oldProductId, - prorationMode - ); - } - - // noinspection UnnecessaryLocalVariableJS - const mappedPermissions: Map = Mapper.convertPermissions(permissions); - - return mappedPermissions; - } catch (e) { - e.userCanceled = e.code === DefinedNativeErrorCodes.PURCHASE_CANCELLED_BY_USER; - throw e; - } - } - - /** - * Android only. Returns `null` if called on iOS. - * - * Upgrading, downgrading, or changing a subscription on Google Play Store requires calling updatePurchase() function. - * - * @param product Qonversion product for purchase. - * @param oldProductId Qonversion product identifier from which the upgrade/downgrade will be initialized. - * @param prorationMode proration mode. - * @returns the promise with the user permissions including updated ones. - * - * @see [Google Play Documentation](https://developer.android.com/google/play/billing/subscriptions#upgrade-downgrade) - * for more details. - * @see [Proration mode](https://developer.android.com/google/play/billing/subscriptions#proration) - * @see [Product Center](https://qonversion.io/docs/product-center) - */ - static async updatePurchaseWithProduct( - product: Product, - oldProductId: String, - prorationMode: ProrationMode | null = null - ): Promise | null> { - if (!isAndroid()) { - return null; - } - - try { - let permissions; - if (prorationMode == null) { - permissions = await RNQonversion.updateProductWithId(product.qonversionID, product.offeringId, oldProductId); - } else { - permissions = await RNQonversion.updateProductWithIdAndProrationMode( - product.qonversionID, - product.offeringId, - oldProductId, - prorationMode); - } - - // noinspection UnnecessaryLocalVariableJS - const mappedPermissions: Map = Mapper.convertPermissions(permissions); - - return mappedPermissions; - } catch (e) { - e.userCanceled = e.code === DefinedNativeErrorCodes.PURCHASE_CANCELLED_BY_USER; - throw e; - } - } - - /** - * Returns Qonversion products in association with Apple and Google Play Store Products. - * - * @returns the promise with Qonversion products. - * - * @see [Product Center](https://qonversion.io/docs/product-center) - */ - static async products(): Promise> { - let products = await RNQonversion.products(); - const mappedProducts: Map = Mapper.convertProducts( - products - ); - - return mappedProducts; - } - - /** - * Return Qonversion Offerings Object. - * - * An offering is a group of products that you can offer to a user on a given paywall based on your business logic. - * For example, you can offer one set of products on a paywall immediately after onboarding and another - * set of products with discounts later on if a user has not converted. - * Offerings allow changing the products offered remotely without releasing app updates. - * - * @returns the promise with Qonversion offerings. - * - * @see [Offerings](https://qonversion.io/docs/offerings) for more details. - * @see [Product Center](https://qonversion.io/docs/product-center) for more details. - */ - static async offerings(): Promise { - let offerings = await RNQonversion.offerings(); - const mappedOfferings = Mapper.convertOfferings(offerings); - - return mappedOfferings; - } - - /** - * Restoring purchases restores users purchases in your app, to maintain access to purchased content. - * Users sometimes need to restore purchased content, such as when they upgrade to a new phone. - * - * @returns the promise with the user permissions. - */ - static async restore(): Promise> { - const permissions = await RNQonversion.restore(); - - const mappedPermissions: Map< - string, - Permission - > = Mapper.convertPermissions(permissions); - - return mappedPermissions; - } - - /** - * You can check if a user is eligible for an introductory offer, including a free trial. - * You can show only a regular price for users who are not eligible for an introductory offer. - * - * @param ids products identifiers that must be checked. - * @returns the promise with eligibility map. - */ - static async checkTrialIntroEligibilityForProductIds( - ids: string[] - ): Promise> { - const eligibilityInfo = await RNQonversion.checkTrialIntroEligibilityForProductIds(ids); - - const mappedEligibility: Map< - string, - IntroEligibility - > = Mapper.convertEligibility(eligibilityInfo); - - return mappedEligibility; - } - - static async experiments(): Promise> { - const experiments = await RNQonversion.experiments(); - - const mappedExperiments: Map< - string, - ExperimentInfo - > = Mapper.convertExperimentInfo(experiments); - - return mappedExperiments; - } - - /** - * This method will send all purchases to the Qonversion backend. Call this every time when purchase is handled - * by your own implementation. - * - * **Warning!** - * - * This method works for Android only. - * It should only be called if you're using Qonversion SDK in observer mode. - * - * @see [Observer mode for Android SDK](https://documentation.qonversion.io/docs/observer-mode#android-sdk) - */ - static syncPurchases() { - if (!isAndroid()) { - return; - } - - RNQonversion.syncPurchases(); - } - - /** - * You can set the flag to distinguish sandbox and production users. - * To see the sandbox users turn on the Viewing test Data toggle on Qonversion Dashboard - */ - static setDebugMode() { - RNQonversion.setDebugMode(); - } - - /** - * Call this function to reset user ID and generate new anonymous user ID. - * Call this function before Qonversion.launchWithKey() - * - * @deprecated This function was used in debug mode only. You can reinstall the app if you need to reset the user ID. - */ - static resetUser() {} - - /** - * iOS only. Returns `null` if called on Android. - * On iOS 14.5+, after requesting the app tracking permission using ATT, you need to notify Qonversion if tracking - * is allowed and IDFA is available. - */ - static setAdvertisingID() { - if (isIos()) { - RNQonversion.setAdvertisingID(); - } - } - - /** - * Enable attribution collection from Apple Search Ads. False by default. - */ - static setAppleSearchAdsAttributionEnabled(enabled: boolean) { - if (isIos()) { - RNQonversion.setAppleSearchAdsAttributionEnabled(enabled); - } - } - - /** - * Set push token to Qonversion to enable Qonversion push notifications - * @param token Firebase device token on Android. APNs device token on iOS. - */ - static setNotificationsToken(token: string) { - RNQonversion.setNotificationsToken(token); - } - - /** - * Call to handle push notifications sent by Qonversion Automation. - * @param notificationData notification payload data - * @return true when a push notification was received from Qonversion. Otherwise, returns false, so you need to handle a notification yourself - * @see [Firebase RemoteMessage data](https://pub.dev/documentation/firebase_messaging_platform_interface/latest/firebase_messaging_platform_interface/RemoteMessage/data.html) - * @see [APNs notification data](https://developer.apple.com/documentation/usernotifications/unnotificationcontent/1649869-userinfo) - */ - static async handleNotification(notificationData: Map): Promise { - try { - return await RNQonversion.handleNotification(notificationData); - } catch (e) { - return false; - } - } - - /** - * Get parsed custom payload, which you added to the notification in the dashboard - * @param notificationData notification payload data - * @return a map with custom payload from the notification or null if it's not provided. - */ - static async getNotificationCustomPayload(notificationData: Map): Promise | null> { - try { - return await RNQonversion.getNotificationCustomPayload(notificationData); - } catch (e) { - return null; - } - } - - /** - * Set the delegate to handle pending purchases. - * The delegate is called when the deferred transaction status updates. - * For example, to handle purchases made using slow credit card or SCA flow purchases. - * @param delegate delegate to be called when event happens. - */ - static setUpdatedPurchasesDelegate(delegate: UpdatedPurchasesDelegate) { - const eventEmitter = new NativeEventEmitter(RNQonversion); - eventEmitter.removeAllListeners(EVENT_PERMISSIONS_UPDATED); - eventEmitter.addListener(EVENT_PERMISSIONS_UPDATED, payload => { - const permissions = Mapper.convertPermissions(payload); - delegate.onPermissionsUpdated(permissions); - }); - } - - /** - * iOS only. Does nothing if called on Android. - * Set the delegate to handle promo purchases. - * The delegate is called when a promo purchase from the App Store happens. - * @param delegate delegate to be called when event happens. - */ - static setPromoPurchasesDelegate(delegate: PromoPurchasesDelegate) { - if (!isIos()) { - return; - } - - const eventEmitter = new NativeEventEmitter(RNQonversion); - eventEmitter.removeAllListeners(EVENT_PROMO_PURCHASE_RECEIVED); - eventEmitter.addListener(EVENT_PROMO_PURCHASE_RECEIVED, productId => { - const promoPurchaseExecutor = async () => { - const permissions = await RNQonversion.promoPurchase(productId); - const mappedPermissions: Map = Mapper.convertPermissions(permissions); - return mappedPermissions; - }; - delegate.onPromoPurchaseReceived(productId, promoPurchaseExecutor); - }); - } - - /** - * iOS only. - * On iOS 14.0+ shows up a sheet for users to redeem AppStore offer codes. - */ - static presentCodeRedemptionSheet() { - if (isIos()) { - RNQonversion.presentCodeRedemptionSheet(); - } - } - - /** - * Permissions cache is used when there are problems with the Qonversion API - * or internet connection. If so, Qonversion will return the last successfully loaded - * permissions. The current method allows you to configure how long that cache may be used. - * The default value is {@link PermissionsCacheLifetime.MONTH}. - * - * @param lifetime desired permissions cache lifetime duration - */ - static setPermissionsCacheLifetime(lifetime: PermissionsCacheLifetime) { - RNQonversion.setPermissionsCacheLifetime(lifetime); - } -} diff --git a/src/classes/UpdatedPurchasesDelegate.ts b/src/classes/UpdatedPurchasesDelegate.ts deleted file mode 100644 index 42b04cf..0000000 --- a/src/classes/UpdatedPurchasesDelegate.ts +++ /dev/null @@ -1,10 +0,0 @@ -import Permission from './Permission'; - -export interface UpdatedPurchasesDelegate { - - /** - * Called when the deferred transaction status updates. - * @param permissions all the client's permissions after update. - */ - onPermissionsUpdated(permissions: Map): void; -} diff --git a/src/classes/ActionResult.ts b/src/dto/ActionResult.ts similarity index 91% rename from src/classes/ActionResult.ts rename to src/dto/ActionResult.ts index df91c56..b6ae8aa 100644 --- a/src/classes/ActionResult.ts +++ b/src/dto/ActionResult.ts @@ -1,4 +1,4 @@ -import {ActionResultType} from "../enums"; +import {ActionResultType} from "./enums"; import QonversionError from "./QonversionError"; class ActionResult { diff --git a/src/classes/AutomationsDelegate.ts b/src/dto/AutomationsDelegate.ts similarity index 89% rename from src/classes/AutomationsDelegate.ts rename to src/dto/AutomationsDelegate.ts index 797f5b2..b713935 100644 --- a/src/classes/AutomationsDelegate.ts +++ b/src/dto/AutomationsDelegate.ts @@ -24,7 +24,7 @@ export interface AutomationsDelegate { * Called when Automations flow finishes executing an action. * @param actionResult executed action. * For instance, if the user made a purchase then action.type = ActionResultType.purchase. - * You can use the Qonversion.checkPermissions() method to get available permissions. + * You can use the {@link QonversionApi.checkEntitlements} method to get available permissions. */ automationsDidFinishExecuting(actionResult: ActionResult): void; diff --git a/src/classes/AutomationsEvent.ts b/src/dto/AutomationsEvent.ts similarity index 82% rename from src/classes/AutomationsEvent.ts rename to src/dto/AutomationsEvent.ts index 1d6809b..5764782 100644 --- a/src/classes/AutomationsEvent.ts +++ b/src/dto/AutomationsEvent.ts @@ -1,4 +1,4 @@ -import {AutomationsEventType} from "../enums"; +import {AutomationsEventType} from "./enums"; class AutomationsEvent { diff --git a/src/dto/Entitlement.ts b/src/dto/Entitlement.ts new file mode 100644 index 0000000..8861741 --- /dev/null +++ b/src/dto/Entitlement.ts @@ -0,0 +1,31 @@ +import {EntitlementSource, EntitlementRenewState} from "./enums"; + +class Entitlement { + id: string; + productId: string; + isActive: boolean; + renewState: EntitlementRenewState; + source: EntitlementSource; + startedDate: number; + expirationDate?: number; + + constructor( + id: string, + productId: string, + isActive: boolean, + renewState: EntitlementRenewState, + source: EntitlementSource, + startedDate: number, + expirationDate: number | undefined, + ) { + this.id = id; + this.productId = productId; + this.isActive = isActive; + this.renewState = renewState; + this.source = source; + this.startedDate = startedDate; + this.expirationDate = expirationDate; + } +} + +export default Entitlement; diff --git a/src/dto/EntitlementsUpdateListener.ts b/src/dto/EntitlementsUpdateListener.ts new file mode 100644 index 0000000..9345604 --- /dev/null +++ b/src/dto/EntitlementsUpdateListener.ts @@ -0,0 +1,10 @@ +import Entitlement from './Entitlement'; + +export interface EntitlementsUpdateListener { + + /** + * Called when the deferred transaction status updates. + * @param entitlements all the client's entitlements after update. + */ + onEntitlementsUpdated(entitlements: Map): void; +} diff --git a/src/classes/IntroEligibility.ts b/src/dto/IntroEligibility.ts similarity index 78% rename from src/classes/IntroEligibility.ts rename to src/dto/IntroEligibility.ts index 0868f92..3809a31 100644 --- a/src/classes/IntroEligibility.ts +++ b/src/dto/IntroEligibility.ts @@ -1,4 +1,4 @@ -import { IntroEligibilityStatus } from "../enums"; +import { IntroEligibilityStatus } from "./enums"; class IntroEligibility { status?: IntroEligibilityStatus; diff --git a/src/classes/Offering.ts b/src/dto/Offering.ts similarity index 91% rename from src/classes/Offering.ts rename to src/dto/Offering.ts index 62eb367..57a7afd 100644 --- a/src/classes/Offering.ts +++ b/src/dto/Offering.ts @@ -1,4 +1,4 @@ -import { OfferingTags } from "../enums"; +import { OfferingTags } from "./enums"; import Product from "./Product"; class Offering { diff --git a/src/classes/Offerings.ts b/src/dto/Offerings.ts similarity index 100% rename from src/classes/Offerings.ts rename to src/dto/Offerings.ts diff --git a/src/classes/Product.ts b/src/dto/Product.ts similarity index 99% rename from src/classes/Product.ts rename to src/dto/Product.ts index b67b182..31ebe92 100644 --- a/src/classes/Product.ts +++ b/src/dto/Product.ts @@ -1,4 +1,4 @@ -import { ProductDurations, ProductTypes, TrialDurations } from "../enums"; +import { ProductDurations, ProductTypes, TrialDurations } from "./enums"; import SKProduct from "./storeProducts/SKProduct"; import SkuDetails from "./storeProducts/SkuDetails"; diff --git a/src/classes/PromoPurchasesDelegate.ts b/src/dto/PromoPurchasesListener.ts similarity index 64% rename from src/classes/PromoPurchasesDelegate.ts rename to src/dto/PromoPurchasesListener.ts index 5567a38..d6ca98b 100644 --- a/src/classes/PromoPurchasesDelegate.ts +++ b/src/dto/PromoPurchasesListener.ts @@ -1,13 +1,13 @@ -import Permission from './Permission'; +import Entitlement from './Entitlement'; -export interface PromoPurchasesDelegate { +export interface PromoPurchasesListener { /** - * Delegate fires each time a promo purchase from the App Store happens. + * Fired each time a promo purchase from the App Store happens. * Call {@param promoPurchaseExecutor} in case of your app is ready to start promo purchase. * Or cache that executor and call later when you need. * @param productId StoreKit product identifier. * @param promoPurchaseExecutor a function that will start a promo purchase flow. */ - onPromoPurchaseReceived(productId: string, promoPurchaseExecutor: () => Promise>): void; + onPromoPurchaseReceived(productId: string, promoPurchaseExecutor: () => Promise>): void; } diff --git a/src/classes/QonversionError.ts b/src/dto/QonversionError.ts similarity index 100% rename from src/classes/QonversionError.ts rename to src/dto/QonversionError.ts diff --git a/src/dto/User.ts b/src/dto/User.ts new file mode 100644 index 0000000..a3b1adb --- /dev/null +++ b/src/dto/User.ts @@ -0,0 +1,11 @@ +class User { + qonversionId: string; + identityId?: string | null; + + constructor(qonversionId: string, identityId?: string | null) { + this.qonversionId = qonversionId; + this.identityId = identityId; + } +} + +export default User; diff --git a/src/enums.ts b/src/dto/enums.ts similarity index 75% rename from src/enums.ts rename to src/dto/enums.ts index 995f557..68a9d38 100644 --- a/src/enums.ts +++ b/src/dto/enums.ts @@ -1,3 +1,13 @@ +export enum LaunchMode { + ANALYTICS = 'Analytics', + SUBSCRIPTION_MANAGEMENT = 'SubscriptionManagement' +} + +export enum Environment { + SANDBOX = "Sandbox", + PRODUCTION = "Production" +} + export const ProductType = { "0": "TRIAL", "1": "DIRECT_SUBSCRIPTION", @@ -32,15 +42,15 @@ export const TrialDuration = { export type TrialDurations = typeof TrialDuration[keyof typeof TrialDuration]; -export enum RenewState { - NON_RENEWABLE = -1, - UNKNOWN = 0, - WILL_RENEW = 1, - CANCELED = 2, - BILLING_ISSUE = 3, +export enum EntitlementRenewState { + NON_RENEWABLE = 'non_renewable', + UNKNOWN = 'unknown', + WILL_RENEW = 'will_renew', + CANCELED = 'canceled', + BILLING_ISSUE = 'billing_issue', } -export enum PermissionSource { +export enum EntitlementSource { UNKNOWN = "Unknown", APP_STORE = "AppStore", PLAY_STORE = "PlayStore", @@ -48,26 +58,25 @@ export enum PermissionSource { MANUAL = "Manual", } -export enum Property { - EMAIL = 0, - NAME = 1, - APPS_FLYER_USER_ID = 2, - ADJUST_USER_ID = 3, - KOCHAVA_DEVICE_ID = 4, - CUSTOM_USER_ID = 5, - FACEBOOK_ATTRIBUTION = 6, // Android only - ADVERTISING_ID = 7, // iOS only - FIREBASE_APP_INSTANCE_ID = 8, - APP_SET_ID = 9, // Android only +export enum UserProperty { + EMAIL = "Email", + NAME = "Name", + KOCHAVA_DEVICE_ID = "KochavaDeviceId", + APPS_FLYER_USER_ID = "AppsFlyerUserId", + ADJUST_AD_ID = "AdjustAdId", + CUSTOM_USER_ID = "CustomUserId", + FACEBOOK_ATTRIBUTION = "FacebookAttribution", // Android only + FIREBASE_APP_INSTANCE_ID = "FirebaseAppInstanceId", + APP_SET_ID = "AppSetId", // Android only + ADVERTISING_ID = "AdvertisingId", // iOS only } -export enum Provider { - APPSFLYER = 0, - BRANCH = 1, - ADJUST = 2, - /** @deprecated Use {@link APPLE_SEARCH_ADS} instead */ APPLE = 3, - APPLE_SEARCH_ADS = 4, - APPLE_AD_SERVICES = 5, +export enum AttributionProvider { + APPSFLYER = "AppsFlyer", + BRANCH = "Branch", + ADJUST = "Adjust", + APPLE_SEARCH_ADS = "AppleSearchAds", // ios only + APPLE_AD_SERVICES = "AppleAdServices", // ios only } export enum ProrationMode { @@ -78,7 +87,7 @@ export enum ProrationMode { DEFERRED = 4, } -export enum PermissionsCacheLifetime { +export enum EntitlementsCacheLifetime { WEEK = "Week", TWO_WEEKS = "TwoWeeks", MONTH = "Month", @@ -124,11 +133,6 @@ export enum IntroEligibilityStatus { INELIGIBLE = "intro_or_trial_ineligible", } -export enum ExperimentGroupType { - GROUP_TYPE_A = 0, - GROUP_TYPE_B = 1, -} - export enum ActionResultType { UNKNOWN = "unknown", URL = "url", diff --git a/src/classes/storeProducts/SKProduct.ts b/src/dto/storeProducts/SKProduct.ts similarity index 100% rename from src/classes/storeProducts/SKProduct.ts rename to src/dto/storeProducts/SKProduct.ts diff --git a/src/classes/storeProducts/SKProductDiscount.ts b/src/dto/storeProducts/SKProductDiscount.ts similarity index 97% rename from src/classes/storeProducts/SKProductDiscount.ts rename to src/dto/storeProducts/SKProductDiscount.ts index ade138c..2d18454 100644 --- a/src/classes/storeProducts/SKProductDiscount.ts +++ b/src/dto/storeProducts/SKProductDiscount.ts @@ -1,7 +1,7 @@ import { SKProductDiscountPaymentModes, SKProductDiscountTypes, -} from "../../enums"; +} from "../enums"; import SKSubscriptionPeriod from "./SKSubscriptionPeriod"; class SKProductDiscount { diff --git a/src/classes/storeProducts/SKSubscriptionPeriod.ts b/src/dto/storeProducts/SKSubscriptionPeriod.ts similarity index 84% rename from src/classes/storeProducts/SKSubscriptionPeriod.ts rename to src/dto/storeProducts/SKSubscriptionPeriod.ts index b1f8d9d..015ad37 100644 --- a/src/classes/storeProducts/SKSubscriptionPeriod.ts +++ b/src/dto/storeProducts/SKSubscriptionPeriod.ts @@ -1,4 +1,4 @@ -import { SKPeriodUnits } from "../../enums"; +import { SKPeriodUnits } from "../enums"; class SKSubscriptionPeriod { numberOfUnits: number; diff --git a/src/classes/storeProducts/SkuDetails.ts b/src/dto/storeProducts/SkuDetails.ts similarity index 100% rename from src/classes/storeProducts/SkuDetails.ts rename to src/dto/storeProducts/SkuDetails.ts diff --git a/src/index.ts b/src/index.ts index e6898aa..0fdf2c4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,18 +1,24 @@ -export { default } from "./classes/Qonversion"; -export { default as Automations } from "./classes/Automations"; +export { default } from './Qonversion'; +export { default as QonversionApi } from './QonversionApi'; +export { default as QonversionConfig } from './QonversionConfig'; +export { default as QonversionConfigBuilder } from './QonversionConfigBuilder'; -export { default as LaunchResult } from "./classes/LaunchResult"; -export { default as Product } from "./classes/Product"; -export { default as Permission } from "./classes/Permission"; -export { default as Offerings } from "./classes/Offerings"; -export { default as Offering } from "./classes/Offering"; -export { default as IntroEligibility } from "./classes/IntroEligibility"; -export { default as ExperimentInfo } from "./classes/ExperimentInfo"; -export { default as ExperimentGroup } from "./classes/ExperimentGroup"; +export { default as Automations } from './Automations'; -export { default as SkuDetails } from "./classes/storeProducts/SkuDetails"; -export { default as SKProduct } from "./classes/storeProducts/SKProduct"; -export { default as SKSubscriptionPeriod } from "./classes/storeProducts/SKSubscriptionPeriod"; -export { default as SKProductDiscount } from "./classes/storeProducts/SKProductDiscount"; - -export * from "./enums"; +export { default as ActionResult } from './dto/ActionResult'; +export { AutomationsDelegate } from './dto/AutomationsDelegate'; +export { default as AutomationsEvent } from './dto/AutomationsEvent'; +export { EntitlementsUpdateListener } from './dto/EntitlementsUpdateListener'; +export * from './dto/enums'; +export { default as IntroEligibility } from './dto/IntroEligibility'; +export { default as Offering } from './dto/Offering'; +export { default as Offerings } from './dto/Offerings'; +export { default as Entitlement } from './dto/Entitlement'; +export { default as Product } from './dto/Product'; +export { PromoPurchasesListener } from './dto/PromoPurchasesListener'; +export { default as QonversionError } from './dto/QonversionError'; +export { default as User } from './dto/User'; +export { default as SkuDetails } from './dto/storeProducts/SkuDetails'; +export { default as SKProduct } from './dto/storeProducts/SKProduct'; +export { default as SKSubscriptionPeriod } from './dto/storeProducts/SKSubscriptionPeriod'; +export { default as SKProductDiscount } from './dto/storeProducts/SKProductDiscount'; diff --git a/src/classes/Automations.ts b/src/internal/AutomationsInternal.ts similarity index 67% rename from src/classes/Automations.ts rename to src/internal/AutomationsInternal.ts index bd32fb4..79e8140 100644 --- a/src/classes/Automations.ts +++ b/src/internal/AutomationsInternal.ts @@ -1,6 +1,8 @@ -import {AutomationsDelegate} from "./AutomationsDelegate"; +import {AutomationsDelegate} from "../dto/AutomationsDelegate"; import {NativeEventEmitter, NativeModules} from "react-native"; import Mapper from "./Mapper"; +import AutomationsApi from '../AutomationsApi'; + const {RNAutomations} = NativeModules; const EVENT_SCREEN_SHOWN = "automations_screen_shown"; @@ -9,15 +11,34 @@ const EVENT_ACTION_FAILED = "automations_action_failed"; const EVENT_ACTION_FINISHED = "automations_action_finished"; const EVENT_AUTOMATIONS_FINISHED = "automations_finished"; -export default class Automations { +export default class AutomationsInternal implements AutomationsApi { + + constructor() { + RNAutomations.initializeSdk(); + } + + setDelegate(delegate: AutomationsDelegate) { + AutomationsInternal.subscribe(delegate); + } + + setNotificationsToken(token: string) { + RNAutomations.setNotificationsToken(token); + } + + async handleNotification(notificationData: Map): Promise { + try { + return await RNAutomations.handleNotification(notificationData); + } catch (e) { + return false; + } + } - /** - * The Automations delegate is responsible for handling in-app screens and actions when push notification is received. - * Make sure the method is called before Qonversion.handleNotification. - * @param delegate the delegate to be notified about Automations events. - */ - static setDelegate(delegate: AutomationsDelegate) { - Automations.subscribe(delegate); + async getNotificationCustomPayload(notificationData: Map): Promise | null> { + try { + return await RNAutomations.getNotificationCustomPayload(notificationData); + } catch (e) { + return null; + } } private static subscribe(automationsDelegate: AutomationsDelegate) { diff --git a/src/classes/Mapper.ts b/src/internal/Mapper.ts similarity index 75% rename from src/classes/Mapper.ts rename to src/internal/Mapper.ts index 982a2f0..8f2ef56 100644 --- a/src/classes/Mapper.ts +++ b/src/internal/Mapper.ts @@ -1,44 +1,32 @@ import { - ActionResultType, AutomationsEventType, - ExperimentGroupType, + EntitlementSource, IntroEligibilityStatus, OfferingTag, - PermissionSource, ProductDuration, ProductDurations, ProductType, ProductTypes, - RenewState, + EntitlementRenewState, SKPeriodUnit, SKProductDiscountPaymentMode, SKProductDiscountType, TrialDuration, TrialDurations, -} from "../enums"; -import ExperimentGroup from "./ExperimentGroup"; -import ExperimentInfo from "./ExperimentInfo"; -import IntroEligibility from "./IntroEligibility"; -import LaunchResult from "./LaunchResult"; -import Offering from "./Offering"; -import Offerings from "./Offerings"; -import Permission from "./Permission"; -import Product from "./Product"; -import SKProduct from "./storeProducts/SKProduct"; -import SKProductDiscount from "./storeProducts/SKProductDiscount"; -import SKSubscriptionPeriod from "./storeProducts/SKSubscriptionPeriod"; -import SkuDetails from "./storeProducts/SkuDetails"; -import ActionResult from "./ActionResult"; -import QonversionError from "./QonversionError"; -import AutomationsEvent from "./AutomationsEvent"; - -type QLaunchResult = { - products: Record; - userProducts: Record; - permissions: Record; - uid: string; - timestamp: number; -}; +} from "../dto/enums"; +import IntroEligibility from "../dto/IntroEligibility"; +import Offering from "../dto/Offering"; +import Offerings from "../dto/Offerings"; +import Entitlement from "../dto/Entitlement"; +import Product from "../dto/Product"; +import SKProduct from "../dto/storeProducts/SKProduct"; +import SKProductDiscount from "../dto/storeProducts/SKProductDiscount"; +import SKSubscriptionPeriod from "../dto/storeProducts/SKSubscriptionPeriod"; +import SkuDetails from "../dto/storeProducts/SkuDetails"; +import ActionResult from "../dto/ActionResult"; +import QonversionError from "../dto/QonversionError"; +import AutomationsEvent from "../dto/AutomationsEvent"; +import User from '../dto/User'; type QProduct = { id: string; @@ -112,11 +100,11 @@ type QLocale = { localeIdentifier: string; }; -type QPermission = { +type QEntitlement = { id: string; - associatedProduct: string; + productId: string; active: boolean; - renewState: number; + renewState: string; source: string; startedTimestamp: number; expirationTimestamp: number; @@ -133,79 +121,54 @@ type QOffering = { products: Array; }; -type QActionResult = { - type: ActionResultType; - value: Map | undefined; - error: QError | undefined; -}; - -type QError = { - code: string; - domain?: string; // ios only - description: string; - additionalMessage: string; -}; - type QAutomationsEvent = { type: AutomationsEventType; timestamp: number; }; +type QUser = { + qonversionId: string; + identityId?: string | null; +}; + const skuDetailsPriceRatio = 1000000; class Mapper { - static convertLaunchResult(launchResult: QLaunchResult): LaunchResult { - const products: Map = this.convertProducts( - launchResult.products - ); - const permissions: Map = this.convertPermissions( - launchResult.permissions - ); - const userProducts: Map = this.convertProducts( - launchResult.userProducts - ); - return new LaunchResult( - launchResult.uid, - launchResult.timestamp, - products, - permissions, - userProducts - ); - } - - static convertPermissions( - permissions: Record - ): Map { + static convertEntitlements( + entitlements: Record + ): Map { let mappedPermissions = new Map(); - for (const [key, permission] of Object.entries(permissions)) { - let renewState: RenewState = RenewState.UNKNOWN; - - switch (permission.renewState) { - case -1: - renewState = RenewState.NON_RENEWABLE; + for (const [key, entitlement] of Object.entries(entitlements)) { + let renewState: EntitlementRenewState; + switch (entitlement.renewState) { + case EntitlementRenewState.NON_RENEWABLE: + renewState = EntitlementRenewState.NON_RENEWABLE; break; - case 1: - renewState = RenewState.WILL_RENEW; + case EntitlementRenewState.WILL_RENEW: + renewState = EntitlementRenewState.WILL_RENEW; break; - case 2: - renewState = RenewState.CANCELED; + case EntitlementRenewState.CANCELED: + renewState = EntitlementRenewState.CANCELED; break; - case 3: - renewState = RenewState.BILLING_ISSUE; + case EntitlementRenewState.BILLING_ISSUE: + renewState = EntitlementRenewState.BILLING_ISSUE; + break; + default: + renewState = EntitlementRenewState.UNKNOWN; break; } - const permissionSource = this.convertPermissionSource(permission.source); + const entitlementSource = this.convertEntitlementSource(entitlement.source); - const mappedPermission = new Permission( - permission.id, - permission.associatedProduct, - permission.active, + const mappedPermission = new Entitlement( + entitlement.id, + entitlement.productId, + entitlement.active, renewState, - permissionSource, - permission.startedTimestamp, - permission.expirationTimestamp + entitlementSource, + entitlement.startedTimestamp, + entitlement.expirationTimestamp ); mappedPermissions.set(key, mappedPermission); } @@ -213,21 +176,21 @@ class Mapper { return mappedPermissions; } - static convertPermissionSource(sourceKey: string): PermissionSource { + static convertEntitlementSource(sourceKey: string): EntitlementSource { switch (sourceKey) { case "Unknown": - return PermissionSource.UNKNOWN; + return EntitlementSource.UNKNOWN; case "AppStore": - return PermissionSource.APP_STORE; + return EntitlementSource.APP_STORE; case "PlayStore": - return PermissionSource.PLAY_STORE; + return EntitlementSource.PLAY_STORE; case "Stripe": - return PermissionSource.STRIPE; + return EntitlementSource.STRIPE; case "Manual": - return PermissionSource.MANUAL; + return EntitlementSource.MANUAL; } - return PermissionSource.UNKNOWN; + return EntitlementSource.UNKNOWN; } static convertProducts(products: Record): Map { @@ -464,25 +427,6 @@ class Mapper { } } - static convertExperimentInfo( - experimentInfo: Record - ): Map { - const mappedExperimentInfo = new Map(); - - for (const [key, value] of Object.entries(experimentInfo)) { - const groupType = - value.group.type === 1 - ? ExperimentGroupType.GROUP_TYPE_B - : ExperimentGroupType.GROUP_TYPE_A; - const group = new ExperimentGroup(groupType); - - const experiment = new ExperimentInfo(value.id, group); - mappedExperimentInfo.set(key, experiment); - } - - return mappedExperimentInfo; - } - static convertActionResult( payload: Record ): ActionResult { @@ -512,6 +456,10 @@ class Mapper { automationsEvent.timestamp ) } + + static convertUserInfo(user: QUser) { + return new User(user.qonversionId, user.identityId); + } } export default Mapper; diff --git a/src/internal/QonversionInternal.ts b/src/internal/QonversionInternal.ts new file mode 100644 index 0000000..71183f0 --- /dev/null +++ b/src/internal/QonversionInternal.ts @@ -0,0 +1,256 @@ +import {NativeEventEmitter, NativeModules} from "react-native"; +import {UserProperty, ProrationMode, AttributionProvider} from "../dto/enums"; +import IntroEligibility from "../dto/IntroEligibility"; +import Mapper from "./Mapper"; +import Offerings from "../dto/Offerings"; +import Entitlement from "../dto/Entitlement"; +import Product from "../dto/Product"; +import {DefinedNativeErrorCodes, isAndroid, isIos} from "./utils"; +import {EntitlementsUpdateListener} from '../dto/EntitlementsUpdateListener'; +import {PromoPurchasesListener} from '../dto/PromoPurchasesListener'; +import User from '../dto/User'; +import QonversionApi from '../QonversionApi'; +import QonversionConfig from '../QonversionConfig'; + +const {RNQonversion} = NativeModules; + +const sdkVersion = "3.6.2"; + +const EVENT_ENTITLEMENTS_UPDATED = "entitlements_updated"; +const EVENT_PROMO_PURCHASE_RECEIVED = "promo_purchase_received"; + +export default class QonversionInternal implements QonversionApi { + + constructor(qonversionConfig: QonversionConfig) { + RNQonversion.storeSDKInfo("rn", sdkVersion); + RNQonversion.initializeSdk( + qonversionConfig.projectKey, + qonversionConfig.launchMode, + qonversionConfig.environment, + qonversionConfig.entitlementsCacheLifetime + ) + + if (qonversionConfig.entitlementsUpdateListener) { + this.setEntitlementsUpdateListener(qonversionConfig.entitlementsUpdateListener) + } + } + + async purchase(productId: string): Promise> { + return QonversionInternal.purchaseProxy(productId); + } + + async purchaseProduct(product: Product): Promise> { + return QonversionInternal.purchaseProxy(product.qonversionID, product.offeringId); + } + + private static async purchaseProxy(productId: string, offeringId: string | null = null): Promise> { + try { + const purchasePromise = !!offeringId ? + RNQonversion.purchaseProduct(productId, offeringId) + : + RNQonversion.purchase(productId); + + const entitlements = await purchasePromise; + + // noinspection UnnecessaryLocalVariableJS + const mappedPermissions = Mapper.convertEntitlements(entitlements); + + return mappedPermissions; + } catch (e) { + e.userCanceled = e.code === DefinedNativeErrorCodes.PURCHASE_CANCELLED_BY_USER; + throw e; + } + } + + async updatePurchase( + productId: string, + oldProductId: string, + prorationMode: ProrationMode | undefined + ): Promise | null> { + if (!isAndroid()) { + return null; + } + + try { + let entitlements; + if (!prorationMode) { + entitlements = await RNQonversion.updatePurchase(productId, oldProductId); + } else { + entitlements = await RNQonversion.updatePurchaseWithProrationMode( + productId, + oldProductId, + prorationMode + ); + } + + // noinspection UnnecessaryLocalVariableJS + const mappedPermissions: Map = Mapper.convertEntitlements(entitlements); + + return mappedPermissions; + } catch (e) { + e.userCanceled = e.code === DefinedNativeErrorCodes.PURCHASE_CANCELLED_BY_USER; + throw e; + } + } + + async updatePurchaseWithProduct( + product: Product, + oldProductId: String, + prorationMode: ProrationMode | undefined + ): Promise | null> { + if (!isAndroid()) { + return null; + } + + try { + let entitlements; + if (!prorationMode) { + entitlements = await RNQonversion.updateProductWithId(product.qonversionID, product.offeringId, oldProductId); + } else { + entitlements = await RNQonversion.updateProductWithIdAndProrationMode( + product.qonversionID, + product.offeringId, + oldProductId, + prorationMode + ); + } + + // noinspection UnnecessaryLocalVariableJS + const mappedPermissions: Map = Mapper.convertEntitlements(entitlements); + + return mappedPermissions; + } catch (e) { + e.userCanceled = e.code === DefinedNativeErrorCodes.PURCHASE_CANCELLED_BY_USER; + throw e; + } + } + + async products(): Promise> { + let products = await RNQonversion.products(); + const mappedProducts: Map = Mapper.convertProducts( + products + ); + + return mappedProducts; + } + + async offerings(): Promise { + let offerings = await RNQonversion.offerings(); + const mappedOfferings = Mapper.convertOfferings(offerings); + + return mappedOfferings; + } + + async checkTrialIntroEligibility( + ids: string[] + ): Promise> { + const eligibilityInfo = await RNQonversion.checkTrialIntroEligibilityForProductIds(ids); + + const mappedEligibility: Map< + string, + IntroEligibility + > = Mapper.convertEligibility(eligibilityInfo); + + return mappedEligibility; + } + + async checkEntitlements(): Promise> { + const entitlements = await RNQonversion.checkEntitlements(); + const mappedPermissions: Map< + string, + Entitlement + > = Mapper.convertEntitlements(entitlements); + + return mappedPermissions; + } + + async restore(): Promise> { + const entitlements = await RNQonversion.restore(); + + const mappedPermissions: Map< + string, + Entitlement + > = Mapper.convertEntitlements(entitlements); + + return mappedPermissions; + } + + syncPurchases() { + if (!isAndroid()) { + return; + } + + RNQonversion.syncPurchases(); + } + + identify(userID: string) { + RNQonversion.identify(userID); + } + + logout() { + RNQonversion.logout(); + } + + async userInfo(): Promise { + const info = await RNQonversion.userInfo(); + const mappedUserInfo: User = Mapper.convertUserInfo(info); + + return mappedUserInfo; + } + + attribution(data: Object, provider: AttributionProvider) { + RNQonversion.addAttributionData(data, provider); + } + + setProperty(property: UserProperty, value: string) { + RNQonversion.setDefinedProperty(property, value); + } + + setUserProperty(property: string, value: string) { + RNQonversion.setCustomProperty(property, value); + } + + setAdvertisingID() { + if (isIos()) { + RNQonversion.setAdvertisingID(); + } + } + + setAppleSearchAdsAttributionEnabled(enabled: boolean) { + if (isIos()) { + RNQonversion.setAppleSearchAdsAttributionEnabled(enabled); + } + } + + setEntitlementsUpdateListener(listener: EntitlementsUpdateListener) { + const eventEmitter = new NativeEventEmitter(RNQonversion); + eventEmitter.removeAllListeners(EVENT_ENTITLEMENTS_UPDATED); + eventEmitter.addListener(EVENT_ENTITLEMENTS_UPDATED, payload => { + const entitlements = Mapper.convertEntitlements(payload); + listener.onEntitlementsUpdated(entitlements); + }); + } + + setPromoPurchasesDelegate(delegate: PromoPurchasesListener) { + if (!isIos()) { + return; + } + + const eventEmitter = new NativeEventEmitter(RNQonversion); + eventEmitter.removeAllListeners(EVENT_PROMO_PURCHASE_RECEIVED); + eventEmitter.addListener(EVENT_PROMO_PURCHASE_RECEIVED, productId => { + const promoPurchaseExecutor = async () => { + const entitlements = await RNQonversion.promoPurchase(productId); + const mappedPermissions: Map = Mapper.convertEntitlements(entitlements); + return mappedPermissions; + }; + delegate.onPromoPurchaseReceived(productId, promoPurchaseExecutor); + }); + } + + presentCodeRedemptionSheet() { + if (isIos()) { + RNQonversion.presentCodeRedemptionSheet(); + } + } +} diff --git a/src/internal/utils.ts b/src/internal/utils.ts new file mode 100644 index 0000000..16e6b19 --- /dev/null +++ b/src/internal/utils.ts @@ -0,0 +1,13 @@ +import {Platform} from "react-native"; + +export const isIos = (): boolean => { + return Platform.OS === "ios" +} + +export const isAndroid = (): boolean => { + return Platform.OS === "android" +} + +export enum DefinedNativeErrorCodes { + PURCHASE_CANCELLED_BY_USER = "PURCHASE_CANCELLED_BY_USER" +} diff --git a/src/utils.ts b/src/utils.ts deleted file mode 100644 index ce5aceb..0000000 --- a/src/utils.ts +++ /dev/null @@ -1,44 +0,0 @@ -import {Property, Provider} from "./enums"; -import {Platform} from "react-native"; - -export const isIos = (): boolean => { - return Platform.OS === "ios" -} - -export const isAndroid = (): boolean => { - return Platform.OS === "android" -} - -const propertyKeyMap = { - [Property.EMAIL]: "Email", - [Property.NAME]: "Name", - [Property.APPS_FLYER_USER_ID]: "AppsFlyerUserId", - [Property.ADJUST_USER_ID]: "AdjustAdId", - [Property.KOCHAVA_DEVICE_ID]: "KochavaDeviceId", - [Property.CUSTOM_USER_ID]: "CustomUserId", - [Property.FACEBOOK_ATTRIBUTION]: "FacebookAttribution", - [Property.ADVERTISING_ID]: "AdvertisingId", - [Property.FIREBASE_APP_INSTANCE_ID]: "FirebaseAppInstanceId", - [Property.APP_SET_ID]: "AppSetId", -}; - -export const convertPropertyToNativeKey = (property: Property): string => { - return propertyKeyMap[property] -} - -const providerKeyMap = { - [Provider.APPSFLYER]: "AppsFlyer", - [Provider.BRANCH]: "Branch", - [Provider.ADJUST]: "Adjust", - [Provider.APPLE]: "AppleSearchAds", // ios only - [Provider.APPLE_SEARCH_ADS]: "AppleSearchAds", // ios only - [Provider.APPLE_AD_SERVICES]: "AppleAdServices", // ios only -}; - -export const convertProviderToNativeKey = (provider: Provider): string => { - return providerKeyMap[provider] -} - -export enum DefinedNativeErrorCodes { - PURCHASE_CANCELLED_BY_USER = "PURCHASE_CANCELLED_BY_USER" -} \ No newline at end of file From 8271a1610edb3753e334b97a2535f6f575b0d3d5 Mon Sep 17 00:00:00 2001 From: Kamo Spertsyan Date: Tue, 6 Dec 2022 18:57:08 +0300 Subject: [PATCH 3/7] iOS part of major release as well as several common fixes. (#198) * Native modules updated. * Native modules updated. * RN part refactoring * RN example refactoring with lib fixes * Testing fixes * Enum values rewritten from ints to strings * Renew state renamed * Comment fix * Comment fix * Comment fix * Enum renamings * Enum renamings * identity nullability fix * Comment fix * Comment fix * Comment fix * Comment fix * Updated iOS part of RN SDK * Final fixes after testing and upgrading native deps. * Native module initialization warnings fix * Review fixes * Review fixes * Comment fixes Co-authored-by: Surik --- android/build.gradle | 2 +- .../com/reactlibrary/AutomationsModule.java | 22 +- .../com/reactlibrary/QonversionModule.java | 42 +- .../src/main/java/com/reactlibrary/Utils.java | 38 ++ example/App.js | 1 - example/android/build.gradle | 2 +- example/ios/Podfile.lock | 458 +++++++++--------- ios/RNAutomations.m | 55 ++- ios/RNQonversion.m | 107 ++-- react-native-qonversion.podspec | 2 +- src/Automations.ts | 31 +- src/AutomationsApi.ts | 13 +- src/Qonversion.ts | 2 +- src/QonversionApi.ts | 9 +- src/dto/Entitlement.ts | 8 +- src/dto/EntitlementsUpdateListener.ts | 3 +- src/dto/enums.ts | 6 +- src/internal/AutomationsInternal.ts | 10 +- src/internal/Mapper.ts | 2 +- src/internal/QonversionInternal.ts | 8 +- 20 files changed, 428 insertions(+), 393 deletions(-) create mode 100644 android/src/main/java/com/reactlibrary/Utils.java diff --git a/android/build.gradle b/android/build.gradle index a32eebc..bfa75e4 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -65,7 +65,7 @@ repositories { dependencies { //noinspection GradleDynamicVersion implementation 'com.facebook.react:react-native:+' // From node_modules - implementation "io.qonversion.sandwich:sandwich_local:1.0.0-RC5" + implementation "io.qonversion.sandwich:sandwich:1.0.0-RC5" } afterEvaluate { project -> diff --git a/android/src/main/java/com/reactlibrary/AutomationsModule.java b/android/src/main/java/com/reactlibrary/AutomationsModule.java index 72e02f4..b836a95 100644 --- a/android/src/main/java/com/reactlibrary/AutomationsModule.java +++ b/android/src/main/java/com/reactlibrary/AutomationsModule.java @@ -17,6 +17,8 @@ import io.qonversion.sandwich.AutomationsEventListener; import io.qonversion.sandwich.AutomationsSandwich; +import io.qonversion.sandwich.ResultListener; +import io.qonversion.sandwich.SandwichError; class AutomationsModule extends ReactContextBaseJavaModule implements AutomationsEventListener { @@ -43,11 +45,6 @@ public String getName() { return "RNAutomations"; } - @ReactMethod - void initializeSdk() { - automationsSandwich.initialize(); - } - @ReactMethod void subscribe() { automationsSandwich.setDelegate(this); @@ -102,6 +99,21 @@ public void handleNotification(final ReadableMap notificationData, final Promise promise.resolve(isQonversionNotification); } + @ReactMethod + public void showScreen(final String screenId, final Promise promise) { + automationsSandwich.showScreen(screenId, new ResultListener() { + @Override + public void onSuccess(@NonNull Map map) { + promise.resolve(null); + } + + @Override + public void onError(@NonNull SandwichError error) { + Utils.rejectWithError(error, promise); + } + }); + } + @Override public void onAutomationEvent(@NonNull AutomationsEventListener.Event event, @Nullable Map payload) { WritableMap payloadMap = null; diff --git a/android/src/main/java/com/reactlibrary/QonversionModule.java b/android/src/main/java/com/reactlibrary/QonversionModule.java index cb31561..ad4c2ae 100644 --- a/android/src/main/java/com/reactlibrary/QonversionModule.java +++ b/android/src/main/java/com/reactlibrary/QonversionModule.java @@ -18,7 +18,6 @@ import io.qonversion.sandwich.PurchaseResultListener; import io.qonversion.sandwich.QonversionEventsListener; import io.qonversion.sandwich.QonversionSandwich; -import io.qonversion.sandwich.ResultListener; import io.qonversion.sandwich.SandwichError; public class QonversionModule extends ReactContextBaseJavaModule implements QonversionEventsListener { @@ -144,28 +143,28 @@ public void addAttributionData(ReadableMap map, String provider) { @ReactMethod public void checkEntitlements(final Promise promise) { - qonversionSandwich.checkEntitlements(getResultListener(promise)); + qonversionSandwich.checkEntitlements(Utils.getResultListener(promise)); } @ReactMethod public void products(final Promise promise) { - qonversionSandwich.products(getResultListener(promise)); + qonversionSandwich.products(Utils.getResultListener(promise)); } @ReactMethod public void offerings(final Promise promise) { - qonversionSandwich.offerings(getResultListener(promise)); + qonversionSandwich.offerings(Utils.getResultListener(promise)); } @ReactMethod public void checkTrialIntroEligibilityForProductIds(ReadableArray ids, final Promise promise) { final List idList = EntitiesConverter.convertArrayToStringList(ids); - qonversionSandwich.checkTrialIntroEligibility(idList, getResultListener(promise)); + qonversionSandwich.checkTrialIntroEligibility(idList, Utils.getResultListener(promise)); } @ReactMethod public void restore(final Promise promise) { - qonversionSandwich.restore(getResultListener(promise)); + qonversionSandwich.restore(Utils.getResultListener(promise)); } @ReactMethod @@ -185,7 +184,7 @@ public void logout() { @ReactMethod public void userInfo(final Promise promise) { - qonversionSandwich.userInfo(getResultListener(promise)); + qonversionSandwich.userInfo(Utils.getResultListener(promise)); } @Override @@ -196,21 +195,6 @@ public void onEntitlementsUpdated(@NonNull Map map) { } } - private ResultListener getResultListener(final Promise promise) { - return new ResultListener() { - @Override - public void onSuccess(@NonNull Map map) { - final WritableMap payload = EntitiesConverter.convertMapToWritableMap(map); - promise.resolve(payload); - } - - @Override - public void onError(@NonNull SandwichError error) { - rejectWithError(error, promise); - } - }; - } - private PurchaseResultListener getPurchaseResultListener(final Promise promise) { return new PurchaseResultListener() { @Override @@ -222,21 +206,11 @@ public void onSuccess(@NonNull Map map) { @Override public void onError(@NonNull SandwichError error, boolean isCancelled) { if (isCancelled) { - rejectWithError(error, promise, ERROR_CODE_PURCHASE_CANCELLED_BY_USER); + Utils.rejectWithError(error, promise, ERROR_CODE_PURCHASE_CANCELLED_BY_USER); } else { - rejectWithError(error, promise); + Utils.rejectWithError(error, promise); } } }; } - - private void rejectWithError(@NonNull SandwichError sandwichError, final Promise promise) { - rejectWithError(sandwichError, promise, null); - } - - private void rejectWithError(@NonNull SandwichError sandwichError, final Promise promise, @Nullable String customErrorCode) { - String errorMessage = sandwichError.getDescription() + "\n" + sandwichError.getAdditionalMessage(); - String errorCode = customErrorCode == null ? sandwichError.getCode() : customErrorCode; - promise.reject(errorCode, errorMessage); - } } diff --git a/android/src/main/java/com/reactlibrary/Utils.java b/android/src/main/java/com/reactlibrary/Utils.java new file mode 100644 index 0000000..5f0e7ef --- /dev/null +++ b/android/src/main/java/com/reactlibrary/Utils.java @@ -0,0 +1,38 @@ +package com.reactlibrary; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Map; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.WritableMap; + +import io.qonversion.sandwich.ResultListener; +import io.qonversion.sandwich.SandwichError; + +public class Utils { + static ResultListener getResultListener(final Promise promise) { + return new ResultListener() { + @Override + public void onSuccess(@NonNull Map map) { + final WritableMap payload = EntitiesConverter.convertMapToWritableMap(map); + promise.resolve(payload); + } + + @Override + public void onError(@NonNull SandwichError error) { + rejectWithError(error, promise); + } + }; + } + + static void rejectWithError(@NonNull SandwichError sandwichError, final Promise promise) { + rejectWithError(sandwichError, promise, null); + } + + static void rejectWithError(@NonNull SandwichError sandwichError, final Promise promise, @Nullable String customErrorCode) { + String errorMessage = sandwichError.getDescription() + "\n" + sandwichError.getAdditionalMessage(); + String errorCode = customErrorCode == null ? sandwichError.getCode() : customErrorCode; + promise.reject(errorCode, errorMessage); + } +} diff --git a/example/App.js b/example/App.js index 9dcfe05..8c9a026 100644 --- a/example/App.js +++ b/example/App.js @@ -65,7 +65,6 @@ export class QonversionSample extends React.PureComponent<{}, StateType> { }) .build(); Qonversion.initialize(config); - Automations.initialize(); Qonversion.getSharedInstance().setPromoPurchasesDelegate({ onPromoPurchaseReceived: async (productId, promoPurchaseExecutor) => { try { diff --git a/example/android/build.gradle b/example/android/build.gradle index 94ccc20..95bfccc 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -22,7 +22,6 @@ buildscript { allprojects { repositories { - mavenLocal() maven { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm url("$rootDir/../node_modules/react-native/android") @@ -35,5 +34,6 @@ allprojects { google() jcenter() maven { url 'https://www.jitpack.io' } + mavenLocal() } } diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index f9f5d30..b9a834b 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2,14 +2,14 @@ PODS: - boost (1.76.0) - CocoaAsyncSocket (7.6.5) - DoubleConversion (1.1.6) - - FBLazyVector (0.70.3) - - FBReactNativeSpec (0.70.3): + - FBLazyVector (0.70.5) + - FBReactNativeSpec (0.70.5): - RCT-Folly (= 2021.07.22.00) - - RCTRequired (= 0.70.3) - - RCTTypeSafety (= 0.70.3) - - React-Core (= 0.70.3) - - React-jsi (= 0.70.3) - - ReactCommon/turbomodule/core (= 0.70.3) + - RCTRequired (= 0.70.5) + - RCTTypeSafety (= 0.70.5) + - React-Core (= 0.70.5) + - React-jsi (= 0.70.5) + - ReactCommon/turbomodule/core (= 0.70.5) - Flipper (0.125.0): - Flipper-Folly (~> 2.6) - Flipper-RSocket (~> 1.4) @@ -75,11 +75,11 @@ PODS: - glog (0.3.5) - libevent (2.1.12) - OpenSSL-Universal (1.1.1100) - - Qonversion (2.21.0): - - Qonversion/Main (= 2.21.0) - - Qonversion/Main (2.21.0) - - QonversionSandwich (0.2.0): - - Qonversion (= 2.21.0) + - Qonversion (3.0.0-RC3): + - Qonversion/Main (= 3.0.0-RC3) + - Qonversion/Main (3.0.0-RC3) + - QonversionSandwich (1.0.0-RC5): + - Qonversion (= 3.0.0-RC3) - RCT-Folly (2021.07.22.00): - boost - DoubleConversion @@ -91,273 +91,273 @@ PODS: - DoubleConversion - fmt (~> 6.2.1) - glog - - RCTRequired (0.70.3) - - RCTTypeSafety (0.70.3): - - FBLazyVector (= 0.70.3) - - RCTRequired (= 0.70.3) - - React-Core (= 0.70.3) - - React (0.70.3): - - React-Core (= 0.70.3) - - React-Core/DevSupport (= 0.70.3) - - React-Core/RCTWebSocket (= 0.70.3) - - React-RCTActionSheet (= 0.70.3) - - React-RCTAnimation (= 0.70.3) - - React-RCTBlob (= 0.70.3) - - React-RCTImage (= 0.70.3) - - React-RCTLinking (= 0.70.3) - - React-RCTNetwork (= 0.70.3) - - React-RCTSettings (= 0.70.3) - - React-RCTText (= 0.70.3) - - React-RCTVibration (= 0.70.3) - - React-bridging (0.70.3): + - RCTRequired (0.70.5) + - RCTTypeSafety (0.70.5): + - FBLazyVector (= 0.70.5) + - RCTRequired (= 0.70.5) + - React-Core (= 0.70.5) + - React (0.70.5): + - React-Core (= 0.70.5) + - React-Core/DevSupport (= 0.70.5) + - React-Core/RCTWebSocket (= 0.70.5) + - React-RCTActionSheet (= 0.70.5) + - React-RCTAnimation (= 0.70.5) + - React-RCTBlob (= 0.70.5) + - React-RCTImage (= 0.70.5) + - React-RCTLinking (= 0.70.5) + - React-RCTNetwork (= 0.70.5) + - React-RCTSettings (= 0.70.5) + - React-RCTText (= 0.70.5) + - React-RCTVibration (= 0.70.5) + - React-bridging (0.70.5): - RCT-Folly (= 2021.07.22.00) - - React-jsi (= 0.70.3) - - React-callinvoker (0.70.3) - - React-Codegen (0.70.3): - - FBReactNativeSpec (= 0.70.3) + - React-jsi (= 0.70.5) + - React-callinvoker (0.70.5) + - React-Codegen (0.70.5): + - FBReactNativeSpec (= 0.70.5) - RCT-Folly (= 2021.07.22.00) - - RCTRequired (= 0.70.3) - - RCTTypeSafety (= 0.70.3) - - React-Core (= 0.70.3) - - React-jsi (= 0.70.3) - - React-jsiexecutor (= 0.70.3) - - ReactCommon/turbomodule/core (= 0.70.3) - - React-Core (0.70.3): + - RCTRequired (= 0.70.5) + - RCTTypeSafety (= 0.70.5) + - React-Core (= 0.70.5) + - React-jsi (= 0.70.5) + - React-jsiexecutor (= 0.70.5) + - ReactCommon/turbomodule/core (= 0.70.5) + - React-Core (0.70.5): - glog - RCT-Folly (= 2021.07.22.00) - - React-Core/Default (= 0.70.3) - - React-cxxreact (= 0.70.3) - - React-jsi (= 0.70.3) - - React-jsiexecutor (= 0.70.3) - - React-perflogger (= 0.70.3) + - React-Core/Default (= 0.70.5) + - React-cxxreact (= 0.70.5) + - React-jsi (= 0.70.5) + - React-jsiexecutor (= 0.70.5) + - React-perflogger (= 0.70.5) - Yoga - - React-Core/CoreModulesHeaders (0.70.3): + - React-Core/CoreModulesHeaders (0.70.5): - glog - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.3) - - React-jsi (= 0.70.3) - - React-jsiexecutor (= 0.70.3) - - React-perflogger (= 0.70.3) + - React-cxxreact (= 0.70.5) + - React-jsi (= 0.70.5) + - React-jsiexecutor (= 0.70.5) + - React-perflogger (= 0.70.5) - Yoga - - React-Core/Default (0.70.3): + - React-Core/Default (0.70.5): - glog - RCT-Folly (= 2021.07.22.00) - - React-cxxreact (= 0.70.3) - - React-jsi (= 0.70.3) - - React-jsiexecutor (= 0.70.3) - - React-perflogger (= 0.70.3) + - React-cxxreact (= 0.70.5) + - React-jsi (= 0.70.5) + - React-jsiexecutor (= 0.70.5) + - React-perflogger (= 0.70.5) - Yoga - - React-Core/DevSupport (0.70.3): + - React-Core/DevSupport (0.70.5): - glog - RCT-Folly (= 2021.07.22.00) - - React-Core/Default (= 0.70.3) - - React-Core/RCTWebSocket (= 0.70.3) - - React-cxxreact (= 0.70.3) - - React-jsi (= 0.70.3) - - React-jsiexecutor (= 0.70.3) - - React-jsinspector (= 0.70.3) - - React-perflogger (= 0.70.3) + - React-Core/Default (= 0.70.5) + - React-Core/RCTWebSocket (= 0.70.5) + - React-cxxreact (= 0.70.5) + - React-jsi (= 0.70.5) + - React-jsiexecutor (= 0.70.5) + - React-jsinspector (= 0.70.5) + - React-perflogger (= 0.70.5) - Yoga - - React-Core/RCTActionSheetHeaders (0.70.3): + - React-Core/RCTActionSheetHeaders (0.70.5): - glog - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.3) - - React-jsi (= 0.70.3) - - React-jsiexecutor (= 0.70.3) - - React-perflogger (= 0.70.3) + - React-cxxreact (= 0.70.5) + - React-jsi (= 0.70.5) + - React-jsiexecutor (= 0.70.5) + - React-perflogger (= 0.70.5) - Yoga - - React-Core/RCTAnimationHeaders (0.70.3): + - React-Core/RCTAnimationHeaders (0.70.5): - glog - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.3) - - React-jsi (= 0.70.3) - - React-jsiexecutor (= 0.70.3) - - React-perflogger (= 0.70.3) + - React-cxxreact (= 0.70.5) + - React-jsi (= 0.70.5) + - React-jsiexecutor (= 0.70.5) + - React-perflogger (= 0.70.5) - Yoga - - React-Core/RCTBlobHeaders (0.70.3): + - React-Core/RCTBlobHeaders (0.70.5): - glog - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.3) - - React-jsi (= 0.70.3) - - React-jsiexecutor (= 0.70.3) - - React-perflogger (= 0.70.3) + - React-cxxreact (= 0.70.5) + - React-jsi (= 0.70.5) + - React-jsiexecutor (= 0.70.5) + - React-perflogger (= 0.70.5) - Yoga - - React-Core/RCTImageHeaders (0.70.3): + - React-Core/RCTImageHeaders (0.70.5): - glog - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.3) - - React-jsi (= 0.70.3) - - React-jsiexecutor (= 0.70.3) - - React-perflogger (= 0.70.3) + - React-cxxreact (= 0.70.5) + - React-jsi (= 0.70.5) + - React-jsiexecutor (= 0.70.5) + - React-perflogger (= 0.70.5) - Yoga - - React-Core/RCTLinkingHeaders (0.70.3): + - React-Core/RCTLinkingHeaders (0.70.5): - glog - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.3) - - React-jsi (= 0.70.3) - - React-jsiexecutor (= 0.70.3) - - React-perflogger (= 0.70.3) + - React-cxxreact (= 0.70.5) + - React-jsi (= 0.70.5) + - React-jsiexecutor (= 0.70.5) + - React-perflogger (= 0.70.5) - Yoga - - React-Core/RCTNetworkHeaders (0.70.3): + - React-Core/RCTNetworkHeaders (0.70.5): - glog - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.3) - - React-jsi (= 0.70.3) - - React-jsiexecutor (= 0.70.3) - - React-perflogger (= 0.70.3) + - React-cxxreact (= 0.70.5) + - React-jsi (= 0.70.5) + - React-jsiexecutor (= 0.70.5) + - React-perflogger (= 0.70.5) - Yoga - - React-Core/RCTSettingsHeaders (0.70.3): + - React-Core/RCTSettingsHeaders (0.70.5): - glog - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.3) - - React-jsi (= 0.70.3) - - React-jsiexecutor (= 0.70.3) - - React-perflogger (= 0.70.3) + - React-cxxreact (= 0.70.5) + - React-jsi (= 0.70.5) + - React-jsiexecutor (= 0.70.5) + - React-perflogger (= 0.70.5) - Yoga - - React-Core/RCTTextHeaders (0.70.3): + - React-Core/RCTTextHeaders (0.70.5): - glog - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.3) - - React-jsi (= 0.70.3) - - React-jsiexecutor (= 0.70.3) - - React-perflogger (= 0.70.3) + - React-cxxreact (= 0.70.5) + - React-jsi (= 0.70.5) + - React-jsiexecutor (= 0.70.5) + - React-perflogger (= 0.70.5) - Yoga - - React-Core/RCTVibrationHeaders (0.70.3): + - React-Core/RCTVibrationHeaders (0.70.5): - glog - RCT-Folly (= 2021.07.22.00) - React-Core/Default - - React-cxxreact (= 0.70.3) - - React-jsi (= 0.70.3) - - React-jsiexecutor (= 0.70.3) - - React-perflogger (= 0.70.3) + - React-cxxreact (= 0.70.5) + - React-jsi (= 0.70.5) + - React-jsiexecutor (= 0.70.5) + - React-perflogger (= 0.70.5) - Yoga - - React-Core/RCTWebSocket (0.70.3): + - React-Core/RCTWebSocket (0.70.5): - glog - RCT-Folly (= 2021.07.22.00) - - React-Core/Default (= 0.70.3) - - React-cxxreact (= 0.70.3) - - React-jsi (= 0.70.3) - - React-jsiexecutor (= 0.70.3) - - React-perflogger (= 0.70.3) + - React-Core/Default (= 0.70.5) + - React-cxxreact (= 0.70.5) + - React-jsi (= 0.70.5) + - React-jsiexecutor (= 0.70.5) + - React-perflogger (= 0.70.5) - Yoga - - React-CoreModules (0.70.3): + - React-CoreModules (0.70.5): - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.70.3) - - React-Codegen (= 0.70.3) - - React-Core/CoreModulesHeaders (= 0.70.3) - - React-jsi (= 0.70.3) - - React-RCTImage (= 0.70.3) - - ReactCommon/turbomodule/core (= 0.70.3) - - React-cxxreact (0.70.3): + - RCTTypeSafety (= 0.70.5) + - React-Codegen (= 0.70.5) + - React-Core/CoreModulesHeaders (= 0.70.5) + - React-jsi (= 0.70.5) + - React-RCTImage (= 0.70.5) + - ReactCommon/turbomodule/core (= 0.70.5) + - React-cxxreact (0.70.5): - boost (= 1.76.0) - DoubleConversion - glog - RCT-Folly (= 2021.07.22.00) - - React-callinvoker (= 0.70.3) - - React-jsi (= 0.70.3) - - React-jsinspector (= 0.70.3) - - React-logger (= 0.70.3) - - React-perflogger (= 0.70.3) - - React-runtimeexecutor (= 0.70.3) - - React-jsi (0.70.3): + - React-callinvoker (= 0.70.5) + - React-jsi (= 0.70.5) + - React-jsinspector (= 0.70.5) + - React-logger (= 0.70.5) + - React-perflogger (= 0.70.5) + - React-runtimeexecutor (= 0.70.5) + - React-jsi (0.70.5): - boost (= 1.76.0) - DoubleConversion - glog - RCT-Folly (= 2021.07.22.00) - - React-jsi/Default (= 0.70.3) - - React-jsi/Default (0.70.3): + - React-jsi/Default (= 0.70.5) + - React-jsi/Default (0.70.5): - boost (= 1.76.0) - DoubleConversion - glog - RCT-Folly (= 2021.07.22.00) - - React-jsiexecutor (0.70.3): + - React-jsiexecutor (0.70.5): - DoubleConversion - glog - RCT-Folly (= 2021.07.22.00) - - React-cxxreact (= 0.70.3) - - React-jsi (= 0.70.3) - - React-perflogger (= 0.70.3) - - React-jsinspector (0.70.3) - - React-logger (0.70.3): + - React-cxxreact (= 0.70.5) + - React-jsi (= 0.70.5) + - React-perflogger (= 0.70.5) + - React-jsinspector (0.70.5) + - React-logger (0.70.5): - glog - - react-native-qonversion (3.6.1): - - QonversionSandwich (= 0.2.0) + - react-native-qonversion (3.6.2): + - QonversionSandwich (= 1.0.0-RC5) - React - - React-perflogger (0.70.3) - - React-RCTActionSheet (0.70.3): - - React-Core/RCTActionSheetHeaders (= 0.70.3) - - React-RCTAnimation (0.70.3): + - React-perflogger (0.70.5) + - React-RCTActionSheet (0.70.5): + - React-Core/RCTActionSheetHeaders (= 0.70.5) + - React-RCTAnimation (0.70.5): - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.70.3) - - React-Codegen (= 0.70.3) - - React-Core/RCTAnimationHeaders (= 0.70.3) - - React-jsi (= 0.70.3) - - ReactCommon/turbomodule/core (= 0.70.3) - - React-RCTBlob (0.70.3): + - RCTTypeSafety (= 0.70.5) + - React-Codegen (= 0.70.5) + - React-Core/RCTAnimationHeaders (= 0.70.5) + - React-jsi (= 0.70.5) + - ReactCommon/turbomodule/core (= 0.70.5) + - React-RCTBlob (0.70.5): - RCT-Folly (= 2021.07.22.00) - - React-Codegen (= 0.70.3) - - React-Core/RCTBlobHeaders (= 0.70.3) - - React-Core/RCTWebSocket (= 0.70.3) - - React-jsi (= 0.70.3) - - React-RCTNetwork (= 0.70.3) - - ReactCommon/turbomodule/core (= 0.70.3) - - React-RCTImage (0.70.3): + - React-Codegen (= 0.70.5) + - React-Core/RCTBlobHeaders (= 0.70.5) + - React-Core/RCTWebSocket (= 0.70.5) + - React-jsi (= 0.70.5) + - React-RCTNetwork (= 0.70.5) + - ReactCommon/turbomodule/core (= 0.70.5) + - React-RCTImage (0.70.5): - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.70.3) - - React-Codegen (= 0.70.3) - - React-Core/RCTImageHeaders (= 0.70.3) - - React-jsi (= 0.70.3) - - React-RCTNetwork (= 0.70.3) - - ReactCommon/turbomodule/core (= 0.70.3) - - React-RCTLinking (0.70.3): - - React-Codegen (= 0.70.3) - - React-Core/RCTLinkingHeaders (= 0.70.3) - - React-jsi (= 0.70.3) - - ReactCommon/turbomodule/core (= 0.70.3) - - React-RCTNetwork (0.70.3): + - RCTTypeSafety (= 0.70.5) + - React-Codegen (= 0.70.5) + - React-Core/RCTImageHeaders (= 0.70.5) + - React-jsi (= 0.70.5) + - React-RCTNetwork (= 0.70.5) + - ReactCommon/turbomodule/core (= 0.70.5) + - React-RCTLinking (0.70.5): + - React-Codegen (= 0.70.5) + - React-Core/RCTLinkingHeaders (= 0.70.5) + - React-jsi (= 0.70.5) + - ReactCommon/turbomodule/core (= 0.70.5) + - React-RCTNetwork (0.70.5): - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.70.3) - - React-Codegen (= 0.70.3) - - React-Core/RCTNetworkHeaders (= 0.70.3) - - React-jsi (= 0.70.3) - - ReactCommon/turbomodule/core (= 0.70.3) - - React-RCTSettings (0.70.3): + - RCTTypeSafety (= 0.70.5) + - React-Codegen (= 0.70.5) + - React-Core/RCTNetworkHeaders (= 0.70.5) + - React-jsi (= 0.70.5) + - ReactCommon/turbomodule/core (= 0.70.5) + - React-RCTSettings (0.70.5): - RCT-Folly (= 2021.07.22.00) - - RCTTypeSafety (= 0.70.3) - - React-Codegen (= 0.70.3) - - React-Core/RCTSettingsHeaders (= 0.70.3) - - React-jsi (= 0.70.3) - - ReactCommon/turbomodule/core (= 0.70.3) - - React-RCTText (0.70.3): - - React-Core/RCTTextHeaders (= 0.70.3) - - React-RCTVibration (0.70.3): + - RCTTypeSafety (= 0.70.5) + - React-Codegen (= 0.70.5) + - React-Core/RCTSettingsHeaders (= 0.70.5) + - React-jsi (= 0.70.5) + - ReactCommon/turbomodule/core (= 0.70.5) + - React-RCTText (0.70.5): + - React-Core/RCTTextHeaders (= 0.70.5) + - React-RCTVibration (0.70.5): - RCT-Folly (= 2021.07.22.00) - - React-Codegen (= 0.70.3) - - React-Core/RCTVibrationHeaders (= 0.70.3) - - React-jsi (= 0.70.3) - - ReactCommon/turbomodule/core (= 0.70.3) - - React-runtimeexecutor (0.70.3): - - React-jsi (= 0.70.3) - - ReactCommon/turbomodule/core (0.70.3): + - React-Codegen (= 0.70.5) + - React-Core/RCTVibrationHeaders (= 0.70.5) + - React-jsi (= 0.70.5) + - ReactCommon/turbomodule/core (= 0.70.5) + - React-runtimeexecutor (0.70.5): + - React-jsi (= 0.70.5) + - ReactCommon/turbomodule/core (0.70.5): - DoubleConversion - glog - RCT-Folly (= 2021.07.22.00) - - React-bridging (= 0.70.3) - - React-callinvoker (= 0.70.3) - - React-Core (= 0.70.3) - - React-cxxreact (= 0.70.3) - - React-jsi (= 0.70.3) - - React-logger (= 0.70.3) - - React-perflogger (= 0.70.3) + - React-bridging (= 0.70.5) + - React-callinvoker (= 0.70.5) + - React-Core (= 0.70.5) + - React-cxxreact (= 0.70.5) + - React-jsi (= 0.70.5) + - React-logger (= 0.70.5) + - React-perflogger (= 0.70.5) - RNCPushNotificationIOS (1.10.1): - React-Core - SocketRocket (0.6.0) @@ -519,8 +519,8 @@ SPEC CHECKSUMS: boost: a7c83b31436843459a1961bfd74b96033dc77234 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 - FBLazyVector: 3b313c3fb52b597f7a9b430798e6367d2b9f07e5 - FBReactNativeSpec: 99a7ecb7e9665d96f2fea706b0844e2f3117f381 + FBLazyVector: affa4ba1bfdaac110a789192f4d452b053a86624 + FBReactNativeSpec: fe8b5f1429cfe83a8d72dc8ed61dc7704cac8745 Flipper: 26fc4b7382499f1281eb8cb921e5c3ad6de91fe0 Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c Flipper-DoubleConversion: 2dc99b02f658daf147069aad9dbd29d8feb06d30 @@ -534,38 +534,38 @@ SPEC CHECKSUMS: glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c - Qonversion: e0d85c09ca4672e724321bef0c013ac2a65f6659 - QonversionSandwich: 538f4a7e1d22edc568ba3a5cb464959f590f5997 + Qonversion: 67d0a7b1e47e8205c83b6b03dc842aedb04827ac + QonversionSandwich: af20860c996123dbe8e2dd3a59cb1edad02f1edb RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda - RCTRequired: 5cf7e7d2f12699724b59f90350257a422eaa9492 - RCTTypeSafety: 3f3ead9673d1ab8bb1aea85b0894ab3220f8f06e - React: 30a333798d1fcf595e8a4108bbaa0f125a655f4a - React-bridging: 92396c03ab446756ddfb7a8e2baff3bcf19eec7d - React-callinvoker: bb66a41b41fa0b7c5f3cc626693a63c9ea0d6252 - React-Codegen: a2a944a9688fae870be0a2ecdca37284034b25c2 - React-Core: a689b4d1bd13e15915a05c9918c2b01df96cd811 - React-CoreModules: d262214db6b704b042bc5c0735b06c346a371d7f - React-cxxreact: 81d5bf256313bf96cb925eb0e654103291161a17 - React-jsi: 7f99dc3055bec9a0eeb4230f8b6ac873514c8421 - React-jsiexecutor: 7e2e1772ef7b97168c880eeaf3749d8c145ffd6e - React-jsinspector: 0553c9fe7218e1f127be070bd5a4d2fc19fb8190 - React-logger: cffcc09e8aba8a3014be8d18da7f922802e9f19e - react-native-qonversion: d296b40bd38ed1338e32b2e8be7a43e8cf9020ea - React-perflogger: 082b4293f0b3914ff41da35a6c06ac4490fcbcc8 - React-RCTActionSheet: 83da3030deb5dea54b398129f56540a44e64d3ae - React-RCTAnimation: bac3a4f4c0436554d9f7fbb1352a0cdcb1fb0f1c - React-RCTBlob: d2c8830ac6b4d55d5624469829fe6d0ef1d534d1 - React-RCTImage: 26ad032b09f90ae5d2283ec19f0c455c444c8189 - React-RCTLinking: 4a8d16586df11fff515a6c52ff51a02c47a20499 - React-RCTNetwork: 843fc75a70f0b5760de0bf59468585f41209bcf0 - React-RCTSettings: 54e59255f94462951b45f84c3f81aedc27cf8615 - React-RCTText: c32e2a60827bd232b2bc95941b9926ccf1c2be4c - React-RCTVibration: b9a58ffdd18446f43d493a4b0ecd603ee86be847 - React-runtimeexecutor: e9b1f9310158a1e265bcdfdfd8c62d6174b947a2 - ReactCommon: 01064177e66d652192c661de899b1076da962fd9 + RCTRequired: 21229f84411088e5d8538f21212de49e46cc83e2 + RCTTypeSafety: 62eed57a32924b09edaaf170a548d1fc96223086 + React: f0254ccddeeef1defe66c6b1bb9133a4f040792b + React-bridging: e46911666b7ec19538a620a221d6396cd293d687 + React-callinvoker: 66b62e2c34546546b2f21ab0b7670346410a2b53 + React-Codegen: b6999435966df3bdf82afa3f319ba0d6f9a8532a + React-Core: dabbc9d1fe0a11d884e6ee1599789cf8eb1058a5 + React-CoreModules: 5b6b7668f156f73a56420df9ec68ca2ec8f2e818 + React-cxxreact: c7ca2baee46db22a30fce9e639277add3c3f6ad1 + React-jsi: a565dcb49130ed20877a9bb1105ffeecbb93d02d + React-jsiexecutor: 31564fa6912459921568e8b0e49024285a4d584b + React-jsinspector: badd81696361249893a80477983e697aab3c1a34 + React-logger: fdda34dd285bdb0232e059b19d9606fa0ec3bb9c + react-native-qonversion: ad66dd9bf1eae3c8534f30cc3e8d9e6dabf19909 + React-perflogger: e68d3795cf5d247a0379735cbac7309adf2fb931 + React-RCTActionSheet: 05452c3b281edb27850253db13ecd4c5a65bc247 + React-RCTAnimation: 578eebac706428e68466118e84aeacf3a282b4da + React-RCTBlob: f47a0aa61e7d1fb1a0e13da832b0da934939d71a + React-RCTImage: 60f54b66eed65d86b6dffaf4733d09161d44929d + React-RCTLinking: 91073205aeec4b29450ca79b709277319368ac9e + React-RCTNetwork: ca91f2c9465a7e335c8a5fae731fd7f10572213b + React-RCTSettings: 1a9a5d01337d55c18168c1abe0f4a589167d134a + React-RCTText: c591e8bd9347a294d8416357ca12d779afec01d5 + React-RCTVibration: 8e5c8c5d17af641f306d7380d8d0fe9b3c142c48 + React-runtimeexecutor: 7401c4a40f8728fd89df4a56104541b760876117 + ReactCommon: c9246996e73bf75a2c6c3ff15f1e16707cdc2da9 RNCPushNotificationIOS: 87b8d16d3ede4532745e05b03c42cff33a36cc45 SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608 - Yoga: 2ed968a4f060a92834227c036279f2736de0fce3 + Yoga: eca980a5771bf114c41a754098cd85e6e0d90ed7 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a PODFILE CHECKSUM: 3c672abbc99da122f88097a7aaa790868936a6d6 diff --git a/ios/RNAutomations.m b/ios/RNAutomations.m index df50161..78a9786 100644 --- a/ios/RNAutomations.m +++ b/ios/RNAutomations.m @@ -19,18 +19,65 @@ @implementation RNAutomations - (instancetype)init { self = [super init]; - + if (self) { _sandwich = [AutomationsSandwich new]; } - + return self; } ++ (BOOL)requiresMainQueueSetup +{ + return NO; +} + RCT_EXPORT_MODULE(); RCT_EXPORT_METHOD(subscribe) { - [_sandwich subscribe:self]; + [_sandwich subscribe:self]; +} + +RCT_EXPORT_METHOD(setNotificationsToken:(NSString *)token) { + [_sandwich setNotificationToken:token]; +} + +RCT_EXPORT_METHOD(getNotificationCustomPayload:(NSDictionary *)notificationData + completion:(RCTResponseSenderBlock)completion + rejecter:(RCTPromiseRejectBlock)reject) { + if (![notificationData isKindOfClass:[NSDictionary class]]) { + completion(nil); + return; + } + + NSDictionary *payload = [_sandwich getNotificationCustomPayload:notificationData]; + completion(payload == nil ? nil : @[payload]); +} + +RCT_EXPORT_METHOD(handleNotification:(NSDictionary *)notificationData + completion:(RCTResponseSenderBlock)completion + rejecter:(RCTPromiseRejectBlock)reject) { + if (![notificationData isKindOfClass:[NSDictionary class]]) { + completion(@[@(NO)]); + return; + } + + BOOL isQonversionNotification = [_sandwich handleNotification:notificationData]; + completion(@[@(isQonversionNotification)]); +} + +RCT_EXPORT_METHOD(showScreen:(NSString *)screenId + completion:(RCTResponseSenderBlock)completion + rejecter:(RCTPromiseRejectBlock)reject) { + [_sandwich showScreen:screenId completion:^(NSDictionary * _Nullable result, SandwichError * _Nullable error) { + if (error) { + reject(error.code, error.details, nil); + + return; + } + + completion(@[result]); + }]; } - (void)automationDidTriggerWithEvent:(NSString * _Nonnull)event payload:(NSDictionary * _Nullable)payload { @@ -40,7 +87,7 @@ - (void)automationDidTriggerWithEvent:(NSString * _Nonnull)event payload:(NSDict #pragma mark - Emitter - (NSArray *)supportedEvents { - return [self.sandwich getAvailableEvents]; + return [self.sandwich getAvailableEvents]; } @end diff --git a/ios/RNQonversion.m b/ios/RNQonversion.m index cc12561..f5bfe3b 100644 --- a/ios/RNQonversion.m +++ b/ios/RNQonversion.m @@ -1,6 +1,6 @@ #import "RNQonversion.h" -static NSString *const kEventPermissionsUpdated = @"permissions_updated"; +static NSString *const kEventEntitlementsUpdated = @"entitlements_updated"; static NSString *const kEventPromoPurchaseReceived = @"promo_purchase_received"; static NSString *const errorCodePurchaseCancelledByUser = @"PURCHASE_CANCELLED_BY_USER"; @@ -23,15 +23,30 @@ - (instancetype)init { return self; } ++ (BOOL)requiresMainQueueSetup +{ + return NO; +} + RCT_EXPORT_MODULE(); RCT_EXPORT_METHOD(storeSDKInfo:(NSString *)source version:(NSString *)version) { [_qonversionSandwich storeSdkInfoWithSource:source version:version]; } -RCT_EXPORT_METHOD(launch:(NSString *)key observerMode:(BOOL)observerMode completion:(RCTResponseSenderBlock)completion rejecter:(RCTPromiseRejectBlock)reject) { - [_qonversionSandwich launchWithProjectKey:key completion:^(NSDictionary * _Nullable result, SandwichError * _Nullable error) { - [self handleResult:result error:error completion:completion rejecter:reject]; +RCT_EXPORT_METHOD(initializeSdk:(NSString *)key launchMode:(NSString *)launchModeKey environment:(NSString *)environmentKey cacheLifetime:(NSString *)cacheLifetimeKey) { + [_qonversionSandwich initializeWithProjectKey:key launchModeKey:launchModeKey environmentKey:environmentKey entitlementsCacheLifetimeKey:cacheLifetimeKey]; +} + +RCT_EXPORT_METHOD(purchase:(NSString *)productId completion:(RCTResponseSenderBlock)completion rejecter:(RCTPromiseRejectBlock)reject) { + [_qonversionSandwich purchase:productId completion:^(NSDictionary * _Nullable result, SandwichError * _Nullable error) { + [self handlePurchaseResult:result error:error completion:completion rejecter:reject]; + }]; +} + +RCT_EXPORT_METHOD(purchaseProduct:(NSString *)productId offeringId:(NSString *)offeringId completion:(RCTResponseSenderBlock)completion rejecter:(RCTPromiseRejectBlock)reject) { + [_qonversionSandwich purchaseProduct:productId offeringId:offeringId completion:^(NSDictionary * _Nullable result, SandwichError * _Nullable error) { + [self handlePurchaseResult:result error:error completion:completion rejecter:reject]; }]; } @@ -44,43 +59,21 @@ - (instancetype)init { } RCT_EXPORT_METHOD(addAttributionData:(NSDictionary *)data provider:(NSString *)provider) { - [_qonversionSandwich addAttributionDataWithSourceKey:provider value:data]; + [_qonversionSandwich attributionWithProviderKey:provider value:data]; } -RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)completion rejecter:(RCTPromiseRejectBlock)reject) { - [_qonversionSandwich checkPermissions:^(NSDictionary * _Nullable result, SandwichError * _Nullable error) { +RCT_EXPORT_METHOD(checkEntitlements:(RCTResponseSenderBlock)completion rejecter:(RCTPromiseRejectBlock)reject) { + [_qonversionSandwich checkEntitlements:^(NSDictionary * _Nullable result, SandwichError * _Nullable error) { [self handleResult:result error:error completion:completion rejecter:reject]; }]; } -RCT_EXPORT_METHOD(purchaseProduct:(NSString *)productId offeringId:(NSString *)offeringId completion:(RCTResponseSenderBlock)completion rejecter:(RCTPromiseRejectBlock)reject) { - [_qonversionSandwich purchaseProduct:productId offeringId:offeringId completion:^(NSDictionary * _Nullable result, SandwichError * _Nullable error) { - [self handlePurchaseResult:result error:error completion:completion rejecter:reject]; - }]; -} - -RCT_EXPORT_METHOD(purchase:(NSString *)productId completion:(RCTResponseSenderBlock)completion rejecter:(RCTPromiseRejectBlock)reject) { - [_qonversionSandwich purchase:productId completion:^(NSDictionary * _Nullable result, SandwichError * _Nullable error) { - [self handlePurchaseResult:result error:error completion:completion rejecter:reject]; - }]; -} - RCT_EXPORT_METHOD(products:(RCTResponseSenderBlock)completion rejecter:(RCTPromiseRejectBlock)reject) { [_qonversionSandwich products:^(NSDictionary * _Nullable result, SandwichError * _Nullable error) { [self handleResult:result error:error completion:completion rejecter:reject]; }]; } -RCT_EXPORT_METHOD(restore:(RCTResponseSenderBlock)completion rejecter:(RCTPromiseRejectBlock)reject) { - [_qonversionSandwich restore:^(NSDictionary * _Nullable result, SandwichError * _Nullable error) { - [self handleResult:result error:error completion:completion rejecter:reject]; - }]; -} - -RCT_EXPORT_METHOD(setDebugMode) { - [_qonversionSandwich setDebugMode]; -} - RCT_EXPORT_METHOD(offerings:(RCTResponseSenderBlock)completion rejecter:(RCTPromiseRejectBlock)reject) { [_qonversionSandwich offerings:^(NSDictionary * _Nullable result, SandwichError * _Nullable error) { [self handleResult:result error:error completion:completion rejecter:reject]; @@ -93,8 +86,8 @@ - (instancetype)init { }]; } -RCT_EXPORT_METHOD(experiments:(RCTResponseSenderBlock)completion rejecter:(RCTPromiseRejectBlock)reject) { - [_qonversionSandwich experiments:^(NSDictionary * _Nullable result, SandwichError * _Nullable error) { +RCT_EXPORT_METHOD(restore:(RCTResponseSenderBlock)completion rejecter:(RCTPromiseRejectBlock)reject) { + [_qonversionSandwich restore:^(NSDictionary * _Nullable result, SandwichError * _Nullable error) { [self handleResult:result error:error completion:completion rejecter:reject]; }]; } @@ -107,40 +100,18 @@ - (instancetype)init { [_qonversionSandwich logout]; } -RCT_EXPORT_METHOD(setAdvertisingID) { - [_qonversionSandwich setAdvertisingId]; +RCT_EXPORT_METHOD(userInfo:(RCTResponseSenderBlock)completion rejecter:(RCTPromiseRejectBlock)reject) { + [_qonversionSandwich userInfo:^(NSDictionary * _Nullable result, SandwichError * _Nullable error) { + [self handleResult:result error:error completion:completion rejecter:reject]; + }]; } -RCT_EXPORT_METHOD(setAppleSearchAdsAttributionEnabled:(BOOL)enabled) { - [_qonversionSandwich setAppleSearchAdsAttributionEnabled:enabled]; +RCT_EXPORT_METHOD(collectAdvertisingID) { + [_qonversionSandwich collectAdvertisingId]; } -RCT_EXPORT_METHOD(setNotificationsToken:(NSString *)token) { - [_qonversionSandwich setNotificationToken:token]; -} - -RCT_EXPORT_METHOD(getNotificationCustomPayload:(NSDictionary *)notificationData - completion:(RCTResponseSenderBlock)completion - rejecter:(RCTPromiseRejectBlock)reject) { - if (![notificationData isKindOfClass:[NSDictionary class]]) { - completion(nil); - return; - } - - NSDictionary *payload = [_qonversionSandwich getNotificationCustomPayload:notificationData]; - completion(@[payload]); -} - -RCT_EXPORT_METHOD(handleNotification:(NSDictionary *)notificationData - completion:(RCTResponseSenderBlock)completion - rejecter:(RCTPromiseRejectBlock)reject) { - if (![notificationData isKindOfClass:[NSDictionary class]]) { - completion(@[@(NO)]); - return; - } - - BOOL isQonversionNotification = [_qonversionSandwich handleNotification:notificationData]; - completion(@[@(isQonversionNotification)]); +RCT_EXPORT_METHOD(collectAppleSearchAdsAttribution) { + [_qonversionSandwich collectAppleSearchAdsAttribution]; } RCT_EXPORT_METHOD(promoPurchase:(NSString *)storeProductId completion:(RCTResponseSenderBlock)completion rejecter:(RCTPromiseRejectBlock)reject) { @@ -155,10 +126,6 @@ - (instancetype)init { } } -RCT_EXPORT_METHOD(setPermissionsCacheLifetime:(NSString *)lifetime) { - [_qonversionSandwich setPermissionsCacheLifetime:lifetime]; -} - #pragma mark - Private - (void)handleResult:(NSDictionary *)result @@ -193,18 +160,18 @@ - (void)handlePurchaseResult:(NSDictionary *)result #pragma mark - QonversionEventListener -- (void)qonversionDidReceiveUpdatedPermissions:(NSDictionary * _Nonnull)permissions { - [self sendEventWithName:kEventPermissionsUpdated body:permissions]; -} - - (void)shouldPurchasePromoProductWith:(NSString * _Nonnull)productId { [self sendEventWithName:kEventPromoPurchaseReceived body:productId]; } +- (void)qonversionDidReceiveUpdatedEntitlements:(NSDictionary * _Nonnull)entitlements { + [self sendEventWithName:kEventEntitlementsUpdated body:entitlements]; +} + #pragma mark - Emitter - (NSArray *)supportedEvents { - return @[kEventPermissionsUpdated, kEventPromoPurchaseReceived]; + return @[kEventEntitlementsUpdated, kEventPromoPurchaseReceived]; } @end diff --git a/react-native-qonversion.podspec b/react-native-qonversion.podspec index 8f84e00..93f6f5b 100644 --- a/react-native-qonversion.podspec +++ b/react-native-qonversion.podspec @@ -22,5 +22,5 @@ Pod::Spec.new do |s| s.requires_arc = true s.dependency "React" - s.dependency "QonversionSandwich", "0.2.0" + s.dependency "QonversionSandwich", "1.0.0-RC5" end diff --git a/src/Automations.ts b/src/Automations.ts index 6770422..28b26c6 100644 --- a/src/Automations.ts +++ b/src/Automations.ts @@ -9,37 +9,24 @@ export default class Automations { /** * Use this variable to get a current initialized instance of the Qonversion Automations. - * Please, use the property only after calling {@link Automations.initialize}. + * Please, use Automations only after calling {@link Qonversion.initialize}. * Otherwise, trying to access the variable will cause an error. * * @return Current initialized instance of the Qonversion Automations. - * @throws error if the instance has not been initialized. + * @throws error if Qonversion has not been initialized. */ static getSharedInstance(): AutomationsApi { if (!this.backingInstance) { - throw "Automations have not been initialized. You should call " + - "the initialize method before accessing the shared instance of Automations." - } - - return this.backingInstance; - } + try { + Qonversion.getSharedInstance(); + } catch (e) { + throw "Qonversion has not been initialized. " + + "Automations should be used after Qonversion is initialized." + } - /** - * An entry point to use Qonversion Automations. Call to initialize Automations. - * Make sure you have initialized {@link Qonversion} first. - * - * @return Initialized instance of the Qonversion Automations. - * @throws error if {@link Qonversion} has not been initialized. - */ - static initialize(): AutomationsApi { - try { - Qonversion.getSharedInstance(); - } catch (e) { - throw "Qonversion has not been initialized. " + - "Automations initialization should be called after Qonversion is initialized." + this.backingInstance = new AutomationsInternal(); } - this.backingInstance = new AutomationsInternal(); return this.backingInstance; } } diff --git a/src/AutomationsApi.ts b/src/AutomationsApi.ts index 31fa548..1a7439b 100644 --- a/src/AutomationsApi.ts +++ b/src/AutomationsApi.ts @@ -12,7 +12,7 @@ interface AutomationsApi { /** * Set push token to Qonversion to enable Qonversion push notifications * - * @param token Firebase device token on Android. APNs device token on iOS. + * @param token Firebase device token for Android. APNs device token for iOS. */ setNotificationsToken(token: string): void; @@ -20,7 +20,7 @@ interface AutomationsApi { * Call to handle push notifications sent by Qonversion Automations. * * @param notificationData notification payload data - * @return true when a push notification was received from Qonversion. Otherwise, returns false, so you need to handle a notification yourself + * @returns true when a push notification was received from Qonversion. Otherwise, returns false, so you need to handle the notification yourself * * @see [Firebase RemoteMessage data](https://pub.dev/documentation/firebase_messaging_platform_interface/latest/firebase_messaging_platform_interface/RemoteMessage/data.html) * @see [APNs notification data](https://developer.apple.com/documentation/usernotifications/unnotificationcontent/1649869-userinfo) @@ -31,9 +31,16 @@ interface AutomationsApi { * Get parsed custom payload, which you added to the notification in the dashboard * * @param notificationData notification payload data - * @return a map with custom payload from the notification or null if it's not provided. + * @returns a map with custom payload from the notification or null if it's not provided. */ getNotificationCustomPayload(notificationData: Map): Promise | null>; + + /** + * Show the screen using its ID. + * @param screenId identifier of the screen which must be shown + * @returns promise to await for completion. + */ + showScreen(screenId: string): Promise; } export default AutomationsApi; diff --git a/src/Qonversion.ts b/src/Qonversion.ts index 0241284..925b152 100644 --- a/src/Qonversion.ts +++ b/src/Qonversion.ts @@ -27,7 +27,7 @@ export default class Qonversion { /** * An entry point to use Qonversion SDK. Call to initialize Qonversion SDK with required and extra configs. * The function is the best way to set additional configs you need to use Qonversion SDK. - * You still have an option to set a part of additional configs later via calling separated setters. + * You still have an option to set a part of additional configs later via calling separate setters. * * @param config a config that contains key SDK settings. * Call {@link QonversionConfigBuilder.build} to configure and create a QonversionConfig instance. diff --git a/src/QonversionApi.ts b/src/QonversionApi.ts index 1302693..290173e 100644 --- a/src/QonversionApi.ts +++ b/src/QonversionApi.ts @@ -140,7 +140,7 @@ interface QonversionApi { * * @param userID unique user ID in your system */ - identify(userID: string): void + identify(userID: string): void; /** * Call this function to unlink a user from his unique ID in your system and his purchase data. @@ -149,6 +149,7 @@ interface QonversionApi { /** * This method returns information about the current Qonversion user. + * @returns the promise with the information about the user. */ userInfo(): Promise; @@ -209,14 +210,14 @@ interface QonversionApi { * On iOS 14.5+, after requesting the app tracking permission using ATT, you need to notify Qonversion if tracking * is allowed and IDFA is available. */ - setAdvertisingID(): void; + collectAdvertisingID(): void; /** * iOS only. Does nothing if called on Android. * - * Enable attribution collection from Apple Search Ads. False by default. + * Enable attribution collection from Apple Search Ads. */ - setAppleSearchAdsAttributionEnabled(enabled: boolean): void; + collectAppleSearchAdsAttribution(): void; /** * iOS only. Does nothing if called on Android. diff --git a/src/dto/Entitlement.ts b/src/dto/Entitlement.ts index 8861741..e1a1ad9 100644 --- a/src/dto/Entitlement.ts +++ b/src/dto/Entitlement.ts @@ -6,8 +6,8 @@ class Entitlement { isActive: boolean; renewState: EntitlementRenewState; source: EntitlementSource; - startedDate: number; - expirationDate?: number; + startedDate: Date; + expirationDate?: Date; constructor( id: string, @@ -23,8 +23,8 @@ class Entitlement { this.isActive = isActive; this.renewState = renewState; this.source = source; - this.startedDate = startedDate; - this.expirationDate = expirationDate; + this.startedDate = new Date(startedDate); + this.expirationDate = expirationDate ? new Date(expirationDate) : undefined; } } diff --git a/src/dto/EntitlementsUpdateListener.ts b/src/dto/EntitlementsUpdateListener.ts index 9345604..48bbef5 100644 --- a/src/dto/EntitlementsUpdateListener.ts +++ b/src/dto/EntitlementsUpdateListener.ts @@ -3,7 +3,8 @@ import Entitlement from './Entitlement'; export interface EntitlementsUpdateListener { /** - * Called when the deferred transaction status updates. + * Called when entitlements update. + * For example, when pending purchases like SCA, Ask to buy, etc., happen. * @param entitlements all the client's entitlements after update. */ onEntitlementsUpdated(entitlements: Map): void; diff --git a/src/dto/enums.ts b/src/dto/enums.ts index 68a9d38..b530c67 100644 --- a/src/dto/enums.ts +++ b/src/dto/enums.ts @@ -29,6 +29,7 @@ export type ProductDurations = typeof ProductDuration[keyof typeof ProductDurati export const TrialDuration = { "-1": "NOT_AVAILABLE", + "0": "UNKNOWN", "1": "THREE_DAYS", "2": "WEEK", "3": "TWO_WEEKS", @@ -121,8 +122,9 @@ export const SKProductDiscountPaymentMode = { export type SKProductDiscountPaymentModes = typeof SKProductDiscountPaymentMode[keyof typeof SKProductDiscountPaymentMode]; export const OfferingTag = { - 0: "NONE", - 1: "MAIN", + "-1": "UNKNOWN", + "0": "NONE", + "1": "MAIN", } as const; export type OfferingTags = typeof OfferingTag[keyof typeof OfferingTag]; diff --git a/src/internal/AutomationsInternal.ts b/src/internal/AutomationsInternal.ts index 79e8140..8694419 100644 --- a/src/internal/AutomationsInternal.ts +++ b/src/internal/AutomationsInternal.ts @@ -13,10 +13,6 @@ const EVENT_AUTOMATIONS_FINISHED = "automations_finished"; export default class AutomationsInternal implements AutomationsApi { - constructor() { - RNAutomations.initializeSdk(); - } - setDelegate(delegate: AutomationsDelegate) { AutomationsInternal.subscribe(delegate); } @@ -35,12 +31,16 @@ export default class AutomationsInternal implements AutomationsApi { async getNotificationCustomPayload(notificationData: Map): Promise | null> { try { - return await RNAutomations.getNotificationCustomPayload(notificationData); + return await RNAutomations.getNotificationCustomPayload(notificationData) ?? null; } catch (e) { return null; } } + async showScreen(screenId: string): Promise { + return await RNAutomations.showScreen(screenId); + } + private static subscribe(automationsDelegate: AutomationsDelegate) { const eventEmitter = new NativeEventEmitter(RNAutomations); diff --git a/src/internal/Mapper.ts b/src/internal/Mapper.ts index 8f2ef56..68f85fe 100644 --- a/src/internal/Mapper.ts +++ b/src/internal/Mapper.ts @@ -292,7 +292,7 @@ class Mapper { products.push(mappedProduct); }); - const tag = OfferingTag[offering.tag] ?? OfferingTag[0]; + const tag = OfferingTag[offering.tag] ?? OfferingTag['0']; return new Offering(offering.id, tag, products); } diff --git a/src/internal/QonversionInternal.ts b/src/internal/QonversionInternal.ts index 71183f0..451027d 100644 --- a/src/internal/QonversionInternal.ts +++ b/src/internal/QonversionInternal.ts @@ -210,15 +210,15 @@ export default class QonversionInternal implements QonversionApi { RNQonversion.setCustomProperty(property, value); } - setAdvertisingID() { + collectAdvertisingID() { if (isIos()) { - RNQonversion.setAdvertisingID(); + RNQonversion.collectAdvertisingID(); } } - setAppleSearchAdsAttributionEnabled(enabled: boolean) { + collectAppleSearchAdsAttribution() { if (isIos()) { - RNQonversion.setAppleSearchAdsAttributionEnabled(enabled); + RNQonversion.collectAppleSearchAdsAttribution(); } } From a61829faed6bab60c6629464e9c5c56a1c07d9f1 Mon Sep 17 00:00:00 2001 From: kamospertsyan Date: Wed, 7 Dec 2022 13:40:40 +0300 Subject: [PATCH 4/7] Method `collectAdvertisingID` renamed to `collectAdvertisingId`. --- example/App.js | 1 - src/QonversionApi.ts | 2 +- src/internal/QonversionInternal.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/example/App.js b/example/App.js index 8c9a026..5622876 100644 --- a/example/App.js +++ b/example/App.js @@ -16,7 +16,6 @@ import Qonversion, { Environment, Entitlement, EntitlementsCacheLifetime, - Automations, } from 'react-native-qonversion'; import NotificationsManager from './notificationsManager'; diff --git a/src/QonversionApi.ts b/src/QonversionApi.ts index 290173e..2b63466 100644 --- a/src/QonversionApi.ts +++ b/src/QonversionApi.ts @@ -210,7 +210,7 @@ interface QonversionApi { * On iOS 14.5+, after requesting the app tracking permission using ATT, you need to notify Qonversion if tracking * is allowed and IDFA is available. */ - collectAdvertisingID(): void; + collectAdvertisingId(): void; /** * iOS only. Does nothing if called on Android. diff --git a/src/internal/QonversionInternal.ts b/src/internal/QonversionInternal.ts index 451027d..012256f 100644 --- a/src/internal/QonversionInternal.ts +++ b/src/internal/QonversionInternal.ts @@ -210,7 +210,7 @@ export default class QonversionInternal implements QonversionApi { RNQonversion.setCustomProperty(property, value); } - collectAdvertisingID() { + collectAdvertisingId() { if (isIos()) { RNQonversion.collectAdvertisingID(); } From b5d5f20f7b78b1f4fa993fd996f5d53cb6cd99fa Mon Sep 17 00:00:00 2001 From: kamospertsyan Date: Mon, 12 Dec 2022 18:44:00 +0300 Subject: [PATCH 5/7] Release 4.0.0-RC1 preparation. --- android/build.gradle | 2 +- example/ios/Podfile.lock | 10 +++++----- fastlane/Fastfile | 4 ++-- package.json | 2 +- react-native-qonversion.podspec | 2 +- src/internal/QonversionInternal.ts | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index bfa75e4..0ff94ad 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -65,7 +65,7 @@ repositories { dependencies { //noinspection GradleDynamicVersion implementation 'com.facebook.react:react-native:+' // From node_modules - implementation "io.qonversion.sandwich:sandwich:1.0.0-RC5" + implementation "io.qonversion.sandwich:sandwich:1.0.0-RC6" } afterEvaluate { project -> diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index b9a834b..e5da616 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -78,7 +78,7 @@ PODS: - Qonversion (3.0.0-RC3): - Qonversion/Main (= 3.0.0-RC3) - Qonversion/Main (3.0.0-RC3) - - QonversionSandwich (1.0.0-RC5): + - QonversionSandwich (1.0.0-RC6): - Qonversion (= 3.0.0-RC3) - RCT-Folly (2021.07.22.00): - boost @@ -289,8 +289,8 @@ PODS: - React-jsinspector (0.70.5) - React-logger (0.70.5): - glog - - react-native-qonversion (3.6.2): - - QonversionSandwich (= 1.0.0-RC5) + - react-native-qonversion (4.0.0-RC1): + - QonversionSandwich (= 1.0.0-RC6) - React - React-perflogger (0.70.5) - React-RCTActionSheet (0.70.5): @@ -535,7 +535,7 @@ SPEC CHECKSUMS: libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c Qonversion: 67d0a7b1e47e8205c83b6b03dc842aedb04827ac - QonversionSandwich: af20860c996123dbe8e2dd3a59cb1edad02f1edb + QonversionSandwich: 06524d9fc86f2068c06b8c6fa1929a0ef4e63470 RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda RCTRequired: 21229f84411088e5d8538f21212de49e46cc83e2 RCTTypeSafety: 62eed57a32924b09edaaf170a548d1fc96223086 @@ -550,7 +550,7 @@ SPEC CHECKSUMS: React-jsiexecutor: 31564fa6912459921568e8b0e49024285a4d584b React-jsinspector: badd81696361249893a80477983e697aab3c1a34 React-logger: fdda34dd285bdb0232e059b19d9606fa0ec3bb9c - react-native-qonversion: ad66dd9bf1eae3c8534f30cc3e8d9e6dabf19909 + react-native-qonversion: d3bcca0746037a4975daa426fd13a367758ed441 React-perflogger: e68d3795cf5d247a0379735cbac7309adf2fb931 React-RCTActionSheet: 05452c3b281edb27850253db13ecd4c5a65bc247 React-RCTAnimation: 578eebac706428e68466118e84aeacf3a282b4da diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 8c9dfe4..941eb12 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -1,5 +1,5 @@ def update_js(new_version) - path = Dir['../src/**/Qonversion.ts'].first + path = Dir['../src/**/QonversionInternal.ts'].first regex = /const sdkVersion = ".*";/ result_value = "const sdkVersion = \"#{new_version}\";" @@ -111,4 +111,4 @@ lane :provide_next_patch_version do tag = get_tag new_version = calculate_patch_version(tag) sh("echo version=#{new_version} >> \"$GITHUB_ENV\"") -end \ No newline at end of file +end diff --git a/package.json b/package.json index 8807e44..d9b1efe 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-native-qonversion", "title": "React Native Qonversion", - "version": "3.6.2", + "version": "4.0.0-RC1", "description": "Qonversion provides full in-app purchases infrastructure, so you do not need to build your own server for receipt validation. Implement in-app subscriptions, validate user receipts, check subscription status, and provide access to your app features and content using our StoreKit wrapper and Google Play Billing wrapper.", "main": "build/index.js", "types": "build/index.d.ts", diff --git a/react-native-qonversion.podspec b/react-native-qonversion.podspec index 93f6f5b..cf56722 100644 --- a/react-native-qonversion.podspec +++ b/react-native-qonversion.podspec @@ -22,5 +22,5 @@ Pod::Spec.new do |s| s.requires_arc = true s.dependency "React" - s.dependency "QonversionSandwich", "1.0.0-RC5" + s.dependency "QonversionSandwich", "1.0.0-RC6" end diff --git a/src/internal/QonversionInternal.ts b/src/internal/QonversionInternal.ts index 012256f..9dfcb6a 100644 --- a/src/internal/QonversionInternal.ts +++ b/src/internal/QonversionInternal.ts @@ -14,7 +14,7 @@ import QonversionConfig from '../QonversionConfig'; const {RNQonversion} = NativeModules; -const sdkVersion = "3.6.2"; +const sdkVersion = "4.0.0-RC1"; const EVENT_ENTITLEMENTS_UPDATED = "entitlements_updated"; const EVENT_PROMO_PURCHASE_RECEIVED = "promo_purchase_received"; From 8c68f19eb43bbeb5be6027eb09e54e976f7a072b Mon Sep 17 00:00:00 2001 From: SpertsyanKM Date: Wed, 14 Dec 2022 08:17:42 +0000 Subject: [PATCH 6/7] [create-pull-request] automated change --- package.json | 2 +- src/internal/QonversionInternal.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d9b1efe..c85a668 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-native-qonversion", "title": "React Native Qonversion", - "version": "4.0.0-RC1", + "version": "4.0.0", "description": "Qonversion provides full in-app purchases infrastructure, so you do not need to build your own server for receipt validation. Implement in-app subscriptions, validate user receipts, check subscription status, and provide access to your app features and content using our StoreKit wrapper and Google Play Billing wrapper.", "main": "build/index.js", "types": "build/index.d.ts", diff --git a/src/internal/QonversionInternal.ts b/src/internal/QonversionInternal.ts index 9dfcb6a..fb5e0d2 100644 --- a/src/internal/QonversionInternal.ts +++ b/src/internal/QonversionInternal.ts @@ -14,7 +14,7 @@ import QonversionConfig from '../QonversionConfig'; const {RNQonversion} = NativeModules; -const sdkVersion = "4.0.0-RC1"; +const sdkVersion = "4.0.0"; const EVENT_ENTITLEMENTS_UPDATED = "entitlements_updated"; const EVENT_PROMO_PURCHASE_RECEIVED = "promo_purchase_received"; From 95c1350e81acbc36ac5b55f32a4ccb7e04550d46 Mon Sep 17 00:00:00 2001 From: kamospertsyan Date: Wed, 14 Dec 2022 11:23:00 +0300 Subject: [PATCH 7/7] Sandwich upgrade to 1.0.0 --- android/build.gradle | 2 +- example/ios/Podfile.lock | 20 ++++++++++---------- react-native-qonversion.podspec | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 0ff94ad..67a4d5f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -65,7 +65,7 @@ repositories { dependencies { //noinspection GradleDynamicVersion implementation 'com.facebook.react:react-native:+' // From node_modules - implementation "io.qonversion.sandwich:sandwich:1.0.0-RC6" + implementation "io.qonversion.sandwich:sandwich:1.0.0" } afterEvaluate { project -> diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index e5da616..6b48f31 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -75,11 +75,11 @@ PODS: - glog (0.3.5) - libevent (2.1.12) - OpenSSL-Universal (1.1.1100) - - Qonversion (3.0.0-RC3): - - Qonversion/Main (= 3.0.0-RC3) - - Qonversion/Main (3.0.0-RC3) - - QonversionSandwich (1.0.0-RC6): - - Qonversion (= 3.0.0-RC3) + - Qonversion (3.0.0): + - Qonversion/Main (= 3.0.0) + - Qonversion/Main (3.0.0) + - QonversionSandwich (1.0.0): + - Qonversion (= 3.0.0) - RCT-Folly (2021.07.22.00): - boost - DoubleConversion @@ -289,8 +289,8 @@ PODS: - React-jsinspector (0.70.5) - React-logger (0.70.5): - glog - - react-native-qonversion (4.0.0-RC1): - - QonversionSandwich (= 1.0.0-RC6) + - react-native-qonversion (4.0.0): + - QonversionSandwich (= 1.0.0) - React - React-perflogger (0.70.5) - React-RCTActionSheet (0.70.5): @@ -534,8 +534,8 @@ SPEC CHECKSUMS: glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c - Qonversion: 67d0a7b1e47e8205c83b6b03dc842aedb04827ac - QonversionSandwich: 06524d9fc86f2068c06b8c6fa1929a0ef4e63470 + Qonversion: 2f9652add78b418f9f284a2a67d9e087d2dd60b7 + QonversionSandwich: b830f7655124c1be602ff36729af3afe1cc667c9 RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda RCTRequired: 21229f84411088e5d8538f21212de49e46cc83e2 RCTTypeSafety: 62eed57a32924b09edaaf170a548d1fc96223086 @@ -550,7 +550,7 @@ SPEC CHECKSUMS: React-jsiexecutor: 31564fa6912459921568e8b0e49024285a4d584b React-jsinspector: badd81696361249893a80477983e697aab3c1a34 React-logger: fdda34dd285bdb0232e059b19d9606fa0ec3bb9c - react-native-qonversion: d3bcca0746037a4975daa426fd13a367758ed441 + react-native-qonversion: 78c454fa9ae15c538fd22e710962a694b24f4c0b React-perflogger: e68d3795cf5d247a0379735cbac7309adf2fb931 React-RCTActionSheet: 05452c3b281edb27850253db13ecd4c5a65bc247 React-RCTAnimation: 578eebac706428e68466118e84aeacf3a282b4da diff --git a/react-native-qonversion.podspec b/react-native-qonversion.podspec index cf56722..337df72 100644 --- a/react-native-qonversion.podspec +++ b/react-native-qonversion.podspec @@ -22,5 +22,5 @@ Pod::Spec.new do |s| s.requires_arc = true s.dependency "React" - s.dependency "QonversionSandwich", "1.0.0-RC6" + s.dependency "QonversionSandwich", "1.0.0" end