Skip to content

Commit 1d2e106

Browse files
committed
feat: implement calendar/reminder requests
1 parent 9f7d856 commit 1d2e106

File tree

4 files changed

+86
-44
lines changed

4 files changed

+86
-44
lines changed

README.md

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ This native Node.js module allows you to manage an app's access to:
1818

1919
* `type` String - The type of system component to which you are requesting access. Can be one of 'contacts', 'full-disk-access', 'photos', 'reminders', or 'calendar'.
2020

21-
Returns `String` - Can be one of 'Not Determined', 'Denied', 'Authorized', or 'Restricted'.
21+
Returns `String` - Can be one of 'not determined', 'denied', 'authorized', or 'restricted'.
2222

2323
Checks the authorization status of the application to access `type` on macOS.
2424

2525
Return Value Descriptions:
26-
* 'Not Determined' - The user has not yet made a choice regarding whether the application may access `type` data.
27-
* 'Not Authorized' - 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.
28-
* 'Denied' - The user explicitly denied access to `type` data for the application.
29-
* 'Authorized' - The application is authorized to access `type` data.
26+
* 'not determined' - The user has not yet made a choice regarding whether the application may access `type` data.
27+
* '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.
28+
* 'denied' - The user explicitly denied access to `type` data for the application.
29+
* 'authorized' - The application is authorized to access `type` data.
3030

3131
**Note:** Access to 'contacts' will always return a status of 'Authorized' prior to macOS 10.13 High Sierra, as access to contacts was unilaterally allowed until that version.
3232

@@ -36,7 +36,7 @@ Return Value Descriptions:
3636
* `error` String | null - An error in performing the request, if one occurred.
3737
* `status` String - Whether or not the request succeeded or failed; can be 'authorized' or 'denied'.
3838

39-
In your app, you should put the reason you're requesting to manipulate user's contacts database in your `Info.plist` like so:
39+
Your app’s `Info.plist` file must provide a value for the `NSContactsUsageDescription` key that explains to the user why your app is requesting Contacts access.
4040

4141
```
4242
<key>NSContactsUsageDescription</key>
@@ -47,6 +47,34 @@ In your app, you should put the reason you're requesting to manipulate user's co
4747
const { askForContactsAccess } = require('node-mac-permissions')
4848

4949
askForContactsAccess((err, status) => {
50-
console.log(`Access to Contacts was ${status}`)
50+
console.log(`Access to Contacts is ${status}`)
51+
})
52+
```
53+
54+
## `permissions.askForCalendarAccess(callback)`
55+
56+
* `callback` Function
57+
* `error` String | null - An error in performing the request, if one occurred.
58+
* `status` String - Whether or not the request succeeded or failed; can be 'authorized' or 'denied'.
59+
60+
```js
61+
const { askForCalendarAccess } = require('node-mac-permissions')
62+
63+
askForCalendarAccess((err, status) => {
64+
console.log(`Access to Calendar is ${status}`)
65+
})
66+
```
67+
68+
## `permissions.askForRemindersAccess(callback)`
69+
70+
* `callback` Function
71+
* `error` String | null - An error in performing the request, if one occurred.
72+
* `status` String - Whether or not the request succeeded or failed; can be 'authorized' or 'denied'.
73+
74+
```js
75+
const { askForRemindersAccess } = require('node-mac-permissions')
76+
77+
askForRemindersAccess((err, status) => {
78+
console.log(`Access to Reminders is ${status}`)
5179
})
5280
```

index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const permissions = require('bindings')('permissions.node')
22

