-
Notifications
You must be signed in to change notification settings - Fork 4.7k
/
EXScopedPermissions.m
172 lines (144 loc) · 7.08 KB
/
EXScopedPermissions.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Copyright 2016-present 650 Industries. All rights reserved.
#if __has_include(<EXPermissions/EXPermissions.h>)
#import "EXScopedPermissions.h"
#import <UMCore/UMUtilities.h>
#import <UMCore/UMDefines.h>
@interface EXScopedPermissions ()
@property (nonatomic, strong) NSString *experienceId;
@property (nonatomic, weak) id<EXPermissionsScopedModuleDelegate> permissionsService;
@property (nonatomic, weak) id<UMUtilitiesInterface> utils;
@property (nonatomic, weak) EXConstantsBinding *constantsBinding;
@end
@implementation EXScopedPermissions
- (instancetype)initWithExperienceId:(NSString *)experienceId andConstantsBinding:(EXConstantsBinding *)constantsBinding
{
if (self = [super init]) {
_experienceId = experienceId;
_constantsBinding = constantsBinding;
}
return self;
}
- (void)setModuleRegistry:(UMModuleRegistry *)moduleRegistry
{
[super setModuleRegistry:moduleRegistry];
_utils = [moduleRegistry getModuleImplementingProtocol:@protocol(UMUtilitiesInterface)];
_permissionsService = [moduleRegistry getSingletonModuleForName:@"Permissions"];
}
# pragma mark - permission requesters / getters
// overriding EXPermission to inject scoped permission logic
- (NSDictionary *)getPermissionUsingRequesterClass:(Class)requesterClass
{
NSDictionary *globalPermission = [super getPermissionUsingRequesterClass:requesterClass];
return [self getScopedPermissionForType:[requesterClass permissionType] withGlobalPermission:globalPermission];
}
- (NSString *)getScopedPermissionStatus:(NSString *)permissionType {
if (!_permissionsService) {
return [[self class] permissionStringForStatus:UMPermissionStatusGranted];
}
return [[self class] permissionStringForStatus:[_permissionsService getPermission:permissionType forExperience:_experienceId]];
}
- (BOOL)hasGrantedScopedPermission:(NSString *)permissionType
{
if (!_permissionsService || ![self shouldVerifyScopedPermission:permissionType]) {
return YES;
}
return [_permissionsService getPermission:permissionType forExperience:_experienceId] == UMPermissionStatusGranted;
}
- (void)askForPermissionUsingRequesterClass:(Class)requesterClass
resolve:(UMPromiseResolveBlock)resolve
reject:(UMPromiseRejectBlock)reject
{
NSDictionary *globalPermissions = [super getPermissionUsingRequesterClass:requesterClass];
NSString *permissionType = [requesterClass permissionType];
UM_WEAKIFY(self)
if (![globalPermissions[@"status"] isEqualToString:@"granted"]) {
// first group
// ask for permission. If granted then save it as scope permission
void (^customOnResults)(NSDictionary *) = ^(NSDictionary *permission){
UM_ENSURE_STRONGIFY(self)
// if permission should be scoped save it
if ([self shouldVerifyScopedPermission:permissionType]) {
[self.permissionsService savePermission:permission ofType:permissionType forExperience:self.experienceId];
}
resolve(permission);
};
return [self askForGlobalPermissionUsingRequesterClass:requesterClass withResolver:customOnResults withRejecter:reject];
} else if ([_constantsBinding.appOwnership isEqualToString:@"expo"] &&
![self hasGrantedScopedPermission:permissionType]) {
// second group
// had to reinitilize UIAlertActions between alertShow invocations
UIAlertAction *allowAction = [UIAlertAction actionWithTitle:@"Allow" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
UM_ENSURE_STRONGIFY(self);
NSMutableDictionary *permission = [globalPermissions mutableCopy];
// if permission should be scoped
if ([self shouldVerifyScopedPermission:permissionType]) {
// try to save scoped permissions - if fails than permission is denied
if (![self.permissionsService savePermission:permission ofType:permissionType forExperience:self.experienceId]) {
permission[@"status"] = [[self class] permissionStringForStatus:UMPermissionStatusDenied];
permission[@"granted"] = @(NO);
}
}
resolve(permission);
}];
UIAlertAction *denyAction = [UIAlertAction actionWithTitle:@"Deny" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
UM_ENSURE_STRONGIFY(self);
NSMutableDictionary *permission = [globalPermissions mutableCopy];
permission[@"status"] = [[self class] permissionStringForStatus:UMPermissionStatusDenied];
permission[@"granted"] = @(NO);
resolve([NSDictionary dictionaryWithDictionary:permission]);
}];
return [self showPermissionRequestAlert:permissionType withAllowAction:allowAction withDenyAction:denyAction];
}
resolve(globalPermissions); // third group
}
# pragma mark - helpers
- (NSDictionary *)getScopedPermissionForType:(NSString *)permissionType withGlobalPermission:(NSDictionary *)globalPermission
{
if (!globalPermission) {
return nil;
}
NSMutableDictionary *permission = [NSMutableDictionary dictionaryWithDictionary:globalPermission];
if ([_constantsBinding.appOwnership isEqualToString:@"expo"]
&& [self shouldVerifyScopedPermission:permissionType]
&& [EXPermissions statusForPermission:permission] == UMPermissionStatusGranted) {
permission[@"status"] = [self getScopedPermissionStatus:permissionType];
permission[@"granted"] = @(NO);
}
return permission;
}
- (void)showPermissionRequestAlert:(NSString *)permissionType
withAllowAction:(UIAlertAction *)allow
withDenyAction:(UIAlertAction *)deny
{
NSString *experienceName = self.experienceId; // TODO: we might want to use name from the manifest?
NSString *messageTemplate = @"%1$@ needs permissions for %2$@. You\'ve already granted permission to another Expo experience. Allow %1$@ to use it also?";
NSString *permissionString = [[self class] textForPermissionType:permissionType];
NSString *message = [NSString stringWithFormat:messageTemplate, experienceName, permissionString];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Experience needs permissions"
message:message
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:deny];
[alert addAction:allow];
UM_WEAKIFY(self);
[UMUtilities performSynchronouslyOnMainThread:^{
UM_ENSURE_STRONGIFY(self);
// TODO: below line is sometimes failing with: "Presenting view controllers on detached view controllers is discourage"
[self->_utils.currentViewController presentViewController:alert animated:YES completion:nil];
}];
}
+ (NSString *)textForPermissionType:(NSString *)type
{
if ([type isEqualToString:@"audioRecording"]) {
return @"recording audio";
} else if ([type isEqualToString:@"cameraRoll"]) {
return @"photos";
}
return type;
}
- (BOOL)shouldVerifyScopedPermission:(NSString *)permissionType
{
// temporarily exclude notifactions from permissions per experience; system brightness is always granted
return ![@[@"notifications", @"userFacingNotifications", @"systemBrightness"] containsObject:permissionType];
}
@end
#endif