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 #752

Merged
merged 4 commits into from
Jun 29, 2021
Merged
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
17 changes: 13 additions & 4 deletions example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ class NotificationsExampleApp extends Component {
}

requestPermissionsIos(options) {
Notifications.ios.registerRemoteNotifications(options);
Notifications.ios.registerRemoteNotifications(
Object.fromEntries(options.map(opt => [opt, true]))
);
}

requestPermissions() {
Expand Down Expand Up @@ -146,6 +148,12 @@ class NotificationsExampleApp extends Component {
);
}

checkPermissions() {
Notifications.ios.checkPermissions().then((currentPermissions) => {
console.warn(currentPermissions);
});
}

render() {
const notifications = this.state.notifications.map((notification, idx) =>
(
Expand All @@ -163,9 +171,10 @@ class NotificationsExampleApp extends Component {
<View style={styles.container}>
<Button title={'Request permissions'} onPress={this.requestPermissions} testID={'requestPermissions'} />
{Platform.OS === 'ios' && Platform.Version > '12.0' && (<>
<Button title={'Request permissions with app notification settings'} onPress={() => this.requestPermissionsIos(['ProvidesAppNotificationSettings'])} testID={'requestPermissionsWithAppSettings'} />
<Button title={'Request permissions with provisional'} onPress={() => this.requestPermissionsIos(['Provisional'])} testID={'requestPermissionsWithAppSettings'} />
<Button title={'Request permissions with app notification settings and provisional'} onPress={() => this.requestPermissionsIos(['ProvidesAppNotificationSettings', 'Provisional'])} testID={'requestPermissionsWithAppSettings'} />
<Button title={'Request permissions with app notification settings'} onPress={() => this.requestPermissionsIos(['providesAppNotificationSettings'])} testID={'requestPermissionsWithAppSettings'} />
<Button title={'Request permissions with provisional'} onPress={() => this.requestPermissionsIos(['provisional'])} testID={'requestPermissionsWithAppSettings'} />
<Button title={'Request permissions with app notification settings and provisional'} onPress={() => this.requestPermissionsIos(['providesAppNotificationSettings', 'provisional'])} testID={'requestPermissionsWithAppSettings'} />
<Button title={'Check permissions'} onPress={this.checkPermissions} />
</>)}
{Platform.OS === 'android' &&
<Button title={'Set channel'} onPress={this.setNotificationChannel} testID={'setNotificationChannel'} />
Expand Down
2 changes: 1 addition & 1 deletion lib/ios/RNBridgeModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ - (dispatch_queue_t)methodQueue {

#pragma mark - JS interface

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

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:(NSArray *)options;
- (void)requestPermissions:(NSDictionary *)options;

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

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

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

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:(NSArray *)options;
- (void)requestPermissions:(NSDictionary *)options;

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

Expand Down
56 changes: 38 additions & 18 deletions lib/ios/RNNotificationCenter.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,30 @@

@implementation RNNotificationCenter

- (void)requestPermissions:(NSArray *)options {
- (void)requestPermissions:(NSDictionary *)options {
BOOL carPlay = [options[@"carPlay"] boolValue];
BOOL criticalAlert = [options[@"criticalAlert"] boolValue];
BOOL providesAppNotificationSettings = [options[@"providesAppNotificationSettings"] boolValue];
BOOL provisional = [options[@"provisional"] boolValue];
BOOL announcement = [options[@"announcement"] boolValue];
UNAuthorizationOptions authOptions = (UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert);
if ([options count] > 0) {
for (NSString* option in options) {
if ([option isEqualToString:@"ProvidesAppNotificationSettings"]) {
if (@available(iOS 12.0, *)) {
authOptions = authOptions | UNAuthorizationOptionProvidesAppNotificationSettings;
}
}
if ([option isEqualToString:@"Provisional"]) {
if (@available(iOS 12.0, *)) {
authOptions = authOptions | UNAuthorizationOptionProvisional;
}
}
if (carPlay) {
authOptions = authOptions | UNAuthorizationOptionCarPlay;
}
if (@available(iOS 12.0, *)) {
if (criticalAlert) {
authOptions = authOptions | UNAuthorizationOptionCriticalAlert;
}
if (providesAppNotificationSettings) {
authOptions = authOptions | UNAuthorizationOptionProvidesAppNotificationSettings;
}
if (provisional) {
authOptions = authOptions | UNAuthorizationOptionProvisional;
}
}
if (@available(iOS 13.0, *)) {
if (announcement) {
authOptions = authOptions | UNAuthorizationOptionAnnouncement;
}
}

Expand Down Expand Up @@ -93,11 +103,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);
}];
}
@end
Original file line number Diff line number Diff line change
Expand Up @@ -34,33 +34,32 @@ - (void)testRequestPermissions_userAuthorizedPermissions {
[[_notificationCenter expect] getNotificationSettingsWithCompletionHandler:[OCMArg invokeBlockWithArgs:settings, nil]];
[[(id)[UIApplication sharedApplication] expect] registerForRemoteNotifications];

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

- (void)testRequestPermissions_withProvidesAppNotificationSettings {
UNAuthorizationOptions authOptions = (UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionProvidesAppNotificationSettings);
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];
- (void)testRequestPermissions_userAuthorizedPermissionsExtraOptions {
UNAuthorizationOptions authOptions = (UNAuthorizationOptionBadge |
UNAuthorizationOptionSound |
UNAuthorizationOptionAlert |
UNAuthorizationOptionAnnouncement |
UNAuthorizationOptionProvidesAppNotificationSettings |
UNAuthorizationOptionCriticalAlert |
UNAuthorizationOptionProvisional |
UNAuthorizationOptionCarPlay);

[_uut requestPermissions:@[@"ProvidesAppNotificationSettings"]];
[_notificationCenter verify];
}

- (void)testRequestPermissions_withProvisional {
UNAuthorizationOptions authOptions = (UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionProvisional);
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:@[@"Provisional"]];
[_uut requestPermissions:@{@"carPlay": @YES,
@"criticalAlert": @YES,
@"providesAppNotificationSettings": @YES,
@"announcement": @YES,
@"provisional": @YES}];
[_notificationCenter verify];
}

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

[_uut requestPermissions:nil];
[_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
@@ -1,4 +1,4 @@
import { NativeCommandsSender, RequestPermissionsOptions } from './adapters/NativeCommandsSender';
import { NativeCommandsSender } from './adapters/NativeCommandsSender';
import { NativeEventsReceiver } from './adapters/NativeEventsReceiver';
import { Commands } from './commands/Commands';
import { EventsRegistry } from './events/EventsRegistry';
Expand All @@ -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/NotificationPermissions';

export class NotificationsRoot {
public readonly _ios: NotificationsIOS;
Expand Down Expand Up @@ -46,7 +47,7 @@ export class NotificationsRoot {
/**
* registerRemoteNotifications
*/
public registerRemoteNotifications(options?: RequestPermissionsOptions[]) {
public registerRemoteNotifications(options?: NotificationPermissionOptions) {
this.ios.registerRemoteNotifications(options);
this.android.registerRemoteNotifications();
}
Expand Down
5 changes: 2 additions & 3 deletions lib/src/NotificationsIOS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { Notification } from './DTO/Notification';
import { Commands } from './commands/Commands';
import { Platform } from 'react-native';
import { EventsRegistryIOS } from './events/EventsRegistryIOS';
import { RequestPermissionsOptions } from './adapters/NativeCommandsSender';

import { NotificationPermissionOptions } from './interfaces/NotificationPermissions';
export class NotificationsIOS {
constructor(private readonly commands: Commands, private readonly eventsRegistry: EventsRegistryIOS) {
return new Proxy(this, {
Expand All @@ -20,7 +19,7 @@ export class NotificationsIOS {
/**
* Request permissions to send remote notifications
*/
public registerRemoteNotifications(options?: RequestPermissionsOptions[]) {
public registerRemoteNotifications(options?: NotificationPermissionOptions) {
return this.commands.requestPermissions(options);
}

Expand Down
11 changes: 5 additions & 6 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/NotificationPermissions';

interface NativeCommandsModule {
getInitialNotification(): Promise<Object>;
postLocalNotification(notification: Notification, id: number): void;
requestPermissions(options?: RequestPermissionsOptions[]): void;
requestPermissions(options: NotificationPermissionOptions): void;
abandonPermissions(): void;
refreshToken(): void;
registerPushKit(): void;
Expand All @@ -28,8 +29,6 @@ interface NativeCommandsModule {
finishHandlingBackgroundAction(notificationId: string, backgroundFetchResult: string): void;
}

export type RequestPermissionsOptions = 'ProvidesAppNotificationSettings' | 'Provisional';

export class NativeCommandsSender {
private readonly nativeCommandsModule: NativeCommandsModule;
constructor() {
Expand All @@ -43,9 +42,9 @@ export class NativeCommandsSender {
getInitialNotification(): Promise<Object> {
return this.nativeCommandsModule.getInitialNotification();
}
requestPermissions(options?: RequestPermissionsOptions[]) {
return this.nativeCommandsModule.requestPermissions(options);

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

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

describe('requestPermissions', () => {
it('sends to native', () => {
uut.requestPermissions();
verify(mockedNativeCommandsSender.requestPermissions(undefined)).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 @@ -186,7 +193,16 @@ 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,
carPlay: false,
criticalAlert: false,
providesAppNotificationSettings: false,
provisional: false,
announcement: 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
@@ -1,10 +1,11 @@
import { NativeCommandsSender, RequestPermissionsOptions } from '../adapters/NativeCommandsSender';
import { NativeCommandsSender } from '../adapters/NativeCommandsSender';
import { Notification } from '../DTO/Notification';
import { NotificationCategory } from '../interfaces/NotificationCategory';
import { NotificationChannel } from '../interfaces/NotificationChannel';
import { NotificationPermissions } from '../interfaces/NotificationPermissions';
import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
import { NotificationFactory } from '../DTO/NotificationFactory';
import { NotificationPermissionOptions } from '../interfaces/NotificationPermissions';

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

public requestPermissions(options?: RequestPermissionsOptions[]) {
public requestPermissions(options?: NotificationPermissionOptions) {
const result = this.nativeCommandsSender.requestPermissions(options);
return result;
}
Expand Down
9 changes: 8 additions & 1 deletion lib/src/interfaces/NotificationPermissions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
export interface NotificationPermissions {
export interface NotificationPermissionOptions {
carPlay?: boolean;
criticalAlert?: boolean;
providesAppNotificationSettings?: boolean;
provisional?: boolean;
announcement?: boolean;
}
export interface NotificationPermissions extends NotificationPermissionOptions {
badge: boolean;
alert: boolean;
sound: boolean;
Expand Down
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?)
Requests remote notification permissions, prompting the user's dialog box on iOS and request a token on Android. See iOS specific [`registerRemoteNotifications`](ios-api.md) for definition of `options`.
If the user accept the remote notifications permissions, `registerRemoteNotificationsRegistered` event will get called with the device token.

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

## registerRemoteNotifications(options?: string[])
## registerRemoteNotifications(options?: NotificationPermissionOptions)
Requests notification permissions from iOS, prompting the user's dialog box if needed.

Available options:
- **`ProvidesAppNotificationSettings`** - An option indicating the iOS notification settings to display a button for in-app notification settings and to be [informed in the app on this event](ios-events.md#appNotificationSettingsLinked).
- **`Provisional`** - Use provisional authorization to send notifications on a trial basis. Users can then evaluate the notifications and decide whether to authorize them.
Notification permission options:
- **`providesAppNotificationSettings`** - An option indicating the iOS notification settings to display a button for in-app notification settings and to be [informed in the app on this event](ios-events.md#appNotificationSettingsLinked).
- **`provisional`** - Use provisional authorization to send notifications on a trial basis. Users can then evaluate the notifications and decide whether to authorize them.
- **`carPlay`** - The ability to display notifications in a CarPlay environment.
- **`criticalAlert`** - 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.registerRemoteNotifications([
'ProvidesAppNotificationSettings',
'Provisional',
]);
Notifications.ios.registerRemoteNotifications({
providesAppNotificationSettings: true,
provisional: true,
carPlay: true,
criticalAlert: true,
});
```

## checkPermissions()
Expand Down
Loading