|
| 1 | +#import <AppKit/AppKit.h> |
1 | 2 | #import <Contacts/Contacts.h> |
2 | 3 | #include <napi.h> |
3 | 4 |
|
|
6 | 7 |
|
7 | 8 | /***** HELPERS *****/ |
8 | 9 |
|
| 10 | +// Dummy value to pass into function parameter for ThreadSafeFunction. |
| 11 | +Napi::Value NoOp(const Napi::CallbackInfo &info) { |
| 12 | + return info.Env().Undefined(); |
| 13 | +} |
| 14 | + |
9 | 15 | // Parses and returns an array of email addresses as strings. |
10 | 16 | Napi::Array GetEmailAddresses(Napi::Env env, CNContact *cncontact) { |
11 | 17 | int num_email_addresses = [[cncontact emailAddresses] count]; |
@@ -263,6 +269,22 @@ CNAuthorizationStatus AuthStatus() { |
263 | 269 | return [CNContactStore authorizationStatusForEntityType:entityType]; |
264 | 270 | } |
265 | 271 |
|
| 272 | +// Returns the authorization status as a string. |
| 273 | +std::string AuthStatusString() { |
| 274 | + std::string auth_status = "Not Determined"; |
| 275 | + |
| 276 | + CNAuthorizationStatus status_for_entity = AuthStatus(); |
| 277 | + |
| 278 | + if (status_for_entity == CNAuthorizationStatusAuthorized) |
| 279 | + auth_status = "Authorized"; |
| 280 | + else if (status_for_entity == CNAuthorizationStatusDenied) |
| 281 | + auth_status = "Denied"; |
| 282 | + else if (status_for_entity == CNAuthorizationStatusRestricted) |
| 283 | + auth_status = "Restricted"; |
| 284 | + |
| 285 | + return auth_status; |
| 286 | +} |
| 287 | + |
266 | 288 | // Returns the set of Contacts properties to retrieve from the CNContactStore. |
267 | 289 | NSArray *GetContactKeys(Napi::Array requested_keys) { |
268 | 290 | NSMutableArray *keys = [NSMutableArray arrayWithArray:@[ |
@@ -392,21 +414,54 @@ CNAuthorizationStatus AuthStatus() { |
392 | 414 |
|
393 | 415 | /***** EXPORTED FUNCTIONS *****/ |
394 | 416 |
|
395 | | -// Returns the user's Contacts access consent status as a string. |
396 | | -Napi::Value GetAuthStatus(const Napi::CallbackInfo &info) { |
| 417 | +// Request Contacts access. |
| 418 | +Napi::Promise RequestAccess(const Napi::CallbackInfo &info) { |
397 | 419 | Napi::Env env = info.Env(); |
398 | | - std::string auth_status = "Not Determined"; |
399 | | - |
400 | | - CNAuthorizationStatus status_for_entity = AuthStatus(); |
| 420 | + Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env); |
| 421 | + Napi::ThreadSafeFunction ts_fn = Napi::ThreadSafeFunction::New( |
| 422 | + env, Napi::Function::New(env, NoOp), "contactsCallback", 0, 1); |
| 423 | + |
| 424 | + if (@available(macOS 10.11, *)) { |
| 425 | + std::string status = AuthStatusString(); |
| 426 | + |
| 427 | + if (status == "Not Determined") { |
| 428 | + __block Napi::ThreadSafeFunction tsfn = ts_fn; |
| 429 | + CNContactStore *store = [CNContactStore new]; |
| 430 | + [store requestAccessForEntityType:CNEntityTypeContacts |
| 431 | + completionHandler:^(BOOL granted, NSError *error) { |
| 432 | + auto callback = [=](Napi::Env env, Napi::Function js_cb, |
| 433 | + const char *granted) { |
| 434 | + deferred.Resolve(Napi::String::New(env, granted)); |
| 435 | + }; |
| 436 | + tsfn.BlockingCall(granted ? "Authorized" : "Denied", |
| 437 | + callback); |
| 438 | + tsfn.Release(); |
| 439 | + }]; |
| 440 | + } else if (status == "Denied") { |
| 441 | + NSWorkspace *workspace = [[NSWorkspace alloc] init]; |
| 442 | + NSString *pref_string = @"x-apple.systempreferences:com.apple.preference." |
| 443 | + @"security?Contacts"; |
| 444 | + |
| 445 | + [workspace openURL:[NSURL URLWithString:pref_string]]; |
| 446 | + |
| 447 | + ts_fn.Release(); |
| 448 | + deferred.Resolve(Napi::String::New(env, "Denied")); |
| 449 | + } else { |
| 450 | + ts_fn.Release(); |
| 451 | + deferred.Resolve(Napi::String::New(env, "Authorized")); |
| 452 | + } |
| 453 | + } else { |
| 454 | + ts_fn.Release(); |
| 455 | + deferred.Resolve(Napi::String::New(env, "Authorized")); |
| 456 | + } |
401 | 457 |
|
402 | | - if (status_for_entity == CNAuthorizationStatusAuthorized) |
403 | | - auth_status = "Authorized"; |
404 | | - else if (status_for_entity == CNAuthorizationStatusDenied) |
405 | | - auth_status = "Denied"; |
406 | | - else if (status_for_entity == CNAuthorizationStatusRestricted) |
407 | | - auth_status = "Restricted"; |
| 458 | + return deferred.Promise(); |
| 459 | +} |
408 | 460 |
|
409 | | - return Napi::Value::From(env, auth_status); |
| 461 | +// Returns the user's Contacts access consent status as a string. |
| 462 | +Napi::Value GetAuthStatus(const Napi::CallbackInfo &info) { |
| 463 | + Napi::Env env = info.Env(); |
| 464 | + return Napi::Value::From(env, AuthStatusString()); |
410 | 465 | } |
411 | 466 |
|
412 | 467 | // Returns an array of all a user's Contacts as objects. |
@@ -606,6 +661,8 @@ CNAuthorizationStatus AuthStatus() { |
606 | 661 | Napi::Function::New(env, RemoveListener)); |
607 | 662 | exports.Set(Napi::String::New(env, "isListening"), |
608 | 663 | Napi::Function::New(env, IsListening)); |
| 664 | + exports.Set(Napi::String::New(env, "requestAccess"), |
| 665 | + Napi::Function::New(env, RequestAccess)); |
609 | 666 | exports.Set(Napi::String::New(env, "getAuthStatus"), |
610 | 667 | Napi::Function::New(env, GetAuthStatus)); |
611 | 668 | exports.Set(Napi::String::New(env, "getAllContacts"), |
|
0 commit comments