From 14418a7323b48f4df962805e4aceee0c9bde4922 Mon Sep 17 00:00:00 2001 From: Phong Cao Date: Mon, 16 Mar 2020 17:32:04 -0400 Subject: [PATCH 1/5] Added new APIs for Android: getInitialNotification, registerTemplate, unregisterTemplate, getUUID and isNotificationEnabledOnOSLevel methods. --- .../notificationhub/ReactNativeConstants.java | 19 + .../ReactNativeNotificationHubModule.java | 196 ++++++++++ .../ReactNativeNotificationHubUtil.java | 32 ++ .../ReactNativeRegistrationIntentService.java | 11 +- .../notificationhub/ReactNativeUtil.java | 6 +- .../ReactNativeNotificationHubModuleTest.java | 335 +++++++++++++++++- .../ReactNativeNotificationHubUtilTest.java | 76 ++++ 7 files changed, 670 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeConstants.java b/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeConstants.java index 799b98a8..73322c15 100644 --- a/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeConstants.java +++ b/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeConstants.java @@ -22,6 +22,9 @@ public final class ReactNativeConstants { public static final String KEY_REGISTRATION_CHANNELSHOWBADGE = "channelShowBadge"; public static final String KEY_REGISTRATION_CHANNELENABLELIGHTS = "channelEnableLights"; public static final String KEY_REGISTRATION_CHANNELENABLEVIBRATION = "channelEnableVibration"; + public static final String KEY_REGISTRATION_TEMPLATENAME = "templateName"; + public static final String KEY_REGISTRATION_TEMPLATE = "template"; + public static final String KEY_REGISTRATION_ISTEMPLATE = "isTemplate"; // Shared prefs used in NotificationHubUtil public static final String SHARED_PREFS_NAME = "com.azure.reactnative.notificationhub.NotificationHubUtil"; @@ -36,6 +39,10 @@ public final class ReactNativeConstants { public static final String KEY_FOR_PREFS_CHANNELSHOWBADGE = "AzureNotificationHub_channelShowBadge"; public static final String KEY_FOR_PREFS_CHANNELENABLELIGHTS = "AzureNotificationHub_channelEnableLights"; public static final String KEY_FOR_PREFS_CHANNELENABLEVIBRATION = "AzureNotificationHub_channelEnableVibration"; + public static final String KEY_FOR_PREFS_TEMPLATENAME = "AzureNotificationHub_templateName"; + public static final String KEY_FOR_PREFS_TEMPLATE = "AzureNotificationHub_template"; + public static final String KEY_FOR_PREFS_ISTEMPLATE = "AzureNotificationHub_isTemplate"; + public static final String KEY_FOR_PREFS_UUID = "AzureNotificationHub_UUID"; // Remote notification payload public static final String KEY_REMOTE_NOTIFICATION_MESSAGE = "message"; @@ -85,6 +92,10 @@ public final class ReactNativeConstants { public static final String RESOURCE_NAME_NOTIFICATION = "ic_notification"; public static final String RESOURCE_NAME_LAUNCHER = "ic_launcher"; + // Promise + public static final String KEY_PROMISE_RESOLVE_UUID = "uuid"; + public static final String AZURE_NOTIFICATION_HUB_UNREGISTERED = "Unregistered successfully"; + // Errors public static final String ERROR_NO_ACTIVITY_CLASS = "No activity class found for the notification"; public static final String ERROR_NO_MESSAGE = "No message specified for the notification"; @@ -96,6 +107,8 @@ public final class ReactNativeConstants { public static final String ERROR_INVALID_CONNECTION_STRING = "Connection string cannot be null."; public static final String ERROR_INVALID_HUBNAME = "Hub name cannot be null."; public static final String ERROR_INVALID_SENDER_ID = "Sender ID cannot be null."; + public static final String ERROR_INVALID_TEMPLATE_NAME = "Template Name cannot be null."; + public static final String ERROR_INVALID_TEMPLATE = "Template cannot be null."; public static final String ERROR_PLAY_SERVICES = "E_PLAY_SERVICES"; public static final String ERROR_PLAY_SERVICES_DISABLED = "User must enable Google Play Services."; public static final String ERROR_PLAY_SERVICES_UNSUPPORTED = "This device is not supported by Google Play Services."; @@ -103,6 +116,12 @@ public final class ReactNativeConstants { public static final String ERROR_NOT_REGISTERED = "E_NOT_REGISTERED"; public static final String ERROR_NOT_REGISTERED_DESC = "No registration to Azure Notification Hub."; public static final String ERROR_FETCH_IMAGE = "Error while fetching image."; + public static final String ERROR_GET_INIT_NOTIFICATION = "E_GET_INIT_NOTIF"; + public static final String ERROR_ACTIVITY_IS_NULL = "Current activity is null"; + public static final String ERROR_INTENT_EXTRAS_IS_NULL = "Intent get extras is null"; + public static final String ERROR_ACTIVITY_INTENT_IS_NULL = "Activity intent is null"; + public static final String ERROR_GET_UUID = "E_GET_UUID"; + public static final String ERROR_NO_UUID_SET = "No uuid set"; private ReactNativeConstants() { } diff --git a/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeNotificationHubModule.java b/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeNotificationHubModule.java index 2f507a96..b4eee759 100644 --- a/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeNotificationHubModule.java +++ b/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeNotificationHubModule.java @@ -7,10 +7,13 @@ import android.content.IntentFilter; import android.os.Bundle; +import androidx.core.app.NotificationManagerCompat; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import com.facebook.react.bridge.ActivityEventListener; +import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.LifecycleEventListener; +import com.facebook.react.bridge.WritableMap; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; @@ -61,6 +64,29 @@ public boolean getIsForeground() { return notificationHubUtil.getAppIsForeground(); } + @ReactMethod + public void getInitialNotification(Promise promise) { + Activity activity = getCurrentActivity(); + if (activity == null) { + promise.reject(ERROR_GET_INIT_NOTIFICATION, ERROR_ACTIVITY_IS_NULL); + return; + } + + Intent intent = activity.getIntent(); + if (intent != null && intent.getAction() != null) { + if (intent.getExtras() == null) { + // In certain cases while app cold launches, i.getExtras() returns null. + // Adding the check to make sure app won't crash, + // and still successfully launches from notification + promise.reject(ERROR_GET_INIT_NOTIFICATION, ERROR_INTENT_EXTRAS_IS_NULL); + } else { + promise.resolve(ReactNativeUtil.convertBundleToMap(intent.getExtras())); + } + } else { + promise.reject(ERROR_GET_INIT_NOTIFICATION, ERROR_ACTIVITY_INTENT_IS_NULL); + } + } + @ReactMethod public void register(ReadableMap config, Promise promise) { ReactNativeNotificationHubUtil notificationHubUtil = ReactNativeNotificationHubUtil.getInstance(); @@ -95,6 +121,114 @@ public void register(ReadableMap config, Promise promise) { notificationHubUtil.setConnectionString(reactContext, connectionString); notificationHubUtil.setHubName(reactContext, hubName); notificationHubUtil.setSenderID(reactContext, senderID); + notificationHubUtil.setTemplated(reactContext, false); + notificationHubUtil.setTags(reactContext, tags); + + if (config.hasKey(KEY_REGISTRATION_CHANNELNAME)) { + String channelName = config.getString(KEY_REGISTRATION_CHANNELNAME); + notificationHubUtil.setChannelName(reactContext, channelName); + } + + if (config.hasKey(KEY_REGISTRATION_CHANNELIMPORTANCE)) { + int channelImportance = config.getInt(KEY_REGISTRATION_CHANNELIMPORTANCE); + notificationHubUtil.setChannelImportance(reactContext, channelImportance); + } + + if (config.hasKey(KEY_REGISTRATION_CHANNELSHOWBADGE)) { + boolean channelShowBadge = config.getBoolean(KEY_REGISTRATION_CHANNELSHOWBADGE); + notificationHubUtil.setChannelShowBadge(reactContext, channelShowBadge); + } + + if (config.hasKey(KEY_REGISTRATION_CHANNELENABLELIGHTS)) { + boolean channelEnableLights = config.getBoolean(KEY_REGISTRATION_CHANNELENABLELIGHTS); + notificationHubUtil.setChannelEnableLights(reactContext, channelEnableLights); + } + + if (config.hasKey(KEY_REGISTRATION_CHANNELENABLEVIBRATION)) { + boolean channelEnableVibration = config.getBoolean(KEY_REGISTRATION_CHANNELENABLEVIBRATION); + notificationHubUtil.setChannelEnableVibration(reactContext, channelEnableVibration); + } + + String uuid = notificationHubUtil.getUUID(reactContext); + if (uuid == null) { + uuid = ReactNativeUtil.genUUID(); + notificationHubUtil.setUUID(reactContext, uuid); + } + + GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance(); + int resultCode = apiAvailability.isGooglePlayServicesAvailable(reactContext); + if (resultCode != ConnectionResult.SUCCESS) { + if (apiAvailability.isUserResolvableError(resultCode)) { + UiThreadUtil.runOnUiThread( + new GoogleApiAvailabilityRunnable( + getCurrentActivity(), + apiAvailability, + resultCode)); + promise.reject(ERROR_PLAY_SERVICES, ERROR_PLAY_SERVICES_DISABLED); + } else { + promise.reject(ERROR_PLAY_SERVICES, ERROR_PLAY_SERVICES_UNSUPPORTED); + } + return; + } + + Intent intent = ReactNativeNotificationHubUtil.IntentFactory.createIntent( + reactContext, ReactNativeRegistrationIntentService.class); + ReactNativeRegistrationIntentService.enqueueWork(reactContext, intent); + + WritableMap res = Arguments.createMap(); + res.putString(KEY_PROMISE_RESOLVE_UUID, uuid); + promise.resolve(res); + } + + @ReactMethod + public void registerTemplate(ReadableMap config, Promise promise) { + ReactNativeNotificationHubUtil notificationHubUtil = ReactNativeNotificationHubUtil.getInstance(); + String connectionString = config.getString(KEY_REGISTRATION_CONNECTIONSTRING); + if (connectionString == null) { + promise.reject(ERROR_INVALID_ARGUMENTS, ERROR_INVALID_CONNECTION_STRING); + return; + } + + String hubName = config.getString(KEY_REGISTRATION_HUBNAME); + if (hubName == null) { + promise.reject(ERROR_INVALID_ARGUMENTS, ERROR_INVALID_HUBNAME); + return; + } + + String senderID = config.getString(KEY_REGISTRATION_SENDERID); + if (senderID == null) { + promise.reject(ERROR_INVALID_ARGUMENTS, ERROR_INVALID_SENDER_ID); + return; + } + + String templateName = config.getString(KEY_REGISTRATION_TEMPLATENAME); + if (templateName == null) { + promise.reject(ERROR_INVALID_ARGUMENTS, ERROR_INVALID_TEMPLATE_NAME); + return; + } + + String template = config.getString(KEY_REGISTRATION_TEMPLATE); + if (template == null) { + promise.reject(ERROR_INVALID_ARGUMENTS, ERROR_INVALID_TEMPLATE); + return; + } + + String[] tags = null; + if (config.hasKey(KEY_REGISTRATION_TAGS) && !config.isNull(KEY_REGISTRATION_TAGS)) { + ReadableArray tagsJson = config.getArray(KEY_REGISTRATION_TAGS); + tags = new String[tagsJson.size()]; + for (int i = 0; i < tagsJson.size(); ++i) { + tags[i] = tagsJson.getString(i); + } + } + + ReactContext reactContext = getReactApplicationContext(); + notificationHubUtil.setConnectionString(reactContext, connectionString); + notificationHubUtil.setHubName(reactContext, hubName); + notificationHubUtil.setSenderID(reactContext, senderID); + notificationHubUtil.setTemplateName(reactContext, templateName); + notificationHubUtil.setTemplate(reactContext, template); + notificationHubUtil.setTemplated(reactContext, true); notificationHubUtil.setTags(reactContext, tags); if (config.hasKey(KEY_REGISTRATION_CHANNELNAME)) { @@ -122,6 +256,12 @@ public void register(ReadableMap config, Promise promise) { notificationHubUtil.setChannelEnableVibration(reactContext, channelEnableVibration); } + String uuid = notificationHubUtil.getUUID(reactContext); + if (uuid == null) { + uuid = ReactNativeUtil.genUUID(); + notificationHubUtil.setUUID(reactContext, uuid); + } + GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance(); int resultCode = apiAvailability.isGooglePlayServicesAvailable(reactContext); if (resultCode != ConnectionResult.SUCCESS) { @@ -141,6 +281,10 @@ public void register(ReadableMap config, Promise promise) { Intent intent = ReactNativeNotificationHubUtil.IntentFactory.createIntent( reactContext, ReactNativeRegistrationIntentService.class); ReactNativeRegistrationIntentService.enqueueWork(reactContext, intent); + + WritableMap res = Arguments.createMap(); + res.putString(KEY_PROMISE_RESOLVE_UUID, uuid); + promise.resolve(res); } @ReactMethod @@ -161,11 +305,63 @@ public void unregister(Promise promise) { try { hub.unregister(); notificationHubUtil.setRegistrationID(reactContext, null); + notificationHubUtil.setUUID(reactContext, null); + promise.resolve(AZURE_NOTIFICATION_HUB_UNREGISTERED); } catch (Exception e) { promise.reject(ERROR_NOTIFICATION_HUB, e); } } + @ReactMethod + public void unregisterTemplate(String templateName, Promise promise) { + ReactNativeNotificationHubUtil notificationHubUtil = ReactNativeNotificationHubUtil.getInstance(); + + ReactContext reactContext = getReactApplicationContext(); + String connectionString = notificationHubUtil.getConnectionString(reactContext); + String hubName = notificationHubUtil.getHubName(reactContext); + String registrationId = notificationHubUtil.getRegistrationID(reactContext); + + if (connectionString == null || hubName == null || registrationId == null) { + promise.reject(ERROR_NOT_REGISTERED, ERROR_NOT_REGISTERED_DESC); + return; + } + + NotificationHub hub = ReactNativeUtil.createNotificationHub(hubName, connectionString, reactContext); + try { + hub.unregisterTemplate(templateName); + notificationHubUtil.setRegistrationID(reactContext, null); + notificationHubUtil.setUUID(reactContext, null); + promise.resolve(AZURE_NOTIFICATION_HUB_UNREGISTERED); + } catch (Exception e) { + promise.reject(ERROR_NOTIFICATION_HUB, e); + } + } + + @ReactMethod + public void getUUID(Boolean autoGen, Promise promise) { + ReactNativeNotificationHubUtil notificationHubUtil = ReactNativeNotificationHubUtil.getInstance(); + ReactContext reactContext = getReactApplicationContext(); + String uuid = notificationHubUtil.getUUID(reactContext); + + if (uuid != null) { + promise.resolve(uuid); + } else if (autoGen) { + uuid = ReactNativeUtil.genUUID(); + notificationHubUtil.setUUID(reactContext, uuid); + promise.resolve(uuid); + } else { + promise.reject(ERROR_GET_UUID, ERROR_NO_UUID_SET); + } + } + + @ReactMethod + public void isNotificationEnabledOnOSLevel(Promise promise) { + ReactContext reactContext = getReactApplicationContext(); + NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(reactContext); + boolean areNotificationsEnabled = notificationManagerCompat.areNotificationsEnabled(); + promise.resolve(areNotificationsEnabled); + } + @Override public void onHostResume() { setIsForeground(true); diff --git a/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeNotificationHubUtil.java b/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeNotificationHubUtil.java index 1ca6a33e..e7890a1a 100644 --- a/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeNotificationHubUtil.java +++ b/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeNotificationHubUtil.java @@ -165,6 +165,38 @@ public boolean hasChannelEnableVibration(Context context) { return hasKey(context, KEY_FOR_PREFS_CHANNELENABLEVIBRATION); } + public String getTemplateName(Context context) { + return getPref(context, KEY_FOR_PREFS_TEMPLATENAME); + } + + public void setTemplateName(Context context, String templateName) { + setPref(context, KEY_FOR_PREFS_TEMPLATENAME, templateName); + } + + public String getTemplate(Context context) { + return getPref(context, KEY_FOR_PREFS_TEMPLATE); + } + + public void setTemplate(Context context, String template) { + setPref(context, KEY_FOR_PREFS_TEMPLATE, template); + } + + public boolean isTemplated(Context context) { + return getPrefBoolean(context, KEY_FOR_PREFS_ISTEMPLATE); + } + + public void setTemplated(Context context, boolean templated) { + setPrefBoolean(context, KEY_FOR_PREFS_ISTEMPLATE, templated); + } + + public String getUUID(Context context) { + return getPref(context, KEY_FOR_PREFS_UUID); + } + + public void setUUID(Context context, String uuid) { + setPref(context, KEY_FOR_PREFS_UUID, uuid); + } + public void setAppIsForeground(boolean isForeground) { mIsForeground = isForeground; } diff --git a/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeRegistrationIntentService.java b/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeRegistrationIntentService.java index 3dbcec63..4c94d275 100644 --- a/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeRegistrationIntentService.java +++ b/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeRegistrationIntentService.java @@ -38,6 +38,9 @@ protected void onHandleWork(Intent intent) { final String hubName = notificationHubUtil.getHubName(this); final String storedToken = notificationHubUtil.getFCMToken(this); final String[] tags = notificationHubUtil.getTags(this); + final boolean isTemplated = notificationHubUtil.isTemplated(this); + final String templateName = notificationHubUtil.getTemplateName(this); + final String template = notificationHubUtil.getTemplate(this); if (connectionString == null || hubName == null) { // The intent was triggered when no connection string has been set. @@ -63,7 +66,13 @@ public void onSuccess(InstanceIdResult instanceIdResult) { ReactNativeRegistrationIntentService.this); Log.d(TAG, "NH Registration refreshing with token : " + token); - regID = hub.register(token, tags).getRegistrationId(); + if (isTemplated) { + regID = hub.registerTemplate( + token, templateName, template, tags).getRegistrationId(); + } else { + regID = hub.register(token, tags).getRegistrationId(); + } + Log.d(TAG, "New NH Registration Successfully - RegId : " + regID); notificationHubUtil.setRegistrationID(ReactNativeRegistrationIntentService.this, regID); diff --git a/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeUtil.java b/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeUtil.java index 0def0bf8..06b241af 100644 --- a/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeUtil.java +++ b/android/src/main/java/com/azure/reactnative/notificationhub/ReactNativeUtil.java @@ -26,9 +26,9 @@ import java.io.InputStream; import java.net.HttpURLConnection; -import java.net.MalformedURLException; import java.net.URL; import java.util.Set; +import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -335,6 +335,10 @@ public static Bitmap fetchImage(String urlString) { } } + public static String genUUID() { + return UUID.randomUUID().toString(); + } + private ReactNativeUtil() { } } diff --git a/sample/android/app/src/test/java/com/reactnativeazurenotificationhubsample/ReactNativeNotificationHubModuleTest.java b/sample/android/app/src/test/java/com/reactnativeazurenotificationhubsample/ReactNativeNotificationHubModuleTest.java index f5201576..7bed952c 100644 --- a/sample/android/app/src/test/java/com/reactnativeazurenotificationhubsample/ReactNativeNotificationHubModuleTest.java +++ b/sample/android/app/src/test/java/com/reactnativeazurenotificationhubsample/ReactNativeNotificationHubModuleTest.java @@ -6,6 +6,8 @@ import android.content.IntentFilter; import android.os.Bundle; +import androidx.annotation.RequiresPermission; +import androidx.core.app.NotificationManagerCompat; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import org.junit.Assert; @@ -14,6 +16,7 @@ import static com.azure.reactnative.notificationhub.ReactNativeConstants.*; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.matches; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; @@ -35,11 +38,13 @@ import com.azure.reactnative.notificationhub.ReactNativeNotificationsHandler; import com.azure.reactnative.notificationhub.ReactNativeRegistrationIntentService; import com.azure.reactnative.notificationhub.ReactNativeUtil; +import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableMap; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; import com.microsoft.windowsazure.messaging.NotificationHub; @@ -54,7 +59,9 @@ ReactNativeUtil.class, ReactNativeNotificationsHandler.class, ReactNativeRegistrationIntentService.class, - GoogleApiAvailability.class + GoogleApiAvailability.class, + Arguments.class, + NotificationManagerCompat.class }) public class ReactNativeNotificationHubModuleTest { @Mock @@ -81,6 +88,9 @@ public class ReactNativeNotificationHubModuleTest { @Mock NotificationHub mNotificationHub; + @Mock + WritableMap mRes; + ReactNativeNotificationHubModule mHubModule; @Before @@ -102,6 +112,9 @@ public void setUp() { PowerMockito.mockStatic(ReactNativeRegistrationIntentService.class); PowerMockito.mockStatic(GoogleApiAvailability.class); when(GoogleApiAvailability.getInstance()).thenReturn(mGoogleApiAvailability); + PowerMockito.mockStatic(Arguments.class); + when(Arguments.createMap()).thenReturn(mRes); + PowerMockito.mockStatic(NotificationManagerCompat.class); mHubModule = new ReactNativeNotificationHubModule(mReactApplicationContext); } @@ -252,6 +265,39 @@ public void testRegisterHasChannelEnableVibration() { any(ReactContext.class), eq(channelEnableVibration)); } + @Test + public void testRegisterGenUUID() { + when(mConfig.getString(KEY_REGISTRATION_CONNECTIONSTRING)).thenReturn(KEY_REGISTRATION_CONNECTIONSTRING); + when(mConfig.getString(KEY_REGISTRATION_HUBNAME)).thenReturn(KEY_REGISTRATION_HUBNAME); + when(mConfig.getString(KEY_REGISTRATION_SENDERID)).thenReturn(KEY_REGISTRATION_SENDERID); + when(ReactNativeUtil.genUUID()).thenReturn(PowerMockito.mock(String.class)); + + mHubModule.register(mConfig, mPromise); + + verify(mNotificationHubUtil, times(1)).getUUID(any(ReactContext.class)); + verify(mNotificationHubUtil, times(1)).setUUID( + any(ReactContext.class), anyString()); + PowerMockito.verifyStatic((ReactNativeUtil.class)); + ReactNativeUtil.genUUID(); + } + + @Test + public void testRegisterExistingUUID() { + when(mConfig.getString(KEY_REGISTRATION_CONNECTIONSTRING)).thenReturn(KEY_REGISTRATION_CONNECTIONSTRING); + when(mConfig.getString(KEY_REGISTRATION_HUBNAME)).thenReturn(KEY_REGISTRATION_HUBNAME); + when(mConfig.getString(KEY_REGISTRATION_SENDERID)).thenReturn(KEY_REGISTRATION_SENDERID); + when(mNotificationHubUtil.getUUID(any(ReactContext.class))).thenReturn( + PowerMockito.mock(String.class)); + + mHubModule.register(mConfig, mPromise); + + verify(mNotificationHubUtil, times(1)).getUUID(any(ReactContext.class)); + verify(mNotificationHubUtil, times(0)).setUUID( + any(ReactContext.class), anyString()); + PowerMockito.verifyStatic((ReactNativeUtil.class), times(0)); + ReactNativeUtil.genUUID(); + } + @Test public void testRegisterSuccessfully() { final String connectionString = "Connection String"; @@ -278,8 +324,12 @@ public void testRegisterSuccessfully() { any(ReactContext.class), eq(hubName)); verify(mNotificationHubUtil, times(1)).setSenderID( any(ReactContext.class), eq(senderID)); + verify(mNotificationHubUtil, times(1)).setTemplated( + any(ReactContext.class), eq(false)); verify(mNotificationHubUtil, times(1)).setTags( any(ReactContext.class), eq(tags)); + verify(mRes, times(1)).putString(eq(KEY_PROMISE_RESOLVE_UUID), any()); + verify(mPromise, times(1)).resolve(mRes); verify(mPromise, times(0)).reject(anyString(), anyString()); PowerMockito.verifyStatic(ReactNativeRegistrationIntentService.class); @@ -319,14 +369,17 @@ public void testUnregisterSuccessfully() throws Exception { mHubModule.unregister(mPromise); - verify(mPromise, times(0)).reject(anyString(), anyString()); verify(mNotificationHub, times(1)).unregister(); verify(mNotificationHubUtil, times(1)).setRegistrationID( any(ReactContext.class), eq(null)); + verify(mNotificationHubUtil, times(1)).setUUID( + any(ReactContext.class), eq(null)); + verify(mPromise, times(0)).reject(anyString(), anyString()); + verify(mPromise, times(1)).resolve(AZURE_NOTIFICATION_HUB_UNREGISTERED); } @Test - public void testUnregisterNoRegistration() throws Exception { + public void testUnregisterNoRegistration() { when(mNotificationHubUtil.getConnectionString(any(ReactContext.class))).thenReturn("Connection String"); when(mNotificationHubUtil.getHubName(any(ReactContext.class))).thenReturn("Hub Name"); when(mNotificationHubUtil.getRegistrationID(any(ReactContext.class))).thenReturn(null); @@ -358,6 +411,282 @@ public void testUnregisterThrowException() throws Exception { unhandledException); } + @Test + public void testRegisterTemplateMissingTemplateName() { + when(mConfig.getString(KEY_REGISTRATION_CONNECTIONSTRING)).thenReturn("Connection String"); + when(mConfig.getString(KEY_REGISTRATION_HUBNAME)).thenReturn("Hub Name"); + when(mConfig.getString(KEY_REGISTRATION_SENDERID)).thenReturn("Connection String"); + + mHubModule.registerTemplate(mConfig, mPromise); + + verify(mPromise, times(1)).reject( + ERROR_INVALID_ARGUMENTS, + ERROR_INVALID_TEMPLATE_NAME); + } + + @Test + public void testRegisterTemplateMissingTemplate() { + when(mConfig.getString(KEY_REGISTRATION_CONNECTIONSTRING)).thenReturn("Connection String"); + when(mConfig.getString(KEY_REGISTRATION_HUBNAME)).thenReturn("Hub Name"); + when(mConfig.getString(KEY_REGISTRATION_SENDERID)).thenReturn("Connection String"); + when(mConfig.getString(KEY_REGISTRATION_TEMPLATENAME)).thenReturn("Template Name"); + + mHubModule.registerTemplate(mConfig, mPromise); + + verify(mPromise, times(1)).reject( + ERROR_INVALID_ARGUMENTS, + ERROR_INVALID_TEMPLATE); + } + + @Test + public void testRegisterTemplateSuccessfully() { + final String connectionString = "Connection String"; + final String hubName = "Hub Name"; + final String senderID = "Sender ID"; + final String templateName = "Template Name"; + final String template = "Template"; + final String[] tags = { "Tag" }; + + when(mConfig.getString(KEY_REGISTRATION_CONNECTIONSTRING)).thenReturn(connectionString); + when(mConfig.getString(KEY_REGISTRATION_HUBNAME)).thenReturn(hubName); + when(mConfig.getString(KEY_REGISTRATION_SENDERID)).thenReturn(senderID); + when(mConfig.getString(KEY_REGISTRATION_TEMPLATENAME)).thenReturn(templateName); + when(mConfig.getString(KEY_REGISTRATION_TEMPLATE)).thenReturn(template); + when(mConfig.hasKey(KEY_REGISTRATION_TAGS)).thenReturn(true); + when(mConfig.isNull(KEY_REGISTRATION_TAGS)).thenReturn(false); + when(mConfig.getArray(KEY_REGISTRATION_TAGS)).thenReturn(mTags); + when(mTags.size()).thenReturn(1); + when(mTags.getString(0)).thenReturn(tags[0]); + when(mGoogleApiAvailability.isGooglePlayServicesAvailable(any())).thenReturn( + ConnectionResult.SUCCESS); + + mHubModule.registerTemplate(mConfig, mPromise); + + verify(mNotificationHubUtil, times(1)).setConnectionString( + any(ReactContext.class), eq(connectionString)); + verify(mNotificationHubUtil, times(1)).setHubName( + any(ReactContext.class), eq(hubName)); + verify(mNotificationHubUtil, times(1)).setSenderID( + any(ReactContext.class), eq(senderID)); + verify(mNotificationHubUtil, times(1)).setTemplateName( + any(ReactContext.class), eq(templateName)); + verify(mNotificationHubUtil, times(1)).setTemplate( + any(ReactContext.class), eq(template)); + verify(mNotificationHubUtil, times(1)).setTemplated( + any(ReactContext.class), eq(true)); + verify(mNotificationHubUtil, times(1)).setTags( + any(ReactContext.class), eq(tags)); + verify(mRes, times(1)).putString(eq(KEY_PROMISE_RESOLVE_UUID), any()); + verify(mPromise, times(1)).resolve(mRes); + verify(mPromise, times(0)).reject(anyString(), anyString()); + + PowerMockito.verifyStatic(ReactNativeRegistrationIntentService.class); + ReactNativeRegistrationIntentService.enqueueWork(eq(mReactApplicationContext), any(Intent.class)); + } + + @Test + public void testRegisterTemplateFailed() { + final String[] tags = { "Tag" }; + + when(mConfig.getString(KEY_REGISTRATION_CONNECTIONSTRING)).thenReturn("Connection String"); + when(mConfig.getString(KEY_REGISTRATION_HUBNAME)).thenReturn("Hub Name"); + when(mConfig.getString(KEY_REGISTRATION_SENDERID)).thenReturn("Sender ID"); + when(mConfig.getString(KEY_REGISTRATION_TEMPLATENAME)).thenReturn("Template Name"); + when(mConfig.getString(KEY_REGISTRATION_TEMPLATE)).thenReturn("Template"); + when(mConfig.hasKey(KEY_REGISTRATION_TAGS)).thenReturn(true); + when(mConfig.isNull(KEY_REGISTRATION_TAGS)).thenReturn(false); + when(mConfig.getArray(KEY_REGISTRATION_TAGS)).thenReturn(mTags); + when(mTags.size()).thenReturn(1); + when(mTags.getString(0)).thenReturn(tags[0]); + when(mGoogleApiAvailability.isGooglePlayServicesAvailable(any())).thenReturn( + ConnectionResult.INTERNAL_ERROR); + when(mGoogleApiAvailability.isUserResolvableError(anyInt())).thenReturn(false); + + mHubModule.registerTemplate(mConfig, mPromise); + + verify(mPromise, times(1)).reject( + ERROR_PLAY_SERVICES, + ERROR_PLAY_SERVICES_UNSUPPORTED); + } + + @Test + public void testUnregisterTemplateSuccessfully() throws Exception { + final String templateName = "Template Name"; + + when(mNotificationHubUtil.getConnectionString(any(ReactContext.class))).thenReturn("Connection String"); + when(mNotificationHubUtil.getHubName(any(ReactContext.class))).thenReturn("Hub Name"); + when(mNotificationHubUtil.getRegistrationID(any(ReactContext.class))).thenReturn("registrationId"); + when(ReactNativeUtil.createNotificationHub( + anyString(), anyString(), any(ReactContext.class))).thenReturn(mNotificationHub); + + mHubModule.unregisterTemplate(templateName, mPromise); + + verify(mNotificationHub, times(1)).unregisterTemplate(templateName); + verify(mNotificationHubUtil, times(1)).setRegistrationID( + any(ReactContext.class), eq(null)); + verify(mNotificationHubUtil, times(1)).setUUID( + any(ReactContext.class), eq(null)); + verify(mPromise, times(0)).reject(anyString(), anyString()); + verify(mPromise, times(1)).resolve(AZURE_NOTIFICATION_HUB_UNREGISTERED); + } + + @Test + public void testUnregisterTemplateNoRegistration() { + when(mNotificationHubUtil.getConnectionString(any(ReactContext.class))).thenReturn("Connection String"); + when(mNotificationHubUtil.getHubName(any(ReactContext.class))).thenReturn("Hub Name"); + when(mNotificationHubUtil.getRegistrationID(any(ReactContext.class))).thenReturn(null); + when(ReactNativeUtil.createNotificationHub( + anyString(), anyString(), any(ReactContext.class))).thenReturn(mNotificationHub); + + mHubModule.unregisterTemplate("Template Name", mPromise); + + verify(mPromise, times(1)).reject( + ERROR_NOT_REGISTERED, + ERROR_NOT_REGISTERED_DESC); + } + + @Test + public void testUnregisterTemplateThrowException() throws Exception { + final String templateName = "Template Name"; + final Exception unhandledException = new Exception("Unhandled exception"); + + when(mNotificationHubUtil.getConnectionString(any(ReactContext.class))).thenReturn("Connection String"); + when(mNotificationHubUtil.getHubName(any(ReactContext.class))).thenReturn("Hub Name"); + when(mNotificationHubUtil.getRegistrationID(any(ReactContext.class))).thenReturn("registrationId"); + when(ReactNativeUtil.createNotificationHub( + anyString(), anyString(), any(ReactContext.class))).thenReturn(mNotificationHub); + doThrow(unhandledException).when(mNotificationHub).unregisterTemplate(templateName); + + mHubModule.unregisterTemplate(templateName, mPromise); + + verify(mPromise, times(1)).reject( + ERROR_NOTIFICATION_HUB, + unhandledException); + } + + @Test + public void testGetInitialNotificationNullActivity() { + when(mReactApplicationContext.getCurrentActivity()).thenReturn(null); + + mHubModule.getInitialNotification(mPromise); + + verify(mPromise, times(1)).reject( + ERROR_GET_INIT_NOTIFICATION, + ERROR_ACTIVITY_IS_NULL); + } + + @Test + public void testGetInitialNotificationNullIntent() { + Activity activity = PowerMockito.mock(Activity.class); + when(mReactApplicationContext.getCurrentActivity()).thenReturn(activity); + when(activity.getIntent()).thenReturn(null); + + mHubModule.getInitialNotification(mPromise); + + verify(mPromise, times(1)).reject( + ERROR_GET_INIT_NOTIFICATION, + ERROR_ACTIVITY_INTENT_IS_NULL); + } + + @Test + public void testGetInitialNotificationNullIntentAction() { + Activity activity = PowerMockito.mock(Activity.class); + when(mReactApplicationContext.getCurrentActivity()).thenReturn(activity); + Intent intent = PowerMockito.mock(Intent.class); + when(activity.getIntent()).thenReturn(intent); + when(intent.getAction()).thenReturn(null); + + mHubModule.getInitialNotification(mPromise); + + verify(mPromise, times(1)).reject( + ERROR_GET_INIT_NOTIFICATION, + ERROR_ACTIVITY_INTENT_IS_NULL); + } + + @Test + public void testGetInitialNotificationNullIntentExtras() { + Activity activity = PowerMockito.mock(Activity.class); + when(mReactApplicationContext.getCurrentActivity()).thenReturn(activity); + Intent intent = PowerMockito.mock(Intent.class); + when(activity.getIntent()).thenReturn(intent); + when(intent.getAction()).thenReturn(PowerMockito.mock(String.class)); + when(intent.getExtras()).thenReturn(null); + + mHubModule.getInitialNotification(mPromise); + + verify(mPromise, times(1)).reject( + ERROR_GET_INIT_NOTIFICATION, + ERROR_INTENT_EXTRAS_IS_NULL); + } + + @Test + public void testGetInitialNotification() { + Activity activity = PowerMockito.mock(Activity.class); + when(mReactApplicationContext.getCurrentActivity()).thenReturn(activity); + Intent intent = PowerMockito.mock(Intent.class); + when(activity.getIntent()).thenReturn(intent); + when(intent.getAction()).thenReturn(PowerMockito.mock(String.class)); + when(intent.getExtras()).thenReturn(PowerMockito.mock(Bundle.class)); + WritableMap map = PowerMockito.mock(WritableMap.class); + when(ReactNativeUtil.convertBundleToMap(any())).thenReturn(map); + + mHubModule.getInitialNotification(mPromise); + + verify(mPromise, times(1)).resolve(map); + } + + @Test + public void testGetUUIDNoUUIDNoAutoGen() { + when(mNotificationHubUtil.getUUID(mReactApplicationContext)).thenReturn(null); + + mHubModule.getUUID(false, mPromise); + + verify(mPromise, times(1)).reject( + ERROR_GET_UUID, + ERROR_NO_UUID_SET); + } + + @Test + public void testGetUUIDNoUUIDAutoGen() { + final String uuid = "uuid"; + + when(mNotificationHubUtil.getUUID(mReactApplicationContext)).thenReturn(null); + when(ReactNativeUtil.genUUID()).thenReturn(uuid); + + mHubModule.getUUID(true, mPromise); + + verify(mNotificationHubUtil, times(1)).setUUID( + mReactApplicationContext, uuid); + verify(mPromise, times(1)).resolve(uuid); + } + + @Test + public void testGetUUID() { + final String uuid = "uuid"; + + when(mNotificationHubUtil.getUUID(mReactApplicationContext)).thenReturn(uuid); + + mHubModule.getUUID(true, mPromise); + + PowerMockito.verifyStatic(ReactNativeUtil.class, times(0)); + ReactNativeUtil.genUUID(); + verify(mPromise, times(1)).resolve(uuid); + } + + @Test + public void testIsNotificationEnabledOnOSLevel() { + final boolean areNotificationsEnabled = true; + + NotificationManagerCompat manager = PowerMockito.mock(NotificationManagerCompat.class); + when(NotificationManagerCompat.from(mReactApplicationContext)).thenReturn(manager); + when(manager.areNotificationsEnabled()).thenReturn(areNotificationsEnabled); + + mHubModule.isNotificationEnabledOnOSLevel(mPromise); + + verify(manager, times(1)).areNotificationsEnabled(); + verify(mPromise, times(1)).resolve(areNotificationsEnabled); + } + @Test public void testOnHostResumeNoNotification() { Activity activity = PowerMockito.mock(Activity.class); diff --git a/sample/android/app/src/test/java/com/reactnativeazurenotificationhubsample/ReactNativeNotificationHubUtilTest.java b/sample/android/app/src/test/java/com/reactnativeazurenotificationhubsample/ReactNativeNotificationHubUtilTest.java index 041df647..d807fd7e 100644 --- a/sample/android/app/src/test/java/com/reactnativeazurenotificationhubsample/ReactNativeNotificationHubUtilTest.java +++ b/sample/android/app/src/test/java/com/reactnativeazurenotificationhubsample/ReactNativeNotificationHubUtilTest.java @@ -323,4 +323,80 @@ public void testHasChannelEnableLights() { verify(mSharedPreferences, times(1)).contains(KEY_FOR_PREFS_CHANNELENABLELIGHTS); } + + @Test + public void testGetTemplateName() { + mHubUtil.getTemplateName(mReactApplicationContext); + + verify(mSharedPreferences, times(1)).getString( + KEY_FOR_PREFS_TEMPLATENAME, null); + } + + @Test + public void testSetTemplateName() { + final String templateName = "Template Name"; + + mHubUtil.setTemplateName(mReactApplicationContext, templateName); + + verify(mEditor, times(1)).putString( + KEY_FOR_PREFS_TEMPLATENAME, templateName); + verify(mEditor, times(1)).apply(); + } + + @Test + public void testGetTemplate() { + mHubUtil.getTemplate(mReactApplicationContext); + + verify(mSharedPreferences, times(1)).getString( + KEY_FOR_PREFS_TEMPLATE, null); + } + + @Test + public void testSetTemplate() { + final String template = "Template"; + + mHubUtil.setTemplate(mReactApplicationContext, template); + + verify(mEditor, times(1)).putString( + KEY_FOR_PREFS_TEMPLATE, template); + verify(mEditor, times(1)).apply(); + } + + @Test + public void testIsTemplated() { + mHubUtil.isTemplated(mReactApplicationContext); + + verify(mSharedPreferences, times(1)).getBoolean( + KEY_FOR_PREFS_ISTEMPLATE, false); + } + + @Test + public void testSetTemplated() { + final boolean isTemplated = true; + + mHubUtil.setTemplated(mReactApplicationContext, isTemplated); + + verify(mEditor, times(1)).putBoolean( + KEY_FOR_PREFS_ISTEMPLATE, isTemplated); + verify(mEditor, times(1)).apply(); + } + + @Test + public void testGetUUID() { + mHubUtil.getUUID(mReactApplicationContext); + + verify(mSharedPreferences, times(1)).getString( + KEY_FOR_PREFS_UUID, null); + } + + @Test + public void testSetUUID() { + final String uuid = "uuid"; + + mHubUtil.setUUID(mReactApplicationContext, uuid); + + verify(mEditor, times(1)).putString( + KEY_FOR_PREFS_UUID, uuid); + verify(mEditor, times(1)).apply(); + } } From 9ba5ff5a7fbf0eb15ee3f4243076651572091f1c Mon Sep 17 00:00:00 2001 From: Phong Cao Date: Mon, 16 Mar 2020 21:19:23 -0400 Subject: [PATCH 2/5] Updated docs and sample code for Android. --- docs/android-installation.md | 99 ++++++++++++- sample/App.js | 275 +++++++++++++++++++++++------------ 2 files changed, 272 insertions(+), 102 deletions(-) diff --git a/docs/android-installation.md b/docs/android-installation.md index e7e142a4..0ce0286a 100644 --- a/docs/android-installation.md +++ b/docs/android-installation.md @@ -203,6 +203,9 @@ const channelImportance = 3; // The channel's importance (NotificationM const channelShowBadge = true; const channelEnableLights = true; const channelEnableVibration = true; +const template = '...'; // Notification hub templates: + // https://docs.microsoft.com/en-us/azure/notification-hubs/notification-hubs-templates-cross-platform-push-messages +const templateName = '...'; // The template's name export default class App extends Component { constructor(props) { @@ -213,7 +216,7 @@ export default class App extends Component { register() { PushNotificationEmitter.addListener(EVENT_AZURE_NOTIFICATION_HUB_REGISTERED, this._onAzureNotificationHubRegistered); PushNotificationEmitter.addListener(EVENT_AZURE_NOTIFICATION_HUB_REGISTERED_ERROR, this._onAzureNotificationHubRegisteredError); - + NotificationHub.register({ connectionString, hubName, @@ -225,12 +228,59 @@ export default class App extends Component { channelEnableLights, channelEnableVibration }) + .then((res) => console.warn(res)) + .catch(reason => console.warn(reason)); + } + + registerTemplate() { + PushNotificationEmitter.addListener(EVENT_AZURE_NOTIFICATION_HUB_REGISTERED, this._onAzureNotificationHubRegistered); + PushNotificationEmitter.addListener(EVENT_AZURE_NOTIFICATION_HUB_REGISTERED_ERROR, this._onAzureNotificationHubRegisteredError); + + NotificationHub.registerTemplate({ + connectionString, + hubName, + senderID, + template, + templateName, + tags, + channelName, + channelImportance, + channelShowBadge, + channelEnableLights, + channelEnableVibration + }) + .then((res) => console.warn(res)) + .catch(reason => console.warn(reason)); + } + + getInitialNotification() { + NotificationHub.getInitialNotification() + .then((res) => console.warn(res)) + .catch(reason => console.warn(reason)); + } + + getUUID() { + NotificationHub.getUUID(false) + .then((res) => console.warn(res)) + .catch(reason => console.warn(reason)); + } + + isNotificationEnabledOnOSLevel() { + NotificationHub.isNotificationEnabledOnOSLevel() + .then((res) => console.warn(res)) .catch(reason => console.warn(reason)); } unregister() { NotificationHub.unregister() - .catch(reason => console.warn(reason)); + .then((res) => console.warn(res)) + .catch(reason => console.warn(reason)); + } + + unregisterTemplate() { + NotificationHub.unregisterTemplate(templateName) + .then((res) => console.warn(res)) + .catch(reason => console.warn(reason)); } render() { @@ -240,28 +290,63 @@ export default class App extends Component { Register - + + + + + + + Register Template + + + + + + + Get initial notification + + + + + + + Get UUID + + + + + + + Check if notification is enabled + Unregister - + + + + + + + Unregister Template + ); } - + _onAzureNotificationHubRegistered(registrationID) { console.warn('RegistrationID: ' + registrationID); } - + _onAzureNotificationHubRegisteredError(error) { console.warn('Error: ' + error); } - + _onRemoteNotification(notification) { console.warn(notification); } diff --git a/sample/App.js b/sample/App.js index bb775809..9a0dc485 100644 --- a/sample/App.js +++ b/sample/App.js @@ -6,109 +6,194 @@ * @flow */ -import React from 'react'; +import React, { Component } from 'react'; +import { NativeEventEmitter } from 'react-native'; import { - SafeAreaView, StyleSheet, - ScrollView, - View, Text, - StatusBar, + View, + TouchableOpacity, } from 'react-native'; -import { - Header, - LearnMoreLinks, - Colors, - DebugInstructions, - ReloadInstructions, -} from 'react-native/Libraries/NewAppScreen'; - -const App: () => React$Node = () => { - return ( - <> - - - -
- {global.HermesInternal == null ? null : ( - - Engine: Hermes - - )} - - - Step One - - Edit App.js to change this - screen and then come back to see your edits. - - - - See Your Changes - - - - - - Debug - - - - - - Learn More - - Read the docs to discover what to do next: - - - - - - - - ); -}; +const NotificationHub = require('react-native-azurenotificationhub'); +const PushNotificationEmitter = new NativeEventEmitter(NotificationHub); + +const EVENT_AZURE_NOTIFICATION_HUB_REGISTERED = 'azureNotificationHubRegistered'; +const EVENT_AZURE_NOTIFICATION_HUB_REGISTERED_ERROR = 'azureNotificationHubRegisteredError'; +const EVENT_REMOTE_NOTIFICATION_RECEIVED = 'remoteNotificationReceived'; + +const connectionString = 'Endpoint=sb://rn-anh.servicebus.windows.net/;SharedAccessKeyName=DefaultFullSharedAccessSignature;SharedAccessKey=12345'; +const hubName = 'azurenotificationhub'; +const senderID = '12345'; +const tags = []; +const channelName = 'Channel Name'; +const channelImportance = 3; +const channelShowBadge = true; +const channelEnableLights = true; +const channelEnableVibration = true; +const template = '{\"data\":{\"message\":\"$(message)\"}}'; +const templateName = 'Template Name'; + +export default class App extends Component { + constructor(props) { + super(props); + PushNotificationEmitter.addListener(EVENT_REMOTE_NOTIFICATION_RECEIVED, this._onRemoteNotification); + } + + register() { + PushNotificationEmitter.addListener(EVENT_AZURE_NOTIFICATION_HUB_REGISTERED, this._onAzureNotificationHubRegistered); + PushNotificationEmitter.addListener(EVENT_AZURE_NOTIFICATION_HUB_REGISTERED_ERROR, this._onAzureNotificationHubRegisteredError); + + NotificationHub.register({ + connectionString, + hubName, + senderID, + tags, + channelName, + channelImportance, + channelShowBadge, + channelEnableLights, + channelEnableVibration + }) + .then((res) => console.warn(res)) + .catch(reason => console.warn(reason)); + } + + registerTemplate() { + PushNotificationEmitter.addListener(EVENT_AZURE_NOTIFICATION_HUB_REGISTERED, this._onAzureNotificationHubRegistered); + PushNotificationEmitter.addListener(EVENT_AZURE_NOTIFICATION_HUB_REGISTERED_ERROR, this._onAzureNotificationHubRegisteredError); + + NotificationHub.registerTemplate({ + connectionString, + hubName, + senderID, + template, + templateName, + tags, + channelName, + channelImportance, + channelShowBadge, + channelEnableLights, + channelEnableVibration + }) + .then((res) => console.warn(res)) + .catch(reason => console.warn(reason)); + } + + getInitialNotification() { + NotificationHub.getInitialNotification() + .then((res) => console.warn(res)) + .catch(reason => console.warn(reason)); + } + + getUUID() { + NotificationHub.getUUID(false) + .then((res) => console.warn(res)) + .catch(reason => console.warn(reason)); + } + + isNotificationEnabledOnOSLevel() { + NotificationHub.isNotificationEnabledOnOSLevel() + .then((res) => console.warn(res)) + .catch(reason => console.warn(reason)); + } + + unregister() { + NotificationHub.unregister() + .then((res) => console.warn(res)) + .catch(reason => console.warn(reason)); + } + + unregisterTemplate() { + NotificationHub.unregisterTemplate(templateName) + .then((res) => console.warn(res)) + .catch(reason => console.warn(reason)); + } + + render() { + return ( + + + + + Register + + + + + + + Register Template + + + + + + + Get initial notification + + + + + + + Get UUID + + + + + + + Check if notification is enabled + + + + + + + Unregister + + + + + + + Unregister Template + + + + + ); + } + + _onAzureNotificationHubRegistered(registrationID) { + console.warn('RegistrationID: ' + registrationID); + } + + _onAzureNotificationHubRegisteredError(error) { + console.warn('Error: ' + error); + } + + _onRemoteNotification(notification) { + console.warn(notification); + } +} const styles = StyleSheet.create({ - scrollView: { - backgroundColor: Colors.lighter, + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#F5FCFF', }, - engine: { - position: 'absolute', - right: 0, + welcome: { + fontSize: 20, + textAlign: 'center', + margin: 10, }, - body: { - backgroundColor: Colors.white, - }, - sectionContainer: { - marginTop: 32, - paddingHorizontal: 24, - }, - sectionTitle: { - fontSize: 24, - fontWeight: '600', - color: Colors.black, - }, - sectionDescription: { - marginTop: 8, - fontSize: 18, - fontWeight: '400', - color: Colors.dark, - }, - highlight: { - fontWeight: '700', - }, - footer: { - color: Colors.dark, - fontSize: 12, - fontWeight: '600', - padding: 4, - paddingRight: 12, - textAlign: 'right', + instructions: { + textAlign: 'center', + color: '#333333', + marginBottom: 5, }, }); - -export default App; From 81d45994be9c20d11906945b77b4066be0da877a Mon Sep 17 00:00:00 2001 From: Phong Cao Date: Thu, 19 Mar 2020 18:29:27 -0400 Subject: [PATCH 3/5] Replaced UIUserNotificationSettings deprecated APIs. Supported Azure notification hub template. --- docs/ios-installation.md | 81 ++- ios/AzureNotificationHubIOS.js | 16 +- .../RCTAzureNotificationHandler.h | 3 - .../RCTAzureNotificationHandler.m | 22 - .../RCTAzureNotificationHub.h | 19 +- .../RCTAzureNotificationHub.m | 13 +- .../RCTAzureNotificationHubManager.h | 40 +- .../RCTAzureNotificationHubManager.m | 279 +++++++--- .../RCTAzureNotificationHubUtil.h | 9 +- .../RCTAzureNotificationHubUtil.m | 43 +- .../project.pbxproj | 4 + .../AppDelegate.m | 32 +- ...iveAzureNotificationHubSample.entitlements | 8 + .../RCTAzureNotificationHandlerTests.m | 44 +- .../RCTAzureNotificationHubManagerTests.m | 515 ++++++++++++++---- .../RCTAzureNotificationHubUtilTests.m | 51 +- 16 files changed, 874 insertions(+), 305 deletions(-) create mode 100644 sample/ios/ReactNativeAzureNotificationHubSample/ReactNativeAzureNotificationHubSample.entitlements diff --git a/docs/ios-installation.md b/docs/ios-installation.md index 65c0593e..c3cf3996 100644 --- a/docs/ios-installation.md +++ b/docs/ios-installation.md @@ -9,7 +9,7 @@ react-native init ReactNativeAzureNotificationHubSample ``` In addition to the standard React Native requirements, you will also need the following: -* An iOS 9 (or later version)-capable device (simulator doesn't work with push notifications) +* An iOS 10 (or later version)-capable device (simulator doesn't work with push notifications) * [Apple Developer Program](https://developer.apple.com/programs/) membership ## Install react-native-azurenotificationhub @@ -156,34 +156,44 @@ Add the following line to your `ios/Podfile` file and run **pod install** * And then add the following code in the same file: ```objective-c -// Required to register for notifications -- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [RCTAzureNotificationHubManager didRegisterUserNotificationSettings:notificationSettings]; + ... + + // Registering for local notifications + [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self]; + + return YES; } -// Required for the register event. +// Invoked when the app successfully registered with Apple Push Notification service (APNs). - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [RCTAzureNotificationHubManager didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } -// Required for the registrationError event. +// Invoked when APNs cannot successfully complete the registration process. - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [RCTAzureNotificationHubManager didFailToRegisterForRemoteNotificationsWithError:error]; } -// Required for the notification event. -- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification +// Invoked when a remote notification arrived and there is data to be fetched. +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo +fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { - [RCTAzureNotificationHubManager didReceiveRemoteNotification:notification]; + [RCTAzureNotificationHubManager didReceiveRemoteNotification:userInfo + fetchCompletionHandler:completionHandler]; } -// Required for the localNotification event. -- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification +// Invoked when a notification arrived while the app was running in the foreground. +- (void)userNotificationCenter:(UNUserNotificationCenter *)center + willPresentNotification:(UNNotification *)notification + withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler { - [RCTAzureNotificationHubManager didReceiveLocalNotification:notification]; + [RCTAzureNotificationHubManager userNotificationCenter:center + willPresentNotification:notification + withCompletionHandler:completionHandler]; } ``` @@ -221,9 +231,12 @@ import { const NotificationHub = require('react-native-azurenotificationhub/index.ios'); -const connectionString = '...'; // The Notification Hub connection string -const hubName = '...'; // The Notification Hub name -const tags = [ ... ]; // The set of tags to subscribe to +const connectionString = '...'; // The Notification Hub connection string +const hubName = '...'; // The Notification Hub name +const tags = [ ... ]; // The set of tags to subscribe to +const template = '...'; // Notification hub templates: + // https://docs.microsoft.com/en-us/azure/notification-hubs/notification-hubs-templates-cross-platform-push-messages +const templateName = '...'; // The template's name let remoteNotificationsDeviceToken = ''; // The device token registered with APNS @@ -267,15 +280,33 @@ export default class App extends Component { // rejects, or if the permissions were previously rejected. The promise // resolves to the current state of the permission of // {alert: boolean, badge: boolean,sound: boolean } - NotificationHub.requestPermissions(); + NotificationHub.requestPermissions() + .then((res) => console.warn(res)) + .catch(reason => console.warn(reason)); } register() { - NotificationHub.register(remoteNotificationsDeviceToken, {connectionString, hubName, tags}); + NotificationHub.register(remoteNotificationsDeviceToken, { connectionString, hubName, tags }) + .then((res) => console.warn(res)) + .catch(reason => console.warn(reason)); + } + + registerTemplate() { + NotificationHub.registerTemplate(remoteNotificationsDeviceToken, { connectionString, hubName, tags, templateName, template }) + .then((res) => console.warn(res)) + .catch(reason => console.warn(reason)); } unregister() { - NotificationHub.unregister(); + NotificationHub.unregister() + .then((res) => console.warn(res)) + .catch(reason => console.warn(reason)); + } + + unregisterTemplate() { + NotificationHub.unregisterTemplate(templateName) + .then((res) => console.warn(res)) + .catch(reason => console.warn(reason)); } render() { @@ -295,6 +326,13 @@ export default class App extends Component { + + + + Register Template + + + @@ -302,6 +340,13 @@ export default class App extends Component { + + + + Unregister Template + + + ); } diff --git a/ios/AzureNotificationHubIOS.js b/ios/AzureNotificationHubIOS.js index 4a729795..952010e4 100644 --- a/ios/AzureNotificationHubIOS.js +++ b/ios/AzureNotificationHubIOS.js @@ -357,12 +357,20 @@ class AzureNotificationHubIOS { RCTAzureNotificationHubManager.checkPermissions(callback); } - static register(deviceToken, config) { - RCTAzureNotificationHubManager.register(deviceToken, config); + static register(deviceToken, config): Promise { + return RCTAzureNotificationHubManager.register(deviceToken, config); } - static unregister() { - RCTAzureNotificationHubManager.unregister(); + static registerTemplate(deviceToken, config): Promise { + return RCTAzureNotificationHubManager.registerTemplate(deviceToken, config); + } + + static unregister(): Promise { + return RCTAzureNotificationHubManager.unregister(); + } + + static unregisterTemplate(templateName): Promise { + return RCTAzureNotificationHubManager.unregisterTemplate(templateName); } /** diff --git a/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHandler.h b/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHandler.h index 34d4c476..2614c39d 100644 --- a/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHandler.h +++ b/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHandler.h @@ -35,9 +35,6 @@ // Handle registration error for Azure Notification Hub - (void)azureNotificationHubRegisteredError:(nonnull NSNotification *)notification; -// Handle successful registration for UIUserNotificationSettings -- (void)userNotificationSettingsRegistered:(nonnull NSNotification *)notification; - @end #endif /* RCTAzureNotificationHandler_h */ diff --git a/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHandler.m b/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHandler.m index 81222d54..cb595091 100644 --- a/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHandler.m +++ b/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHandler.m @@ -10,8 +10,6 @@ #import "RCTAzureNotificationHub.h" #import "RCTAzureNotificationHandler.h" -extern RCTPromiseResolveBlock requestPermissionsResolveBlock; - @implementation RCTAzureNotificationHandler { @private @@ -92,24 +90,4 @@ - (void)azureNotificationHubRegisteredError:(nonnull NSNotification *)notificati body:errorDetails]; } -// Handle successful registration for UIUserNotificationSettings -- (void)userNotificationSettingsRegistered:(nonnull NSNotification *)notification -{ - RCTPromiseResolveBlock resolve = notification.userInfo[RCTUserInfoResolveBlock]; - if (resolve == nil) - { - return; - } - - UIUserNotificationSettings *notificationSettings = notification.userInfo[RCTUserInfoNotificationSettings]; - NSDictionary *notificationTypes = @{ - RCTNotificationTypeAlert: @((notificationSettings.types & UIUserNotificationTypeAlert) > 0), - RCTNotificationTypeSound: @((notificationSettings.types & UIUserNotificationTypeSound) > 0), - RCTNotificationTypeBadge: @((notificationSettings.types & UIUserNotificationTypeBadge) > 0), - }; - - resolve(notificationTypes); - requestPermissionsResolveBlock = nil; -} - @end diff --git a/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHub.h b/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHub.h index 005a602b..2f997ac4 100644 --- a/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHub.h +++ b/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHub.h @@ -10,6 +10,8 @@ #ifndef RCTAzureNotificationHub_h #define RCTAzureNotificationHub_h +@import UserNotifications; + // Notification Hub events extern NSString *const RCTLocalNotificationReceived; extern NSString *const RCTRemoteNotificationReceived; @@ -23,9 +25,10 @@ extern NSString *const RCTUserNotificationSettingsRegistered; extern NSString *const RCTConnectionStringKey; extern NSString *const RCTHubNameKey; extern NSString *const RCTTagsKey; +extern NSString *const RCTTemplateNameKey; +extern NSString *const RCTTemplateKey; // User info -extern NSString *const RCTUserInfoNotificationSettings; extern NSString *const RCTUserInfoDeviceToken; extern NSString *const RCTUserInfoRemote; extern NSString *const RCTUserInfoResolveBlock; @@ -38,15 +41,25 @@ extern NSString *const RCTNotificationTypeBadge; extern NSString *const RCTNotificationTypeSound; extern NSString *const RCTNotificationTypeAlert; +// Messages +extern NSString *const RCTPromiseResolveUnregiseredSuccessfully; + // Errors extern NSString *const RCTErrorUnableToRequestPermissions; -extern NSString *const RCTErrorUnableToRequestPermissionsDetails; +extern NSString *const RCTErrorUnableToRequestPermissionsAppExt; extern NSString *const RCTErrorUnableToRequestPermissionsTwice; +extern NSString *const RCTErrorUnableToRequestPermissionsUserReject; extern NSString *const RCTErrorInvalidArguments; extern NSString *const RCTErrorMissingConnectionString; extern NSString *const RCTErrorMissingHubName; +extern NSString *const RCTErrorMissingTemplateName; +extern NSString *const RCTErrorMissingTemplate; +extern NSString *const RCTErrorUnableToUnregister; +extern NSString *const RCTErrorUnableToUnregisterNoRegistration; -// Completion type used in Azure Notification Hub's native methods +// Completion types typedef void (^RCTNativeCompletion)(NSError *error); +typedef void (^RCTNotificationCompletion)(BOOL granted, NSError * _Nullable error); +typedef void (^RCTNotificationSettingsCompletion)(UNNotificationSettings * _Nonnull settings); #endif /* RCTAzureNotificationHub_h */ diff --git a/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHub.m b/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHub.m index c1834d3e..d682014c 100644 --- a/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHub.m +++ b/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHub.m @@ -20,9 +20,10 @@ NSString *const RCTConnectionStringKey = @"connectionString"; NSString *const RCTHubNameKey = @"hubName"; NSString *const RCTTagsKey = @"tags"; +NSString *const RCTTemplateNameKey = @"templateName"; +NSString *const RCTTemplateKey = @"template"; // User info -NSString *const RCTUserInfoNotificationSettings = @"notificationSettings"; NSString *const RCTUserInfoDeviceToken = @"deviceToken"; NSString *const RCTUserInfoRemote = @"remote"; NSString *const RCTUserInfoResolveBlock = @"resolveBlock"; @@ -35,10 +36,18 @@ NSString *const RCTNotificationTypeSound = @"sound"; NSString *const RCTNotificationTypeAlert = @"alert"; +// Messages +NSString *const RCTPromiseResolveUnregiseredSuccessfully = @"Unregisted successfully"; + // Errors NSString *const RCTErrorUnableToRequestPermissions = @"Unabled to request permissions"; -NSString *const RCTErrorUnableToRequestPermissionsDetails = @"Requesting push notifications is currently unavailable in an app extension"; +NSString *const RCTErrorUnableToRequestPermissionsAppExt = @"Requesting push notifications is currently unavailable in an app extension."; NSString *const RCTErrorUnableToRequestPermissionsTwice = @"Cannot call requestPermissions twice before the first has returned."; +NSString *const RCTErrorUnableToRequestPermissionsUserReject = @"User didn't allow permissions."; NSString *const RCTErrorInvalidArguments = @"Invalid arguments"; NSString *const RCTErrorMissingConnectionString = @"Connection string cannot be null."; NSString *const RCTErrorMissingHubName = @"Hub name cannot be null."; +NSString *const RCTErrorMissingTemplateName = @"Template name cannot be null."; +NSString *const RCTErrorMissingTemplate = @"Template cannot be null."; +NSString *const RCTErrorUnableToUnregister = @"Unabled to unregister"; +NSString *const RCTErrorUnableToUnregisterNoRegistration = @"There is no registration."; diff --git a/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubManager.h b/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubManager.h index bfd707e6..6d09f99f 100644 --- a/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubManager.h +++ b/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubManager.h @@ -9,22 +9,26 @@ #import "React/RCTEventEmitter.h" -@interface RCTAzureNotificationHubManager : RCTEventEmitter +#import "RCTAzureNotificationHandler.h" + +@import UserNotifications; -// Required to register for notifications, invoked from AppDelegate -+ (void)didRegisterUserNotificationSettings:(nonnull UIUserNotificationSettings *)notificationSettings; +@interface RCTAzureNotificationHubManager : RCTEventEmitter -// Required for the register event, invoked from AppDelegate +// Invoked from AppDelegate when the app successfully registered with Apple Push Notification service (APNs). + (void)didRegisterForRemoteNotificationsWithDeviceToken:(nonnull NSData *)deviceToken; -// Required for the notification event, invoked from AppDelegate -+ (void)didReceiveRemoteNotification:(nonnull NSDictionary *)notification; +// Invoked from AppDelegate when APNs cannot successfully complete the registration process. ++ (void)didFailToRegisterForRemoteNotificationsWithError:(nonnull NSError *)error; -// Required for the localNotification event, invoked from AppDelegate -+ (void)didReceiveLocalNotification:(nonnull UILocalNotification *)notification; +// Invoked from AppDelegate when a remote notification arrived and there is data to be fetched. ++ (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo + fetchCompletionHandler:(void (__unused ^_Nonnull)(UIBackgroundFetchResult result))completionHandler; -// Required for the registrationError event, invoked from AppDelegate -+ (void)didFailToRegisterForRemoteNotificationsWithError:(nonnull NSError *)error; +// Invoked from AppDelegate when a notification arrived while the app was running in the foreground. ++ (void)userNotificationCenter:(nonnull __unused UNUserNotificationCenter *)center + willPresentNotification:(nonnull UNNotification *)notification + withCompletionHandler:(void (__unused ^_Nonnull)(UNNotificationPresentationOptions options))completionHandler; // Set application icon badge number - (void)setApplicationIconBadgeNumber:(NSInteger)number; @@ -65,11 +69,25 @@ // Register with Azure Notification Hub - (void)register:(nonnull NSString *)deviceToken config:(nonnull NSDictionary *)config - resolver:(nonnull RCTPromiseResolveBlock)resolve + resolver:(nonnull __unused RCTPromiseResolveBlock)resolve rejecter:(nonnull RCTPromiseRejectBlock)reject; +// Register template +- (void)registerTemplate:(nonnull NSString *)deviceToken + config:(nonnull NSDictionary *)config + resolver:(nonnull __unused RCTPromiseResolveBlock)resolve + rejecter:(nonnull RCTPromiseRejectBlock)reject; + // Unregister with Azure Notification Hub - (void)unregister:(nonnull RCTPromiseResolveBlock)resolve rejecter:(nonnull RCTPromiseRejectBlock)reject; +// Unregister template +- (void)unregisterTemplate:(nonnull NSString *)templateName + resolver:(nonnull RCTPromiseResolveBlock)resolve + rejecter:(nonnull RCTPromiseRejectBlock)reject; + +// Set notification handler +- (void)setNotificationHandler:(nonnull RCTAzureNotificationHandler *)handler; + @end diff --git a/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubManager.m b/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubManager.m index deba2f41..b99de1b1 100644 --- a/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubManager.m +++ b/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubManager.m @@ -15,7 +15,6 @@ #import "React/RCTUtils.h" #import "RCTAzureNotificationHub.h" -#import "RCTAzureNotificationHandler.h" #import "RCTAzureNotificationHubManager.h" #import "RCTAzureNotificationHubUtil.h" @@ -23,20 +22,30 @@ RCT_EXTERN NSString *const RCTErrorUnspecified; static RCTAzureNotificationHandler *notificationHandler; -RCTPromiseResolveBlock requestPermissionsResolveBlock; -RCTPromiseRejectBlock requestPermissionsRejectBlock; @implementation RCTAzureNotificationHubManager { @private // The Notification Hub connection string NSString *_connectionString; - + // The Notification Hub name NSString *_hubName; - + // The Notification Hub tags NSSet *_tags; + + // The template name + NSString *_templateName; + + // The template JSON blob + NSString *_template; + + // The Promise resolve block + RCTPromiseResolveBlock _requestPermissionsResolveBlock; + + // The Promise reject block + RCTPromiseRejectBlock _requestPermissionsRejectBlock; } RCT_EXPORT_MODULE() @@ -90,11 +99,6 @@ - (void)startObserving selector:@selector(azureNotificationHubRegisteredError:) name:RCTAzureNotificationHubRegisteredError object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:notificationHandler - selector:@selector(userNotificationSettingsRegistered:) - name:RCTUserNotificationSettingsRegistered - object:nil]; } - (void)stopObserving @@ -102,35 +106,10 @@ - (void)stopObserving [[NSNotificationCenter defaultCenter] removeObserver:self]; } -- (bool)assertArguments:(RCTPromiseRejectBlock)reject +// Set notification handler +- (void)setNotificationHandler:(nonnull RCTAzureNotificationHandler *)handler { - if (_connectionString == nil) - { - reject(RCTErrorInvalidArguments, RCTErrorMissingConnectionString, nil); - return false; - } - - if (_hubName == nil) - { - reject(RCTErrorInvalidArguments, RCTErrorMissingHubName, nil); - return false; - } - - return true; -} - -+ (void)didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings -{ - if ([UIApplication instancesRespondToSelector:@selector(registerForRemoteNotifications)]) - { - [[UIApplication sharedApplication] registerForRemoteNotifications]; - [[NSNotificationCenter defaultCenter] postNotificationName:RCTUserNotificationSettingsRegistered - object:notificationHandler - userInfo:@{ - RCTUserInfoNotificationSettings: notificationSettings, - RCTUserInfoResolveBlock: requestPermissionsResolveBlock - }]; - } + notificationHandler = handler; } + (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken @@ -148,18 +127,21 @@ + (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error userInfo:@{RCTUserInfoError: error}]; } -+ (void)didReceiveRemoteNotification:(NSDictionary *)notification ++ (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo + fetchCompletionHandler:(void (__unused ^_Nonnull)(UIBackgroundFetchResult result))completionHandler { [[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationReceived object:notificationHandler - userInfo:notification]; + userInfo:userInfo]; } -+ (void)didReceiveLocalNotification:(UILocalNotification *)notification ++ (void)userNotificationCenter:(nonnull __unused UNUserNotificationCenter *)center + willPresentNotification:(nonnull UNNotification *)notification + withCompletionHandler:(void (__unused ^_Nonnull)(UNNotificationPresentationOptions options))completionHandler { [[NSNotificationCenter defaultCenter] postNotificationName:RCTLocalNotificationReceived object:notificationHandler - userInfo:[RCTAzureNotificationHubUtil formatLocalNotification:notification]]; + userInfo:[RCTAzureNotificationHubUtil formatUNNotification:notification]]; } // Update the application icon badge number on the home screen @@ -180,31 +162,49 @@ + (void)didReceiveLocalNotification:(UILocalNotification *)notification { if (RCTRunningInAppExtension()) { - reject(RCTErrorUnableToRequestPermissions, nil, RCTErrorWithMessage(RCTErrorUnableToRequestPermissionsDetails)); + reject(RCTErrorUnableToRequestPermissions, nil, RCTErrorWithMessage(RCTErrorUnableToRequestPermissionsAppExt)); return; } - - if (requestPermissionsResolveBlock != nil) + + if (_requestPermissionsResolveBlock != nil) { RCTLogError(@"%@", RCTErrorUnableToRequestPermissionsTwice); return; } - - requestPermissionsResolveBlock = resolve; - requestPermissionsRejectBlock = reject; - - UIUserNotificationType types = [RCTAzureNotificationHubUtil getNotificationTypesWithPermissions:permissions]; - UIApplication *app = RCTSharedApplication(); - if ([app respondsToSelector:@selector(registerUserNotificationSettings:)]) - { - UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:(NSUInteger)types - categories:nil]; - [app registerUserNotificationSettings:notificationSettings]; - } - else - { - [app registerForRemoteNotificationTypes:(NSUInteger)types]; - } + + _requestPermissionsResolveBlock = resolve; + _requestPermissionsRejectBlock = reject; + + UNAuthorizationOptions options = [RCTAzureNotificationHubUtil getNotificationTypesWithPermissions:permissions]; + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center requestAuthorizationWithOptions:options + completionHandler:^(BOOL granted, NSError * _Nullable error) { + if (error != nil) + { + reject(RCTErrorUnableToRequestPermissions, nil, RCTErrorWithMessage(error.localizedDescription)); + } + else if (!granted) + { + reject(RCTErrorUnableToRequestPermissions, nil, RCTErrorWithMessage(RCTErrorUnableToRequestPermissionsUserReject)); + } + else + { + [RCTAzureNotificationHubUtil runOnMainThread:^{ + [[UIApplication sharedApplication] registerForRemoteNotifications]; + }]; + + NSDictionary *notificationTypes = @{ + RCTNotificationTypeAlert: @((options & UNAuthorizationOptionAlert) > 0), + RCTNotificationTypeSound: @((options & UNAuthorizationOptionSound) > 0), + RCTNotificationTypeBadge: @((options & UNAuthorizationOptionBadge) > 0), + }; + + resolve(notificationTypes); + } + + _requestPermissionsResolveBlock = nil; + _requestPermissionsRejectBlock = nil; + }]; } RCT_EXPORT_METHOD(abandonPermissions) @@ -219,13 +219,18 @@ + (void)didReceiveLocalNotification:(UILocalNotification *)notification callback(@[@{RCTNotificationTypeAlert: @NO, RCTNotificationTypeBadge: @NO, RCTNotificationTypeSound: @NO}]); return; } - - NSUInteger types = [RCTSharedApplication() currentUserNotificationSettings].types; - callback(@[@{ - RCTNotificationTypeAlert: @((types & UIUserNotificationTypeAlert) > 0), - RCTNotificationTypeBadge: @((types & UIUserNotificationTypeBadge) > 0), - RCTNotificationTypeSound: @((types & UIUserNotificationTypeSound) > 0), - }]); + + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + + [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) { + callback(@[@{ + RCTNotificationTypeAlert: @(settings.notificationCenterSetting == UNNotificationSettingEnabled + || settings.lockScreenSetting == UNNotificationSettingEnabled + || settings.alertSetting == UNNotificationSettingEnabled), + RCTNotificationTypeBadge: @(settings.badgeSetting == UNNotificationSettingEnabled), + RCTNotificationTypeSound: @(settings.soundSetting == UNNotificationSettingEnabled) + }]); + }]; } RCT_EXPORT_METHOD(presentLocalNotification:(UILocalNotification *)notification) @@ -307,8 +312,8 @@ + (void)didReceiveLocalNotification:(UILocalNotification *)notification RCT_EXPORT_METHOD(register:(nonnull NSString *)deviceToken config:(nonnull NSDictionary *)config - resolver:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) + resolver:(__unused RCTPromiseResolveBlock)resolve + rejecter:(nonnull RCTPromiseRejectBlock)reject) { // Store the connection string, hub name and tags _connectionString = [config objectForKey:RCTConnectionStringKey]; @@ -316,15 +321,22 @@ + (void)didReceiveLocalNotification:(UILocalNotification *)notification _tags = [config objectForKey:RCTTagsKey]; // Check arguments - if (![self assertArguments:reject]) + if (_connectionString == nil) { + reject(RCTErrorInvalidArguments, RCTErrorMissingConnectionString, nil); return; } - + + if (_hubName == nil) + { + reject(RCTErrorInvalidArguments, RCTErrorMissingHubName, nil); + return; + } + // Initialize hub SBNotificationHub *hub = [RCTAzureNotificationHubUtil createAzureNotificationHub:_connectionString hubName:_hubName]; - + // Register for native notifications [RCTAzureNotificationHubUtil runOnMainThread:^ { @@ -348,19 +360,87 @@ + (void)didReceiveLocalNotification:(UILocalNotification *)notification }]; } -RCT_EXPORT_METHOD(unregister:(RCTPromiseResolveBlock)resolve - rejecter:(RCTPromiseRejectBlock)reject) +RCT_EXPORT_METHOD(registerTemplate:(nonnull NSString *)deviceToken + config:(nonnull NSDictionary *)config + resolver:(nonnull __unused RCTPromiseResolveBlock)resolve + rejecter:(nonnull RCTPromiseRejectBlock)reject) { + // Store the connection string, hub name and tags + _connectionString = [config objectForKey:RCTConnectionStringKey]; + _hubName = [config objectForKey:RCTHubNameKey]; + _tags = [config objectForKey:RCTTagsKey]; + _templateName = [config objectForKey:RCTTemplateNameKey]; + _template = [config objectForKey:RCTTemplateKey]; + // Check arguments - if (![self assertArguments:reject]) + if (_connectionString == nil) { + reject(RCTErrorInvalidArguments, RCTErrorMissingConnectionString, nil); return; } - + + if (_hubName == nil) + { + reject(RCTErrorInvalidArguments, RCTErrorMissingHubName, nil); + return; + } + + if (_templateName == nil) + { + reject(RCTErrorInvalidArguments, RCTErrorMissingTemplateName, nil); + return; + } + + if (_template == nil) + { + reject(RCTErrorInvalidArguments, RCTErrorMissingTemplate, nil); + return; + } + // Initialize hub SBNotificationHub *hub = [RCTAzureNotificationHubUtil createAzureNotificationHub:_connectionString hubName:_hubName]; - + + // Register for native notifications + [RCTAzureNotificationHubUtil runOnMainThread:^ + { + [hub registerTemplateWithDeviceToken:deviceToken + name:_templateName + jsonBodyTemplate:_template + expiryTemplate:nil + tags:_tags + completion:^(NSError* error) + { + if (error != nil) + { + [[NSNotificationCenter defaultCenter] postNotificationName:RCTAzureNotificationHubRegisteredError + object:notificationHandler + userInfo:@{RCTUserInfoError: error}]; + } + else + { + [[NSNotificationCenter defaultCenter] postNotificationName:RCTAzureNotificationHubRegistered + object:notificationHandler + userInfo:@{RCTUserInfoSuccess: @YES}]; + } + }]; + }]; +} + +RCT_EXPORT_METHOD(unregister:(nonnull RCTPromiseResolveBlock)resolve + rejecter:(nonnull RCTPromiseRejectBlock)reject) +{ + // Check arguments + if (_connectionString == nil || _hubName == nil) + { + reject(RCTErrorUnableToUnregister, RCTErrorUnableToUnregisterNoRegistration, nil); + return; + } + + // Initialize hub + SBNotificationHub *hub = [RCTAzureNotificationHubUtil createAzureNotificationHub:_connectionString + hubName:_hubName]; + // Unregister for native notifications [RCTAzureNotificationHubUtil runOnMainThread:^ { @@ -371,6 +451,49 @@ + (void)didReceiveLocalNotification:(UILocalNotification *)notification [[NSNotificationCenter defaultCenter] postNotificationName:RCTErrorUnspecified object:notificationHandler userInfo:@{RCTUserInfoError: error}]; + + reject(RCTErrorUnableToUnregister, [error localizedDescription], nil); + } + else + { + _connectionString = nil; + resolve(RCTPromiseResolveUnregiseredSuccessfully); + } + }]; + }]; +} + +RCT_EXPORT_METHOD(unregisterTemplate:(nonnull NSString *)templateName + resolver:(nonnull RCTPromiseResolveBlock)resolve + rejecter:(nonnull RCTPromiseRejectBlock)reject) +{ + // Check arguments + if (_connectionString == nil || _hubName == nil || _template == nil) + { + reject(RCTErrorUnableToUnregister, RCTErrorUnableToUnregisterNoRegistration, nil); + return; + } + + // Initialize hub + SBNotificationHub *hub = [RCTAzureNotificationHubUtil createAzureNotificationHub:_connectionString + hubName:_hubName]; + + // Unregister for native notifications + [RCTAzureNotificationHubUtil runOnMainThread:^ + { + [hub unregisterTemplateWithName:templateName completion:^(NSError *error) + { + if (error != nil) + { + [[NSNotificationCenter defaultCenter] postNotificationName:RCTErrorUnspecified + object:notificationHandler + userInfo:@{RCTUserInfoError: error}]; + + reject(RCTErrorUnableToUnregister, [error localizedDescription], nil); + } + else + { + resolve(RCTPromiseResolveUnregiseredSuccessfully); } }]; }]; diff --git a/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubUtil.h b/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubUtil.h index f319b226..23c0316e 100644 --- a/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubUtil.h +++ b/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubUtil.h @@ -12,8 +12,13 @@ #import +@import UserNotifications; + @interface RCTAzureNotificationHubUtil : NSObject +// Format UNNotification ++ (nonnull NSDictionary *)formatUNNotification:(nonnull UNNotification *)notification; + // Format local notification + (nonnull NSDictionary *)formatLocalNotification:(nonnull UILocalNotification *)notification; @@ -25,10 +30,10 @@ + (nonnull NSString *)convertDeviceTokenToString:(nonnull NSData *)deviceToken; // Get notification types with permissions -+ (UIUserNotificationType)getNotificationTypesWithPermissions:(nullable NSDictionary *)permissions; ++ (UNAuthorizationOptions)getNotificationTypesWithPermissions:(nullable NSDictionary *)permissions; // Run block on the main thread -+ (void)runOnMainThread:(dispatch_block_t)block; ++ (void)runOnMainThread:(nonnull dispatch_block_t)block; @end diff --git a/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubUtil.m b/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubUtil.m index 49bed9a4..8e850e08 100644 --- a/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubUtil.m +++ b/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubUtil.m @@ -15,6 +15,33 @@ @implementation RCTAzureNotificationHubUtil +// Format UNNotification ++ (nonnull NSDictionary *)formatUNNotification:(nonnull UNNotification *)notification +{ + NSMutableDictionary *formattedNotification = [NSMutableDictionary dictionary]; + UNNotificationContent *content = notification.request.content; + + formattedNotification[@"identifier"] = notification.request.identifier; + + if (notification.date) + { + NSDateFormatter *formatter = [NSDateFormatter new]; + [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"]; + NSString *dateString = [formatter stringFromDate:notification.date]; + formattedNotification[@"date"] = dateString; + } + + formattedNotification[@"title"] = RCTNullIfNil(content.title); + formattedNotification[@"thread-id"] = RCTNullIfNil(content.threadIdentifier); + formattedNotification[@"alertBody"] = RCTNullIfNil(content.body); + formattedNotification[@"applicationIconBadgeNumber"] = RCTNullIfNil(content.badge); + formattedNotification[@"category"] = RCTNullIfNil(content.categoryIdentifier); + formattedNotification[@"soundName"] = RCTNullIfNil(content.sound); + formattedNotification[@"userInfo"] = RCTNullIfNil(RCTJSONClean(content.userInfo)); + + return formattedNotification; +} + // Format local notification + (nonnull NSDictionary *)formatLocalNotification:(nonnull UILocalNotification *)notification { @@ -26,7 +53,7 @@ + (nonnull NSDictionary *)formatLocalNotification:(nonnull UILocalNotification * NSString *fireDateString = [formatter stringFromDate:notification.fireDate]; formattedLocalNotification[@"fireDate"] = fireDateString; } - + formattedLocalNotification[@"alertAction"] = RCTNullIfNil(notification.alertAction); formattedLocalNotification[@"alertBody"] = RCTNullIfNil(notification.alertBody); formattedLocalNotification[@"applicationIconBadgeNumber"] = @(notification.applicationIconBadgeNumber); @@ -62,36 +89,36 @@ + (nonnull NSString *)convertDeviceTokenToString:(nonnull NSData *)deviceToken } // Get notification types with permissions -+ (UIUserNotificationType)getNotificationTypesWithPermissions:(nullable NSDictionary *)permissions ++ (UNAuthorizationOptions)getNotificationTypesWithPermissions:(nullable NSDictionary *)permissions { - UIUserNotificationType types = UIUserNotificationTypeNone; + UNAuthorizationOptions types = 0; if (permissions) { if ([RCTConvert BOOL:permissions[RCTNotificationTypeAlert]]) { - types |= UIUserNotificationTypeAlert; + types |= UNAuthorizationOptionAlert; } if ([RCTConvert BOOL:permissions[RCTNotificationTypeBadge]]) { - types |= UIUserNotificationTypeBadge; + types |= UNAuthorizationOptionBadge; } if ([RCTConvert BOOL:permissions[RCTNotificationTypeSound]]) { - types |= UIUserNotificationTypeSound; + types |= UNAuthorizationOptionSound; } } else { - types = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound; + types = UNAuthorizationOptionAlert | UNAuthorizationOptionBadge | UNAuthorizationOptionSound; } return types; } // Run block on the main thread -+ (void)runOnMainThread:(dispatch_block_t)block ++ (void)runOnMainThread:(nonnull dispatch_block_t)block { dispatch_async(dispatch_get_main_queue(), block); } diff --git a/sample/ios/ReactNativeAzureNotificationHubSample.xcodeproj/project.pbxproj b/sample/ios/ReactNativeAzureNotificationHubSample.xcodeproj/project.pbxproj index dce4d5c0..d620dba3 100644 --- a/sample/ios/ReactNativeAzureNotificationHubSample.xcodeproj/project.pbxproj +++ b/sample/ios/ReactNativeAzureNotificationHubSample.xcodeproj/project.pbxproj @@ -55,6 +55,7 @@ 2D02E4901E0B4A5D006451C7 /* ReactNativeAzureNotificationHubSample-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "ReactNativeAzureNotificationHubSample-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 3AAB39D4BDBE7F53EC74C4C4 /* libPods-ReactNativeAzureNotificationHubSample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeAzureNotificationHubSample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 3B18387DF86E68D789995173 /* Pods-ReactNativeAzureNotificationHubSample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeAzureNotificationHubSample.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeAzureNotificationHubSample/Pods-ReactNativeAzureNotificationHubSample.release.xcconfig"; sourceTree = ""; }; + 45454162242451A2003F4618 /* ReactNativeAzureNotificationHubSample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = ReactNativeAzureNotificationHubSample.entitlements; path = ReactNativeAzureNotificationHubSample/ReactNativeAzureNotificationHubSample.entitlements; sourceTree = ""; }; 45E30B45240821F000A339F1 /* RCTAzureNotificationHubManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAzureNotificationHubManagerTests.m; sourceTree = ""; tabWidth = 4; }; 45E30B46240821F000A339F1 /* RCTAzureNotificationHubUtilTests.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAzureNotificationHubUtilTests.m; sourceTree = ""; tabWidth = 4; }; 45E30B47240821F100A339F1 /* RCTAzureNotificationHandlerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAzureNotificationHandlerTests.m; sourceTree = ""; tabWidth = 4; }; @@ -120,6 +121,7 @@ 13B07FAE1A68108700A75B9A /* ReactNativeAzureNotificationHubSample */ = { isa = PBXGroup; children = ( + 45454162242451A2003F4618 /* ReactNativeAzureNotificationHubSample.entitlements */, 008F07F21AC5B25A0029DE68 /* main.jsbundle */, 13B07FAF1A68108700A75B9A /* AppDelegate.h */, 13B07FB01A68108700A75B9A /* AppDelegate.m */, @@ -589,6 +591,7 @@ baseConfigurationReference = 2351567499705FB80A85A312 /* Pods-ReactNativeAzureNotificationHubSample.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = ReactNativeAzureNotificationHubSample/ReactNativeAzureNotificationHubSample.entitlements; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; INFOPLIST_FILE = ReactNativeAzureNotificationHubSample/Info.plist; @@ -609,6 +612,7 @@ baseConfigurationReference = 3B18387DF86E68D789995173 /* Pods-ReactNativeAzureNotificationHubSample.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = ReactNativeAzureNotificationHubSample/ReactNativeAzureNotificationHubSample.entitlements; CURRENT_PROJECT_VERSION = 1; INFOPLIST_FILE = ReactNativeAzureNotificationHubSample/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; diff --git a/sample/ios/ReactNativeAzureNotificationHubSample/AppDelegate.m b/sample/ios/ReactNativeAzureNotificationHubSample/AppDelegate.m index e6d60560..781aa107 100644 --- a/sample/ios/ReactNativeAzureNotificationHubSample/AppDelegate.m +++ b/sample/ios/ReactNativeAzureNotificationHubSample/AppDelegate.m @@ -28,6 +28,10 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( rootViewController.view = rootView; self.window.rootViewController = rootViewController; [self.window makeKeyAndVisible]; + + // Registering for local notifications + [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self]; + return YES; } @@ -40,34 +44,34 @@ - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge #endif } -// Required to register for notifications -- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings -{ - [RCTAzureNotificationHubManager didRegisterUserNotificationSettings:notificationSettings]; -} - -// Required for the register event. +// Invoked when the app successfully registered with Apple Push Notification service (APNs). - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [RCTAzureNotificationHubManager didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } -// Required for the registrationError event. +// Invoked when APNs cannot successfully complete the registration process. - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [RCTAzureNotificationHubManager didFailToRegisterForRemoteNotificationsWithError:error]; } -// Required for the notification event. -- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification +// Invoked when a remote notification arrived and there is data to be fetched. +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { - [RCTAzureNotificationHubManager didReceiveRemoteNotification:notification]; + [RCTAzureNotificationHubManager didReceiveRemoteNotification:userInfo + fetchCompletionHandler:completionHandler]; } -// Required for the localNotification event. -- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification +// Invoked when a notification arrived while the app was running in the foreground. +- (void)userNotificationCenter:(UNUserNotificationCenter *)center + willPresentNotification:(UNNotification *)notification + withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler { - [RCTAzureNotificationHubManager didReceiveLocalNotification:notification]; + [RCTAzureNotificationHubManager userNotificationCenter:center + willPresentNotification:notification + withCompletionHandler:completionHandler]; } @end diff --git a/sample/ios/ReactNativeAzureNotificationHubSample/ReactNativeAzureNotificationHubSample.entitlements b/sample/ios/ReactNativeAzureNotificationHubSample/ReactNativeAzureNotificationHubSample.entitlements new file mode 100644 index 00000000..903def2a --- /dev/null +++ b/sample/ios/ReactNativeAzureNotificationHubSample/ReactNativeAzureNotificationHubSample.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/sample/ios/ReactNativeAzureNotificationHubSampleTests/RCTAzureNotificationHandlerTests.m b/sample/ios/ReactNativeAzureNotificationHubSampleTests/RCTAzureNotificationHandlerTests.m index a3273415..a5482799 100644 --- a/sample/ios/ReactNativeAzureNotificationHubSampleTests/RCTAzureNotificationHandlerTests.m +++ b/sample/ios/ReactNativeAzureNotificationHubSampleTests/RCTAzureNotificationHandlerTests.m @@ -93,31 +93,39 @@ - (void)testRemoteNotificationRegisteredError body:expectedErrorDetails]); } -- (void)testUserNotificationSettingsRegistered +- (void)testAzureNotificationHubRegistered { - __block NSDictionary *notificationTypes = nil; - RCTPromiseResolveBlock resolver = ^(NSDictionary *callbackNotificationTypes) - { - notificationTypes = callbackNotificationTypes; - }; + NSNotification* notification = [NSNotification notificationWithName:@"Notification" object:_userInfo userInfo:_userInfo]; + + [_notificationHandler azureNotificationHubRegistered:notification]; - UIUserNotificationType types = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge; - UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(NSUInteger)types - categories:nil]; + OCMVerify([_eventEmitter sendEventWithName:RCTAzureNotificationHubRegistered + body:_userInfo]); +} + +- (void)testAzureNotificationHubRegisteredError +{ + NSDictionary *errorUserInfo = [[NSDictionary alloc] + initWithObjectsAndKeys:NSLocalizedDescriptionKey, NSLocalizedDescriptionKey, nil]; + + NSError* error = [NSError errorWithDomain:@"Error domain" + code:100 + userInfo:errorUserInfo]; - NSArray *keys = [NSArray arrayWithObjects:RCTUserInfoNotificationSettings, RCTUserInfoResolveBlock, nil]; - NSArray *objects = [NSArray arrayWithObjects:settings, resolver, nil]; + NSArray *keys = [NSArray arrayWithObjects:RCTUserInfoError, nil]; + NSArray *objects = [NSArray arrayWithObjects:error, nil]; NSDictionary *userInfo = [[NSMutableDictionary alloc] initWithObjects:objects forKeys:keys]; NSNotification* notification = [NSNotification notificationWithName:@"Notification" object:userInfo userInfo:userInfo]; - NSDictionary *expectedNotificationTypes = @{ - RCTNotificationTypeAlert: @YES, - RCTNotificationTypeSound: @NO, - RCTNotificationTypeBadge: @YES + NSDictionary *expectedErrorDetails = @{ + @"message": NSLocalizedDescriptionKey, + @"code": [NSNumber numberWithInt:100], + @"details": errorUserInfo }; - - [_notificationHandler userNotificationSettingsRegistered:notification]; - XCTAssertEqualObjects(notificationTypes, expectedNotificationTypes); + [_notificationHandler azureNotificationHubRegisteredError:notification]; + + OCMVerify([_eventEmitter sendEventWithName:RCTAzureNotificationHubRegisteredError + body:expectedErrorDetails]); } @end diff --git a/sample/ios/ReactNativeAzureNotificationHubSampleTests/RCTAzureNotificationHubManagerTests.m b/sample/ios/ReactNativeAzureNotificationHubSampleTests/RCTAzureNotificationHubManagerTests.m index 429afe78..d3f54fb0 100644 --- a/sample/ios/ReactNativeAzureNotificationHubSampleTests/RCTAzureNotificationHubManagerTests.m +++ b/sample/ios/ReactNativeAzureNotificationHubSampleTests/RCTAzureNotificationHubManagerTests.m @@ -22,6 +22,10 @@ @interface RCTAzureNotificationHubManagerTests : XCTestCase static NSString *const RCTTestDeviceToken = @"Device Token"; static NSString *const RCTTestConnectionString = @"Connection String"; static NSString *const RCTTestHubName = @"Hub Name"; +static NSString *const RCTTestTemplate = @"Template"; +static NSString *const RCTTestTemplateName = @"Template Name"; + +id sharedApplicationMock; @implementation RCTAzureNotificationHubManagerTests { @@ -31,38 +35,55 @@ @implementation RCTAzureNotificationHubManagerTests RCTPromiseResolveBlock _resolver; RCTPromiseRejectBlock _rejecter; NSSet *_tags; - UILocalNotification *_notification; + UILocalNotification *_localNotification; + id _notificationMock; id _hubMock; id _hubUtilMock; + id _notificationHandlerMock; +} + ++ (void)setUp +{ + sharedApplicationMock = OCMPartialMock([UIApplication sharedApplication]); } - (void)setUp { [super setUp]; - + _hubManager = [[RCTAzureNotificationHubManager alloc] init]; _config = [[NSMutableDictionary alloc] init]; _resolver = ^(id result) {}; _rejecter = ^(NSString *code, NSString *message, NSError *error) {}; _tags = [[NSSet alloc] initWithArray:@[ @"Tag" ]]; - _notification = [[UILocalNotification alloc] init]; + _localNotification = [[UILocalNotification alloc] init]; NSArray *keys = [NSArray arrayWithObjects:@"Title", @"Message", nil]; NSArray *objects = [NSArray arrayWithObjects:@"Title", @"Message", nil]; NSDictionary *info = [[NSDictionary alloc] initWithObjects:objects forKeys:keys]; - [_notification setUserInfo:info]; + [_localNotification setUserInfo:info]; + + _notificationMock = OCMClassMock([UNNotification class]); + UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; + id notificationRequestMock = OCMClassMock([UNNotificationRequest class]); + OCMStub([notificationRequestMock content]).andReturn(content); + OCMStub([notificationRequestMock identifier]).andReturn( @"identifier"); + OCMStub([_notificationMock request]).andReturn(notificationRequestMock); + content.userInfo = info; _hubMock = OCMClassMock([SBNotificationHub class]); _hubUtilMock = OCMClassMock([RCTAzureNotificationHubUtil class]); OCMStub([_hubUtilMock createAzureNotificationHub:OCMOCK_ANY hubName:OCMOCK_ANY]).andReturn(_hubMock); - + _notificationHandlerMock = OCMClassMock([RCTAzureNotificationHandler class]); + [_hubManager setNotificationHandler:_notificationHandlerMock]; + void (^runOnMainThread)(NSInvocation *) = ^(NSInvocation *invocation) { __unsafe_unretained dispatch_block_t block = nil; [invocation getArgument:&block atIndex:2]; // First argument starts at 2 - [block invoke]; + block(); }; - + OCMStub([_hubUtilMock runOnMainThread:OCMOCK_ANY]).andDo(runOnMainThread); } @@ -73,12 +94,12 @@ - (void)testRegisterNoConnectionString { errorMsg = message; }; - + [_hubManager register:RCTTestDeviceToken config:_config resolver:_resolver rejecter:_rejecter]; - + XCTAssertEqualObjects(errorMsg, RCTErrorMissingConnectionString); } @@ -89,13 +110,13 @@ - (void)testRegisterNoHubName { errorMsg = message; }; - + [_config setObject:RCTTestConnectionString forKey:RCTConnectionStringKey]; [_hubManager register:RCTTestDeviceToken config:_config resolver:_resolver rejecter:_rejecter]; - + XCTAssertEqualObjects(errorMsg, RCTErrorMissingHubName); } @@ -104,7 +125,7 @@ - (void)testRegisterNativeError [_config setObject:RCTTestConnectionString forKey:RCTConnectionStringKey]; [_config setObject:RCTTestHubName forKey:RCTHubNameKey]; [_config setObject:_tags forKey:RCTTagsKey]; - + NSError *error = [NSError errorWithDomain:@"Mock Error" code:0 userInfo:nil]; void (^registerNativeWithDeviceToken)(NSInvocation *) = ^(NSInvocation *invocation) { @@ -112,25 +133,27 @@ - (void)testRegisterNativeError [invocation getArgument:&completion atIndex:4]; // First argument starts at 2 completion(error); }; - + OCMStub([_hubMock registerNativeWithDeviceToken:OCMOCK_ANY tags:OCMOCK_ANY completion:OCMOCK_ANY]).andDo(registerNativeWithDeviceToken); - + id defaultCenterMock = OCMPartialMock([NSNotificationCenter defaultCenter]); [_hubManager register:RCTTestDeviceToken config:_config resolver:_resolver rejecter:_rejecter]; - + + OCMVerify([_hubUtilMock runOnMainThread:OCMOCK_ANY]); + OCMVerify([_hubUtilMock createAzureNotificationHub:RCTTestConnectionString hubName:RCTTestHubName]); - + OCMVerify([_hubMock registerNativeWithDeviceToken:RCTTestDeviceToken tags:_tags completion:OCMOCK_ANY]); - + OCMVerify([defaultCenterMock postNotificationName:RCTAzureNotificationHubRegisteredError object:OCMOCK_ANY userInfo:@{RCTUserInfoError: error}]); @@ -141,32 +164,197 @@ - (void)testRegisterNativeSuccessfully [_config setObject:RCTTestConnectionString forKey:RCTConnectionStringKey]; [_config setObject:RCTTestHubName forKey:RCTHubNameKey]; [_config setObject:_tags forKey:RCTTagsKey]; - + void (^registerNativeWithDeviceToken)(NSInvocation *) = ^(NSInvocation *invocation) { __unsafe_unretained RCTNativeCompletion completion = nil; [invocation getArgument:&completion atIndex:4]; // First argument starts at 2 completion(nil); }; - + OCMStub([_hubMock registerNativeWithDeviceToken:OCMOCK_ANY tags:OCMOCK_ANY completion:OCMOCK_ANY]).andDo(registerNativeWithDeviceToken); - + id defaultCenterMock = OCMPartialMock([NSNotificationCenter defaultCenter]); [_hubManager register:RCTTestDeviceToken config:_config resolver:_resolver rejecter:_rejecter]; - + + OCMVerify([_hubUtilMock runOnMainThread:OCMOCK_ANY]); + OCMVerify([_hubUtilMock createAzureNotificationHub:RCTTestConnectionString hubName:RCTTestHubName]); - + OCMVerify([_hubMock registerNativeWithDeviceToken:RCTTestDeviceToken tags:_tags completion:OCMOCK_ANY]); - + + OCMVerify([defaultCenterMock postNotificationName:RCTAzureNotificationHubRegistered + object:OCMOCK_ANY + userInfo:@{RCTUserInfoSuccess: @YES}]); +} + +- (void)testRegisterTemplateNoConnectionString +{ + __block NSString *errorMsg; + _rejecter = ^(NSString *code, NSString *message, NSError *error) + { + errorMsg = message; + }; + + [_hubManager registerTemplate:RCTTestDeviceToken + config:_config + resolver:_resolver + rejecter:_rejecter]; + + XCTAssertEqualObjects(errorMsg, RCTErrorMissingConnectionString); +} + +- (void)testRegisterTemplateNoHubName +{ + __block NSString *errorMsg; + _rejecter = ^(NSString *code, NSString *message, NSError *error) + { + errorMsg = message; + }; + + [_config setObject:RCTTestConnectionString forKey:RCTConnectionStringKey]; + [_hubManager registerTemplate:RCTTestDeviceToken + config:_config + resolver:_resolver + rejecter:_rejecter]; + + XCTAssertEqualObjects(errorMsg, RCTErrorMissingHubName); +} + +- (void)testRegisterTemplateNoTemplateName +{ + __block NSString *errorMsg; + _rejecter = ^(NSString *code, NSString *message, NSError *error) + { + errorMsg = message; + }; + + [_config setObject:RCTTestConnectionString forKey:RCTConnectionStringKey]; + [_config setObject:RCTTestHubName forKey:RCTHubNameKey]; + [_hubManager registerTemplate:RCTTestDeviceToken + config:_config + resolver:_resolver + rejecter:_rejecter]; + + XCTAssertEqualObjects(errorMsg, RCTErrorMissingTemplateName); +} + +- (void)testRegisterTemplateNoTemplate +{ + __block NSString *errorMsg; + _rejecter = ^(NSString *code, NSString *message, NSError *error) + { + errorMsg = message; + }; + + [_config setObject:RCTTestConnectionString forKey:RCTConnectionStringKey]; + [_config setObject:RCTTestHubName forKey:RCTHubNameKey]; + [_config setObject:RCTTestTemplateName forKey:RCTTemplateNameKey]; + [_hubManager registerTemplate:RCTTestDeviceToken + config:_config + resolver:_resolver + rejecter:_rejecter]; + + XCTAssertEqualObjects(errorMsg, RCTErrorMissingTemplate); +} + +- (void)testRegisterTemplateNativeError +{ + [_config setObject:RCTTestConnectionString forKey:RCTConnectionStringKey]; + [_config setObject:RCTTestHubName forKey:RCTHubNameKey]; + [_config setObject:_tags forKey:RCTTagsKey]; + [_config setObject:RCTTestTemplateName forKey:RCTTemplateNameKey]; + [_config setObject:RCTTestTemplate forKey:RCTTemplateKey]; + + NSError *error = [NSError errorWithDomain:@"Mock Error" code:0 userInfo:nil]; + void (^registerTemplateNativeWithDeviceToken)(NSInvocation *) = ^(NSInvocation *invocation) + { + __unsafe_unretained RCTNativeCompletion completion = nil; + [invocation getArgument:&completion atIndex:7]; // First argument starts at 2 + completion(error); + }; + + OCMStub([_hubMock registerTemplateWithDeviceToken:RCTTestDeviceToken + name:RCTTestTemplateName + jsonBodyTemplate:RCTTestTemplate + expiryTemplate:nil + tags:_tags + completion:OCMOCK_ANY]).andDo(registerTemplateNativeWithDeviceToken); + + id defaultCenterMock = OCMPartialMock([NSNotificationCenter defaultCenter]); + + [_hubManager registerTemplate:RCTTestDeviceToken + config:_config + resolver:_resolver + rejecter:_rejecter]; + + OCMVerify([_hubUtilMock runOnMainThread:OCMOCK_ANY]); + + OCMVerify([_hubUtilMock createAzureNotificationHub:RCTTestConnectionString + hubName:RCTTestHubName]); + + OCMVerify([_hubMock registerTemplateWithDeviceToken:RCTTestDeviceToken + name:RCTTestTemplateName + jsonBodyTemplate:RCTTestTemplate + expiryTemplate:nil + tags:_tags + completion:OCMOCK_ANY]); + + OCMVerify([defaultCenterMock postNotificationName:RCTAzureNotificationHubRegisteredError + object:OCMOCK_ANY + userInfo:@{RCTUserInfoError: error}]); +} + +- (void)testRegisterTemplateNativeSuccessfully +{ + [_config setObject:RCTTestConnectionString forKey:RCTConnectionStringKey]; + [_config setObject:RCTTestHubName forKey:RCTHubNameKey]; + [_config setObject:_tags forKey:RCTTagsKey]; + [_config setObject:RCTTestTemplateName forKey:RCTTemplateNameKey]; + [_config setObject:RCTTestTemplate forKey:RCTTemplateKey]; + + void (^registerTemplateNativeWithDeviceToken)(NSInvocation *) = ^(NSInvocation *invocation) + { + __unsafe_unretained RCTNativeCompletion completion = nil; + [invocation getArgument:&completion atIndex:7]; // First argument starts at 2 + completion(nil); + }; + + OCMStub([_hubMock registerTemplateWithDeviceToken:RCTTestDeviceToken + name:RCTTestTemplateName + jsonBodyTemplate:RCTTestTemplate + expiryTemplate:nil + tags:_tags + completion:OCMOCK_ANY]).andDo(registerTemplateNativeWithDeviceToken); + + id defaultCenterMock = OCMPartialMock([NSNotificationCenter defaultCenter]); + + [_hubManager registerTemplate:RCTTestDeviceToken + config:_config + resolver:_resolver + rejecter:_rejecter]; + + OCMVerify([_hubUtilMock runOnMainThread:OCMOCK_ANY]); + + OCMVerify([_hubUtilMock createAzureNotificationHub:RCTTestConnectionString + hubName:RCTTestHubName]); + + OCMVerify([_hubMock registerTemplateWithDeviceToken:RCTTestDeviceToken + name:RCTTestTemplateName + jsonBodyTemplate:RCTTestTemplate + expiryTemplate:nil + tags:_tags + completion:OCMOCK_ANY]); + OCMVerify([defaultCenterMock postNotificationName:RCTAzureNotificationHubRegistered object:OCMOCK_ANY userInfo:@{RCTUserInfoSuccess: @YES}]); @@ -179,10 +367,10 @@ - (void)testUnregisterNoRegistration { errorMsg = message; }; - + [_hubManager unregister:_resolver rejecter:_rejecter]; - - XCTAssertEqualObjects(errorMsg, RCTErrorMissingConnectionString); + + XCTAssertEqualObjects(errorMsg, RCTErrorUnableToUnregisterNoRegistration); } - (void)testUnregisterNativeError @@ -190,7 +378,7 @@ - (void)testUnregisterNativeError [_config setObject:RCTTestConnectionString forKey:RCTConnectionStringKey]; [_config setObject:RCTTestHubName forKey:RCTHubNameKey]; [_config setObject:_tags forKey:RCTTagsKey]; - + NSError *error = [NSError errorWithDomain:@"Mock Error" code:0 userInfo:nil]; void (^unregisterNativeWithCompletion)(NSInvocation *) = ^(NSInvocation *invocation) { @@ -198,17 +386,18 @@ - (void)testUnregisterNativeError [invocation getArgument:&completion atIndex:2]; // First argument starts at 2 completion(error); }; - + OCMStub([_hubMock unregisterNativeWithCompletion:OCMOCK_ANY]).andDo(unregisterNativeWithCompletion); id defaultCenterMock = OCMPartialMock([NSNotificationCenter defaultCenter]); - + [_hubManager register:RCTTestDeviceToken config:_config resolver:_resolver rejecter:_rejecter]; - + [_hubManager unregister:_resolver rejecter:_rejecter]; - + + OCMVerify([_hubUtilMock runOnMainThread:OCMOCK_ANY]); OCMVerify([_hubMock unregisterNativeWithCompletion:OCMOCK_ANY]); OCMVerify([defaultCenterMock postNotificationName:RCTErrorUnspecified object:OCMOCK_ANY @@ -220,14 +409,14 @@ - (void)testUnregisterNativeSuccessfully [_config setObject:RCTTestConnectionString forKey:RCTConnectionStringKey]; [_config setObject:RCTTestHubName forKey:RCTHubNameKey]; [_config setObject:_tags forKey:RCTTagsKey]; - + void (^unregisterNativeWithCompletion)(NSInvocation *) = ^(NSInvocation *invocation) { __unsafe_unretained RCTNativeCompletion completion = nil; [invocation getArgument:&completion atIndex:2]; // First argument starts at 2 completion(nil); }; - + OCMStub([_hubMock unregisterNativeWithCompletion:OCMOCK_ANY]).andDo(unregisterNativeWithCompletion); id defaultCenterMock = OCMPartialMock([NSNotificationCenter defaultCenter]); OCMReject([defaultCenterMock postNotificationName:OCMOCK_ANY object:OCMOCK_ANY userInfo:OCMOCK_ANY]); @@ -236,19 +425,116 @@ - (void)testUnregisterNativeSuccessfully config:_config resolver:_resolver rejecter:_rejecter]; - + [_hubManager unregister:_resolver rejecter:_rejecter]; - + + OCMVerify([_hubUtilMock runOnMainThread:OCMOCK_ANY]); OCMVerify([_hubMock unregisterNativeWithCompletion:OCMOCK_ANY]); + OCMReject([defaultCenterMock postNotificationName:RCTErrorUnspecified + object:OCMOCK_ANY + userInfo:OCMOCK_ANY]); +} + +- (void)testUnregisterTemplateNoRegistration +{ + __block NSString *errorMsg; + _rejecter = ^(NSString *code, NSString *message, NSError *error) + { + errorMsg = message; + }; + + [_hubManager unregisterTemplate:RCTTestTemplateName + resolver:_resolver + rejecter:_rejecter]; + + XCTAssertEqualObjects(errorMsg, RCTErrorUnableToUnregisterNoRegistration); +} + +- (void)testUnregisterTemplateNativeError +{ + [_config setObject:RCTTestConnectionString forKey:RCTConnectionStringKey]; + [_config setObject:RCTTestHubName forKey:RCTHubNameKey]; + [_config setObject:_tags forKey:RCTTagsKey]; + [_config setObject:RCTTestTemplateName forKey:RCTTemplateNameKey]; + [_config setObject:RCTTestTemplate forKey:RCTTemplateKey]; + + NSError *error = [NSError errorWithDomain:@"Mock Error" code:0 userInfo:nil]; + void (^unregisterTemplateNativeWithCompletion)(NSInvocation *) = ^(NSInvocation *invocation) + { + __unsafe_unretained RCTNativeCompletion completion = nil; + [invocation getArgument:&completion atIndex:3]; // First argument starts at 2 + completion(error); + }; + + OCMStub([_hubMock unregisterTemplateWithName:RCTTestTemplateName + completion:OCMOCK_ANY]).andDo(unregisterTemplateNativeWithCompletion); + + id defaultCenterMock = OCMPartialMock([NSNotificationCenter defaultCenter]); + + [_hubManager registerTemplate:RCTTestDeviceToken + config:_config + resolver:_resolver + rejecter:_rejecter]; + + [_hubManager unregisterTemplate:RCTTestTemplateName + resolver:_resolver + rejecter:_rejecter]; + + OCMVerify([_hubUtilMock runOnMainThread:OCMOCK_ANY]); + + OCMVerify([_hubMock unregisterTemplateWithName:RCTTestTemplateName + completion:OCMOCK_ANY]); + + OCMVerify([defaultCenterMock postNotificationName:RCTErrorUnspecified + object:OCMOCK_ANY + userInfo:@{RCTUserInfoError: error}]); +} + +- (void)testUnregisterTemplateNativeSuccessfully +{ + [_config setObject:RCTTestConnectionString forKey:RCTConnectionStringKey]; + [_config setObject:RCTTestHubName forKey:RCTHubNameKey]; + [_config setObject:_tags forKey:RCTTagsKey]; + [_config setObject:RCTTestTemplateName forKey:RCTTemplateNameKey]; + [_config setObject:RCTTestTemplate forKey:RCTTemplateKey]; + + void (^unregisterTemplateNativeWithCompletion)(NSInvocation *) = ^(NSInvocation *invocation) + { + __unsafe_unretained RCTNativeCompletion completion = nil; + [invocation getArgument:&completion atIndex:3]; // First argument starts at 2 + completion(nil); + }; + + OCMStub([_hubMock unregisterTemplateWithName:RCTTestTemplateName + completion:OCMOCK_ANY]).andDo(unregisterTemplateNativeWithCompletion); + + id defaultCenterMock = OCMPartialMock([NSNotificationCenter defaultCenter]); + + [_hubManager registerTemplate:RCTTestDeviceToken + config:_config + resolver:_resolver + rejecter:_rejecter]; + + [_hubManager unregisterTemplate:RCTTestTemplateName + resolver:_resolver + rejecter:_rejecter]; + + OCMVerify([_hubUtilMock runOnMainThread:OCMOCK_ANY]); + + OCMVerify([_hubMock unregisterTemplateWithName:RCTTestTemplateName + completion:OCMOCK_ANY]); + + OCMReject([defaultCenterMock postNotificationName:RCTErrorUnspecified + object:OCMOCK_ANY + userInfo:OCMOCK_ANY]); } - (void)testSetApplicationIconBadgeNumber { NSInteger badgeNumber = 1; - id sharedApplicationMock = OCMPartialMock([UIApplication sharedApplication]); - + [_hubManager setApplicationIconBadgeNumber:badgeNumber]; - + OCMVerify([sharedApplicationMock setApplicationIconBadgeNumber:badgeNumber]); } @@ -259,45 +545,43 @@ - (void)testGetApplicationIconBadgeNumber { XCTAssertEqual([response[0] longValue], badgeNumber); }; - - id sharedApplicationMock = OCMPartialMock([UIApplication sharedApplication]); + OCMStub([sharedApplicationMock applicationIconBadgeNumber]).andReturn(badgeNumber); - + [_hubManager setApplicationIconBadgeNumber:badgeNumber]; [_hubManager getApplicationIconBadgeNumber:block]; } - (void)testRequestPermissions { - id sharedApplicationMock = OCMPartialMock([UIApplication sharedApplication]); NSArray *keys = [NSArray arrayWithObjects:RCTNotificationTypeAlert, RCTNotificationTypeBadge, RCTNotificationTypeSound, nil]; NSArray *objects = [NSArray arrayWithObjects:@YES, @YES, @YES, nil]; NSDictionary *permissions = [[NSDictionary alloc] initWithObjects:objects forKeys:keys]; - UIUserNotificationType types = [RCTAzureNotificationHubUtil getNotificationTypesWithPermissions:permissions]; - UIUserNotificationSettings *expectedSettings = [UIUserNotificationSettings settingsForTypes:(NSUInteger)types - categories:nil]; - - [_hubManager requestPermissions:permissions resolver:_resolver rejecter:_rejecter]; - - OCMVerify([sharedApplicationMock registerUserNotificationSettings:expectedSettings]); -} + UNAuthorizationOptions expectedOptions = [RCTAzureNotificationHubUtil getNotificationTypesWithPermissions:permissions]; + id center = OCMPartialMock([UNUserNotificationCenter currentNotificationCenter]); + void (^requestAuthorizationCompletionHandler)(NSInvocation *) = ^(NSInvocation *invocation) + { + __unsafe_unretained RCTNotificationCompletion completion = nil; + [invocation getArgument:&completion atIndex:3]; // First argument starts at 2 + completion(true, nil); + }; + + OCMStub([center requestAuthorizationWithOptions:expectedOptions + completionHandler:OCMOCK_ANY]).andDo(requestAuthorizationCompletionHandler); -- (void)testRequestPermissionsTwice -{ - NSDictionary *permissions = [[NSDictionary alloc] init]; - [_hubManager requestPermissions:permissions resolver:_resolver rejecter:_rejecter]; - - OCMReject([_hubUtilMock getNotificationTypesWithPermissions:permissions]); - [_hubManager requestPermissions:permissions resolver:_resolver rejecter:_rejecter]; + + OCMVerify([center requestAuthorizationWithOptions:expectedOptions + completionHandler:OCMOCK_ANY]); + + OCMVerify([_hubUtilMock runOnMainThread:OCMOCK_ANY]); + OCMVerify([sharedApplicationMock registerForRemoteNotifications]); } - (void)testAbandonPermissions { - id sharedApplicationMock = OCMPartialMock([UIApplication sharedApplication]); - [_hubManager abandonPermissions]; - + OCMVerify([sharedApplicationMock unregisterForRemoteNotifications]); } @@ -306,75 +590,80 @@ - (void)testCheckPermissions NSArray *keys = [NSArray arrayWithObjects:RCTNotificationTypeAlert, RCTNotificationTypeBadge, RCTNotificationTypeSound, nil]; NSArray *objects = [NSArray arrayWithObjects:@YES, @NO, @YES, nil]; NSDictionary *permissions = [[NSDictionary alloc] initWithObjects:objects forKeys:keys]; - UIUserNotificationType types = [RCTAzureNotificationHubUtil getNotificationTypesWithPermissions:permissions]; - id settingsMock = OCMClassMock([UIUserNotificationSettings class]); - OCMStub([settingsMock types]).andReturn(types); - id sharedApplicationMock = OCMPartialMock([UIApplication sharedApplication]); - OCMStub([sharedApplicationMock currentUserNotificationSettings]).andReturn(settingsMock); - + id center = OCMPartialMock([UNUserNotificationCenter currentNotificationCenter]); + UNNotificationSettings *settings = [UNNotificationSettings alloc]; + [settings setValue:@(UNNotificationSettingEnabled) forKey:@"notificationCenterSetting"]; + [settings setValue:@(UNNotificationSettingDisabled) forKey:@"badgeSetting"]; + [settings setValue:@(UNNotificationSettingEnabled) forKey:@"soundSetting"]; + void (^getNotificationSettingsWithCompletionHandler)(NSInvocation *) = ^(NSInvocation *invocation) + { + __unsafe_unretained RCTNotificationSettingsCompletion completion = nil; + [invocation getArgument:&completion atIndex:2]; // First argument starts at 2 + completion(settings); + }; + + OCMStub([center getNotificationSettingsWithCompletionHandler:OCMOCK_ANY]).andDo(getNotificationSettingsWithCompletionHandler); + NSArray *expectedResponse = @[@{ RCTNotificationTypeAlert: @YES, RCTNotificationTypeBadge: @NO, RCTNotificationTypeSound: @YES, }]; - + RCTResponseSenderBlock callback = ^(NSArray *response) { XCTAssertEqualObjects(response, expectedResponse); }; - + [_hubManager checkPermissions:callback]; } - (void)testPresentLocalNotification { - id sharedApplicationMock = OCMPartialMock([UIApplication sharedApplication]); id notificationMock = OCMClassMock([UILocalNotification class]); - + [_hubManager presentLocalNotification:notificationMock]; - + OCMVerify([sharedApplicationMock presentLocalNotificationNow:notificationMock]); } - (void)testScheduleLocalNotification { - id sharedApplicationMock = OCMPartialMock([UIApplication sharedApplication]); id notificationMock = OCMClassMock([UILocalNotification class]); - + [_hubManager scheduleLocalNotification:notificationMock]; - + OCMVerify([sharedApplicationMock scheduleLocalNotification:notificationMock]); } - (void)testCancelAllLocalNotifications { - id sharedApplicationMock = OCMPartialMock([UIApplication sharedApplication]); - [_hubManager cancelAllLocalNotifications]; - + OCMVerify([sharedApplicationMock cancelAllLocalNotifications]); } - (void)testCancelLocalNotificationsMatched { NSMutableArray *notifications = [[NSMutableArray alloc] init]; - [notifications addObject:_notification]; - id sharedApplicationMock = OCMPartialMock([UIApplication sharedApplication]); + [notifications addObject:_localNotification]; OCMStub([sharedApplicationMock scheduledLocalNotifications]).andReturn(notifications); - - [_hubManager cancelLocalNotifications:[_notification userInfo]]; - - OCMVerify([sharedApplicationMock cancelLocalNotification:_notification]); + UNNotificationRequest *request = [_notificationMock request]; + UNMutableNotificationContent *content = [request content]; + NSDictionary *userInfo = content.userInfo; + + [_hubManager cancelLocalNotifications:userInfo]; + + OCMVerify([sharedApplicationMock cancelLocalNotification:_localNotification]); } - (void)testCancelLocalNotificationsNotMatched { NSMutableArray *notifications = [[NSMutableArray alloc] init]; - [notifications addObject:_notification]; - id sharedApplicationMock = OCMPartialMock([UIApplication sharedApplication]); + [notifications addObject:_localNotification]; OCMStub([sharedApplicationMock scheduledLocalNotifications]).andReturn(notifications); OCMReject([sharedApplicationMock cancelLocalNotification:OCMOCK_ANY]); - + NSArray *keys = [NSArray arrayWithObjects:@"Different Title", @"Different Message", nil]; NSArray *objects = [NSArray arrayWithObjects:@"Title", @"Message", nil]; NSDictionary *info = [[NSDictionary alloc] initWithObjects:objects forKeys:keys]; @@ -389,10 +678,10 @@ - (void)testGetInitialNotificationRemoteNotification NSArray *launchOptionsObjects = [NSArray arrayWithObjects:[[NSMutableDictionary alloc] initWithObjects:@[ remoteNotificationObject ] forKeys:@[ remoteNotificationKey ]], nil]; - + NSDictionary *launchOptionsMock = [[NSDictionary alloc] initWithObjects:launchOptionsObjects forKeys:launchOptionsKeys]; - + id bridgeMock = OCMClassMock([RCTBridge class]); OCMStub([bridgeMock launchOptions]).andReturn(launchOptionsMock); _hubManager.bridge = bridgeMock; @@ -403,9 +692,9 @@ - (void)testGetInitialNotificationRemoteNotification userInfoRemote = initialNotification[RCTUserInfoRemote]; notification = initialNotification[remoteNotificationKey]; }; - + [_hubManager getInitialNotification:_resolver reject:_rejecter]; - + XCTAssertEqual(userInfoRemote, true); XCTAssertEqualObjects(notification, remoteNotificationObject); } @@ -413,10 +702,10 @@ - (void)testGetInitialNotificationRemoteNotification - (void)testGetInitialNotificationLocalNotification { NSArray *launchOptionsKeys = [NSArray arrayWithObjects:UIApplicationLaunchOptionsLocalNotificationKey, nil]; - NSArray *launchOptionsObjects = [NSArray arrayWithObjects: _notification, nil]; + NSArray *launchOptionsObjects = [NSArray arrayWithObjects: _localNotification, nil]; NSDictionary *launchOptionsMock = [[NSDictionary alloc] initWithObjects:launchOptionsObjects forKeys:launchOptionsKeys]; - + id bridgeMock = OCMClassMock([RCTBridge class]); OCMStub([bridgeMock launchOptions]).andReturn(launchOptionsMock); _hubManager.bridge = bridgeMock; @@ -425,10 +714,10 @@ - (void)testGetInitialNotificationLocalNotification { notification = initialLocalNotification; }; - + [_hubManager getInitialNotification:_resolver reject:_rejecter]; - - XCTAssertEqualObjects(notification, [RCTAzureNotificationHubUtil formatLocalNotification:_notification]); + + XCTAssertEqualObjects(notification, [RCTAzureNotificationHubUtil formatLocalNotification:_localNotification]); } - (void)testGetInitialNotificationNoNotification @@ -441,27 +730,26 @@ - (void)testGetInitialNotificationNoNotification { notification = result; }; - + [_hubManager getInitialNotification:_resolver reject:_rejecter]; - + XCTAssertEqualObjects(notification, (id)kCFNull); } - (void)testGetScheduledLocalNotifications { NSMutableArray *notifications = [[NSMutableArray alloc] init]; - [notifications addObject:_notification]; - id sharedApplicationMock = OCMPartialMock([UIApplication sharedApplication]); + [notifications addObject:_localNotification]; OCMStub([sharedApplicationMock scheduledLocalNotifications]).andReturn(notifications); __block NSMutableArray *scheduledNotifications = nil; RCTResponseSenderBlock callback = ^(NSArray *result) { scheduledNotifications = result; }; - + [_hubManager getScheduledLocalNotifications:callback]; - XCTAssertEqualObjects(scheduledNotifications[0][0], [RCTAzureNotificationHubUtil formatLocalNotification:_notification]); + XCTAssertEqualObjects(scheduledNotifications[0][0], [RCTAzureNotificationHubUtil formatLocalNotification:_localNotification]); } - (void)testStartObserving @@ -499,11 +787,6 @@ - (void)testStartObserving selector:[OCMArg anySelector] name:RCTAzureNotificationHubRegisteredError object:nil]); - - OCMVerify([defaultCenterMock addObserver:OCMOCK_ANY - selector:[OCMArg anySelector] - name:RCTUserNotificationSettingsRegistered - object:nil]); } - (void)testStopObserving @@ -557,27 +840,29 @@ - (void)testDidFailToRegisterForRemoteNotificationsWithError - (void)testDidReceiveRemoteNotification { id defaultCenterMock = OCMPartialMock([NSNotificationCenter defaultCenter]); - NSDictionary *notification = [[NSDictionary alloc] initWithObjectsAndKeys:@"key", @"value", nil]; + NSDictionary *userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:@"key", @"value", nil]; - [RCTAzureNotificationHubManager didReceiveRemoteNotification:notification]; + [RCTAzureNotificationHubManager didReceiveRemoteNotification:userInfo + fetchCompletionHandler:nil]; OCMVerify([defaultCenterMock postNotificationName:RCTRemoteNotificationReceived object:OCMOCK_ANY - userInfo:notification]); + userInfo:userInfo]); } -- (void)testDidReceiveLocalNotification +- (void)testUserNotificationCenter { id defaultCenterMock = OCMPartialMock([NSNotificationCenter defaultCenter]); - UILocalNotification *notification = [[UILocalNotification alloc] init]; - NSDictionary *info = [[NSDictionary alloc] initWithObjectsAndKeys:@"Key", @"Value", nil]; - [notification setUserInfo:info]; + id notificationMock = OCMClassMock([UNNotification class]); - [RCTAzureNotificationHubManager didReceiveLocalNotification:notification]; + [RCTAzureNotificationHubManager userNotificationCenter:nil + willPresentNotification:notificationMock + withCompletionHandler:nil]; + OCMVerify([RCTAzureNotificationHubUtil formatUNNotification:notificationMock]); OCMVerify([defaultCenterMock postNotificationName:RCTLocalNotificationReceived object:OCMOCK_ANY - userInfo:[RCTAzureNotificationHubUtil formatLocalNotification:notification]]); + userInfo:OCMOCK_ANY]); } @end diff --git a/sample/ios/ReactNativeAzureNotificationHubSampleTests/RCTAzureNotificationHubUtilTests.m b/sample/ios/ReactNativeAzureNotificationHubSampleTests/RCTAzureNotificationHubUtilTests.m index bc2fdd59..8ab23a84 100644 --- a/sample/ios/ReactNativeAzureNotificationHubSampleTests/RCTAzureNotificationHubUtilTests.m +++ b/sample/ios/ReactNativeAzureNotificationHubSampleTests/RCTAzureNotificationHubUtilTests.m @@ -11,11 +11,49 @@ #import #import +@import OCMock; +@import UserNotifications; + @interface RCTAzureNotificationHubUtilTests : XCTestCase @end @implementation RCTAzureNotificationHubUtilTests +- (void)testFormatUNNotification +{ + UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; + id notificationRequestMock = OCMClassMock([UNNotificationRequest class]); + OCMStub([notificationRequestMock content]).andReturn(content); + OCMStub([notificationRequestMock identifier]).andReturn( @"identifier"); + id notificationMock = OCMClassMock([UNNotification class]); + OCMStub([notificationMock request]).andReturn(notificationRequestMock); + + NSArray *keys = [NSArray arrayWithObjects:@"userInfoKey", nil]; + NSArray *objects = [NSArray arrayWithObjects:@"userInfoObject", nil]; + NSDictionary *info = [[NSDictionary alloc] initWithObjects:objects forKeys:keys]; + content.title = @"title"; + content.threadIdentifier = @"threadIdentifier"; + content.body = @"body"; + content.badge = @(1); + content.categoryIdentifier = @"category"; + content.sound = @"sound"; + content.userInfo = info; + NSDictionary *expectedNotification = @{ + @"identifier": @"identifier", + @"title": @"title", + @"thread-id": @"threadIdentifier", + @"userInfo": @{ @"userInfoKey": @"userInfoObject" }, + @"alertBody": @"body", + @"applicationIconBadgeNumber": [NSNumber numberWithInt:1], + @"category": @"category", + @"soundName": @"sound" + }; + + NSDictionary *formattedNotification = [RCTAzureNotificationHubUtil formatUNNotification:notificationMock]; + + XCTAssertEqualObjects(formattedNotification, expectedNotification); +} + - (void)testFormatLocalNotification { UILocalNotification *notification = [[UILocalNotification alloc] init]; @@ -37,9 +75,9 @@ - (void)testFormatLocalNotification @"soundName": @"soundName", @"remote": @NO }; - + NSDictionary *formattedNotification = [RCTAzureNotificationHubUtil formatLocalNotification:notification]; - + XCTAssertEqualObjects(formattedNotification, expectedNotification); } @@ -57,20 +95,19 @@ - (void)testConvertDeviceTokenToString - (void)testGetNotificationTypesWithPermissions { NSMutableDictionary *permissions = [[NSMutableDictionary alloc] init]; - XCTAssertEqual([RCTAzureNotificationHubUtil getNotificationTypesWithPermissions:permissions], - UIUserNotificationTypeNone); + XCTAssertEqual([RCTAzureNotificationHubUtil getNotificationTypesWithPermissions:permissions], 0); [permissions setValue:@YES forKey:RCTNotificationTypeAlert]; XCTAssertEqual([RCTAzureNotificationHubUtil getNotificationTypesWithPermissions:permissions], - UIUserNotificationTypeAlert); + UNAuthorizationOptionAlert); [permissions setValue:@YES forKey:RCTNotificationTypeBadge]; XCTAssertEqual([RCTAzureNotificationHubUtil getNotificationTypesWithPermissions:permissions], - UIUserNotificationTypeAlert | UIUserNotificationTypeBadge); + UNAuthorizationOptionAlert | UNAuthorizationOptionBadge); [permissions setValue:@YES forKey:RCTNotificationTypeSound]; XCTAssertEqual([RCTAzureNotificationHubUtil getNotificationTypesWithPermissions:permissions], - UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound); + UNAuthorizationOptionAlert | UNAuthorizationOptionBadge | UNAuthorizationOptionSound); } @end From bf55df1456206f9d327ef60e306591f287c21c5c Mon Sep 17 00:00:00 2001 From: Phong Cao Date: Sat, 21 Mar 2020 15:54:21 -0400 Subject: [PATCH 4/5] Fixed ci issue with pod install. Refactored code. --- azure-pipelines.yml | 5 +---- .../RCTAzureNotificationHubManager.m | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ed2869f0..11ff3445 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -42,10 +42,6 @@ steps: displayName: 'Run lint' workingDirectory: '../sample' -- script: npm run test - displayName: 'Run unit tests' - workingDirectory: '../sample' - - task: Gradle@2 displayName: 'Building Android' inputs: @@ -78,6 +74,7 @@ steps: - script: | pod install + pod update sudo cp -R ../node_modules/react-native-azurenotificationhub/ios Pods/RNAzureNotificationHub sudo chown -R $(id -u):$(id -g) Pods/RNAzureNotificationHub pod update diff --git a/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubManager.m b/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubManager.m index b99de1b1..534da9d8 100644 --- a/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubManager.m +++ b/ios/RCTAzureNotificationHubManager/RCTAzureNotificationHubManager.m @@ -312,7 +312,7 @@ + (void)userNotificationCenter:(nonnull __unused UNUserNotificationCenter *)cent RCT_EXPORT_METHOD(register:(nonnull NSString *)deviceToken config:(nonnull NSDictionary *)config - resolver:(__unused RCTPromiseResolveBlock)resolve + resolver:(nonnull __unused RCTPromiseResolveBlock)resolve rejecter:(nonnull RCTPromiseRejectBlock)reject) { // Store the connection string, hub name and tags @@ -365,7 +365,7 @@ + (void)userNotificationCenter:(nonnull __unused UNUserNotificationCenter *)cent resolver:(nonnull __unused RCTPromiseResolveBlock)resolve rejecter:(nonnull RCTPromiseRejectBlock)reject) { - // Store the connection string, hub name and tags + // Store the connection string, hub name, tags, template name and template _connectionString = [config objectForKey:RCTConnectionStringKey]; _hubName = [config objectForKey:RCTHubNameKey]; _tags = [config objectForKey:RCTTagsKey]; From 75593a5ab5924095824d2995b98b9a1d8e8f38e8 Mon Sep 17 00:00:00 2001 From: Phong Cao Date: Sat, 21 Mar 2020 16:09:18 -0400 Subject: [PATCH 5/5] Bump version to 0.9.5. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 98285bc3..c748fff3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-azurenotificationhub", - "version": "0.9.4-Patched.1", + "version": "0.9.5", "description": "React Native module to support Azure Notification Hub push notifications on Android, iOS, and Windows.", "main": "index.js", "repository": {