33
function getAuthStatus(type) {
4-
const validTypes = ['contacts', 'calendar', 'reminders', 'photos', 'full-disk-access']
4+
const validTypes = ['contacts', 'calendar', 'reminders', 'full-disk-access']
55
if (!validTypes.includes(type)) {
66
throw new TypeError(`${type} is not a valid type`)
77
}
@@ -11,5 +11,7 @@ function getAuthStatus(type) {
1111

1212
module.exports = {
1313
getAuthStatus,
14-
askForContactsAccess: permissions.askForContactsAccess
14+
askForContactsAccess: permissions.askForContactsAccess,
15+
askForCalendarAccess: permissions.askForCalendarAccess,
16+
askForRemindersAccess: permissions.askForRemindersAccess
1517
}

permissions.mm

Lines changed: 45 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -27,60 +27,40 @@
2727

2828
// Returns a status indicating whether or not the user has authorized Contacts access
2929
std::string ContactAuthStatus() {
30-
std::string auth_status = "Not Determined";
30+
std::string auth_status = "not determined";
3131

3232
CNEntityType entityType = CNEntityTypeContacts;
3333
CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:entityType];
3434

3535
if (status == CNAuthorizationStatusAuthorized)
36-
auth_status = "Authorized";
36+
auth_status = "authorized";
3737
else if (status == CNAuthorizationStatusDenied)
38-
auth_status = "Denied";
38+
auth_status = "denied";
3939
else if (status == CNAuthorizationStatusRestricted)
40-
auth_status = "Restricted";
40+
auth_status = "restricted";
4141

4242
return auth_status;
4343
}
4444

4545
// Returns a status indicating whether or not the user has authorized Calendar/Reminders access
4646
std::string EventAuthStatus(std::string type) {
47-
std::string auth_status = "Not Determined";
47+
std::string auth_status = "not determined";
4848

4949
EKEntityType entityType = (type == "calendar") ? EKEntityTypeEvent : EKEntityTypeReminder;
5050
EKAuthorizationStatus status = [EKEventStore authorizationStatusForEntityType:entityType];
5151

5252
if (status == EKAuthorizationStatusAuthorized)
53-
auth_status = "Authorized";
53+
auth_status = "authorized";
5454
else if (status == EKAuthorizationStatusDenied)
55-
auth_status = "Denied";
55+
auth_status = "denied";
5656
else if (status == EKAuthorizationStatusRestricted)
57-
auth_status = "Restricted";
58-
59-
return auth_status;
60-
}
61-
62-
// Returns a status indicating whether or not the user has authorized Photos access
63-
std::string PhotosAuthStatus() {
64-
std::string auth_status = "Not Determined";
65-
66-
if (@available(macOS 10.13, *)) {
67-
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
68-
69-
if (status == PHAuthorizationStatusAuthorized)
70-
auth_status = "Authorized";
71-
else if (status == PHAuthorizationStatusDenied)
72-
auth_status = "Denied";
73-
else if (status == PHAuthorizationStatusRestricted)
74-
auth_status = "Restricted";
75-
} else {
76-
auth_status = "Authorized";
77-
}
57+
auth_status = "restricted";
7858

7959
return auth_status;
8060
}
8161

8262
std::string FDAAuthStatus() {
83-
std::string auth_status = "Not Determined";
63+
std::string auth_status = "not determined";
8464
NSString *path;
8565
NSString* home_folder = GetUserHomeFolderPath();
8666

@@ -94,9 +74,9 @@
9474
BOOL file_exists = [manager fileExistsAtPath:path];
9575
NSData *data = [NSData dataWithContentsOfFile:path];
9676
if (data == nil && file_exists) {
97-
auth_status = "Denied";
77+
auth_status = "denied";
9878
} else if (file_exists) {
99-
auth_status = "Authorized";
79+
auth_status = "authorized";
10080
}
10181

10282
return auth_status;
@@ -114,8 +94,6 @@
11494
auth_status = ContactAuthStatus();
11595
} else if (type == "calendar") {
11696
auth_status = EventAuthStatus("calendar");
117-
} else if (type == "photos") {
118-
auth_status = PhotosAuthStatus();
11997
} else if (type == "reminders") {
12098
auth_status = EventAuthStatus("reminders");
12199
} else if (type == "full-disk-access") {
@@ -145,6 +123,34 @@ void AskForContactsAccess(const Napi::CallbackInfo &info) {
145123
}
146124
}
147125

126+
// Request access to Calendar.
127+
void AskForCalendarAccess(const Napi::CallbackInfo &info) {
128+
Napi::Env env = info.Env();
129+
Napi::ThreadSafeFunction ts_fn = Napi::ThreadSafeFunction::New(env, info[0].As<Napi::Function>(),
130+
"Resource Name", 0, 1, [](Napi::Env){});
131+
132+
[[EKEventStore new] requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError * error) {
133+
auto callback = [](Napi::Env env, Napi::Function js_cb, const char* granted) {
134+
js_cb.Call({Napi::String::New(env, granted)});
135+
};
136+
ts_fn.BlockingCall(granted ? "authorized" : "denied", callback);
137+
}];
138+
}
139+
140+
// Request access to Reminders.
141+
void AskForRemindersAccess(const Napi::CallbackInfo &info) {
142+
Napi::Env env = info.Env();
143+
Napi::ThreadSafeFunction ts_fn = Napi::ThreadSafeFunction::New(env, info[0].As<Napi::Function>(),
144+
"Resource Name", 0, 1, [](Napi::Env){});
145+
146+
[[EKEventStore new] requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError * error) {
147+
auto callback = [](Napi::Env env, Napi::Function js_cb, const char* granted) {
148+
js_cb.Call({Napi::String::New(env, granted)});
149+
};
150+
ts_fn.BlockingCall(granted ? "authorized" : "denied", callback);
151+
}];
152+
}
153+
148154
// Initializes all functions exposed to JS
149155
Napi::Object Init(Napi::Env env, Napi::Object exports) {
150156
exports.Set(
@@ -153,6 +159,12 @@ void AskForContactsAccess(const Napi::CallbackInfo &info) {
153159
exports.Set(
154160
Napi::String::New(env, "askForContactsAccess"), Napi::Function::New(env, AskForContactsAccess)
155161
);
162+
exports.Set(
163+
Napi::String::New(env, "askForCalendarAccess"), Napi::Function::New(env, AskForCalendarAccess)
164+
);
165+
exports.Set(
166+
Napi::String::New(env, "askForRemindersAccess"), Napi::Function::New(env, AskForRemindersAccess)
167+
);
156168

157169
return exports;
158170
}

test/module.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ describe('node-mac-permissions', () => {
1212
})
1313

1414
it('should return a string', () => {
15-
const types = ['contacts', 'calendar', 'reminders', 'photos', 'full-disk-access']
16-
const statuses = ['Not Determined', 'Denied', 'Authorized', 'Restricted']
15+
const types = ['contacts', 'calendar', 'reminders', 'full-disk-access']
16+
const statuses = ['not determined', 'denied', 'authorized', 'restricted']
1717
for (const type of types) {
1818
const status = getAuthStatus(type)
1919
expect(statuses).to.contain(status)

0 commit comments

Comments
 (0)