RNNotifications.m 13 KB
Newer Older
Lidan Hifi's avatar
Lidan Hifi committed
1 2 3 4

#import <UIKit/UIKit.h>
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
Lidan Hifi's avatar
Lidan Hifi committed
5
#import "RNNotifications.h"
6
#import "RCTConvert.h"
Lidan Hifi's avatar
Lidan Hifi committed
7 8
#import "RCTUtils.h"

9 10
NSString* const RNNotificationCreateAction = @"CREATE";
NSString* const RNNotificationClearAction = @"CLEAR";
Lidan Hifi's avatar
Lidan Hifi committed
11

12 13 14 15
NSString* const RNNotificationReceivedForeground = @"RNNotificationReceivedForeground";
NSString* const RNNotificationReceivedBackground = @"RNNotificationReceivedBackground";
NSString* const RNNotificationOpened = @"RNNotificationOpened";
NSString* const RNNotificationActionTriggered = @"RNNotificationActionTriggered";
Lidan Hifi's avatar
Lidan Hifi committed
16

17
/*
18
 * Converters for Interactive Notifications
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
 */
@implementation RCTConvert (UIUserNotificationActivationMode)
RCT_ENUM_CONVERTER(UIUserNotificationActivationMode, (@{
                                                        @"foreground": @(UIUserNotificationActivationModeForeground),
                                                        @"background": @(UIUserNotificationActivationModeBackground)
                                                        }), UIUserNotificationActivationModeForeground, integerValue)
@end

@implementation RCTConvert (UIUserNotificationActionContext)
RCT_ENUM_CONVERTER(UIUserNotificationActionContext, (@{
                                                       @"default": @(UIUserNotificationActionContextDefault),
                                                       @"minimal": @(UIUserNotificationActionContextMinimal)
                                                       }), UIUserNotificationActionContextDefault, integerValue)
@end

@implementation RCTConvert (UIUserNotificationActionBehavior)
/* iOS 9 only */
RCT_ENUM_CONVERTER(UIUserNotificationActionBehavior, (@{
                                                        @"default": @(UIUserNotificationActionBehaviorDefault),
                                                        @"textInput": @(UIUserNotificationActionBehaviorTextInput)
                                                        }), UIUserNotificationActionBehaviorDefault, integerValue)
@end

42 43 44 45
@implementation RCTConvert (UIMutableUserNotificationAction)
+ (UIMutableUserNotificationAction *)UIMutableUserNotificationAction:(id)json
{
    NSDictionary<NSString *, id> *details = [self NSDictionary:json];
Lidan Hifi's avatar
Lidan Hifi committed
46

47 48 49 50 51 52 53
    UIMutableUserNotificationAction* action =[[UIMutableUserNotificationAction alloc] init];
    action.activationMode = [RCTConvert UIUserNotificationActivationMode:details[@"activationMode"]];
    action.behavior = [RCTConvert UIUserNotificationActionBehavior:details[@"behavior"]];
    action.authenticationRequired = [RCTConvert BOOL:details[@"authenticationRequired"]];
    action.destructive = [RCTConvert BOOL:details[@"destructive"]];
    action.title = [RCTConvert NSString:details[@"title"]];
    action.identifier = [RCTConvert NSString:details[@"identifier"]];
Lidan Hifi's avatar
Lidan Hifi committed
54

55 56 57
    return action;
}
@end
58

59 60
@implementation RCTConvert (UIMutableUserNotificationCategory)
+ (UIMutableUserNotificationCategory *)UIMutableUserNotificationCategory:(id)json
61
{
62 63 64 65 66 67 68 69 70
    NSDictionary<NSString *, id> *details = [self NSDictionary:json];

    UIMutableUserNotificationCategory* category = [[UIMutableUserNotificationCategory alloc] init];
    category.identifier = details[@"identifier"];

    // category actions
    NSMutableArray* actions = [[NSMutableArray alloc] init];
    for (NSDictionary* actionJson in [RCTConvert NSArray:details[@"actions"]]) {
        [actions addObject:[RCTConvert UIMutableUserNotificationAction:actionJson]];
71
    }
72 73 74 75

    [category setActions:actions forContext:[RCTConvert UIUserNotificationActionContext:details[@"context"]]];

    return category;
76
}
77 78 79 80 81 82 83
@end

