1// Copyright 2018-present 650 Industries. All rights reserved. 2 3#import <EXNotifications/EXNotificationSerializer.h> 4#import <CoreLocation/CoreLocation.h> 5 6NS_ASSUME_NONNULL_BEGIN 7 8static NSString * const EXNotificationResponseDefaultActionIdentifier = @"expo.modules.notifications.actions.DEFAULT"; 9 10@implementation EXNotificationSerializer 11 12+ (NSDictionary *)serializedNotificationResponse:(UNNotificationResponse *)response 13{ 14 NSMutableDictionary *serializedResponse = [NSMutableDictionary dictionary]; 15 NSString *actionIdentifier = response.actionIdentifier; 16 if ([UNNotificationDefaultActionIdentifier isEqualToString:actionIdentifier]) { 17 actionIdentifier = EXNotificationResponseDefaultActionIdentifier; 18 } 19 serializedResponse[@"actionIdentifier"] = actionIdentifier; 20 serializedResponse[@"notification"] = [self serializedNotification:response.notification]; 21 if ([response isKindOfClass:[UNTextInputNotificationResponse class]]) { 22 UNTextInputNotificationResponse *textInputResponse = (UNTextInputNotificationResponse *)response; 23 serializedResponse[@"userText"] = textInputResponse.userText ?: [NSNull null]; 24 } 25 return serializedResponse; 26} 27 28+ (NSDictionary *)serializedNotification:(UNNotification *)notification 29{ 30 NSMutableDictionary *serializedNotification = [NSMutableDictionary dictionary]; 31 serializedNotification[@"request"] = [self serializedNotificationRequest:notification.request]; 32 serializedNotification[@"date"] = @(notification.date.timeIntervalSince1970); 33 return serializedNotification; 34} 35 36+ (NSDictionary *)serializedNotificationRequest:(UNNotificationRequest *)request 37{ 38 NSMutableDictionary *serializedRequest = [NSMutableDictionary dictionary]; 39 serializedRequest[@"identifier"] = request.identifier; 40 serializedRequest[@"content"] = [self serializedNotificationContent:request]; 41 serializedRequest[@"trigger"] = [self serializedNotificationTrigger:request]; 42 return serializedRequest; 43} 44 45+ (NSDictionary *)serializedNotificationContent:(UNNotificationRequest *)request 46{ 47 UNNotificationContent *content = request.content; 48 NSMutableDictionary *serializedContent = [NSMutableDictionary dictionary]; 49 serializedContent[@"title"] = content.title ?: [NSNull null]; 50 serializedContent[@"subtitle"] = content.subtitle ?: [NSNull null]; 51 serializedContent[@"body"] = content.body ?: [NSNull null]; 52 serializedContent[@"badge"] = content.badge ?: [NSNull null]; 53 serializedContent[@"sound"] = [self serializedNotificationSound:content.sound] ?: [NSNull null]; 54 serializedContent[@"launchImageName"] = content.launchImageName ?: [NSNull null]; 55 serializedContent[@"data"] = [self serializedNotificationData:request] ?: [NSNull null]; 56 serializedContent[@"attachments"] = [self serializedNotificationAttachments:content.attachments]; 57 58 if (@available(iOS 12.0, *)) { 59 serializedContent[@"summaryArgument"] = content.summaryArgument ?: [NSNull null]; 60 serializedContent[@"summaryArgumentCount"] = @(content.summaryArgumentCount); 61 } 62 serializedContent[@"categoryIdentifier"] = content.categoryIdentifier ? content.categoryIdentifier : [NSNull null]; 63 serializedContent[@"threadIdentifier"] = content.threadIdentifier ?: [NSNull null]; 64 if (@available(iOS 13.0, *)) { 65 serializedContent[@"targetContentIdentifier"] = content.targetContentIdentifier ?: [NSNull null]; 66 } 67 68 return serializedContent; 69} 70 71+ (NSDictionary *)serializedNotificationData:(UNNotificationRequest *)request 72{ 73 BOOL isRemote = [request.trigger isKindOfClass:[UNPushNotificationTrigger class]]; 74 return isRemote ? request.content.userInfo[@"body"] : request.content.userInfo; 75} 76 77+ (NSString *)serializedNotificationSound:(UNNotificationSound *)sound 78{ 79 // nil compared to defaultCriticalSound returns true 80 if (!sound) { 81 return nil; 82 } 83 84 if (@available(iOS 12.0, *)) { 85 if ([[UNNotificationSound defaultCriticalSound] isEqual:sound]) { 86 return @"defaultCritical"; 87 } 88 } 89 90 if ([[UNNotificationSound defaultSound] isEqual:sound]) { 91 return @"default"; 92 } 93 94 return @"custom"; 95} 96 97+ (NSArray *)serializedNotificationAttachments:(NSArray<UNNotificationAttachment *> *)attachments 98{ 99 NSMutableArray *serializedAttachments = [NSMutableArray array]; 100 for (UNNotificationAttachment *attachment in attachments) { 101 [serializedAttachments addObject:[self serializedNotificationAttachment:attachment]]; 102 } 103 return serializedAttachments; 104} 105 106+ (NSDictionary *)serializedNotificationAttachment:(UNNotificationAttachment *)attachment 107{ 108 NSMutableDictionary *serializedAttachment = [NSMutableDictionary dictionary]; 109 serializedAttachment[@"identifier"] = attachment.identifier ?: [NSNull null]; 110 serializedAttachment[@"url"] = attachment.URL.absoluteString ?: [NSNull null]; 111 serializedAttachment[@"type"] = attachment.type ?: [NSNull null]; 112 return serializedAttachment; 113} 114 115+ (NSDictionary *)serializedNotificationTrigger:(UNNotificationRequest *)request 116{ 117 UNNotificationTrigger *trigger = request.trigger; 118 NSMutableDictionary *serializedTrigger = [NSMutableDictionary dictionary]; 119 serializedTrigger[@"class"] = NSStringFromClass(trigger.class); 120 if ([trigger isKindOfClass:[UNPushNotificationTrigger class]]) { 121 serializedTrigger[@"type"] = @"push"; 122 serializedTrigger[@"payload"] = request.content.userInfo; 123 } else if ([trigger isKindOfClass:[UNCalendarNotificationTrigger class]]) { 124 serializedTrigger[@"type"] = @"calendar"; 125 serializedTrigger[@"repeats"] = @(trigger.repeats); 126 UNCalendarNotificationTrigger *calendarTrigger = (UNCalendarNotificationTrigger *)trigger; 127 serializedTrigger[@"dateComponents"] = [self serializedDateComponents:calendarTrigger.dateComponents]; 128#if !(TARGET_OS_MACCATALYST) 129 } else if ([trigger isKindOfClass:[UNLocationNotificationTrigger class]]) { 130 serializedTrigger[@"type"] = @"location"; 131 serializedTrigger[@"repeats"] = @(trigger.repeats); 132 UNLocationNotificationTrigger *locationTrigger = (UNLocationNotificationTrigger *)trigger; 133 serializedTrigger[@"region"] = [self serializedRegion:locationTrigger.region]; 134#endif 135 } else if ([trigger isKindOfClass:[UNTimeIntervalNotificationTrigger class]]) { 136 serializedTrigger[@"type"] = @"timeInterval"; 137 UNTimeIntervalNotificationTrigger *timeIntervalTrigger = (UNTimeIntervalNotificationTrigger *)trigger; 138 serializedTrigger[@"seconds"] = @(timeIntervalTrigger.timeInterval); 139 serializedTrigger[@"repeats"] = @(trigger.repeats); 140 } else { 141 serializedTrigger[@"type"] = @"unknown"; 142 } 143 return serializedTrigger; 144} 145 146+ (NSDictionary *)serializedDateComponents:(NSDateComponents *)dateComponents 147{ 148 NSMutableDictionary *serializedComponents = [NSMutableDictionary dictionary]; 149 NSArray<NSNumber *> *autoConvertedUnits = [[self calendarUnitsConversionMap] allKeys]; 150 for (NSNumber *calendarUnitNumber in autoConvertedUnits) { 151 NSCalendarUnit calendarUnit = [calendarUnitNumber unsignedIntegerValue]; 152 NSInteger unitValue = [dateComponents valueForComponent:calendarUnit]; 153 if (unitValue != NSDateComponentUndefined) { 154 serializedComponents[[self keyForCalendarUnit:calendarUnit]] = @([dateComponents valueForComponent:calendarUnit]); 155 } 156 } 157 serializedComponents[@"calendar"] = dateComponents.calendar.calendarIdentifier ?: [NSNull null]; 158 serializedComponents[@"timeZone"] = dateComponents.timeZone.description ?: [NSNull null]; 159 serializedComponents[@"isLeapMonth"] = @(dateComponents.isLeapMonth); 160 return serializedComponents; 161} 162 163+ (NSDictionary *)calendarUnitsConversionMap 164{ 165 static NSDictionary *keysMap = nil; 166 if (!keysMap) { 167 keysMap = @{ 168 @(NSCalendarUnitEra): @"era", 169 @(NSCalendarUnitYear): @"year", 170 @(NSCalendarUnitMonth): @"month", 171 @(NSCalendarUnitDay): @"day", 172 @(NSCalendarUnitHour): @"hour", 173 @(NSCalendarUnitMinute): @"minute", 174 @(NSCalendarUnitSecond): @"second", 175 @(NSCalendarUnitWeekday): @"weekday", 176 @(NSCalendarUnitWeekdayOrdinal): @"weekdayOrdinal", 177 @(NSCalendarUnitQuarter): @"quarter", 178 @(NSCalendarUnitWeekOfMonth): @"weekOfMonth", 179 @(NSCalendarUnitWeekOfYear): @"weekOfYear", 180 @(NSCalendarUnitYearForWeekOfYear): @"yearForWeekOfYear", 181 @(NSCalendarUnitNanosecond): @"nanosecond" 182 // NSCalendarUnitCalendar and NSCalendarUnitTimeZone 183 // should be handled separately 184 }; 185 } 186 return keysMap; 187} 188 189+ (NSString *)keyForCalendarUnit:(NSCalendarUnit)calendarUnit 190{ 191 return [self calendarUnitsConversionMap][@(calendarUnit)]; 192} 193 194+ (NSDictionary *)serializedRegion:(CLRegion *)region 195{ 196 NSMutableDictionary *serializedRegion = [NSMutableDictionary dictionary]; 197 serializedRegion[@"identifier"] = region.identifier; 198 serializedRegion[@"notifyOnEntry"] = @(region.notifyOnEntry); 199 serializedRegion[@"notifyOnExit"] = @(region.notifyOnExit); 200 if ([region isKindOfClass:[CLCircularRegion class]]) { 201 serializedRegion[@"type"] = @"circular"; 202 CLCircularRegion *circularRegion = (CLCircularRegion *)region; 203 NSDictionary *serializedCenter = @{ 204 @"latitude": @(circularRegion.center.latitude), 205 @"longitude": @(circularRegion.center.longitude) 206 }; 207 serializedRegion[@"center"] = serializedCenter; 208 serializedRegion[@"radius"] = @(circularRegion.radius); 209 } else if ([region isKindOfClass:[CLBeaconRegion class]]) { 210 serializedRegion[@"type"] = @"beacon"; 211 CLBeaconRegion *beaconRegion = (CLBeaconRegion *)region; 212 serializedRegion[@"notifyEntryStateOnDisplay"] = @(beaconRegion.notifyEntryStateOnDisplay); 213 serializedRegion[@"major"] = beaconRegion.major ?: [NSNull null]; 214 serializedRegion[@"minor"] = beaconRegion.minor ?: [NSNull null]; 215 if (@available(iOS 13.0, *)) { 216 serializedRegion[@"uuid"] = beaconRegion.UUID; 217 NSDictionary *serializedConstraint = @{ 218 @"uuid": beaconRegion.beaconIdentityConstraint.UUID, 219 @"major": beaconRegion.beaconIdentityConstraint.major ?: [NSNull null], 220 @"minor": beaconRegion.beaconIdentityConstraint.minor ?: [NSNull null], 221 }; 222 serializedRegion[@"beaconIdentityConstraint"] = serializedConstraint; 223 } 224 } else { 225 serializedRegion[@"type"] = @"unknown"; 226 } 227 return serializedRegion; 228} 229 230@end 231 232NS_ASSUME_NONNULL_END 233