Skip to content

Commit 3daae3b

Browse files
committed
feat: allow requesting Photos access
1 parent e0e1356 commit 3daae3b

File tree

3 files changed

+91
-5
lines changed

3 files changed

+91
-5
lines changed

README.md

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ Your app must provide an explanation for its use of capture devices using the `N
141141

142142
**Note:**
143143

144-
- `status` will be resolved back as `authorized` prior to macOS 10.14 High Sierra, as access to the camera and microphone was unilaterally allowed until that version.
144+
- `status` will be resolved back as `authorized` prior to macOS 10.14, as access to the camera and microphone was unilaterally allowed until that version.
145145

146146
Example:
147147

@@ -170,14 +170,47 @@ Your app must provide an explanation for its use of capture devices using the `N
170170

171171
**Note:**
172172

173-
- `status` will be resolved back as `authorized` prior to macOS 10.14 High Sierra, as access to the camera and microphone was unilaterally allowed until that version.
173+
- `status` will be resolved back as `authorized` prior to macOS 10.14, as access to the camera and microphone was unilaterally allowed until that version.
174174

175175
Example:
176176

177177
```js
178178
const { askForMicrophoneAccess } = require("node-mac-permissions");
179179

180-
const status = await askForMicrophoneAccess();
180+
askForMicrophoneAccess().then(status => {
181+
console.log(`Access to Microphone is ${status}`)
182+
});
183+
```
184+
185+
## `permissions.askForPhotosAccess()`
186+
187+
Returns `Promise<String>` - Current permission status; can be `authorized`, `denied`, or `restricted`.
188+
189+
Checks the authorization status for Photos access. If the status check returns:
190+
191+
* `not determined` - The Photos access authorization will prompt the user to authorize or deny. The Promise is resolved after the user selection with either `authorized` or `denied`.
192+
* `denied` - The `Security & Privacy` System Preferences window is opened with the Photos privacy key highlighted. On open of the `Security & Privacy` window, the Promise is resolved as `denied`.
193+
* `restricted` - The Promise is resolved as `restricted`.
194+
195+
Your app must provide an explanation for its use of the photo library using the `NSPhotoLibraryUsageDescription` `Info.plist` key.
196+
197+
```
198+
<key>NSPhotoLibraryUsageDescription</key>
199+
<string>Your reason for wanting to access Photos</string>
200+
```
201+
202+
**Note:**
203+
204+
- `status` will be resolved back as `authorized` prior to macOS 10.13, as access to Photos was unilaterally allowed until that version.
205+
206+
Example:
207+
208+
```js
209+
const { askForPhotosAccess } = require("node-mac-permissions");
210+
211+
askForPhotosAccess().then(status => {
212+
console.log(`Access to Photos is ${status}`)
213+
});
181214
```
182215

