Skip to content

Commit

Permalink
Add iOS notification service support (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
kasperisager authored Sep 20, 2024
1 parent f7a9ec8 commit a4f98e1
Showing 4 changed files with 152 additions and 2 deletions.
18 changes: 18 additions & 0 deletions apple/BareKit/BareKit.h
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
#if defined(__OBJC__)

#import <Foundation/Foundation.h>
#import <UserNotifications/UserNotifications.h>

@interface BareWorklet : NSObject

@@ -92,6 +93,23 @@ typedef void (^BareRPCResponseHandler)(NSData *_Nullable data, NSError *_Nullabl

@end

@interface BareNotificationService : UNNotificationServiceExtension

- (_Nullable instancetype)initWithFilename:(NSString *_Nonnull)filename
source:(NSData *_Nonnull)source;
- (_Nullable instancetype)initWithFilename:(NSString *_Nonnull)filename
source:(NSString *_Nonnull)source
encoding:(NSStringEncoding)encoding;
- (_Nullable instancetype)initWithResource:(NSString *_Nonnull)name
ofType:(NSString *_Nonnull)type
inBundle:(NSBundle *_Nonnull)bundle;
- (_Nullable instancetype)initWithResource:(NSString *_Nonnull)name
ofType:(NSString *_Nonnull)type
inDirectory:(NSString *_Nonnull)subpath
inBundle:(NSBundle *_Nonnull)bundle;

@end

#endif

#endif // BARE_KIT_H
123 changes: 123 additions & 0 deletions apple/BareKit/BareKit.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#import <Foundation/Foundation.h>
#import <UserNotifications/UserNotifications.h>

#import <assert.h>
#import <compact.h>
@@ -552,3 +553,125 @@ - (void)_reply:(BareRPCIncomingRequest *_Nonnull)request
}

@end

@implementation BareNotificationService {
BareWorklet *_worklet;
}

- (_Nullable instancetype)initWithFilename:(NSString *_Nonnull)filename
source:(NSData *_Nonnull)source {
self = [super init];

if (self) {
[BareWorklet optimizeForMemory:YES];

_worklet = [[BareWorklet alloc] init];

[_worklet start:filename source:source];
}

return self;
}

- (_Nullable instancetype)initWithFilename:(NSString *_Nonnull)filename
source:(NSString *_Nonnull)source
encoding:(NSStringEncoding)encoding {
return [self initWithFilename:filename
source:[source dataUsingEncoding:encoding]];
}

- (_Nullable instancetype)initWithResource:(NSString *_Nonnull)name
ofType:(NSString *_Nonnull)type
inBundle:(NSBundle *_Nonnull)bundle {
NSString *path = [bundle pathForResource:name ofType:type];

return [self initWithFilename:path
source:[NSData dataWithContentsOfFile:path]];
}

- (_Nullable instancetype)initWithResource:(NSString *_Nonnull)name
ofType:(NSString *_Nonnull)type
inDirectory:(NSString *_Nonnull)subpath
inBundle:(NSBundle *_Nonnull)bundle {
NSString *path = [bundle pathForResource:name ofType:type inDirectory:subpath];

return [self initWithFilename:path
source:[NSData dataWithContentsOfFile:path]];
}

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request
withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler {
NSError *error;

NSData *json = [NSJSONSerialization dataWithJSONObject:request.content.userInfo options:0 error:&error];

assert(error == nil); // `userInfo` has already been validated

[_worklet push:json
completion:^(NSData *_Nullable reply, NSError *_Nullable error) {
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];

if (reply) {
NSError *error;

id data = [NSJSONSerialization JSONObjectWithData:reply options:0 error:&error];

if (data) {
// Primary content
content.title = data[@"title"];
content.subtitle = data[@"subtitle"];
content.body = data[@"body"];

// Supplementary content
content.userInfo = data[@"userInfo"];

// App behavior
content.badge = data[@"badge"];
content.targetContentIdentifier = data[@"targetContentIdentifier"];

// System integration
id sound = data[@"sound"];

if (sound) {
id type = sound[@"type"];
id critical = sound[@"critical"];
id volume = sound[@"volume"];

if ([type isEqualToString:@"default"]) {
if (critical) {
if (volume) {
content.sound = [UNNotificationSound defaultCriticalSoundWithAudioVolume:[volume floatValue]];
} else {
content.sound = [UNNotificationSound defaultCriticalSound];
}
} else {
content.sound = [UNNotificationSound defaultSound];
}
} else if ([type isEqualToString:@"named"]) {
if (critical) {
if (volume) {
content.sound = [UNNotificationSound criticalSoundNamed:sound[@"name"] withAudioVolume:[volume floatValue]];
} else {
content.sound = [UNNotificationSound criticalSoundNamed:sound[@"name"]];
}
} else {
content.sound = [UNNotificationSound soundNamed:sound[@"name"]];
}
}
}

// Grouping
content.threadIdentifier = data[@"threadIdentifier"];
content.categoryIdentifier = data[@"categoryIdentifier"];
}
}

contentHandler(content);
}];
}

- (void)serviceExtensionTimeWillExpire {
[_worklet suspend];
}

@end
1 change: 1 addition & 0 deletions apple/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@ target_link_libraries(
bare_kit
PUBLIC
"-framework Foundation"
"-framework UserNotifications"
PUBLIC
bare_worklet
PRIVATE
12 changes: 10 additions & 2 deletions shared/worklet.js
Original file line number Diff line number Diff line change
@@ -29,8 +29,14 @@ exports.BareKit = new BareKit()
exports.port = ports[1]

exports.push = function push (payload, reply) {
if (exports.BareKit.emit('push', Buffer.from(payload), reply) === false) {
reply(null, null)
if (exports.BareKit.emit('push', Buffer.from(payload), replyOnce) === false) {
replyOnce(null, null)
}

function replyOnce (err, payload, encoding) {
reply(err, typeof payload === 'string' ? Buffer.from(payload, encoding) : payload)

reply = noop
}
}

@@ -40,3 +46,5 @@ Object.defineProperty(global, 'BareKit', {
writable: false,
configurable: true
})

function noop () {}

0 comments on commit a4f98e1

Please sign in to comment.