@implementation RNNotifications

RCT_EXPORT_MODULE()

@synthesize bridge = _bridge;
Lidan Hifi's avatar
Lidan Hifi committed
84 85 86 87 88 89 90 91 92

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)setBridge:(RCTBridge *)bridge
{
    _bridge = bridge;
Lidan Hifi's avatar
Lidan Hifi committed
93

Lidan Hifi's avatar
Lidan Hifi committed
94 95
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(handleNotificationReceivedForeground:)
Lidan Hifi's avatar
Lidan Hifi committed
96
                                                 name:RNNotificationReceivedForeground
Lidan Hifi's avatar
Lidan Hifi committed
97
                                               object:nil];
Lidan Hifi's avatar
Lidan Hifi committed
98

Lidan Hifi's avatar
Lidan Hifi committed
99 100
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(handleNotificationReceivedBackground:)
Lidan Hifi's avatar
Lidan Hifi committed
101
                                                 name:RNNotificationReceivedBackground
Lidan Hifi's avatar
Lidan Hifi committed
102
                                               object:nil];
Lidan Hifi's avatar
Lidan Hifi committed
103

Lidan Hifi's avatar
Lidan Hifi committed
104 105
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(handleNotificationOpened:)
Lidan Hifi's avatar
Lidan Hifi committed
106
                                                 name:RNNotificationOpened
Lidan Hifi's avatar
Lidan Hifi committed
107
                                               object:nil];
108 109 110 111 112

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(handleNotificationActionTriggered:)
                                                 name:RNNotificationActionTriggered
                                               object:nil];
Lidan Hifi's avatar
Lidan Hifi committed
113 114 115
}

/*
116
 * Public Methods
Lidan Hifi's avatar
Lidan Hifi committed
117 118 119 120
 */
+ (void)didReceiveRemoteNotification:(NSDictionary *)notification
{
    UIApplicationState state = [UIApplication sharedApplication].applicationState;
Lidan Hifi's avatar
Lidan Hifi committed
121

Lidan Hifi's avatar
Lidan Hifi committed
122
    if (state == UIApplicationStateActive) {
123
        // Notification received foreground
Lidan Hifi's avatar
Lidan Hifi committed
124 125
        [self didReceiveNotificationOnForegroundState:notification];
    } else if (state == UIApplicationStateInactive) {
126
        // Notification opened
Lidan Hifi's avatar
Lidan Hifi committed
127 128
        [self didNotificationOpen:notification];
    } else {
129
        // Notification received background
Lidan Hifi's avatar
Lidan Hifi committed
130 131 132 133 134 135 136
        [self didReceiveNotificationOnBackgroundState:notification];
    }
}

+ (void)didReceiveLocalNotification:(UILocalNotification *)notification
{
    UIApplicationState state = [UIApplication sharedApplication].applicationState;
Lidan Hifi's avatar
Lidan Hifi committed
137

Lidan Hifi's avatar
Lidan Hifi committed
138 139 140 141 142 143 144 145 146
    if (state == UIApplicationStateInactive) {
        NSString* notificationId = [notification.userInfo objectForKey:@"notificationId"];
        if (notificationId) {
            [self clearNotificationFromNotificationsCenter:notificationId];
        }
        [self didNotificationOpen:notification.userInfo];
    }
}

