Skip to content

Commit

Permalink
feat: support new PHPhotos access
Browse files Browse the repository at this point in the history
Closes #44
  • Loading branch information
codebytere committed May 26, 2022
1 parent 03d0b0d commit 4eb156b
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 19 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Return Value Descriptions:
* `restricted` - The application is not authorized to access `type` data. The user cannot change this application’s status, possibly due to active restrictions such as parental controls being in place.
* `denied` - The user explicitly denied access to `type` data for the application.
* `authorized` - The application is authorized to access `type` data.
* `limited` - The application is authorized for limited access to `type` data. Currently only applicable to the `photos` type.

**Notes:**
* Access to `bluetooth` will always return a status of `authorized` prior to macOS 10.15, as the underlying API was not introduced until that version.
Expand Down Expand Up @@ -330,7 +331,9 @@ askForMusicLibraryAccess().then(status => {
})
```

### `permissions.askForPhotosAccess()`
### `permissions.askForPhotosAccess([accessLevel])`

* `accessLevel` String (optional) - The access level being requested of Photos. Can be either `add-only` or `read-write`. Only available on macOS 11 or higher.

Returns `Promise<String>` - Current permission status; can be `authorized`, `denied`, or `restricted`.

Expand All @@ -347,6 +350,11 @@ Your app must provide an explanation for its use of the photo library using the
<string>Your reason for wanting to access Photos</string>
```

**Note:**

You should add the `PHPhotoLibraryPreventAutomaticLimitedAccessAlert` key with a Boolean value of `YES` to your app’s `Info.plist` file to prevent the system from automatically presenting the limited library selection prompt. See [`PHAuthorizationStatusLimited`](https://developer.apple.com/documentation/photokit/phauthorizationstatus/phauthorizationstatuslimited?language=objc) for more information.


Example:

```js
Expand Down
13 changes: 11 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ function getAuthStatus(type) {
'location',
'microphone',
'music-library',
'photos',
'photos-add-only',
'photos-read-write',
'reminders',
'speech-recognition',
'screen',
Expand All @@ -35,6 +36,14 @@ function askForFoldersAccess(folder) {
return permissions.askForFoldersAccess.call(this, folder)
}

function askForPhotosAccess(accessLevel = 'add-only') {
if (!['add-only', 'read-write'].includes(accessLevel)) {
throw new TypeError(`${accessLevel} must be one of either 'add-only' or 'read-write'`)
}

return permissions.askForPhotosAccess.call(this, accessLevel)
}

module.exports = {
askForAccessibilityAccess: permissions.askForAccessibilityAccess,
askForCalendarAccess: permissions.askForCalendarAccess,
Expand All @@ -46,7 +55,7 @@ module.exports = {
askForRemindersAccess: permissions.askForRemindersAccess,
askForMicrophoneAccess: permissions.askForMicrophoneAccess,
askForMusicLibraryAccess: permissions.askForMusicLibraryAccess,
askForPhotosAccess: permissions.askForPhotosAccess,
askForPhotosAccess,
askForSpeechRecognitionAccess: permissions.askForSpeechRecognitionAccess,
askForScreenCaptureAccess: permissions.askForScreenCaptureAccess,
getAuthStatus,
Expand Down
67 changes: 52 additions & 15 deletions permissions.mm
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
const std::string kDenied{"denied"};
const std::string kRestricted{"restricted"};
const std::string kNotDetermined{"not determined"};
const std::string kLimited{"limited"};

PHAccessLevel GetPHAccessLevel(const std::string &type)
API_AVAILABLE(macosx(11.0)) {
return type == "read-write" ? PHAccessLevelReadWrite : PHAccessLevelAddOnly;
}

NSURL *URLForDirectory(NSSearchPathDirectory directory) {
NSFileManager *fm = [NSFileManager defaultManager];
Expand Down Expand Up @@ -367,8 +373,16 @@ bool HasOpenSystemPreferencesDialog() {

// Returns a status indicating whether or not the user has authorized Photos
// access.
std::string PhotosAuthStatus() {
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
std::string PhotosAuthStatus(const std::string &access_level) {
PHAuthorizationStatus status = PHAuthorizationStatusNotDetermined;

if (@available(macOS 11, *)) {
PHAccessLevel level = GetPHAccessLevel(access_level);
status = [PHPhotoLibrary authorizationStatusForAccessLevel:level];
} else {
status = [PHPhotoLibrary authorizationStatus];
}

return StringFromPhotosStatus(status);
}

Expand All @@ -390,8 +404,10 @@ bool HasOpenSystemPreferencesDialog() {
auth_status = FDAAuthStatus();
} else if (type == "microphone") {
auth_status = MediaAuthStatus("microphone");
} else if (type == "photos") {
auth_status = PhotosAuthStatus();
} else if (type == "photos-add-only") {
auth_status = PhotosAuthStatus("add-only");
} else if (type == "photos-read-write") {
auth_status = PhotosAuthStatus("read-write");
} else if (type == "speech-recognition") {
auth_status = SpeechRecognitionAuthStatus();
} else if (type == "camera") {
Expand Down Expand Up @@ -603,18 +619,40 @@ void AskForFullDiskAccess(const Napi::CallbackInfo &info) {
Napi::ThreadSafeFunction ts_fn = Napi::ThreadSafeFunction::New(
env, Napi::Function::New(env, NoOp), "photosCallback", 0, 1);

std::string auth_status = PhotosAuthStatus();
std::string access_level = info[0].As<Napi::String>().Utf8Value();
std::string auth_status = PhotosAuthStatus(access_level);

if (auth_status == kNotDetermined) {
__block Napi::ThreadSafeFunction tsfn = ts_fn;
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
auto callback = [=](Napi::Env env, Napi::Function js_cb,
const char *granted) {
deferred.Resolve(Napi::String::New(env, granted));
};
std::string auth_result = StringFromPhotosStatus(status);
tsfn.BlockingCall(auth_result.c_str(), callback);
tsfn.Release();
}];
if (@available(macOS 11, *)) {
[PHPhotoLibrary
requestAuthorizationForAccessLevel:GetPHAccessLevel(access_level)
handler:^(PHAuthorizationStatus status) {
auto callback =
[=](Napi::Env env,
Napi::Function js_cb,
const char *granted) {
deferred.Resolve(Napi::String::New(
env, granted));
};
std::string auth_result =
StringFromPhotosStatus(status);
tsfn.BlockingCall(auth_result.c_str(),
callback);
tsfn.Release();
}];
} else {
__block Napi::ThreadSafeFunction tsfn = ts_fn;
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
auto callback = [=](Napi::Env env, Napi::Function js_cb,
const char *granted) {
deferred.Resolve(Napi::String::New(env, granted));
};
std::string auth_result = StringFromPhotosStatus(status);
tsfn.BlockingCall(auth_result.c_str(), callback);
tsfn.Release();
}];
}
} else if (auth_status == kDenied) {
OpenPrefPane("Privacy_Photos");

Expand All @@ -624,7 +662,6 @@ void AskForFullDiskAccess(const Napi::CallbackInfo &info) {
ts_fn.Release();
deferred.Resolve(Napi::String::New(env, auth_status));
}

return deferred.Promise();
}

Expand Down
3 changes: 2 additions & 1 deletion test/module.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ describe('node-mac-permissions', () => {
'location',
'microphone',
'music-library',
'photos',
'photos-add-only',
'photos-read-write',
'reminders',
'speech-recognition',
'screen',
Expand Down

0 comments on commit 4eb156b

Please sign in to comment.