fix desktop notification handling on macos
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
#import <stdbool.h>
|
||||
|
||||
typedef void (*PaseoNotificationClickCallback)(const char* payload_json);
|
||||
|
||||
bool paseo_notifications_initialize(PaseoNotificationClickCallback callback, char** error_out);
|
||||
bool paseo_notifications_send(
|
||||
const char* title,
|
||||
const char* body,
|
||||
const char* payload_json,
|
||||
char** error_out
|
||||
);
|
||||
void paseo_notifications_free_string(char* value);
|
||||
@@ -0,0 +1,176 @@
|
||||
#import "paseo_notifications.h"
|
||||
|
||||
#import <dispatch/dispatch.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
|
||||
@interface PaseoNotificationDelegate : NSObject <UNUserNotificationCenterDelegate>
|
||||
@property(nonatomic, assign) PaseoNotificationClickCallback clickCallback;
|
||||
@end
|
||||
|
||||
@implementation PaseoNotificationDelegate
|
||||
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter*)center
|
||||
didReceiveNotificationResponse:(UNNotificationResponse*)response
|
||||
withCompletionHandler:(void (^)(void))completionHandler {
|
||||
if ([response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier] &&
|
||||
self.clickCallback != NULL) {
|
||||
id payload = response.notification.request.content.userInfo[@"paseoPayloadJson"];
|
||||
if ([payload isKindOfClass:[NSString class]]) {
|
||||
self.clickCallback([(NSString*)payload UTF8String]);
|
||||
} else {
|
||||
self.clickCallback(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
[center removeDeliveredNotificationsWithIdentifiers:@[
|
||||
response.notification.request.identifier,
|
||||
]];
|
||||
completionHandler();
|
||||
}
|
||||
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter*)center
|
||||
willPresentNotification:(UNNotification*)notification
|
||||
withCompletionHandler:
|
||||
(void (^)(UNNotificationPresentationOptions options))completionHandler {
|
||||
if (@available(macOS 11.0, *)) {
|
||||
completionHandler(
|
||||
UNNotificationPresentationOptionBanner | UNNotificationPresentationOptionList
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
completionHandler((UNNotificationPresentationOptions)0);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static PaseoNotificationDelegate* paseoNotificationDelegate = nil;
|
||||
static BOOL paseoNotificationsAvailable = NO;
|
||||
|
||||
static void paseo_run_on_main_sync(dispatch_block_t block) {
|
||||
if ([NSThread isMainThread]) {
|
||||
block();
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_sync(dispatch_get_main_queue(), block);
|
||||
}
|
||||
|
||||
static char* paseo_strdup(NSString* value) {
|
||||
if (value == nil) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* utf8 = [value UTF8String];
|
||||
if (utf8 == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t length = strlen(utf8);
|
||||
char* buffer = malloc(length + 1);
|
||||
if (buffer == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(buffer, utf8, length + 1);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static BOOL paseo_is_bundled_app_host(void) {
|
||||
NSURL* bundleURL = [[NSBundle mainBundle] bundleURL];
|
||||
NSString* pathExtension = [[bundleURL pathExtension] lowercaseString];
|
||||
return [pathExtension isEqualToString:@"app"];
|
||||
}
|
||||
|
||||
bool paseo_notifications_initialize(PaseoNotificationClickCallback callback, char** error_out) {
|
||||
@autoreleasepool {
|
||||
paseo_run_on_main_sync(^{
|
||||
if (paseoNotificationDelegate == nil) {
|
||||
paseoNotificationDelegate = [[PaseoNotificationDelegate alloc] init];
|
||||
paseoNotificationsAvailable = paseo_is_bundled_app_host();
|
||||
if (paseoNotificationsAvailable) {
|
||||
[[UNUserNotificationCenter currentNotificationCenter]
|
||||
setDelegate:paseoNotificationDelegate];
|
||||
}
|
||||
}
|
||||
|
||||
paseoNotificationDelegate.clickCallback = callback;
|
||||
});
|
||||
|
||||
if (error_out != NULL) {
|
||||
*error_out = NULL;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool paseo_notifications_send(
|
||||
const char* title,
|
||||
const char* body,
|
||||
const char* payload_json,
|
||||
char** error_out
|
||||
) {
|
||||
@autoreleasepool {
|
||||
if (paseoNotificationDelegate == nil) {
|
||||
if (error_out != NULL) {
|
||||
*error_out = paseo_strdup(@"Notification bridge is not initialized.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!paseoNotificationsAvailable) {
|
||||
if (error_out != NULL) {
|
||||
*error_out = NULL;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
NSString* notificationTitle =
|
||||
[NSString stringWithUTF8String:(title != NULL ? title : "")];
|
||||
NSString* notificationBody =
|
||||
(body != NULL && body[0] != '\0') ? [NSString stringWithUTF8String:body] : nil;
|
||||
NSString* payloadString =
|
||||
(payload_json != NULL && payload_json[0] != '\0')
|
||||
? [NSString stringWithUTF8String:payload_json]
|
||||
: nil;
|
||||
NSString* identifier = [[NSUUID UUID] UUIDString];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
|
||||
content.title = notificationTitle;
|
||||
if (notificationBody != nil) {
|
||||
content.body = notificationBody;
|
||||
}
|
||||
if (payloadString != nil) {
|
||||
content.userInfo = @{
|
||||
@"paseoPayloadJson" : payloadString,
|
||||
};
|
||||
}
|
||||
|
||||
UNNotificationRequest* request =
|
||||
[UNNotificationRequest requestWithIdentifier:identifier
|
||||
content:content
|
||||
trigger:nil];
|
||||
[[UNUserNotificationCenter currentNotificationCenter]
|
||||
addNotificationRequest:request
|
||||
withCompletionHandler:^(NSError* _Nullable error) {
|
||||
if (error != nil) {
|
||||
NSLog(@"[PaseoNotifications] Failed to schedule notification: %@",
|
||||
error.localizedDescription);
|
||||
}
|
||||
}];
|
||||
});
|
||||
|
||||
if (error_out != NULL) {
|
||||
*error_out = NULL;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void paseo_notifications_free_string(char* value) {
|
||||
if (value != NULL) {
|
||||
free(value);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user