147 148 149 150 151 152 153 154 155 156 157 158
+ (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification withResponseInfo:(NSDictionary *)responseInfo completionHandler:(void (^)())completionHandler
{
    [self emitNotificationActionForIdentifier:identifier responseInfo:responseInfo userInfo:notification.userInfo];
    completionHandler();
}

+ (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo withResponseInfo:(NSDictionary *)responseInfo completionHandler:(void (^)())completionHandler
{
    [self emitNotificationActionForIdentifier:identifier responseInfo:responseInfo userInfo:userInfo];
    completionHandler();
}

Lidan Hifi's avatar
Lidan Hifi committed
159 160 161 162 163
/*
 * Notification handlers
 */
+ (void)didReceiveNotificationOnForegroundState:(NSDictionary *)notification
{
Lidan Hifi's avatar
Lidan Hifi committed
164
    [[NSNotificationCenter defaultCenter] postNotificationName:RNNotificationReceivedForeground
Lidan Hifi's avatar
Lidan Hifi committed
165 166 167 168 169 170
                                                        object:self
                                                      userInfo:notification];
}

+ (void)didReceiveNotificationOnBackgroundState:(NSDictionary *)notification
{
171 172 173 174
    NSDictionary* managedAps  = [notification objectForKey:@"managedAps"];
    NSDictionary* alert = [managedAps objectForKey:@"alert"];
    NSString* action = [managedAps objectForKey:@"action"];
    NSString* notificationId = [managedAps objectForKey:@"notificationId"];
Lidan Hifi's avatar
Lidan Hifi committed
175 176 177

    if (action) {
        // create or delete notification
Lidan Hifi's avatar
Lidan Hifi committed
178
        if ([action isEqualToString: RNNotificationCreateAction]
Lidan Hifi's avatar
Lidan Hifi committed
179 180 181
            && notificationId
            && alert) {
            [self dispatchLocalNotificationFromNotification:notification];
Lidan Hifi's avatar
Lidan Hifi committed
182 183

        } else if ([action isEqualToString: RNNotificationClearAction] && notificationId) {
Lidan Hifi's avatar
Lidan Hifi committed
184 185 186
            [self clearNotificationFromNotificationsCenter:notificationId];
        }
    }
Lidan Hifi's avatar
Lidan Hifi committed
187 188

    [[NSNotificationCenter defaultCenter] postNotificationName:RNNotificationReceivedBackground
Lidan Hifi's avatar
Lidan Hifi committed
189 190 191 192 193 194
                                                        object:self
                                                      userInfo:notification];
}

+ (void)didNotificationOpen:(NSDictionary *)notification
{
Lidan Hifi's avatar
Lidan Hifi committed
195
    [[NSNotificationCenter defaultCenter] postNotificationName:RNNotificationOpened
Lidan Hifi's avatar
Lidan Hifi committed
196 197 198 199 200 201 202 203 204
                                                        object:self
                                                      userInfo:notification];
}

/*
 * Helper methods
 */
+ (void)dispatchLocalNotificationFromNotification:(NSDictionary *)notification
{
205 206 207 208
    NSDictionary* managedAps  = [notification objectForKey:@"managedAps"];
    NSDictionary* alert = [managedAps objectForKey:@"alert"];
    NSString* action = [managedAps objectForKey:@"action"];
    NSString* notificationId = [managedAps objectForKey:@"notificationId"];
Lidan Hifi's avatar
Lidan Hifi committed
209 210

    if ([action isEqualToString: RNNotificationCreateAction]
Lidan Hifi's avatar
Lidan Hifi committed
211 212
        && notificationId
        && alert) {
Lidan Hifi's avatar
Lidan Hifi committed
213

Lidan Hifi's avatar
Lidan Hifi committed
214 215 216 217
        // trigger new client push notification
        UILocalNotification* note = [[UILocalNotification alloc] init];
        note.alertTitle = [alert objectForKey:@"title"];
        note.alertBody = [alert objectForKey:@"body"];
218
        note.userInfo = notification;
219
        note.soundName = [managedAps objectForKey:@"sound"];
220
        note.category = [managedAps objectForKey:@"category"];
Lidan Hifi's avatar
Lidan Hifi committed
221

Lidan Hifi's avatar
Lidan Hifi committed
222
        [[UIApplication sharedApplication] presentLocalNotificationNow:note];
Lidan Hifi's avatar
Lidan Hifi committed
223

Lidan Hifi's avatar
Lidan Hifi committed
224 225 226 227 228
        // Serialize it and store so we can delete it later
        NSData* data = [NSKeyedArchiver archivedDataWithRootObject:note];
        NSString* notificationKey = [self buildNotificationKeyfromNotification:notificationId];
        [[NSUserDefaults standardUserDefaults] setObject:data forKey:notificationKey];
        [[NSUserDefaults standardUserDefaults] synchronize];
Lidan Hifi's avatar
Lidan Hifi committed
229

Lidan Hifi's avatar
Lidan Hifi committed
230 231 232 233 234 235 236 237 238 239
        NSLog(@"Local notification was triggered: %@", notificationKey);
    }
}

+ (void)clearNotificationFromNotificationsCenter:(NSString *)notificationId
{
    NSString* notificationKey = [self buildNotificationKeyfromNotification:notificationId];
    NSData* data = [[NSUserDefaults standardUserDefaults] objectForKey:notificationKey];
    if (data) {
        UILocalNotification* notification = [NSKeyedUnarchiver unarchiveObjectWithData: data];
Lidan Hifi's avatar
Lidan Hifi committed
240

Lidan Hifi's avatar
Lidan Hifi committed
241 242 243
        // delete the notification
        [[UIApplication sharedApplication] cancelLocalNotification:notification];
        [[NSUserDefaults standardUserDefaults] removeObjectForKey:notificationKey];
Lidan Hifi's avatar
Lidan Hifi committed
244

245 246
        NSLog(@"Local notification removed: %@", notificationKey);

Lidan Hifi's avatar
Lidan Hifi committed
247 248 249 250 251 252 253 254 255
        return;
    }
}

+ (NSString *)buildNotificationKeyfromNotification:(NSString *)notificationId
{
    return [NSString stringWithFormat:@"%@.%@", [[NSBundle mainBundle] bundleIdentifier], notificationId];
}

256
+ (void)updateNotificationCategories:(NSArray *)json
257
{
258
    NSMutableSet* categories = nil;
259

260 261 262 263 264
    if ([json count] > 0) {
        categories = [[NSMutableSet alloc] init];
        for (NSDictionary* categoryJson in json) {
            [categories addObject:[RCTConvert UIMutableUserNotificationCategory:categoryJson]];
        }
265 266
    }

267 268
    UIUserNotificationType types = (UIUserNotificationType) (UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert);
    UIUserNotificationSettings* settings = [UIUserNotificationSettings settingsForTypes:types categories:categories];
269

270
    [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
271 272
}

273
+ (void)emitNotificationActionForIdentifier:(NSString *)identifier responseInfo:(NSDictionary *)responseInfo userInfo:(NSDictionary *)userInfo
274
{
275 276 277 278 279 280
    NSMutableDictionary* info = [[NSMutableDictionary alloc] initWithDictionary:@{ @"identifier": identifier }];

    // add text
    NSString* text = [responseInfo objectForKey:UIUserNotificationActionResponseTypedTextKey];
    if (text != NULL) {
        info[@"text"] = text;
281 282
    }

283 284
    // add notification custom data
    if (userInfo != NULL) {
285
        info[@"notification"] = userInfo;
286
    }
287

288 289 290 291
    // Emit event
    [[NSNotificationCenter defaultCenter] postNotificationName:RNNotificationActionTriggered
                                                        object:self
                                                      userInfo:info];
292 293
}

294 295 296
/*
 * Javascript events
 */
Lidan Hifi's avatar
Lidan Hifi committed
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
- (void)handleNotificationReceivedForeground:(NSNotification *)notification
{
    [_bridge.eventDispatcher sendDeviceEventWithName:@"notificationReceivedForeground" body:notification.userInfo];
}

- (void)handleNotificationReceivedBackground:(NSNotification *)notification
{
    [_bridge.eventDispatcher sendDeviceEventWithName:@"notificationReceivedBackground" body:notification.userInfo];
}

- (void)handleNotificationOpened:(NSNotification *)notification
{
    [_bridge.eventDispatcher sendDeviceEventWithName:@"notificationOpened" body:notification.userInfo];
}

312 313 314 315 316
- (void)handleNotificationActionTriggered:(NSNotification *)notification
{
    [_bridge.eventDispatcher sendAppEventWithName:@"notificationActionReceived" body:notification.userInfo];
}

Lidan Hifi's avatar
Lidan Hifi committed
317 318 319
/*
 * React Native exported methods
 */
320
RCT_EXPORT_METHOD(updateNotificationCategories:(NSArray *)json)
Lidan Hifi's avatar
Lidan Hifi committed
321
{
322
    [RNNotifications updateNotificationCategories:json];
Lidan Hifi's avatar
Lidan Hifi committed
323 324 325
}

@end