Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to request/check more iOS permissions such as critical alerts #733

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ class NotificationsExampleApp extends Component {
});
}

requestPermissions() {
Notifications.registerRemoteNotifications();
requestPermissions(options) {
Notifications.registerRemoteNotifications(options);
}

setCategories() {
Expand Down
4 changes: 2 additions & 2 deletions lib/ios/RNBridgeModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ - (dispatch_queue_t)methodQueue {

#pragma mark - JS interface

RCT_EXPORT_METHOD(requestPermissions) {
[_commandsHandler requestPermissions];
RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)options) {
[_commandsHandler requestPermissions:options];
}

RCT_EXPORT_METHOD(setCategories:(NSArray *)categories) {
Expand Down
2 changes: 1 addition & 1 deletion lib/ios/RNCommandsHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

- (instancetype)init;

- (void)requestPermissions;
- (void)requestPermissions:(NSDictionary *)options;

- (void)setCategories:(NSArray *)categories;

Expand Down
4 changes: 2 additions & 2 deletions lib/ios/RNCommandsHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ - (instancetype)init {
return self;
}

- (void)requestPermissions {
[_notificationCenter requestPermissions];
- (void)requestPermissions:(NSDictionary *)options {
[_notificationCenter requestPermissions:options];
}

- (void)setCategories:(NSArray *)categories {
Expand Down
2 changes: 1 addition & 1 deletion lib/ios/RNNotificationCenter.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ typedef void (^RCTPromiseRejectBlock)(NSString *code, NSString *message, NSError

- (void)isRegisteredForRemoteNotifications:(RCTPromiseResolveBlock)resolve;

- (void)requestPermissions;
- (void)requestPermissions:(NSDictionary *)options;

- (void)setCategories:(NSArray *)json;

Expand Down
43 changes: 37 additions & 6 deletions lib/ios/RNNotificationCenter.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,29 @@

@implementation RNNotificationCenter

- (void)requestPermissions {
- (void)requestPermissions:(NSDictionary *)options {
BOOL carPlay = [options[@"carPlay"] boolValue];
BOOL criticalAlert = [options[@"criticalAlert"] boolValue];
BOOL providesAppNotificationSettings = [options[@"providesAppNotificationSettings"] boolValue];
BOOL announcement = [options[@"announcement"] boolValue];
UNAuthorizationOptions authOptions = (UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert);
if (carPlay) {
authOptions = authOptions | UNAuthorizationOptionCarPlay;
}
if (@available(iOS 12.0, *)) {
if (criticalAlert) {
authOptions = authOptions | UNAuthorizationOptionCriticalAlert;
}
if (providesAppNotificationSettings) {
authOptions = authOptions | UNAuthorizationOptionProvidesAppNotificationSettings;
}
}
if (@available(iOS 13.0, *)) {
if (announcement) {
authOptions = authOptions | UNAuthorizationOptionAnnouncement;
}
}

[UNUserNotificationCenter.currentNotificationCenter requestAuthorizationWithOptions:authOptions completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (!error && granted) {
[UNUserNotificationCenter.currentNotificationCenter getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
Expand Down Expand Up @@ -78,11 +99,21 @@ - (void)isRegisteredForRemoteNotifications:(RCTPromiseResolveBlock)resolve {

- (void)checkPermissions:(RCTPromiseResolveBlock)resolve {
[[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
resolve(@{
@"badge": [NSNumber numberWithBool:settings.badgeSetting == UNNotificationSettingEnabled],
@"sound": [NSNumber numberWithBool:settings.soundSetting == UNNotificationSettingEnabled],
@"alert": [NSNumber numberWithBool:settings.alertSetting == UNNotificationSettingEnabled],
});
NSMutableDictionary* allSettings = [NSMutableDictionary new];
[allSettings addEntriesFromDictionary:@{
@"badge": [NSNumber numberWithBool:settings.badgeSetting == UNNotificationSettingEnabled],
@"sound": [NSNumber numberWithBool:settings.soundSetting == UNNotificationSettingEnabled],
@"alert": [NSNumber numberWithBool:settings.alertSetting == UNNotificationSettingEnabled],
@"carPlay": [NSNumber numberWithBool:settings.carPlaySetting == UNNotificationSettingEnabled],
}];
if (@available(iOS 12.0, *)) {
allSettings[@"criticalAlert"] = [NSNumber numberWithBool:settings.criticalAlertSetting == UNNotificationSettingEnabled];
allSettings[@"providesAppNotificationSettings"] = [NSNumber numberWithBool:settings.providesAppNotificationSettings];
}
if (@available(iOS 13.0, *)) {
allSettings[@"announcement"] = [NSNumber numberWithBool:settings.announcementSetting == UNNotificationSettingEnabled];
}
resolve(allSettings);
}];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,32 @@ - (void)testRequestPermissions_userAuthorizedPermissions {
[[_notificationCenter expect] getNotificationSettingsWithCompletionHandler:[OCMArg invokeBlockWithArgs:settings, nil]];
[[(id)[UIApplication sharedApplication] expect] registerForRemoteNotifications];

[_uut requestPermissions];
[_uut requestPermissions:@{}];
[_notificationCenter verify];
}

- (void)testRequestPermissions_userAuthorizedPermissionsExtraOptions {
UNAuthorizationOptions authOptions = (UNAuthorizationOptionBadge |
UNAuthorizationOptionSound |
UNAuthorizationOptionAlert |
UNAuthorizationOptionAnnouncement |
UNAuthorizationOptionProvidesAppNotificationSettings |
UNAuthorizationOptionCriticalAlert |
UNAuthorizationOptionCarPlay);


UNNotificationSettings* settings = [UNNotificationSettings new];
[settings setValue:@(UNAuthorizationStatusAuthorized) forKey:@"authorizationStatus"];

[[_notificationCenter expect] requestAuthorizationWithOptions:authOptions completionHandler:[OCMArg invokeBlockWithArgs:@(YES), [NSNull null], nil]];
[[_notificationCenter expect] getNotificationSettingsWithCompletionHandler:[OCMArg invokeBlockWithArgs:settings, nil]];
[[(id)[UIApplication sharedApplication] expect] registerForRemoteNotifications];

[_uut requestPermissions:@{@"carPlay": @YES,
@"criticalAlert": @YES,
@"providesAppNotificationSettings": @YES,
@"provisional": @YES,
@"announcement": @YES}];
[_notificationCenter verify];
}

Expand All @@ -47,7 +72,7 @@ - (void)testRequestPermissions_userDeniedPermissions {
[[_notificationCenter expect] getNotificationSettingsWithCompletionHandler:[OCMArg invokeBlockWithArgs:settings, nil]];
[[(id)[UIApplication sharedApplication] reject] registerForRemoteNotifications];

[_uut requestPermissions];
[_uut requestPermissions:@{}];
[_notificationCenter verify];
}

Expand Down
5 changes: 3 additions & 2 deletions lib/src/Notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { NotificationChannel } from './interfaces/NotificationChannel';
import { NotificationsIOS } from './NotificationsIOS';
import { NotificationsAndroid } from './NotificationsAndroid';
import { NotificationFactory } from './DTO/NotificationFactory';
import { NotificationPermissionOptions } from './interfaces/NotificationPermissionOptions';

export class NotificationsRoot {
public readonly _ios: NotificationsIOS;
Expand Down Expand Up @@ -46,8 +47,8 @@ export class NotificationsRoot {
/**
* registerRemoteNotifications
*/
public registerRemoteNotifications() {
this.ios.registerRemoteNotifications();
public registerRemoteNotifications(options?: NotificationPermissionOptions) {
this.ios.registerRemoteNotifications(options);
this.android.registerRemoteNotifications();
}

Expand Down
5 changes: 3 additions & 2 deletions lib/src/NotificationsIOS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Notification } from './DTO/Notification';
import { Commands } from './commands/Commands';
import { Platform } from 'react-native';
import { EventsRegistryIOS } from './events/EventsRegistryIOS';
import { NotificationPermissionOptions } from './interfaces/NotificationPermissionOptions';

export class NotificationsIOS {
constructor(private readonly commands: Commands, private readonly eventsRegistry: EventsRegistryIOS) {
Expand All @@ -19,8 +20,8 @@ export class NotificationsIOS {
/**
* Request permissions to send remote notifications
*/
public registerRemoteNotifications() {
return this.commands.requestPermissions();
public registerRemoteNotifications(options?: NotificationPermissionOptions) {
return this.commands.requestPermissions(options);
}

/**
Expand Down
7 changes: 4 additions & 3 deletions lib/src/adapters/NativeCommandsSender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { NotificationCompletion } from '../interfaces/NotificationCompletion';
import { NotificationPermissions } from '../interfaces/NotificationPermissions';
import { NotificationCategory } from '../interfaces/NotificationCategory';
import { NotificationChannel } from '../interfaces/NotificationChannel';
import { NotificationPermissionOptions } from '../interfaces/NotificationPermissionOptions';

interface NativeCommandsModule {
getInitialNotification(): Promise<Object>;
postLocalNotification(notification: Notification, id: number): void;
requestPermissions(): void;
requestPermissions(options: NotificationPermissionOptions): void;
abandonPermissions(): void;
refreshToken(): void;
registerPushKit(): void;
Expand Down Expand Up @@ -42,8 +43,8 @@ export class NativeCommandsSender {
return this.nativeCommandsModule.getInitialNotification();
}

requestPermissions() {
return this.nativeCommandsModule.requestPermissions();
requestPermissions(options?: NotificationPermissionOptions) {
return this.nativeCommandsModule.requestPermissions(options || {});
}

abandonPermissions() {
Expand Down
21 changes: 18 additions & 3 deletions lib/src/commands/Commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,15 @@ describe('Commands', () => {

describe('requestPermissions', () => {
it('sends to native', () => {
uut.requestPermissions();
verify(mockedNativeCommandsSender.requestPermissions()).called();
const opts = {};
uut.requestPermissions(opts);
verify(mockedNativeCommandsSender.requestPermissions(opts)).called();
});

it('sends to native with options', () => {
const opts = { criticalAlert: true };
uut.requestPermissions(opts);
verify(mockedNativeCommandsSender.requestPermissions(opts)).called();
});
});

Expand Down Expand Up @@ -187,7 +194,15 @@ describe('Commands', () => {
});

it('return negative response from native', async () => {
const expectedPermissions: NotificationPermissions = {badge: false, alert: true, sound: false};
const expectedPermissions: NotificationPermissions = {
badge: false,
alert: true,
sound: false,
announcement: false,
carPlay: false,
criticalAlert: false,
providesAppNotificationSettings: false,
};
when(mockedNativeCommandsSender.checkPermissions()).thenResolve(
expectedPermissions
);
Expand Down
5 changes: 3 additions & 2 deletions lib/src/commands/Commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { NotificationChannel } from '../interfaces/NotificationChannel';
import { NotificationPermissions } from '../interfaces/NotificationPermissions';
import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
import { NotificationFactory } from '../DTO/NotificationFactory';
import { NotificationPermissionOptions } from '../interfaces/NotificationPermissionOptions';

export class Commands {
constructor(
Expand All @@ -30,8 +31,8 @@ export class Commands {
});
}

public requestPermissions() {
const result = this.nativeCommandsSender.requestPermissions();
public requestPermissions(options?: NotificationPermissionOptions) {
const result = this.nativeCommandsSender.requestPermissions(options);
return result;
}

Expand Down
6 changes: 6 additions & 0 deletions lib/src/interfaces/NotificationPermissionOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface NotificationPermissionOptions {
carPlay?: boolean;
criticalAlert?: boolean;
providesAppNotificationSettings?: boolean;
announcement?: boolean;
}
4 changes: 4 additions & 0 deletions lib/src/interfaces/NotificationPermissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ export interface NotificationPermissions {
badge: boolean;
alert: boolean;
sound: boolean;
carPlay: boolean;
criticalAlert: boolean;
providesAppNotificationSettings: boolean;
announcement: boolean;
}
4 changes: 2 additions & 2 deletions website/docs/api/general-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ title: General Commands
sidebar_label: General
---

## registerRemoteNotifications()
Requests remote notification permissions, prompting the user's dialog box on iOS and request a token on Android.
## registerRemoteNotifications(options?: NotificationPermissionOptions)
Requests remote notification permissions, prompting the user's dialog box on iOS and request a token on Android. See iOS specific `requestPermissions` for definition of `options`.
If the user accept the remote notifications permissions, `registerRemoteNotificationsRegistered` event will get called with the device token.

```js
Expand Down
5 changes: 3 additions & 2 deletions website/docs/api/ios-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ title: iOS Specific Commands
sidebar_label: iOS specific
---

## requestPermissions()
Requests notification permissions from iOS, prompting the user's dialog box.
## requestPermissions(options?: NotificationPermissionOptions)
Requests notification permissions from iOS, prompting the user's dialog box. Options may request
`criticalAlert` but you must first [Request a Critical Alert Notifications Entitlement](https://developer.apple.com/contact/request/notifications-critical-alerts-entitlement/).

```js
Notifications.ios.requestPermissions();
Expand Down
4 changes: 4 additions & 0 deletions website/docs/docs/subscription.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,9 @@ Notifications.ios.checkPermissions().then((currentPermissions) => {
console.log('Badges enabled: ' + !!currentPermissions.badge);
console.log('Sounds enabled: ' + !!currentPermissions.sound);
console.log('Alerts enabled: ' + !!currentPermissions.alert);
console.log('Car Play enabled: ' + !!currentPermissions.carPlay);
console.log('Critical Alerts enabled: ' + !!currentPermissions.criticalAlert);
console.log('Provides App Notification Settings enabled: ' + !!currentPermissions.providesAppNotificationSettings);
console.log('Announcement enabled: ' + !!currentPermissions.announcement);
});
```