Skip to content

Commit e74fd17

Browse files
committed
feat: add askForFoldersAccess()
1 parent 5599014 commit e74fd17

File tree

4 files changed

+101
-1
lines changed

4 files changed

+101
-1
lines changed

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ This native Node.js module allows you to manage an app's access to:
2020
* Location
2121
* Screen Capture
2222
* Speech Recognition
23+
* Protected Folders
2324

2425
## API
2526

@@ -140,6 +141,37 @@ askForRemindersAccess().then(status => {
140141
})
141142
```
142143

144+
## `permissions.askForFoldersAccess(folder)`
145+
146+
* `type` String - The folder to which you are requesting access. Can be one of `desktop`, `documents`, or `downloads`.
147+
148+
Returns `Promise<String>` - Whether or not the request succeeded or failed; can be `authorized` or `denied`.
149+
150+
Example:
151+
152+
```js
153+
const { askForFoldersAccess } = require('node-mac-permissions')
154+
155+
askForFoldersAccess('desktop').then(status => {
156+
console.log(`Access to Desktop is ${status}`)
157+
})
158+
```
159+
160+
```
161+
<key>NSDesktopFolderUsageDescription</key>
162+
<string>Your reason for wanting to access the Desktop folder</string>
163+
```
164+
165+
```
166+
<key>NSDocumentsFolderUsageDescription</key>
167+
<string>Your reason for wanting to access the Documents folder</string>
168+
```
169+
170+
```
171+
<key>NSDownloadsFolderUsageDescription</key>
172+
<string>Your reason for wanting to access the Downloads folder</string>
173+
```
174+
143175
## `permissions.askForFullDiskAccess()`
144176

145177
There is no API for programmatically requesting Full Disk Access on macOS at this time, and so calling this method will trigger opening of System Preferences at the Full Disk pane of Security and Privacy.
@@ -321,4 +353,13 @@ $ tccutil reset SystemPolicyAllFiles
321353

322354
# Reset Contacts permissions
323355
$ tccutil reset AddressBook
356+
357+
# Reset Desktop folder access
358+
$ tccutil reset SystemPolicyDesktopFolder <bundleID>
359+
360+
# Reset Documents folder access
361+
$ tccutil reset SystemPolicyDocumentsFolder <bundleID>
362+
363+
# Reset Downloads folder access
364+
$ tccutil reset SystemPolicyDownloadsFolder <bundleID>
324365
```

index.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,20 @@ function getAuthStatus(type) {
2222
return permissions.getAuthStatus.call(this, type)
2323
}
2424

25+
function askForFoldersAccess(folder) {
26+
const validFolders = ['desktop', 'documents', 'downloads']
27+
28+
if (!validFolders.includes(folder)) {
29+
throw new TypeError(`${folder} is not a valid protected folder`)
30+
}
31+
32+
return permissions.askForFoldersAccess.call(this, folder)
33+
}
34+
2535
module.exports = {
2636
askForCalendarAccess: permissions.askForCalendarAccess,
2737
askForContactsAccess: permissions.askForContactsAccess,
38+
askForFoldersAccess,
2839
askForFullDiskAccess: permissions.askForFullDiskAccess,
2940
askForRemindersAccess: permissions.askForRemindersAccess,
3041
askForCameraAccess: permissions.askForCameraAccess,

permissions.mm

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@
1313

1414
/***** HELPER FUNCTIONS *****/
1515

16+
NSURL *URLForDirectory(NSSearchPathDirectory directory) {
17+
NSFileManager *fm = [NSFileManager defaultManager];
18+
return [fm URLForDirectory:directory
19+
inDomain:NSUserDomainMask
20+
appropriateForURL:nil
21+
create:false
22+
error:nil];
23+
}
24+
1625
// Dummy value to pass into function parameter for ThreadSafeFunction.
1726
Napi::Value NoOp(const Napi::CallbackInfo &info) {
1827
return info.Env().Undefined();
@@ -316,6 +325,34 @@ bool HasOpenSystemPreferencesDialog() {
316325
return Napi::Value::From(env, auth_status);
317326
}
318327

328+
// Request access to various protected folders on the system.
329+
Napi::Promise AskForFoldersAccess(const Napi::CallbackInfo &info) {
330+
Napi::Env env = info.Env();
331+
Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env);
332+
const std::string folder_name = info[0].As<Napi::String>().Utf8Value();
333+
334+
NSString *path = @"";
335+
if (folder_name == "documents") {
336+
NSURL *url = URLForDirectory(NSDocumentDirectory);
337+
path = [url path];
338+
} else if (folder_name == "downloads") {
339+
NSURL *url = URLForDirectory(NSDownloadsDirectory);
340+
path = [url path];
341+
} else if (folder_name == "desktop") {
342+
NSURL *url = URLForDirectory(NSDesktopDirectory);
343+
path = [url path];
344+
}
345+
346+
NSError *error = nil;
347+
NSFileManager *fm = [NSFileManager defaultManager];
348+
NSArray<NSString *> *contents __unused =
349+
[fm contentsOfDirectoryAtPath:path error:&error];
350+
351+
std::string status = (error) ? "denied" : "authorized";
352+
deferred.Resolve(Napi::String::New(env, status));
353+
return deferred.Promise();
354+
}
355+
319356
// Request Contacts access.
320357
Napi::Promise AskForContactsAccess(const Napi::CallbackInfo &info) {
321358
Napi::Env env = info.Env();
@@ -627,6 +664,8 @@ void AskForAccessibilityAccess(const Napi::CallbackInfo &info) {
627664
Napi::Function::New(env, AskForCalendarAccess));
628665
exports.Set(Napi::String::New(env, "askForRemindersAccess"),
629666
Napi::Function::New(env, AskForRemindersAccess));
667+
exports.Set(Napi::String::New(env, "askForFoldersAccess"),
668+
Napi::Function::New(env, AskForFoldersAccess));
630669
exports.Set(Napi::String::New(env, "askForFullDiskAccess"),
631670
Napi::Function::New(env, AskForFullDiskAccess));
632671
exports.Set(Napi::String::New(env, "askForCameraAccess"),

test/module.spec.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const { expect } = require('chai')
2-
const {
2+
const {
3+
askForFoldersAccess,
34
getAuthStatus,
45
} = require('../index')
56

@@ -33,4 +34,12 @@ describe('node-mac-permissions', () => {
3334
}
3435
})
3536
})
37+
38+
describe('askForFoldersAccess()', () => {
39+
it('should throw on invalid types', () => {
40+
expect(() => {
41+
askForFoldersAccess('bad-type')
42+
}).to.throw(/bad-type is not a valid protected folder/)
43+
})
44+
})
3645
})

0 commit comments

Comments
 (0)