Skip to content

Commit 48a95d5

Browse files
DanielElirazVincent Pizzo
and
Vincent Pizzo
authored
Add ability to request/check more iOS permissions (wix#752)
Co-authored-by: Vincent Pizzo <[email protected]>
1 parent f5b25b5 commit 48a95d5

16 files changed

+130
-70
lines changed

example/index.js

+13-4
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ class NotificationsExampleApp extends Component {
5454
}
5555

5656
requestPermissionsIos(options) {
57-
Notifications.ios.registerRemoteNotifications(options);
57+
Notifications.ios.registerRemoteNotifications(
58+
Object.fromEntries(options.map(opt => [opt, true]))
59+
);
5860
}
5961

6062
requestPermissions() {
@@ -146,6 +148,12 @@ class NotificationsExampleApp extends Component {
146148
);
147149
}
148150

151+
checkPermissions() {
152+
Notifications.ios.checkPermissions().then((currentPermissions) => {
153+
console.warn(currentPermissions);
154+
});
155+
}
156+
149157
render() {
150158
const notifications = this.state.notifications.map((notification, idx) =>
151159
(
@@ -163,9 +171,10 @@ class NotificationsExampleApp extends Component {
163171
<View style={styles.container}>
164172
<Button title={'Request permissions'} onPress={this.requestPermissions} testID={'requestPermissions'} />
165173
{Platform.OS === 'ios' && Platform.Version > '12.0' && (<>
166-
<Button title={'Request permissions with app notification settings'} onPress={() => this.requestPermissionsIos(['ProvidesAppNotificationSettings'])} testID={'requestPermissionsWithAppSettings'} />
167-
<Button title={'Request permissions with provisional'} onPress={() => this.requestPermissionsIos(['Provisional'])} testID={'requestPermissionsWithAppSettings'} />
168-
<Button title={'Request permissions with app notification settings and provisional'} onPress={() => this.requestPermissionsIos(['ProvidesAppNotificationSettings', 'Provisional'])} testID={'requestPermissionsWithAppSettings'} />
174+
<Button title={'Request permissions with app notification settings'} onPress={() => this.requestPermissionsIos(['providesAppNotificationSettings'])} testID={'requestPermissionsWithAppSettings'} />
175+
<Button title={'Request permissions with provisional'} onPress={() => this.requestPermissionsIos(['provisional'])} testID={'requestPermissionsWithAppSettings'} />
176+
<Button title={'Request permissions with app notification settings and provisional'} onPress={() => this.requestPermissionsIos(['providesAppNotificationSettings', 'provisional'])} testID={'requestPermissionsWithAppSettings'} />
177+
<Button title={'Check permissions'} onPress={this.checkPermissions} />
169178
</>)}
170179
{Platform.OS === 'android' &&
171180
<Button title={'Set channel'} onPress={this.setNotificationChannel} testID={'setNotificationChannel'} />

lib/ios/RNBridgeModule.m

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ - (dispatch_queue_t)methodQueue {
3636

3737
#pragma mark - JS interface
3838

39-
RCT_EXPORT_METHOD(requestPermissions:(NSArray *)options) {
39+
RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)options) {
4040
[_commandsHandler requestPermissions:options];
4141
}
4242

lib/ios/RNCommandsHandler.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
- (instancetype)init;
77

8-
- (void)requestPermissions:(NSArray *)options;
8+
- (void)requestPermissions:(NSDictionary *)options;
99

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

lib/ios/RNCommandsHandler.m

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ - (instancetype)init {
1313
return self;
1414
}
1515

16-
- (void)requestPermissions:(NSArray *)options {
16+
- (void)requestPermissions:(NSDictionary *)options {
1717
[_notificationCenter requestPermissions:options];
1818
}
1919

lib/ios/RNNotificationCenter.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ typedef void (^RCTPromiseRejectBlock)(NSString *code, NSString *message, NSError
1010

1111
- (void)isRegisteredForRemoteNotifications:(RCTPromiseResolveBlock)resolve;
1212

13-
- (void)requestPermissions:(NSArray *)options;
13+
- (void)requestPermissions:(NSDictionary *)options;
1414

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

lib/ios/RNNotificationCenter.m

+38-18
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,30 @@
33

44
@implementation RNNotificationCenter
55

6-
- (void)requestPermissions:(NSArray *)options {
6+
- (void)requestPermissions:(NSDictionary *)options {
7+
BOOL carPlay = [options[@"carPlay"] boolValue];
8+
BOOL criticalAlert = [options[@"criticalAlert"] boolValue];
9+
BOOL providesAppNotificationSettings = [options[@"providesAppNotificationSettings"] boolValue];
10+
BOOL provisional = [options[@"provisional"] boolValue];
11+
BOOL announcement = [options[@"announcement"] boolValue];
712
UNAuthorizationOptions authOptions = (UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert);
8-
if ([options count] > 0) {
9-
for (NSString* option in options) {
10-
if ([option isEqualToString:@"ProvidesAppNotificationSettings"]) {
11-
if (@available(iOS 12.0, *)) {
12-
authOptions = authOptions | UNAuthorizationOptionProvidesAppNotificationSettings;
13-
}
14-
}
15-
if ([option isEqualToString:@"Provisional"]) {
16-
if (@available(iOS 12.0, *)) {
17-
authOptions = authOptions | UNAuthorizationOptionProvisional;
18-
}
19-
}
13+
if (carPlay) {
14+
authOptions = authOptions | UNAuthorizationOptionCarPlay;
15+
}
16+
if (@available(iOS 12.0, *)) {
17+
if (criticalAlert) {
18+
authOptions = authOptions | UNAuthorizationOptionCriticalAlert;
19+
}
20+
if (providesAppNotificationSettings) {
21+
authOptions = authOptions | UNAuthorizationOptionProvidesAppNotificationSettings;
22+
}
23+
if (provisional) {
24+
authOptions = authOptions | UNAuthorizationOptionProvisional;
25+
}
26+
}
27+
if (@available(iOS 13.0, *)) {
28+
if (announcement) {
29+
authOptions = authOptions | UNAuthorizationOptionAnnouncement;
2030
}
2131
}
2232

@@ -93,11 +103,21 @@ - (void)isRegisteredForRemoteNotifications:(RCTPromiseResolveBlock)resolve {
93103

94104
- (void)checkPermissions:(RCTPromiseResolveBlock)resolve {
95105
[[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
96-
resolve(@{
97-
@"badge": [NSNumber numberWithBool:settings.badgeSetting == UNNotificationSettingEnabled],
98-
@"sound": [NSNumber numberWithBool:settings.soundSetting == UNNotificationSettingEnabled],
99-
@"alert": [NSNumber numberWithBool:settings.alertSetting == UNNotificationSettingEnabled],
100-
});
106+
NSMutableDictionary* allSettings = [NSMutableDictionary new];
107+
[allSettings addEntriesFromDictionary:@{
108+
@"badge": [NSNumber numberWithBool:settings.badgeSetting == UNNotificationSettingEnabled],
109+
@"sound": [NSNumber numberWithBool:settings.soundSetting == UNNotificationSettingEnabled],
110+
@"alert": [NSNumber numberWithBool:settings.alertSetting == UNNotificationSettingEnabled],
111+
@"carPlay": [NSNumber numberWithBool:settings.carPlaySetting == UNNotificationSettingEnabled],
112+
}];
113+
if (@available(iOS 12.0, *)) {
114+
allSettings[@"criticalAlert"] = [NSNumber numberWithBool:settings.criticalAlertSetting == UNNotificationSettingEnabled];
115+
allSettings[@"providesAppNotificationSettings"] = [NSNumber numberWithBool:settings.providesAppNotificationSettings];
116+
}
117+
if (@available(iOS 13.0, *)) {
118+
allSettings[@"announcement"] = [NSNumber numberWithBool:settings.announcementSetting == UNNotificationSettingEnabled];
119+
}
120+
resolve(allSettings);
101121
}];
102122
}
103123
@end

lib/ios/RNNotificationsTests/Integration/RNCommandsHandlerIntegrationTest.m

+16-17
Original file line numberDiff line numberDiff line change
@@ -34,33 +34,32 @@ - (void)testRequestPermissions_userAuthorizedPermissions {
3434
[[_notificationCenter expect] getNotificationSettingsWithCompletionHandler:[OCMArg invokeBlockWithArgs:settings, nil]];
3535
[[(id)[UIApplication sharedApplication] expect] registerForRemoteNotifications];
3636

37-
[_uut requestPermissions:nil];
37+
[_uut requestPermissions:@{}];
3838
[_notificationCenter verify];
3939
}
4040

41-
- (void)testRequestPermissions_withProvidesAppNotificationSettings {
42-
UNAuthorizationOptions authOptions = (UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionProvidesAppNotificationSettings);
43-
UNNotificationSettings* settings = [UNNotificationSettings new];
44-
[settings setValue:@(UNAuthorizationStatusAuthorized) forKey:@"authorizationStatus"];
45-
46-
[[_notificationCenter expect] requestAuthorizationWithOptions:authOptions completionHandler:[OCMArg invokeBlockWithArgs:@(YES), [NSNull null], nil]];
47-
[[_notificationCenter expect] getNotificationSettingsWithCompletionHandler:[OCMArg invokeBlockWithArgs:settings, nil]];
48-
[[(id)[UIApplication sharedApplication] expect] registerForRemoteNotifications];
41+
- (void)testRequestPermissions_userAuthorizedPermissionsExtraOptions {
42+
UNAuthorizationOptions authOptions = (UNAuthorizationOptionBadge |
43+
UNAuthorizationOptionSound |
44+
UNAuthorizationOptionAlert |
45+
UNAuthorizationOptionAnnouncement |
46+
UNAuthorizationOptionProvidesAppNotificationSettings |
47+
UNAuthorizationOptionCriticalAlert |
48+
UNAuthorizationOptionProvisional |
49+
UNAuthorizationOptionCarPlay);
4950

50-
[_uut requestPermissions:@[@"ProvidesAppNotificationSettings"]];
51-
[_notificationCenter verify];
52-
}
53-
54-
- (void)testRequestPermissions_withProvisional {
55-
UNAuthorizationOptions authOptions = (UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionProvisional);
5651
UNNotificationSettings* settings = [UNNotificationSettings new];
5752
[settings setValue:@(UNAuthorizationStatusAuthorized) forKey:@"authorizationStatus"];
5853

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

63-
[_uut requestPermissions:@[@"Provisional"]];
58+
[_uut requestPermissions:@{@"carPlay": @YES,
59+
@"criticalAlert": @YES,
60+
@"providesAppNotificationSettings": @YES,
61+
@"announcement": @YES,
62+
@"provisional": @YES}];
6463
[_notificationCenter verify];
6564
}
6665

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

76-
[_uut requestPermissions:nil];
75+
[_uut requestPermissions:@{}];
7776
[_notificationCenter verify];
7877
}
7978

lib/src/Notifications.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NativeCommandsSender, RequestPermissionsOptions } from './adapters/NativeCommandsSender';
1+
import { NativeCommandsSender } from './adapters/NativeCommandsSender';
22
import { NativeEventsReceiver } from './adapters/NativeEventsReceiver';
33
import { Commands } from './commands/Commands';
44
import { EventsRegistry } from './events/EventsRegistry';
@@ -11,6 +11,7 @@ import { NotificationChannel } from './interfaces/NotificationChannel';
1111
import { NotificationsIOS } from './NotificationsIOS';
1212
import { NotificationsAndroid } from './NotificationsAndroid';
1313
import { NotificationFactory } from './DTO/NotificationFactory';
14+
import { NotificationPermissionOptions } from './interfaces/NotificationPermissions';
1415

1516
export class NotificationsRoot {
1617
public readonly _ios: NotificationsIOS;
@@ -46,7 +47,7 @@ export class NotificationsRoot {
4647
/**
4748
* registerRemoteNotifications
4849
*/
49-
public registerRemoteNotifications(options?: RequestPermissionsOptions[]) {
50+
public registerRemoteNotifications(options?: NotificationPermissionOptions) {
5051
this.ios.registerRemoteNotifications(options);
5152
this.android.registerRemoteNotifications();
5253
}

lib/src/NotificationsIOS.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import { Notification } from './DTO/Notification';
22
import { Commands } from './commands/Commands';
33
import { Platform } from 'react-native';
44
import { EventsRegistryIOS } from './events/EventsRegistryIOS';
5-
import { RequestPermissionsOptions } from './adapters/NativeCommandsSender';
6-
5+
import { NotificationPermissionOptions } from './interfaces/NotificationPermissions';
76
export class NotificationsIOS {
87
constructor(private readonly commands: Commands, private readonly eventsRegistry: EventsRegistryIOS) {
98
return new Proxy(this, {
@@ -20,7 +19,7 @@ export class NotificationsIOS {
2019
/**
2120
* Request permissions to send remote notifications
2221
*/
23-
public registerRemoteNotifications(options?: RequestPermissionsOptions[]) {
22+
public registerRemoteNotifications(options?: NotificationPermissionOptions) {
2423
return this.commands.requestPermissions(options);
2524
}
2625

lib/src/adapters/NativeCommandsSender.ts

+5-6
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import { NotificationCompletion } from '../interfaces/NotificationCompletion';
44
import { NotificationPermissions } from '../interfaces/NotificationPermissions';
55
import { NotificationCategory } from '../interfaces/NotificationCategory';
66
import { NotificationChannel } from '../interfaces/NotificationChannel';
7+
import { NotificationPermissionOptions } from '../interfaces/NotificationPermissions';
78

89
interface NativeCommandsModule {
910
getInitialNotification(): Promise<Object>;
1011
postLocalNotification(notification: Notification, id: number): void;
11-
requestPermissions(options?: RequestPermissionsOptions[]): void;
12+
requestPermissions(options: NotificationPermissionOptions): void;
1213
abandonPermissions(): void;
1314
refreshToken(): void;
1415
registerPushKit(): void;
@@ -28,8 +29,6 @@ interface NativeCommandsModule {
2829
finishHandlingBackgroundAction(notificationId: string, backgroundFetchResult: string): void;
2930
}
3031

31-
export type RequestPermissionsOptions = 'ProvidesAppNotificationSettings' | 'Provisional';
32-
3332
export class NativeCommandsSender {
3433
private readonly nativeCommandsModule: NativeCommandsModule;
3534
constructor() {
@@ -43,9 +42,9 @@ export class NativeCommandsSender {
4342
getInitialNotification(): Promise<Object> {
4443
return this.nativeCommandsModule.getInitialNotification();
4544
}
46-
47-
requestPermissions(options?: RequestPermissionsOptions[]) {
48-
return this.nativeCommandsModule.requestPermissions(options);
45+
46+
requestPermissions(options?: NotificationPermissionOptions) {
47+
return this.nativeCommandsModule.requestPermissions(options || {});
4948
}
5049

5150
abandonPermissions() {

lib/src/commands/Commands.test.ts

+19-3
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,15 @@ describe('Commands', () => {
6565

6666
describe('requestPermissions', () => {
6767
it('sends to native', () => {
68-
uut.requestPermissions();
69-
verify(mockedNativeCommandsSender.requestPermissions(undefined)).called();
68+
const opts = {};
69+
uut.requestPermissions(opts);
70+
verify(mockedNativeCommandsSender.requestPermissions(opts)).called();
71+
});
72+
73+
it('sends to native with options', () => {
74+
const opts = { criticalAlert: true };
75+
uut.requestPermissions(opts);
76+
verify(mockedNativeCommandsSender.requestPermissions(opts)).called();
7077
});
7178
});
7279

@@ -186,7 +193,16 @@ describe('Commands', () => {
186193
});
187194

188195
it('return negative response from native', async () => {
189-
const expectedPermissions: NotificationPermissions = {badge: false, alert: true, sound: false};
196+
const expectedPermissions: NotificationPermissions = {
197+
badge: false,
198+
alert: true,
199+
sound: false,
200+
carPlay: false,
201+
criticalAlert: false,
202+
providesAppNotificationSettings: false,
203+
provisional: false,
204+
announcement: false,
205+
};
190206
when(mockedNativeCommandsSender.checkPermissions()).thenResolve(
191207
expectedPermissions
192208
);

lib/src/commands/Commands.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { NativeCommandsSender, RequestPermissionsOptions } from '../adapters/NativeCommandsSender';
1+
import { NativeCommandsSender } from '../adapters/NativeCommandsSender';
22
import { Notification } from '../DTO/Notification';
33
import { NotificationCategory } from '../interfaces/NotificationCategory';
44
import { NotificationChannel } from '../interfaces/NotificationChannel';
55
import { NotificationPermissions } from '../interfaces/NotificationPermissions';
66
import { UniqueIdProvider } from '../adapters/UniqueIdProvider';
77
import { NotificationFactory } from '../DTO/NotificationFactory';
8+
import { NotificationPermissionOptions } from '../interfaces/NotificationPermissions';
89

910
export class Commands {
1011
constructor(
@@ -29,7 +30,7 @@ export class Commands {
2930
});
3031
}
3132

32-
public requestPermissions(options?: RequestPermissionsOptions[]) {
33+
public requestPermissions(options?: NotificationPermissionOptions) {
3334
const result = this.nativeCommandsSender.requestPermissions(options);
3435
return result;
3536
}

lib/src/interfaces/NotificationPermissions.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
export interface NotificationPermissions {
1+
export interface NotificationPermissionOptions {
2+
carPlay?: boolean;
3+
criticalAlert?: boolean;
4+
providesAppNotificationSettings?: boolean;
5+
provisional?: boolean;
6+
announcement?: boolean;
7+
}
8+
export interface NotificationPermissions extends NotificationPermissionOptions {
29
badge: boolean;
310
alert: boolean;
411
sound: boolean;

website/docs/api/general-api.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ title: General Commands
44
sidebar_label: General
55
---
66

7-
## registerRemoteNotifications()
8-
Requests remote notification permissions, prompting the user's dialog box on iOS and request a token on Android.
7+
## registerRemoteNotifications(options?)
8+
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`.
99
If the user accept the remote notifications permissions, `registerRemoteNotificationsRegistered` event will get called with the device token.
1010

1111
```js

website/docs/api/ios-api.md

+13-8
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,24 @@ title: iOS Specific Commands
44
sidebar_label: iOS specific
55
---
66

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

10-
Available options:
11-
- **`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).
12-
- **`Provisional`** - Use provisional authorization to send notifications on a trial basis. Users can then evaluate the notifications and decide whether to authorize them.
10+
Notification permission options:
11+
- **`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).
12+
- **`provisional`** - Use provisional authorization to send notifications on a trial basis. Users can then evaluate the notifications and decide whether to authorize them.
13+
- **`carPlay`** - The ability to display notifications in a CarPlay environment.
14+
- **`criticalAlert`** - Requests notification permissions from iOS, prompting the user's dialog box. Options may request
15+
`criticalAlert` but you must first [Request a Critical Alert Notifications Entitlement](https://developer.apple.com/contact/request/notifications-critical-alerts-entitlement/).
1316

1417

1518
```js
16-
Notifications.ios.registerRemoteNotifications([
17-
'ProvidesAppNotificationSettings',
18-
'Provisional',
19-
]);
19+
Notifications.ios.registerRemoteNotifications({
20+
providesAppNotificationSettings: true,
21+
provisional: true,
22+
carPlay: true,
23+
criticalAlert: true,
24+
});
2025
```
2126

2227
## checkPermissions()

0 commit comments

Comments
 (0)