diff --git a/.gitignore b/.gitignore index b658d0bb..6cb7556c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,15 +15,16 @@ examples/CocoapodsDemo/ios/CocoapodsDemo.xcodeproj/xcuserdata examples/CocoapodsDemo/ios/CocoapodsDemo.xcworkspace/xcshareddata examples/CocoapodsDemo/ios/CocoapodsDemo.xcworkspace/xcuserdata examples/CocoapodsDemo/ios/Pods/Pods.xcodeproj/xcuserdata -examples/RNOneSignal/android/app/build -examples/RNOneSignal/android/.gradle -examples/RNOneSignal/android/.idea -examples/RNOneSignal/android/build -examples/RNOneSignal/ios/RNOneSignal.xcodeproj/project.xcworkspace/xcshareddata -examples/RNOneSignal/ios/RNOneSignal.xcodeproj/project.xcworkspace/xcuserdata -examples/RNOneSignal/ios/RNOneSignal.xcodeproj/xcuserdata -examples/RNOneSignal/ios/RNOneSignal.xcworkspace/xcshareddata -examples/RNOneSignal/ios/RNOneSignal.xcworkspace/xcuserdata +examples/RNOneSignalTS/android/app/build +examples/RNOneSignalTS/android/app/debug.keystore +examples/RNOneSignalTS/android/.gradle +examples/RNOneSignalTS/android/.idea +examples/RNOneSignalTS/android/build +examples/RNOneSignalTS/ios/RNOneSignal.xcodeproj/project.xcworkspace/xcshareddata +examples/RNOneSignalTS/ios/RNOneSignal.xcodeproj/project.xcworkspace/xcuserdata +examples/RNOneSignalTS/ios/RNOneSignal.xcodeproj/xcuserdata +examples/RNOneSignalTS/ios/RNOneSignal.xcworkspace/xcshareddata +examples/RNOneSignalTS/ios/RNOneSignal.xcworkspace/xcuserdata ios/RCTOneSignal.xcodeproj/project.xcworkspace/xcshareddata examples/RNOneSignal/ios/Pods/Pods.xcodeproj/xcuserdata android/.idea diff --git a/android/build.gradle b/android/build.gradle index 6e670329..8960e7f0 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -24,50 +24,11 @@ dependencies { // api is used instead of implementation so the parent :app project can access any of the OneSignal Java // classes if needed. Such as com.onesignal.NotificationExtenderService - api 'com.onesignal:OneSignal:3.15.6' + api 'com.onesignal:OneSignal:4.1.0' testImplementation 'junit:junit:4.12' } -// Adds required manifestPlaceholders keys to allow mainifest merge gradle step to complete -// The OneSignal app id should be set in your JS code. -// Google project number / FCM Sender ID will be pulled in from the OneSignal dashbaord -class DefaultManifestPlaceHolders { - static final MANIFEST_PLACEHOLDERS_DEFAULTS = [ - onesignal_app_id: '', - onesignal_google_project_number: 'REMOTE' - ] - - static void addManifestToAppProject(Project proj) { - def androidApp = proj.android - MANIFEST_PLACEHOLDERS_DEFAULTS.each { defKey, defValue -> - if (!androidApp.defaultConfig.manifestPlaceholders.containsKey(defKey)) { - androidApp.defaultConfig.manifestPlaceholders[defKey] = defValue - - androidApp.buildTypes.each { buildType -> - if (!buildType.manifestPlaceholders.containsKey(defKey)) - buildType.manifestPlaceholders[defKey] = defValue - } - } - } - } -} - -rootProject.childProjects.each { projName, proj -> - if (projName != 'app' && projName != 'react-native-onesignal') - return - - if (proj.hasProperty('android')) { - DefaultManifestPlaceHolders.addManifestToAppProject(proj) - return - } - - proj.afterEvaluate { - DefaultManifestPlaceHolders.addManifestToAppProject(proj) - } -} - - // Add the following to the top (Line 1) of your app/build.gradle if you run into any issues with duplicate classes. // Such as the following error // Error: more than one library with package name 'com.google.android.gms.license' diff --git a/android/src/main/java/com/geektime/rnonesignalandroid/NotificationNotDisplayingExtender.java b/android/src/main/java/com/geektime/rnonesignalandroid/NotificationNotDisplayingExtender.java deleted file mode 100644 index e4486f7d..00000000 --- a/android/src/main/java/com/geektime/rnonesignalandroid/NotificationNotDisplayingExtender.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.geektime.rnonesignalandroid; - -import android.util.Log; - -import com.onesignal.NotificationExtenderService; -import com.onesignal.OSNotificationReceivedResult; - -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Created by Andrey Beletsky on 6/5/17. - */ -public class NotificationNotDisplayingExtender extends NotificationExtenderService { - @Override - protected boolean onNotificationProcessing(OSNotificationReceivedResult receivedResult) { - JSONObject additionalData = receivedResult.payload.additionalData; - boolean hidden = false; - try { - if (additionalData.has(RNOneSignal.HIDDEN_MESSAGE_KEY)) { - hidden = additionalData.getBoolean(RNOneSignal.HIDDEN_MESSAGE_KEY); - } - } catch (JSONException e) { - Log.e("OneSignal", "onNotificationProcessing Failure: " + e.getMessage()); - } - - // Return true to stop the notification from displaying. - return hidden; - } -} diff --git a/android/src/main/java/com/geektime/rnonesignalandroid/RNOneSignal.java b/android/src/main/java/com/geektime/rnonesignalandroid/RNOneSignal.java index 7900424a..6a9fa4b5 100644 --- a/android/src/main/java/com/geektime/rnonesignalandroid/RNOneSignal.java +++ b/android/src/main/java/com/geektime/rnonesignalandroid/RNOneSignal.java @@ -1,78 +1,117 @@ +/* +Modified MIT License + +Copyright 2020 OneSignal + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +2. All copies of substantial portions of the Software may only be used in connection +with services provided by OneSignal. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +Authors: + - Avishay Bar (creator) - 1/31/16 + - Brad Hesse + - Josh Kasten + - Rodrigo Gomez-Palacio + - Michael DiCioccio +*/ + package com.geektime.rnonesignalandroid; import java.util.Iterator; +import java.util.HashMap; import android.content.Context; import android.os.Bundle; import android.util.Log; import android.content.pm.ApplicationInfo; -import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.Callback; -import com.facebook.react.bridge.LifecycleEventListener; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.WritableNativeMap; +import com.facebook.react.bridge.LifecycleEventListener; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.modules.core.DeviceEventManagerModule; -import com.facebook.react.bridge.Promise; -import com.onesignal.OSInAppMessageAction; -import com.onesignal.OSPermissionState; -import com.onesignal.OSPermissionSubscriptionState; -import com.onesignal.OSSubscriptionState; -import com.onesignal.OSEmailSubscriptionState; + +import com.onesignal.OSEmailSubscriptionStateChanges; import com.onesignal.OneSignal; -import com.onesignal.OneSignal.EmailUpdateHandler; -import com.onesignal.OneSignal.EmailUpdateError; -import com.onesignal.OneSignal.OSExternalUserIdUpdateCompletionHandler; +import com.onesignal.OSOutcomeEvent; +import com.onesignal.OSDeviceState; +import com.onesignal.OSInAppMessageAction; +import com.onesignal.OSNotification; +import com.onesignal.OSNotificationReceivedEvent; +import com.onesignal.OSNotificationOpenedResult; import com.onesignal.OneSignal.OutcomeCallback; +import com.onesignal.OneSignal.EmailUpdateError; -import com.onesignal.OneSignal.InAppMessageClickHandler; -import com.onesignal.OneSignal.NotificationOpenedHandler; -import com.onesignal.OneSignal.NotificationReceivedHandler; -import com.onesignal.OSNotificationOpenResult; -import com.onesignal.OSNotification; -import com.onesignal.OutcomeEvent; +import com.onesignal.OneSignal.EmailUpdateHandler; +import com.onesignal.OneSignal.OSInAppMessageClickHandler; +import com.onesignal.OneSignal.OSNotificationOpenedHandler; + +import com.onesignal.OSPermissionObserver; +import com.onesignal.OSSubscriptionObserver; +import com.onesignal.OSEmailSubscriptionObserver; + +import com.onesignal.OSPermissionStateChanges; +import com.onesignal.OSSubscriptionStateChanges; import org.json.JSONObject; import org.json.JSONArray; import org.json.JSONException; +public class RNOneSignal extends ReactContextBaseJavaModule + implements + OSPermissionObserver, + OSSubscriptionObserver, + OSNotificationOpenedHandler, + OSEmailSubscriptionObserver, + LifecycleEventListener, + OSInAppMessageClickHandler +{ -/** - * Created by Avishay on 1/31/16. - */ -public class RNOneSignal extends ReactContextBaseJavaModule implements LifecycleEventListener, NotificationReceivedHandler, NotificationOpenedHandler, InAppMessageClickHandler { public static final String HIDDEN_MESSAGE_KEY = "hidden"; private ReactApplicationContext mReactApplicationContext; private ReactContext mReactContext; + private boolean oneSignalInitDone; - private boolean registeredEvents = false; - private OSNotificationOpenResult coldStartNotificationResult; private OSInAppMessageAction inAppMessageActionResult; - private boolean hasSetNotificationOpenedHandler = false; - private boolean hasSetInAppClickedHandler = false; - private boolean hasSetRequiresPrivacyConsent = false; - private boolean waitingForUserPrivacyConsent = false; + private HashMap notificationReceivedEventCache; - //ensure only one callback exists at a given time due to react-native restriction + private boolean hasSetInAppClickedHandler = false; + private boolean hasSetSubscriptionObserver = false; + private boolean hasSetEmailSubscriptionObserver = false; + private boolean hasSetPermissionObserver = false; + + // A native module is supposed to invoke its callback only once. It can, however, store the callback and invoke it later. + // It is very important to highlight that the callback is not invoked immediately after the native function completes + // - remember that bridge communication is asynchronous, and this too is tied to the run loop. + // Once you have done invoke() on the callback, you cannot use it again. Store it here. private Callback pendingGetTagsCallback; - public RNOneSignal(ReactApplicationContext reactContext) { - super(reactContext); - mReactApplicationContext = reactContext; - mReactContext = reactContext; - mReactContext.addLifecycleEventListener(this); - initOneSignal(); - } - private String appIdFromManifest(ReactApplicationContext context) { try { ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(), context.getPackageManager().GET_META_DATA); @@ -84,21 +123,19 @@ private String appIdFromManifest(ReactApplicationContext context) { } } - // Initialize OneSignal only once when an Activity is available. - // React creates an instance of this class to late for OneSignal to get the current Activity - // based on registerActivityLifecycleCallbacks it uses to listen for the first Activity. - // However it seems it is also to soon to call getCurrentActivity() from the reactContext as well. - // This will normally succeed when onHostResume fires instead. - private void initOneSignal() { - // Uncomment to debug init issues. - // OneSignal.setLogLevel(OneSignal.LOG_LEVEL.VERBOSE, OneSignal.LOG_LEVEL.ERROR); - - OneSignal.sdkType = "react"; - - String appId = appIdFromManifest(mReactApplicationContext); + private void removeObservers() { + OneSignal.removeEmailSubscriptionObserver(this); + OneSignal.removePermissionObserver(this); + OneSignal.removeSubscriptionObserver(this); + hasSetEmailSubscriptionObserver = false; + hasSetPermissionObserver = false; + hasSetSubscriptionObserver = false; + } - if (appId != null && appId.length() > 0) - init(appId); + private void removeHandlers() { + OneSignal.setInAppMessageClickHandler(null); + OneSignal.setNotificationOpenedHandler(null); + OneSignal.setNotificationWillShowInForegroundHandler(null); } private void sendEvent(String eventName, Object params) { @@ -111,8 +148,21 @@ private JSONObject jsonFromErrorMessageString(String errorMessage) throws JSONEx return new JSONObject().put("error", errorMessage); } - @ReactMethod - public void init(String appId) { + public RNOneSignal(ReactApplicationContext reactContext) { + super(reactContext); + mReactApplicationContext = reactContext; + mReactContext = reactContext; + mReactContext.addLifecycleEventListener(this); + notificationReceivedEventCache = new HashMap(); + } + + // Initialize OneSignal only once when an Activity is available. + // React creates an instance of this class to late for OneSignal to get the current Activity + // based on registerActivityLifecycleCallbacks it uses to listen for the first Activity. + // However it seems it is also to soon to call getCurrentActivity() from the reactContext as well. + // This will normally succeed when onHostResume fires instead. + private void initOneSignal() { + OneSignal.sdkType = "react"; Context context = mReactApplicationContext.getCurrentActivity(); if (oneSignalInitDone) { @@ -122,7 +172,6 @@ public void init(String appId) { oneSignalInitDone = true; - OneSignal.sdkType = "react"; if (context == null) { // in some cases, especially when react-native-navigation is installed, @@ -130,11 +179,69 @@ public void init(String appId) { context = mReactApplicationContext.getApplicationContext(); } - OneSignal.getCurrentOrNewInitBuilder().setInAppMessageClickHandler(this); - OneSignal.init(context, null, appId, this, this); + OneSignal.setInAppMessageClickHandler(this); + OneSignal.initWithContext(context); + } + + @ReactMethod + public void setAppId(String appId) { + OneSignal.setAppId(appId); + } + + /* Observers */ + @Override + public void onOSPermissionChanged(OSPermissionStateChanges stateChanges) { + Log.i("Onesignal", "sending permission change event"); + sendEvent("OneSignal-permissionChanged", RNUtils.jsonToWritableMap(stateChanges.toJSONObject())); + } + + @Override + public void onOSSubscriptionChanged(OSSubscriptionStateChanges stateChanges) { + Log.i("Onesignal", "sending subscription change event"); + sendEvent("OneSignal-subscriptionChanged", RNUtils.jsonToWritableMap(stateChanges.toJSONObject())); + } - if (this.hasSetRequiresPrivacyConsent) - this.waitingForUserPrivacyConsent = true; + @Override + public void onOSEmailSubscriptionChanged(OSEmailSubscriptionStateChanges stateChanges) { + Log.i("Onesignal", "sending email subscription change event"); + sendEvent("OneSignal-emailSubscriptionChanged", RNUtils.jsonToWritableMap(stateChanges.toJSONObject())); + } + + @ReactMethod + public void addPermissionObserver() { + if (!hasSetPermissionObserver) { + OneSignal.addPermissionObserver(this); + hasSetPermissionObserver = true; + } + } + + @ReactMethod + public void addSubscriptionObserver() { + if (!hasSetSubscriptionObserver) { + OneSignal.addSubscriptionObserver(this); + hasSetSubscriptionObserver = true; + } + } + + @ReactMethod + public void addEmailSubscriptionObserver() { + if (!hasSetEmailSubscriptionObserver) { + OneSignal.addEmailSubscriptionObserver(this); + hasSetEmailSubscriptionObserver = true; + } + } + + /* Other methods */ + + @ReactMethod + public void getDeviceState(Promise promise) { + OSDeviceState state = OneSignal.getDeviceState(); + promise.resolve(RNUtils.jsonToWritableMap(state.toJSONObject())); + } + + @ReactMethod + public void disablePush(boolean disable) { + OneSignal.disablePush(disable); } @ReactMethod @@ -147,12 +254,17 @@ public void sendTags(ReadableMap tags) { OneSignal.sendTags(RNUtils.readableMapToJson(tags)); } + @ReactMethod + public void deleteTags(ReadableArray tagKeys) { + OneSignal.deleteTags(RNUtils.convertReableArrayIntoStringCollection(tagKeys)); + } + @ReactMethod public void getTags(final Callback callback) { if (pendingGetTagsCallback == null) pendingGetTagsCallback = callback; - OneSignal.getTags(new OneSignal.GetTagsHandler() { + OneSignal.getTags(new OneSignal.OSGetTagsHandler() { @Override public void tagsAvailable(JSONObject tags) { if (pendingGetTagsCallback != null) @@ -201,96 +313,11 @@ public void onFailure(EmailUpdateError error) { }); } - @ReactMethod - public void idsAvailable() { - OneSignal.idsAvailable(new OneSignal.IdsAvailableHandler() { - public void idsAvailable(String userId, String registrationId) { - final WritableMap params = Arguments.createMap(); - - params.putString("userId", userId); - params.putString("pushToken", registrationId); - - sendEvent("OneSignal-idsAvailable", params); - } - }); - } - - // TODO: This needs to be split out into several different callbacks to the JS and connect - // to the correct native methods - @ReactMethod - public void getPermissionSubscriptionState(final Callback callback) { - OSPermissionSubscriptionState state = OneSignal.getPermissionSubscriptionState(); - - if (state == null) - return; - - OSPermissionState permissionState = state.getPermissionStatus(); - OSSubscriptionState subscriptionState = state.getSubscriptionStatus(); - OSEmailSubscriptionState emailSubscriptionState = state.getEmailSubscriptionStatus(); - - // Notifications enabled for app? (Android Settings) - boolean notificationsEnabled = permissionState.getEnabled(); - - // User subscribed to OneSignal? (automatically toggles with notificationsEnabled) - boolean subscriptionEnabled = subscriptionState.getSubscribed(); - - // User's original subscription preference (regardless of notificationsEnabled) - boolean userSubscriptionEnabled = subscriptionState.getUserSubscriptionSetting(); - - try { - JSONObject result = new JSONObject(); - - result.put("notificationsEnabled", notificationsEnabled) - .put("subscriptionEnabled", subscriptionEnabled) - .put("userSubscriptionEnabled", userSubscriptionEnabled) - .put("pushToken", subscriptionState.getPushToken()) - .put("userId", subscriptionState.getUserId()) - .put("emailUserId", emailSubscriptionState.getEmailUserId()) - .put("emailAddress", emailSubscriptionState.getEmailAddress()); - - Log.d("onesignal", "permission subscription state: " + result.toString()); - - callback.invoke(RNUtils.jsonToWritableMap(result)); - } catch (JSONException e) { - e.printStackTrace(); - } - } - - @ReactMethod - public void inFocusDisplaying(int displayOption) { - OneSignal.setInFocusDisplaying(displayOption); - } - - @ReactMethod - public void deleteTag(String key) { - OneSignal.deleteTag(key); - } - - @ReactMethod - public void enableVibrate(Boolean enable) { - OneSignal.enableVibrate(enable); - } - - @ReactMethod - public void enableSound(Boolean enable) { - OneSignal.enableSound(enable); - } - - @ReactMethod - public void setSubscription(Boolean enable) { - OneSignal.setSubscription(enable); - } - @ReactMethod public void promptLocation() { OneSignal.promptLocation(); } - @ReactMethod - public void syncHashedEmail(String email) { - OneSignal.syncHashedEmail(email); - } - @ReactMethod public void setLogLevel(int logLevel, int visualLogLevel) { OneSignal.setLogLevel(logLevel, visualLogLevel); @@ -302,52 +329,23 @@ public void setLocationShared(Boolean shared) { } @ReactMethod - public void postNotification(String contents, String data, String playerId, String otherParameters) { - try { - JSONObject postNotification = new JSONObject(); - postNotification.put("contents", new JSONObject(contents)); - - if (playerId != null) { - JSONArray playerIds = new JSONArray(playerId); - postNotification.put("include_player_ids", playerIds); - } + public void postNotification(String jsonObjectString, final Callback successCallback, final Callback failureCallback) { + OneSignal.postNotification( + jsonObjectString, + new OneSignal.PostNotificationResponseHandler() { + @Override + public void onSuccess(JSONObject response) { + Log.i("OneSignal", "postNotification Success: " + response.toString()); + successCallback.invoke(RNUtils.jsonToWritableMap(response)); + } - if (data != null) { - JSONObject additionalData = new JSONObject(); - additionalData.put("p2p_notification", new JSONObject(data)); - postNotification.put("data", additionalData); - } - - if (otherParameters != null && !otherParameters.trim().isEmpty()) { - JSONObject parametersJson = new JSONObject(otherParameters.trim()); - Iterator keys = parametersJson.keys(); - while (keys.hasNext()) { - String key = keys.next(); - postNotification.put(key, parametersJson.get(key)); - } - - if (parametersJson.has(HIDDEN_MESSAGE_KEY) && parametersJson.getBoolean(HIDDEN_MESSAGE_KEY)) { - postNotification.getJSONObject("data").put(HIDDEN_MESSAGE_KEY, true); - } + @Override + public void onFailure(JSONObject response) { + Log.e("OneSignal", "postNotification Failure: " + response.toString()); + failureCallback.invoke(RNUtils.jsonToWritableMap(response)); + } } - - OneSignal.postNotification( - postNotification, - new OneSignal.PostNotificationResponseHandler() { - @Override - public void onSuccess(JSONObject response) { - Log.i("OneSignal", "postNotification Success: " + response.toString()); - } - - @Override - public void onFailure(JSONObject response) { - Log.e("OneSignal", "postNotification Failure: " + response.toString()); - } - } - ); - } catch (JSONException e) { - e.printStackTrace(); - } + ); } @ReactMethod @@ -356,8 +354,8 @@ public void clearOneSignalNotifications() { } @ReactMethod - public void cancelNotification(int id) { - OneSignal.cancelNotification(id); + public void removeNotification(int id) { + OneSignal.removeNotification(id); } @ReactMethod @@ -379,11 +377,18 @@ public void userProvidedPrivacyConsent(Promise promise) { public void setExternalUserId(final String externalId, final String authHashToken, final Callback callback) { OneSignal.setExternalUserId(externalId, authHashToken, new OneSignal.OSExternalUserIdUpdateCompletionHandler() { @Override - public void onComplete(JSONObject results) { + public void onSuccess(JSONObject results) { Log.i("OneSignal", "Completed setting external user id: " + externalId + "with results: " + results.toString()); + if (callback != null) callback.invoke(RNUtils.jsonToWritableMap(results)); } + + @Override + public void onFailure(OneSignal.ExternalIdError error) { + if (callback != null) + callback.invoke(error.getMessage()); + } }); } @@ -391,41 +396,71 @@ public void onComplete(JSONObject results) { public void removeExternalUserId(final Callback callback) { OneSignal.removeExternalUserId(new OneSignal.OSExternalUserIdUpdateCompletionHandler() { @Override - public void onComplete(JSONObject results) { + public void onSuccess(JSONObject results) { Log.i("OneSignal", "Completed removing external user id with results: " + results.toString()); + if (callback != null) callback.invoke(RNUtils.jsonToWritableMap(results)); } + + @Override + public void onFailure(OneSignal.ExternalIdError error) { + if (callback != null) + callback.invoke(error.getMessage()); + } }); } + /* notification opened / received */ + @ReactMethod - public void initNotificationOpenedHandlerParams() { - this.hasSetNotificationOpenedHandler = true; - if (this.coldStartNotificationResult != null) { - this.notificationOpened(this.coldStartNotificationResult); - this.coldStartNotificationResult = null; - } + public void setNotificationOpenedHandler() { + OneSignal.setNotificationOpenedHandler(this); } @Override - public void notificationReceived(OSNotification notification) { - this.sendEvent("OneSignal-remoteNotificationReceived", RNUtils.jsonToWritableMap(notification.toJSONObject())); + public void notificationOpened(OSNotificationOpenedResult result) { + sendEvent("OneSignal-remoteNotificationOpened", RNUtils.jsonToWritableMap(result.toJSONObject())); } - @Override - public void notificationOpened(OSNotificationOpenResult result) { - if (!this.hasSetNotificationOpenedHandler) { - this.coldStartNotificationResult = result; + @ReactMethod + public void setNotificationWillShowInForegroundHandler() { + OneSignal.setNotificationWillShowInForegroundHandler(new OneSignal.OSNotificationWillShowInForegroundHandler() { + @Override + public void notificationWillShowInForeground(OSNotificationReceivedEvent notificationReceivedEvent) { + OSNotification notification = notificationReceivedEvent.getNotification(); + String notificationId = notification.getNotificationId(); + notificationReceivedEventCache.put(notificationId, notificationReceivedEvent); + + sendEvent("OneSignal-notificationWillShowInForeground", RNUtils.jsonToWritableMap(notification.toJSONObject())); + } + }); + } + + @ReactMethod + public void completeNotificationEvent(final String uuid, final boolean shouldDisplay) { + OSNotificationReceivedEvent receivedEvent = notificationReceivedEventCache.get(uuid); + + if (receivedEvent == null) { + Log.e("OneSignal", "(java): could not find cached notification received event with id "+uuid); return; } - this.sendEvent("OneSignal-remoteNotificationOpened", RNUtils.jsonToWritableMap(result.toJSONObject())); + + if (shouldDisplay) { + receivedEvent.complete(receivedEvent.getNotification()); + } else { + receivedEvent.complete(null); + } + + notificationReceivedEventCache.remove(uuid); } /** * In-App Messaging */ + /* triggers */ + @ReactMethod public void addTrigger(String key, Object object) { OneSignal.addTrigger(key, object); @@ -451,15 +486,26 @@ public void getTriggerValueForKey(String key, Promise promise) { promise.resolve(OneSignal.getTriggerValueForKey(key)); } + /* in app message click */ + @ReactMethod - public void pauseInAppMessages(Boolean pause) { - OneSignal.pauseInAppMessages(pause); + public void setInAppMessageClickHandler() { + OneSignal.setInAppMessageClickHandler(new OneSignal.OSInAppMessageClickHandler() { + @Override + public void inAppMessageClicked(OSInAppMessageAction result) { + if (!hasSetInAppClickedHandler) { + inAppMessageActionResult = result; + return; + } + sendEvent("OneSignal-inAppMessageClicked", RNUtils.jsonToWritableMap(result.toJSONObject())); + } + }); } @ReactMethod public void initInAppMessageClickHandlerParams() { this.hasSetInAppClickedHandler = true; - if (inAppMessageActionResult != null) { + if (this.inAppMessageActionResult != null) { this.inAppMessageClicked(this.inAppMessageActionResult); this.inAppMessageActionResult = null; } @@ -474,6 +520,13 @@ public void inAppMessageClicked(OSInAppMessageAction result) { this.sendEvent("OneSignal-inAppMessageClicked", RNUtils.jsonToWritableMap(result.toJSONObject())); } + /* other IAM functions */ + + @ReactMethod + public void pauseInAppMessages(Boolean pause) { + OneSignal.pauseInAppMessages(pause); + } + /** * Outcomes */ @@ -482,16 +535,17 @@ public void inAppMessageClicked(OSInAppMessageAction result) { public void sendOutcome(final String name, final Callback callback) { OneSignal.sendOutcome(name, new OutcomeCallback() { @Override - public void onSuccess(OutcomeEvent outcomeEvent) { - if (outcomeEvent == null) - callback.invoke(new WritableNativeMap()); - else { + public void onSuccess(OSOutcomeEvent outcomeEvent) { + if (outcomeEvent) { try { callback.invoke(RNUtils.jsonToWritableMap(outcomeEvent.toJSONObject())); } catch (JSONException e) { Log.e("OneSignal", "sendOutcome with name: " + name + ", failed with message: " + e.getMessage()); } + return; } + + Log.e("OneSignal", "sendOutcome OSOutcomeEvent is null"); } }); } @@ -500,16 +554,17 @@ public void onSuccess(OutcomeEvent outcomeEvent) { public void sendUniqueOutcome(final String name, final Callback callback) { OneSignal.sendUniqueOutcome(name, new OutcomeCallback() { @Override - public void onSuccess(OutcomeEvent outcomeEvent) { - if (outcomeEvent == null) - callback.invoke(new WritableNativeMap()); - else { + public void onSuccess(OSOutcomeEvent outcomeEvent) { + if (outcomeEvent) { try { callback.invoke(RNUtils.jsonToWritableMap(outcomeEvent.toJSONObject())); } catch (JSONException e) { Log.e("OneSignal", "sendUniqueOutcome with name: " + name + ", failed with message: " + e.getMessage()); } + return; } + + Log.e("OneSignal", "sendUniqueOutcome OSOutcomeEvent is null"); } }); } @@ -518,22 +573,23 @@ public void onSuccess(OutcomeEvent outcomeEvent) { public void sendOutcomeWithValue(final String name, final float value, final Callback callback) { OneSignal.sendOutcomeWithValue(name, value, new OutcomeCallback() { @Override - public void onSuccess(OutcomeEvent outcomeEvent) { - if (outcomeEvent == null) - callback.invoke(new WritableNativeMap()); - else { + public void onSuccess(OSOutcomeEvent outcomeEvent) { + if (outcomeEvent) { try { callback.invoke(RNUtils.jsonToWritableMap(outcomeEvent.toJSONObject())); } catch (JSONException e) { Log.e("OneSignal", "sendOutcomeWithValue with name: " + name + " and value: " + value + ", failed with message: " + e.getMessage()); } + return; } + + Log.e("OneSignal", "sendOutcomeWithValue OSOutcomeEvent is null"); } }); } /** - * Overrides + * Native Module Overrides */ @Override @@ -543,7 +599,8 @@ public String getName() { @Override public void onHostDestroy() { - + removeHandlers(); + removeObservers(); } @Override @@ -556,4 +613,9 @@ public void onHostResume() { initOneSignal(); } + @Override + public void onCatalystInstanceDestroy() { + removeHandlers(); + removeObservers(); + } } diff --git a/examples/RNOneSignal/.flowconfig b/examples/RNOneSignal/.flowconfig deleted file mode 100644 index 1319ea12..00000000 --- a/examples/RNOneSignal/.flowconfig +++ /dev/null @@ -1,99 +0,0 @@ -[ignore] -; We fork some components by platform -.*/*[.]android.js - -; Ignore "BUCK" generated dirs -/\.buckd/ - -; Ignore unexpected extra "@providesModule" -.*/node_modules/.*/node_modules/fbjs/.* - -; Ignore duplicate module providers -; For RN Apps installed via npm, "Libraries" folder is inside -; "node_modules/react-native" but in the source repo it is in the root -node_modules/react-native/Libraries/react-native/React.js - -; Ignore polyfills -node_modules/react-native/Libraries/polyfills/.* - -; These should not be required directly -; require from fbjs/lib instead: require('fbjs/lib/warning') -node_modules/warning/.* - -; Flow doesn't support platforms -.*/Libraries/Utilities/HMRLoadingView.js - -[untyped] -.*/node_modules/@react-native-community/cli/.*/.* - -[include] - -[libs] -node_modules/react-native/Libraries/react-native/react-native-interface.js -node_modules/react-native/flow/ - -[options] -emoji=true - -esproposal.optional_chaining=enable -esproposal.nullish_coalescing=enable - -module.file_ext=.js -module.file_ext=.json -module.file_ext=.ios.js - -module.system=haste -module.system.haste.use_name_reducers=true -# get basename -module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1' -# strip .js or .js.flow suffix -module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1' -# strip .ios suffix -module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1' -module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1' -module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1' -module.system.haste.paths.blacklist=.*/__tests__/.* -module.system.haste.paths.blacklist=.*/__mocks__/.* -module.system.haste.paths.whitelist=/node_modules/react-native/Libraries/.* -module.system.haste.paths.whitelist=/node_modules/react-native/RNTester/.* -module.system.haste.paths.whitelist=/node_modules/react-native/IntegrationTests/.* -module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/react-native/react-native-implementation.js -module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/Animated/src/polyfills/.* - -munge_underscores=true - -module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' - -suppress_type=$FlowIssue -suppress_type=$FlowFixMe -suppress_type=$FlowFixMeProps -suppress_type=$FlowFixMeState - -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+ -suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError - -[lints] -sketchy-null-number=warn -sketchy-null-mixed=warn -sketchy-number=warn -untyped-type-import=warn -nonstrict-import=warn -deprecated-type=warn -unsafe-getters-setters=warn -inexact-spread=warn -unnecessary-invariant=warn -signature-verification-failure=warn -deprecated-utility=error - -[strict] -deprecated-type -nonstrict-import -sketchy-null -unclear-type -unsafe-getters-setters -untyped-import -untyped-type-import - -[version] -^0.98.0 diff --git a/examples/RNOneSignal/.yarnclean b/examples/RNOneSignal/.yarnclean deleted file mode 100644 index 3d1e1cd5..00000000 --- a/examples/RNOneSignal/.yarnclean +++ /dev/null @@ -1,44 +0,0 @@ -# test directories -__tests__ -test -tests -powered-test - -# asset directories -docs -doc -website -images - -# examples -example -examples - -# code coverage directories -coverage -.nyc_output - -# build scripts -Makefile -Gulpfile.js -Gruntfile.js - -# configs -appveyor.yml -circle.yml -codeship-services.yml -codeship-steps.yml -wercker.yml -.tern-project -.gitattributes -.editorconfig -.*ignore -.eslintrc -.jshintrc -.flowconfig -.documentup.json -.yarn-metadata.json -.travis.yml - -# misc -*.md diff --git a/examples/RNOneSignal/App.js b/examples/RNOneSignal/App.js deleted file mode 100644 index a0efdc6c..00000000 --- a/examples/RNOneSignal/App.js +++ /dev/null @@ -1,686 +0,0 @@ -/** - * Sample React Native App - * https://github.com/facebook/react-native - * - * @format - * @flow - */ - -import React, {Component} from 'react'; -import { - Platform, - StyleSheet, - Text, - View, - ScrollView, - ActivityIndicator, - KeyboardAvoidingView, - TextInput, - Image, - Button, -} from 'react-native'; -import OneSignal from 'react-native-onesignal'; -import { sleep } from './Util'; - -const imageUri = 'https://cdn-images-1.medium.com/max/300/1*7xHdCFeYfD8zrIivMiQcCQ.png'; -const buttonColor = Platform.OS == 'ios' ? '#3C8AE7' : '#D45653'; -const textInputBorderColor = Platform.OS == 'ios' ? '#3C8AE7' : '#D45653'; -const disabledColor = '#BEBEBE'; - -/** - Change to desired app id (dashboard app) - */ -var appId = 'ce8572ae-ff57-4e77-a265-5c91f00ecc4c'; - -/** - Controls whether the app needs privacy consent or not - Will hide the button when false and show it when true - */ -var requiresPrivacyConsent = true; - -export default class App extends Component { - - constructor(properties) { - super(properties); - - this.state = { - // OneSignal states - // Privacy Consent states - hasPrivacyConsent: false, // App starts without privacy consent - isPrivacyConsentLoading: requiresPrivacyConsent, - - // Device states - userId: '', - pushToken: '', - - // Subscription states - isSubscribed: false, - isSubscriptionLoading: false, - - // External User Id states - externalUserId: '', - isExternalUserIdLoading: false, - - // Email states - email: '', - isEmailLoading: false, - - // In-App Messaging states - iam_paused: false, - - // Add more states here... - - // Demo App states - debugText: '' - }; - - // Log level logcat is 6 (VERBOSE) and log level alert is 0 (NONE) - OneSignal.setLogLevel(6, 0); - - // Share location of device - OneSignal.setLocationShared(true); - - OneSignal.setRequiresUserPrivacyConsent(requiresPrivacyConsent); - OneSignal.init(appId, { - kOSSettingsKeyAutoPrompt: true, - }); - - // Notifications will display as NOTIFICATION type - OneSignal.inFocusDisplaying(2); - - // If the app requires privacy consent check if it has been set yet - if (requiresPrivacyConsent) { - // async 'then' is only so I can sleep using the Promise helper method - OneSignal.userProvidedPrivacyConsent().then(async (granted) => { - // For UI testing purposes wait X seconds to see the loading state - await sleep(0); - - console.log('Privacy Consent status: ' + granted); - this.setState({hasPrivacyConsent:granted, isPrivacyConsentLoading:false}); - }); - } - - OneSignal.getPermissionSubscriptionState((response) => { - console.log('Device state:'); - console.log(response); - - let notificationsEnabled = response['notificationsEnabled']; - let isSubscribed = response['subscriptionEnabled']; - - this.setState({isSubscribed:notificationsEnabled && isSubscribed, isSubscriptionLoading:false}, () => { - OneSignal.setSubscription(isSubscribed); - }); - }); - - // Examples for using native IAM public methods -// this.oneSignalInAppMessagingExamples(); - - // Examples for using native Outcome Event public methods -// this.oneSignalOutcomeEventsExamples(); - - } - - async componentDidMount() { - OneSignal.addEventListener('received', this.onNotificationReceived); - OneSignal.addEventListener('opened', this.onNotificationOpened); - OneSignal.addEventListener('ids', this.onIdsAvailable); -// OneSignal.addEventListener('subscription', this.onSubscriptionChange); -// OneSignal.addEventListener('permission', this.onPermissionChange); - OneSignal.addEventListener('emailSubscription', this.onEmailSubscriptionChange); - OneSignal.addEventListener('inAppMessageClicked', this.onInAppMessageClicked); - } - - componentWillUnmount() { - OneSignal.removeEventListener('received', this.onNotificationReceived); - OneSignal.removeEventListener('opened', this.onNotificationOpened); - OneSignal.removeEventListener('ids', this.onIdsAvailable); -// OneSignal.removeEventListener('subscription', this.onSubscriptionChange); -// OneSignal.removeEventListener('permission', this.onPermissionChange); - OneSignal.removeEventListener('emailSubscription', this.onEmailSubscriptionChange); - OneSignal.removeEventListener('inAppMessageClicked', this.onInAppMessageClicked); - } - - /** - Validate email method - */ - validateEmail(email) { - var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - return re.test(String(email).toLowerCase()); - } - - /** - In-App Message public method examples - */ - oneSignalInAppMessagingExamples() { - // Add a single trigger with a value associated with it - OneSignal.addTrigger('trigger1', 'one'); - - // Get trigger value for the key - OneSignal.getTriggerValueForKey('trigger1') - .then(response => { - console.log('trigger1 value: ' + response); - }) - .catch(e => { - console.error(e); - }); - - // Create a set of triggers in a map and add them all at once - var triggers = { - trigger2: '2', - trigger3: true, - }; - OneSignal.addTriggers(triggers); - - // Get trigger value for the key - OneSignal.getTriggerValueForKey('trigger3') - .then(response => { - console.log('trigger3 value: ' + response); - }) - .catch(e => { - console.error(e); - }); - - // Create an array of keys to remove triggers for - var removeTriggers = ['trigger1', 'trigger2']; - OneSignal.removeTriggersForKeys(removeTriggers); - } - - /** - Outcomes public method examples - */ - oneSignalOutcomeEventsExamples() { - // Send an outcome event with and without a callback - OneSignal.sendOutcome('normal_1'); - OneSignal.sendOutcome('normal_2', response => { - console.log('Normal outcome sent successfully!'); - console.log(response); - }); - - // Send a unique outcome event with and without a callback - OneSignal.sendUniqueOutcome('unique_1'); - OneSignal.sendUniqueOutcome('unique_2', response => { - console.log('Unique outcome sent successfully!'); - console.log(response); - }); - - // Send an outcome event with and without a callback - OneSignal.sendOutcomeWithValue('value_1', 9.99); - OneSignal.sendOutcomeWithValue('value_2', 5, response => { - console.log('Outcome with value sent successfully!'); - console.log(response); - }); - } - - /** - When a notification is received this will fire - */ - onNotificationReceived = (notification) => { - console.log('Notification received: ', notification); - - let debugMsg = 'RECEIVED: \n' + JSON.stringify(notification, null, 2); - this.setState({debugText:debugMsg}, () => { - console.log("Debug text successfully changed!"); - }); - } - - /** - When a notification is opened this will fire - The openResult will contain information about the notification opened - */ - onNotificationOpened = (openResult) => { - console.log('Message: ', openResult.notification.payload.body); - console.log('Data: ', openResult.notification.payload.additionalData); - console.log('isActive: ', openResult.notification.isAppInFocus); - console.log('openResult: ', openResult); - - let debugMsg = 'OPENED: \n' + JSON.stringify(openResult.notification, null, 2); - this.setState({debugText:debugMsg}, () => { - console.log("Debug text successfully changed!"); - }); - } - - /** - Once the user is registered/updated the onIds will send back the userId and pushToken - of the device - */ - onIdsAvailable = (device) => { - console.log('Device info: ', device); - // Save the userId and pushToken for the device, important for updating the device - // record using the SDK, and sending notifications - this.setState({ - userId: device.userId, - pushToken: device.pushToken - }); - } - - /** - TODO: Needs to be implemented still in index.js and RNOneSignal.java - */ - onSubscriptionChange = (change) => { - console.log('onSubscriptionChange: ', change); - } - - /** - TODO: Needs to be implemented still in index.js and RNOneSignal.java - */ - onPermissionChange = (change) => { - console.log('onPermissionChange: ', change); - } - - /** - Success for the change of state for the email record? or setting subscription state of email record (so logging out)? - TODO: Validate functionality and make sure name is correct - Should match the onSubscriptionChange and - - TODO: Validate this is working, might be broken after changing name - */ - onEmailSubscriptionChange = (change) => { - console.log('onEmailSubscriptionChange: ', change); - this.setState({isEmailLoading:false}); - } - - /** - When an element on an IAM is clicked this will fire - The actionResult will contain information about the element clicked - */ - onInAppMessageClicked = (actionResult) => { - console.log('actionResult: ', actionResult); - - let debugMsg = 'CLICKED: \n' + JSON.stringify(actionResult, null, 2); - this.setState({debugText:debugMsg}, () => { - console.log("Debug text successfully changed!"); - }); - } - - render() { - return ( - - { this.createTitleFields() } - - - { this.createSubscribeFields() } - { this.createPrivacyConsentFields() } - { this.createEmailFields() } - { this.createExternalUserIdFields() } - - - - - ); - } - - /** - Create a red OneSignal Button with a name, loading state, and callback (onPress) - */ - renderButtonView = (name, isLoading, callback) => { - let isPrivacyConsentButton = name.includes("Consent"); - - if (isPrivacyConsentButton && !requiresPrivacyConsent) - return null; - - let isClickable = !isLoading - && (!requiresPrivacyConsent - || this.state.hasPrivacyConsent - || isPrivacyConsentButton); - - return ( - - -