183216
## `permissions.askForScreenCaptureAccess()`

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ module.exports = {
2828
askForRemindersAccess: permissions.askForRemindersAccess,
2929
askForCameraAccess: permissions.askForCameraAccess,
3030
askForMicrophoneAccess: permissions.askForMicrophoneAccess,
31+
askForPhotosAccess: permissions.askForPhotosAccess,
3132
askForScreenCaptureAccess: permissions.askForScreenCaptureAccess,
3233
askForAccessibilityAccess: permissions.askForAccessibilityAccess,
3334
getAuthStatus,

permissions.mm

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
#import <Contacts/Contacts.h>
77
#import <CoreLocation/CoreLocation.h>
88
#import <EventKit/EventKit.h>
9-
#import <Photos/Photos.h>
109
#import <Foundation/Foundation.h>
10+
#import <Photos/Photos.h>
1111
#import <pwd.h>
1212

1313
/***** HELPER FUNCTIONS *****/
@@ -249,8 +249,8 @@
249249
Napi::ThreadSafeFunction ts_fn = Napi::ThreadSafeFunction::New(
250250
env, Napi::Function::New(env, NoOp), "contactsCallback", 0, 1);
251251

252-
__block Napi::ThreadSafeFunction tsfn = ts_fn;
253252
if (@available(macOS 10.11, *)) {
253+
__block Napi::ThreadSafeFunction tsfn = ts_fn;
254254
CNContactStore *store = [CNContactStore new];
255255
[store requestAccessForEntityType:CNEntityTypeContacts
256256
completionHandler:^(BOOL granted, NSError *error) {
@@ -263,6 +263,7 @@
263263
tsfn.Release();
264264
}];
265265
} else {
266+
ts_fn.Release();
266267
deferred.Resolve(Napi::String::New(env, "authorized"));
267268
}
268269

@@ -355,11 +356,57 @@ void AskForFullDiskAccess(const Napi::CallbackInfo &info) {
355356

356357
[workspace openURL:[NSURL URLWithString:pref_string]];
357358

359+
ts_fn.Release();
360+
deferred.Resolve(Napi::String::New(env, "denied"));
361+
} else {
362+
ts_fn.Release();
363+
deferred.Resolve(Napi::String::New(env, auth_status));
364+
}
365+
} else {
366+
ts_fn.Release();
367+
deferred.Resolve(Napi::String::New(env, "authorized"));
368+
}
369+
370+
return deferred.Promise();
371+
}
372+
373+
// Request Photos access.
374+
Napi::Promise AskForPhotosAccess(const Napi::CallbackInfo &info) {
375+
Napi::Env env = info.Env();
376+
Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env);
377+
Napi::ThreadSafeFunction ts_fn = Napi::ThreadSafeFunction::New(
378+
env, Napi::Function::New(env, NoOp), "photosCallback", 0, 1);
379+
380+
if (@available(macOS 10.13, *)) {
381+
std::string auth_status = PhotosAuthStatus();
382+
383+
if (auth_status == "not determined") {
384+
__block Napi::ThreadSafeFunction tsfn = ts_fn;
385+
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
386+
auto callback = [=](Napi::Env env, Napi::Function js_cb,
387+
const char *granted) {
388+
deferred.Resolve(Napi::String::New(env, granted));
389+
};
390+
tsfn.BlockingCall(
391+
status == PHAuthorizationStatusAuthorized ? "authorized" : "denied",
392+
callback);
393+
tsfn.Release();
394+
}];
395+
} else if (auth_status == "denied") {
396+
NSWorkspace *workspace = [[NSWorkspace alloc] init];
397+
NSString *pref_string = @"x-apple.systempreferences:com.apple.preference."
398+
@"security?Privacy_Photos";
399+
400+
[workspace openURL:[NSURL URLWithString:pref_string]];
401+
402+
ts_fn.Release();
358403
deferred.Resolve(Napi::String::New(env, "denied"));
359404
} else {
405+
ts_fn.Release();
360406
deferred.Resolve(Napi::String::New(env, auth_status));
361407
}
362408
} else {
409+
ts_fn.Release();
363410
deferred.Resolve(Napi::String::New(env, "authorized"));
364411
}
365412

@@ -397,11 +444,14 @@ void AskForFullDiskAccess(const Napi::CallbackInfo &info) {
397444

398445
[workspace openURL:[NSURL URLWithString:pref_string]];
399446

447+
ts_fn.Release();
400448
deferred.Resolve(Napi::String::New(env, "denied"));
401449
} else {
450+
ts_fn.Release();
402451
deferred.Resolve(Napi::String::New(env, auth_status));
403452
}
404453
} else {
454+
ts_fn.Release();
405455
deferred.Resolve(Napi::String::New(env, "authorized"));
406456
}
407457

@@ -459,6 +509,8 @@ void AskForAccessibilityAccess(const Napi::CallbackInfo &info) {
459509
Napi::Function::New(env, AskForCameraAccess));
460510
exports.Set(Napi::String::New(env, "askForMicrophoneAccess"),
461511
Napi::Function::New(env, AskForMicrophoneAccess));
512+
exports.Set(Napi::String::New(env, "askForPhotosAccess"),
513+
Napi::Function::New(env, AskForPhotosAccess));
462514
exports.Set(Napi::String::New(env, "askForScreenCaptureAccess"),
463515
Napi::Function::New(env, AskForScreenCaptureAccess));
464516
exports.Set(Napi::String::New(env, "askForAccessibilityAccess"),

0 commit comments

Comments
 (0)