|
23 | 23 | const std::string kNotDetermined{"not determined"}; |
24 | 24 | const std::string kLimited{"limited"}; |
25 | 25 |
|
| 26 | +// Check if a bundle ID is valid (i.e., corresponds to an installed app) |
| 27 | +bool IsValidBundleID(NSString *bundleID) { |
| 28 | + if (!bundleID) |
| 29 | + return false; |
| 30 | + |
| 31 | + CFArrayRef appURLs = LSCopyApplicationURLsForBundleIdentifier( |
| 32 | + (__bridge CFStringRef)bundleID, NULL); |
| 33 | + |
| 34 | + if (appURLs) { |
| 35 | + CFRelease(appURLs); |
| 36 | + return true; |
| 37 | + } |
| 38 | + |
| 39 | + return false; |
| 40 | +} |
| 41 | + |
26 | 42 | std::string CheckFileAccessLevel(NSString *path) { |
27 | 43 | int fd = open([path cStringUsingEncoding:kCFStringEncodingUTF8], O_RDONLY); |
28 | 44 | if (fd != -1) { |
@@ -227,30 +243,6 @@ bool HasOpenSystemPreferencesDialog() { |
227 | 243 | } |
228 | 244 | } |
229 | 245 |
|
230 | | -// Returns a status indicating whether the user has authorized Apple Events. |
231 | | -std::string AppleEventsAuthStatus(Napi::Env env) { |
232 | | - AEDesc target_app = {typeNull, NULL}; |
233 | | - OSStatus status = AECreateDesc(typeApplicationBundleID, "com.apple.finder", |
234 | | - strlen("com.apple.finder"), &target_app); |
235 | | - if (status != noErr) { |
236 | | - std::string err_msg = "Failed to query for Apple Events access"; |
237 | | - Napi::Error::New(env, err_msg).ThrowAsJavaScriptException(); |
238 | | - return kNotDetermined; |
239 | | - } |
240 | | - |
241 | | - status = AEDeterminePermissionToAutomateTarget(&target_app, kCoreEventClass, |
242 | | - kAEOpenDocuments, false); |
243 | | - |
244 | | - AEDisposeDesc(&target_app); |
245 | | - |
246 | | - // User prompt has not yet been shown. |
247 | | - if (status == errAEEventWouldRequireUserConsent) { |
248 | | - return kNotDetermined; |
249 | | - } |
250 | | - |
251 | | - return status == noErr ? kAuthorized : kDenied; |
252 | | -} |
253 | | - |
254 | 246 | // Returns a status indicating whether the user has authorized Apple Music |
255 | 247 | // Library access. |
256 | 248 | std::string MusicLibraryAuthStatus() { |
@@ -452,13 +444,51 @@ bool HasOpenSystemPreferencesDialog() { |
452 | 444 | auth_status = MusicLibraryAuthStatus(); |
453 | 445 | } else if (type == "input-monitoring") { |
454 | 446 | auth_status = InputMonitoringAuthStatus(); |
455 | | - } else if (type == "apple-events") { |
456 | | - auth_status = AppleEventsAuthStatus(env); |
457 | 447 | } |
458 | 448 |
|
459 | 449 | return Napi::Value::From(env, auth_status); |
460 | 450 | } |
461 | 451 |
|
| 452 | +// Request Apple Events access. |
| 453 | +Napi::Promise AskForAppleEventsAccess(const Napi::CallbackInfo &info) { |
| 454 | + Napi::Env env = info.Env(); |
| 455 | + Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env); |
| 456 | + |
| 457 | + std::string bundle_id = info[0].As<Napi::String>().Utf8Value(); |
| 458 | + bool should_prompt = info[1].As<Napi::Boolean>().Value() ? true : false; |
| 459 | + |
| 460 | + if (IsValidBundleID([NSString stringWithUTF8String:bundle_id.c_str()])) { |
| 461 | + std::string err_msg = "Bundle identifier: " + bundle_id + " is not valid"; |
| 462 | + deferred.Reject(Napi::String::New(env, err_msg)); |
| 463 | + return deferred.Promise(); |
| 464 | + } |
| 465 | + |
| 466 | + AEDesc target_app = {typeNull, NULL}; |
| 467 | + OSStatus status = AECreateDesc(typeApplicationBundleID, bundle_id.c_str(), |
| 468 | + bundle_id.length(), &target_app); |
| 469 | + if (status != noErr) { |
| 470 | + std::string err_msg = "Failed to query for Apple Events access"; |
| 471 | + deferred.Reject(Napi::String::New(env, err_msg)); |
| 472 | + return deferred.Promise(); |
| 473 | + } |
| 474 | + |
| 475 | + status = AEDeterminePermissionToAutomateTarget( |
| 476 | + &target_app, kCoreEventClass, kAEOpenDocuments, should_prompt); |
| 477 | + |
| 478 | + AEDisposeDesc(&target_app); |
| 479 | + |
| 480 | + // User prompt has not yet been shown or it was cancelled. |
| 481 | + if (status == errAEEventWouldRequireUserConsent || |
| 482 | + status == userCanceledErr) { |
| 483 | + deferred.Resolve(Napi::String::New(env, kNotDetermined)); |
| 484 | + } else { |
| 485 | + deferred.Resolve( |
| 486 | + Napi::String::New(env, status == noErr ? kAuthorized : kDenied)); |
| 487 | + } |
| 488 | + |
| 489 | + return deferred.Promise(); |
| 490 | +} |
| 491 | + |
462 | 492 | // Request access to various protected folders on the system. |
463 | 493 | Napi::Promise AskForFoldersAccess(const Napi::CallbackInfo &info) { |
464 | 494 | Napi::Env env = info.Env(); |
@@ -821,37 +851,38 @@ void AskForAccessibilityAccess(const Napi::CallbackInfo &info) { |
821 | 851 |
|
822 | 852 | // Initializes all functions exposed to JS |
823 | 853 | Napi::Object Init(Napi::Env env, Napi::Object exports) { |
824 | | - exports.Set(Napi::String::New(env, "getAuthStatus"), |
825 | | - Napi::Function::New(env, GetAuthStatus)); |
826 | | - exports.Set(Napi::String::New(env, "askForContactsAccess"), |
827 | | - Napi::Function::New(env, AskForContactsAccess)); |
| 854 | + exports.Set(Napi::String::New(env, "askForAccessibilityAccess"), |
| 855 | + Napi::Function::New(env, AskForAccessibilityAccess)); |
| 856 | + exports.Set(Napi::String::New(env, "askForAppleEventsAccess"), |
| 857 | + Napi::Function::New(env, AskForAppleEventsAccess)); |
828 | 858 | exports.Set(Napi::String::New(env, "askForCalendarAccess"), |
829 | 859 | Napi::Function::New(env, AskForCalendarAccess)); |
830 | | - exports.Set(Napi::String::New(env, "askForRemindersAccess"), |
831 | | - Napi::Function::New(env, AskForRemindersAccess)); |
| 860 | + exports.Set(Napi::String::New(env, "askForCameraAccess"), |
| 861 | + Napi::Function::New(env, AskForCameraAccess)); |
| 862 | + exports.Set(Napi::String::New(env, "askForContactsAccess"), |
| 863 | + Napi::Function::New(env, AskForContactsAccess)); |
832 | 864 | exports.Set(Napi::String::New(env, "askForFoldersAccess"), |
833 | 865 | Napi::Function::New(env, AskForFoldersAccess)); |
834 | 866 | exports.Set(Napi::String::New(env, "askForFullDiskAccess"), |
835 | 867 | Napi::Function::New(env, AskForFullDiskAccess)); |
836 | | - exports.Set(Napi::String::New(env, "askForCameraAccess"), |
837 | | - Napi::Function::New(env, AskForCameraAccess)); |
| 868 | + exports.Set(Napi::String::New(env, "askForInputMonitoringAccess"), |
| 869 | + Napi::Function::New(env, AskForInputMonitoringAccess)); |
838 | 870 | exports.Set(Napi::String::New(env, "askForLocationAccess"), |
839 | 871 | Napi::Function::New(env, AskForLocationAccess)); |
840 | 872 | exports.Set(Napi::String::New(env, "askForMicrophoneAccess"), |
841 | 873 | Napi::Function::New(env, AskForMicrophoneAccess)); |
842 | 874 | exports.Set(Napi::String::New(env, "askForMusicLibraryAccess"), |
843 | 875 | Napi::Function::New(env, AskForMusicLibraryAccess)); |
844 | | - exports.Set(Napi::String::New(env, "askForSpeechRecognitionAccess"), |
845 | | - Napi::Function::New(env, AskForSpeechRecognitionAccess)); |
846 | 876 | exports.Set(Napi::String::New(env, "askForPhotosAccess"), |
847 | 877 | Napi::Function::New(env, AskForPhotosAccess)); |
| 878 | + exports.Set(Napi::String::New(env, "askForRemindersAccess"), |
| 879 | + Napi::Function::New(env, AskForRemindersAccess)); |
848 | 880 | exports.Set(Napi::String::New(env, "askForScreenCaptureAccess"), |
849 | 881 | Napi::Function::New(env, AskForScreenCaptureAccess)); |
850 | | - exports.Set(Napi::String::New(env, "askForAccessibilityAccess"), |
851 | | - Napi::Function::New(env, AskForAccessibilityAccess)); |
852 | | - exports.Set(Napi::String::New(env, "askForInputMonitoringAccess"), |
853 | | - Napi::Function::New(env, AskForInputMonitoringAccess)); |
854 | | - |
| 882 | + exports.Set(Napi::String::New(env, "askForSpeechRecognitionAccess"), |
| 883 | + Napi::Function::New(env, AskForSpeechRecognitionAccess)); |
| 884 | + exports.Set(Napi::String::New(env, "getAuthStatus"), |
| 885 | + Napi::Function::New(env, GetAuthStatus)); |
855 | 886 | return exports; |
856 | 887 | } |
857 | 888 |
|
|
0 commit comments