diff --git a/android/src/main/java/io/invertase/googleads/ReactNativeAppModule.java b/android/src/main/java/io/invertase/googleads/ReactNativeAppModule.java new file mode 100644 index 00000000..755aa0de --- /dev/null +++ b/android/src/main/java/io/invertase/googleads/ReactNativeAppModule.java @@ -0,0 +1,173 @@ +package io.invertase.googleads; + +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableMap; +import io.invertase.googleads.common.ReactNativeApp; +import io.invertase.googleads.common.RCTConvert; +import io.invertase.googleads.common.ReactNativeEvent; +import io.invertase.googleads.common.ReactNativeEventEmitter; +import io.invertase.googleads.common.ReactNativeJSON; +import io.invertase.googleads.common.ReactNativeMeta; +import io.invertase.googleads.common.ReactNativePreferences; +import io.invertase.googleads.common.ReactNativeModule; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ReactNativeAppModule extends ReactNativeModule { + private static final String TAG = "RNAppModule"; + + ReactNativeAppModule(ReactApplicationContext reactContext) { + super(reactContext, TAG); + } + + @Override + public void initialize() { + super.initialize(); + ReactNativeEventEmitter.getSharedInstance().attachReactContext(getContext()); + } + + @ReactMethod + public void initializeApp(ReadableMap options, ReadableMap appConfig, Promise promise) { + // ReactNativeApp reactNativeApp = + // RCTConvertFirebase.readableMapToFirebaseApp(options, appConfig, getContext()); + + // WritableMap reactNativeAppMap = RCTConvertFirebase.reactNativeAppToWritableMap(reactNativeApp); + // promise.resolve(reactNativeAppMap); + promise.resolve(options); + } + + @ReactMethod + public void setAutomaticDataCollectionEnabled(String appName, Boolean enabled) { + // ReactNativeApp reactNativeApp = ReactNativeApp.getInstance(appName); + // reactNativeApp.setDataCollectionDefaultEnabled(enabled); + } + + @ReactMethod + public void deleteApp(String appName, Promise promise) { + // ReactNativeApp reactNativeApp = ReactNativeApp.getInstance(appName); + + // if (reactNativeApp != null) { + // reactNativeApp.delete(); + // } + + promise.resolve(null); + } + + @ReactMethod + public void eventsNotifyReady(Boolean ready) { + ReactNativeEventEmitter emitter = ReactNativeEventEmitter.getSharedInstance(); + emitter.notifyJsReady(ready); + } + + @ReactMethod + public void eventsGetListeners(Promise promise) { + ReactNativeEventEmitter emitter = ReactNativeEventEmitter.getSharedInstance(); + promise.resolve(emitter.getListenersMap()); + } + + @ReactMethod + public void eventsPing(String eventName, ReadableMap eventBody, Promise promise) { + ReactNativeEventEmitter emitter = ReactNativeEventEmitter.getSharedInstance(); + emitter.sendEvent( + new ReactNativeEvent( + eventName, RCTConvert.readableMapToWritableMap(eventBody))); + promise.resolve(RCTConvert.readableMapToWritableMap(eventBody)); + } + + @ReactMethod + public void eventsAddListener(String eventName) { + ReactNativeEventEmitter emitter = ReactNativeEventEmitter.getSharedInstance(); + emitter.addListener(eventName); + } + + @ReactMethod + public void eventsRemoveListener(String eventName, Boolean all) { + ReactNativeEventEmitter emitter = ReactNativeEventEmitter.getSharedInstance(); + emitter.removeListener(eventName, all); + } + + @ReactMethod + public void addListener(String eventName) { + // Keep: Required for RN built in Event Emitter Calls. + } + + @ReactMethod + public void removeListeners(Integer count) { + // Keep: Required for RN built in Event Emitter Calls. + } + + /** ------------------ META ------------------ */ + @ReactMethod + public void metaGetAll(Promise promise) { + promise.resolve(ReactNativeMeta.getSharedInstance().getAll()); + } + + /** ------------------ JSON ------------------ */ + @ReactMethod + public void jsonGetAll(Promise promise) { + promise.resolve(ReactNativeJSON.getSharedInstance().getAll()); + } + + /** ------------------ PREFERENCES ------------------ */ + @ReactMethod + public void preferencesSetBool(String key, boolean value, Promise promise) { + ReactNativePreferences.getSharedInstance().setBooleanValue(key, value); + promise.resolve(null); + } + + @ReactMethod + public void preferencesSetString(String key, String value, Promise promise) { + ReactNativePreferences.getSharedInstance().setStringValue(key, value); + promise.resolve(null); + } + + @ReactMethod + public void preferencesGetAll(Promise promise) { + promise.resolve(ReactNativePreferences.getSharedInstance().getAll()); + } + + @ReactMethod + public void preferencesClearAll(Promise promise) { + ReactNativePreferences.getSharedInstance().clearAll(); + promise.resolve(null); + } + + @Override + public Map getConstants() { + Map constants = new HashMap<>(); + // List> appsList = new ArrayList<>(); + // List reactNativeApps = ReactNativeApp.getApps(getReactApplicationContext()); + + // for (ReactNativeApp app : reactNativeApps) { + // appsList.add(RCTConvertFirebase.reactNativeAppToMap(app)); + // } + + // constants.put("NATIVE_FIREBASE_APPS", appsList); + + // constants.put("FIREBASE_RAW_JSON", ReactNativeJSON.getSharedInstance().getRawJSON()); + + return constants; + } +} diff --git a/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsConsentModule.java b/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsConsentModule.java index 93fa031f..2ecb5938 100644 --- a/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsConsentModule.java +++ b/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsConsentModule.java @@ -38,7 +38,7 @@ import java.util.List; public class ReactNativeGoogleAdsConsentModule extends ReactNativeModule { - private static final String TAG = "GoogleAdsConsent"; + private static final String TAG = "RNGoogleAdsConsentModule"; private ConsentInformation consentInformation; private ConsentForm consentForm; diff --git a/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsInterstitialModule.java b/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsInterstitialModule.java index 62eb089a..044fab72 100644 --- a/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsInterstitialModule.java +++ b/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsInterstitialModule.java @@ -41,7 +41,7 @@ import javax.annotation.Nullable; public class ReactNativeGoogleAdsInterstitialModule extends ReactNativeModule { - private static final String SERVICE = "GoogleAdsInterstitial"; + private static final String SERVICE = "RNGoogleAdsInterstitialModule"; private static SparseArray interstitialAdArray = new SparseArray<>(); public ReactNativeGoogleAdsInterstitialModule(ReactApplicationContext reactContext) { diff --git a/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsModule.java b/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsModule.java index ad4f0212..79758afd 100644 --- a/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsModule.java +++ b/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsModule.java @@ -27,7 +27,7 @@ import java.util.Objects; public class ReactNativeGoogleAdsModule extends ReactNativeModule { - private static final String SERVICE = "GoogleAds"; + private static final String SERVICE = "RNGoogleAdsModule"; ReactNativeGoogleAdsModule(ReactApplicationContext reactContext) { super(reactContext, SERVICE); diff --git a/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsPackage.java b/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsPackage.java index f9f30296..64aab3e3 100644 --- a/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsPackage.java +++ b/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsPackage.java @@ -31,6 +31,7 @@ public class ReactNativeGoogleAdsPackage implements ReactPackage { @Override public List createNativeModules(ReactApplicationContext reactContext) { List modules = new ArrayList<>(); + modules.add(new ReactNativeAppModule(reactContext)); modules.add(new ReactNativeGoogleAdsModule(reactContext)); modules.add(new ReactNativeGoogleAdsConsentModule(reactContext)); modules.add(new ReactNativeGoogleAdsInterstitialModule(reactContext)); diff --git a/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsRewardedModule.java b/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsRewardedModule.java index 12f80f89..6c670866 100644 --- a/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsRewardedModule.java +++ b/android/src/main/java/io/invertase/googleads/ReactNativeGoogleAdsRewardedModule.java @@ -27,7 +27,7 @@ import io.invertase.googleads.common.ReactNativeModule; public class ReactNativeGoogleAdsRewardedModule extends ReactNativeModule { - private static final String SERVICE = "GoogleAdsRewarded"; + private static final String SERVICE = "RNGoogleAdsRewardedModule"; private static SparseArray rewardedAdArray = new SparseArray<>(); public ReactNativeGoogleAdsRewardedModule(ReactApplicationContext reactContext) { diff --git a/android/src/main/java/io/invertase/googleads/common/RCTConvert.java b/android/src/main/java/io/invertase/googleads/common/RCTConvert.java new file mode 100644 index 00000000..fd53de1c --- /dev/null +++ b/android/src/main/java/io/invertase/googleads/common/RCTConvert.java @@ -0,0 +1,118 @@ +package io.invertase.googleads.common; + +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import android.content.Context; +import android.util.Log; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableMap; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * Utilities to convert to and from React Native bridge formats. + */ +public class RCTConvert { + private static String TAG = "RCTConvert"; + + /** + * Takes a value and calls the appropriate setter for its type on the target map + key + * + * @param key String key to set on target map + * @param value Object value to set on target map + * @param map WritableMap target map to write the value to + */ + @SuppressWarnings("unchecked") + public static WritableMap mapPutValue(String key, @Nullable Object value, WritableMap map) { + if (value == null) { + map.putNull(key); + return map; + } + + String type = value.getClass().getName(); + + switch (type) { + case "java.lang.Boolean": + map.putBoolean(key, (Boolean) value); + break; + case "java.lang.Long": + Long longVal = (Long) value; + map.putDouble(key, (double) longVal); + break; + case "java.lang.Float": + float floatVal = (float) value; + map.putDouble(key, (double) floatVal); + break; + case "java.lang.Double": + map.putDouble(key, (Double) value); + break; + case "java.lang.Integer": + map.putInt(key, (int) value); + break; + case "java.lang.String": + map.putString(key, (String) value); + break; + case "org.json.JSONObject$1": + map.putString(key, value.toString()); + break; + default: + if (List.class.isAssignableFrom(value.getClass())) { + map.putArray(key, Arguments.makeNativeArray((List) value)); + } else if (Map.class.isAssignableFrom(value.getClass())) { + WritableMap childMap = Arguments.createMap(); + Map valueMap = (Map) value; + + for (Map.Entry entry : valueMap.entrySet()) { + mapPutValue(entry.getKey(), entry.getValue(), childMap); + } + + map.putMap(key, childMap); + } else { + Log.d(TAG, "utils:mapPutValue:unknownType:" + type); + map.putNull(key); + } + } + + return map; + } + + // TODO Remove me - also in SharedUtils + public static WritableMap readableMapToWritableMap(ReadableMap map) { + WritableMap writableMap = Arguments.createMap(); + // https://github.com/facebook/react-native/blob/main/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.java#L58 + writableMap.merge(map); + return writableMap; + } + + public static Map toHashMap(ReadableMap readableMap) { + // https://github.com/facebook/react-native/blob/main/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.java#L263 + return readableMap.toHashMap(); + } + + public static List toArrayList(ReadableArray readableArray) { + // https://github.com/facebook/react-native/blob/main/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.java#L140 + return readableArray.toArrayList(); + } +} diff --git a/android/src/main/java/io/invertase/googleads/common/ReactNativeEventEmitter.java b/android/src/main/java/io/invertase/googleads/common/ReactNativeEventEmitter.java index 330c4161..fca9dfa9 100644 --- a/android/src/main/java/io/invertase/googleads/common/ReactNativeEventEmitter.java +++ b/android/src/main/java/io/invertase/googleads/common/ReactNativeEventEmitter.java @@ -139,7 +139,7 @@ private boolean emit(final NativeEvent event) { try { reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit(event.getEventName(), event.getEventBody()); + .emit("rnapp_" + event.getEventName(), event.getEventBody()); } catch (Exception e) { Log.wtf("RN_EVENT_EMITTER", "Error sending Event " + event.getEventName(), e); return false; diff --git a/android/src/main/java/io/invertase/googleads/common/ReactNativeModule.java b/android/src/main/java/io/invertase/googleads/common/ReactNativeModule.java index f5d33b9f..40546baa 100644 --- a/android/src/main/java/io/invertase/googleads/common/ReactNativeModule.java +++ b/android/src/main/java/io/invertase/googleads/common/ReactNativeModule.java @@ -99,7 +99,7 @@ public Activity getActivity() { @Nonnull @Override public String getName() { - return "RNAdMob" + moduleName + "Module"; + return moduleName; } @Override diff --git a/ios/RNGoogleAds/common/RNRCTEventEmitter.m b/ios/RNGoogleAds/common/RNRCTEventEmitter.m index e10fa73c..d4088ce9 100644 --- a/ios/RNGoogleAds/common/RNRCTEventEmitter.m +++ b/ios/RNGoogleAds/common/RNRCTEventEmitter.m @@ -75,7 +75,7 @@ - (void)notifyJsReady:(BOOL)jsReady { - (void)sendEventWithName:(NSString *)eventName body:(id)body { @synchronized(self.jsListeners) { if (self.bridge && self.isObserving && self.jsListeners[eventName] != nil) { - NSString *prefixedEventName = [@"rnfb_" stringByAppendingString:eventName]; + NSString *prefixedEventName = [@"rnapp_" stringByAppendingString:eventName]; [self.bridge enqueueJSCall:@"RCTDeviceEventEmitter" method:@"emit" args:body ? @[ prefixedEventName, body ] : @[ prefixedEventName ] diff --git a/lib/index.js b/lib/index.js index fecb590e..13322c49 100644 --- a/lib/index.js +++ b/lib/index.js @@ -18,17 +18,45 @@ import { Module } from './internal'; import validateAdRequestConfiguration from './validateAdRequestConfiguration'; import version from './version'; +import AdEventType from './AdEventType'; +import AdsConsentDebugGeography from './AdsConsentDebugGeography'; +import AdsConsentStatus from './AdsConsentStatus'; +import MaxAdContentRating from './MaxAdContentRating'; +import RewardedAdEventType from './RewardedAdEventType'; +import BannerAdSize from './BannerAdSize'; +import TestIds from './TestIds'; + +const statics = { + AdsConsentDebugGeography, + AdsConsentStatus, + AdEventType, + RewardedAdEventType, + MaxAdContentRating, + TestIds, + BannerAdSize, +}; + +const namespace = 'google_ads'; + +const nativeModuleName = [ + 'RNGoogleAdsModule', + 'RNGoogleAdsInterstitialModule', + 'RNGoogleAdsRewardedModule', +]; class GoogleAdsModule extends Module { constructor(...args) { super(...args); - this.emitter.addListener('interstitial_event', event => { - this.emitter.emit(`interstitial_event:${event.adUnitId}:${event.requestId}`, event); + this.emitter.addListener('google_ads_interstitial_event', event => { + this.emitter.emit( + `google_ads_interstitial_event:${event.adUnitId}:${event.requestId}`, + event, + ); }); - this.emitter.addListener('rewarded_event', event => { - this.emitter.emit(`rewarded_event:${event.adUnitId}:${event.requestId}`, event); + this.emitter.addListener('google_ads_rewarded_event', event => { + this.emitter.emit(`google_ads_rewarded_event:${event.adUnitId}:${event.requestId}`, event); }); } @@ -47,7 +75,13 @@ class GoogleAdsModule extends Module { // import { SDK_VERSION } from '@invertase/react-native-google-ads'; export const SDK_VERSION = version; -export default new GoogleAdsModule(); +export default new GoogleAdsModule('AppName', { + statics, + version, + namespace, + nativeModuleName, + nativeEvents: ['google_ads_interstitial_event', 'google_ads_rewarded_event'], +}); export { default as AdsConsentDebugGeography } from './AdsConsentDebugGeography'; export { default as AdsConsentStatus } from './AdsConsentStatus'; diff --git a/lib/internal/GoogleAdsNativeEventEmitter.js b/lib/internal/GoogleAdsNativeEventEmitter.js index 7a2d2282..3b6f2a81 100644 --- a/lib/internal/GoogleAdsNativeEventEmitter.js +++ b/lib/internal/GoogleAdsNativeEventEmitter.js @@ -17,35 +17,35 @@ import { NativeEventEmitter, NativeModules } from 'react-native'; -const { RNGoogleAdsModule } = NativeModules; +const { RNAppModule } = NativeModules; class GoogleAdsNativeEventEmitter extends NativeEventEmitter { constructor() { - super(RNGoogleAdsModule); + super(RNAppModule); this.ready = false; } addListener(eventType, listener, context) { if (!this.ready) { - RNGoogleAdsModule.eventsNotifyReady(true); + RNAppModule.eventsNotifyReady(true); this.ready = true; } - RNGoogleAdsModule.eventsAddListener(eventType); + RNAppModule.eventsAddListener(eventType); - let subscription = super.addListener(`google_ads_${eventType}`, listener, context); + let subscription = super.addListener(`rnapp_${eventType}`, listener, context); // React Native 0.65+ altered EventEmitter: // - removeSubscription is gone // - addListener returns an unsubscriber instead of a more complex object with eventType etc // make sure eventType for backwards compatibility just in case - subscription.eventType = `google_ads_${eventType}`; + subscription.eventType = `rnapp_${eventType}`; // New style is to return a remove function on the object, just in csae people call that, // we will modify it to do our native unsubscription then call the original let originalRemove = subscription.remove; let newRemove = () => { - RNGoogleAdsModule.eventsRemoveListener(eventType, false); + RNAppModule.eventsRemoveListener(eventType, false); if (super.removeSubscription != null) { // This is for RN <= 0.64 - 65 and greater no longer have removeSubscription super.removeSubscription(subscription); @@ -59,13 +59,13 @@ class GoogleAdsNativeEventEmitter extends NativeEventEmitter { } removeAllListeners(eventType) { - RNGoogleAdsModule.eventsRemoveListener(eventType, true); - super.removeAllListeners(`google_ads_${eventType}`); + RNAppModule.eventsRemoveListener(eventType, true); + super.removeAllListeners(`rnapp_${eventType}`); } // This is likely no longer ever called, but it is here for backwards compatibility with RN <= 0.64 removeSubscription(subscription) { - RNGoogleAdsModule.eventsRemoveListener(subscription.eventType.replace('google_ads_'), false); + RNAppModule.eventsRemoveListener(subscription.eventType.replace('rnapp_'), false); if (super.removeSubscription) { super.removeSubscription(subscription); } diff --git a/lib/internal/registry/nativeModule.js b/lib/internal/registry/nativeModule.js index 55309df8..47c2987c 100644 --- a/lib/internal/registry/nativeModule.js +++ b/lib/internal/registry/nativeModule.js @@ -88,7 +88,9 @@ function initialiseNativeModule(module) { const config = module._config; const key = nativeModuleKey(module); const { namespace, nativeEvents, nativeModuleName } = config; - const nativeModuleNames = [nativeModuleName]; + const multiModuleRoot = {}; + const multiModule = Array.isArray(nativeModuleName); + const nativeModuleNames = multiModule ? nativeModuleName : [nativeModuleName]; for (let i = 0; i < nativeModuleNames.length; i++) { const nativeModule = NativeModules[nativeModuleNames[i]]; @@ -99,6 +101,10 @@ function initialiseNativeModule(module) { throw new Error(getMissingModuleHelpText(namespace)); } + if (multiModule) { + multiModuleRoot[nativeModuleNames[i]] = !!nativeModule; + } + const argToPrepend = []; Object.assign(multiModuleRoot, nativeModuleWrapped(namespace, nativeModule, argToPrepend));