#import "CDVBluememeUtils.h" #import #import #import #import #import static NSString * const BluememeDeviceKeyDefaultsKey = @"bluememe_utils_device_key"; @interface CDVBluememeUtils () @property (nonatomic, copy) NSString *notificationCallbackId; @property (nonatomic, strong) NSDictionary *initialNotification; @property (nonatomic, weak) id previousNotificationDelegate; @end @implementation CDVBluememeUtils - (void)pluginInitialize { if ([self ensureFirebaseConfigured:nil]) { [FIRMessaging messaging].delegate = self; } if (@available(iOS 10.0, *)) { UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; self.previousNotificationDelegate = center.delegate; center.delegate = self; } } - (void)saveWifi:(CDVInvokedUrlCommand *)command { [self applyWifiCommand:command defaultJoinOnce:NO]; } - (void)connectWifi:(CDVInvokedUrlCommand *)command { [self applyWifiCommand:command defaultJoinOnce:YES]; } - (void)getDeviceInfo:(CDVInvokedUrlCommand *)command { NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary]; NSString *bundleIdentifier = [self safeString:[[NSBundle mainBundle] bundleIdentifier]]; NSString *appVersion = [self safeString:[infoDictionary objectForKey:@"CFBundleShortVersionString"]]; NSString *buildNumber = [self safeString:[infoDictionary objectForKey:@"CFBundleVersion"]]; UIDevice *device = [UIDevice currentDevice]; NSLocale *locale = [NSLocale currentLocale]; NSString *localeIdentifier = [self safeString:[locale localeIdentifier]]; NSString *timeZone = [self safeString:[[NSTimeZone localTimeZone] name]]; NSDictionary *result = @{ @"deviceKey": [self getOrCreateDeviceKey], @"platform": @"iOS", @"appIdentifier": bundleIdentifier, @"appVersion": appVersion, @"buildNumber": buildNumber, @"osVersion": [self safeString:device.systemVersion], @"systemName": [self safeString:device.systemName], @"deviceModel": [self safeString:device.model], @"model": [self safeString:device.model], @"locale": localeIdentifier, @"timeZone": timeZone }; [self sendSuccessForCommand:command dictionary:result]; } - (void)removeWifi:(CDVInvokedUrlCommand *)command { NSDictionary *options = [self firstDictionaryArgument:command]; NSString *ssid = [self stringFromDictionary:options key:@"ssid"]; if (ssid.length == 0) { [self sendErrorForCommand:command code:@"INVALID_WIFI_OPTIONS" message:@"ssid is required."]; return; } if (@available(iOS 11.0, *)) { [[NEHotspotConfigurationManager sharedManager] removeConfigurationForSSID:ssid]; [self sendSuccessForCommand:command dictionary:@{@"ssid": ssid, @"removed": @YES}]; } else { [self sendErrorForCommand:command code:@"UNSUPPORTED_IOS_VERSION" message:@"WiFi configuration requires iOS 11 or newer."]; } } - (void)disconnectWifi:(CDVInvokedUrlCommand *)command { [self sendErrorForCommand:command code:@"UNSUPPORTED_PLATFORM_ACTION" message:@"iOS does not expose a public API for disconnecting WiFi from third-party apps."]; } - (void)requestWifiPermission:(CDVInvokedUrlCommand *)command { [self sendSuccessForCommand:command dictionary:@{@"type": @"wifi", @"granted": @YES, @"missing": @[]}]; } - (void)isBiometricAvailable:(CDVInvokedUrlCommand *)command { [self sendSuccessForCommand:command dictionary:[self biometricAvailability]]; } - (void)unlockWithBiometrics:(CDVInvokedUrlCommand *)command { NSDictionary *options = [self firstDictionaryArgument:command]; LAContext *context = [[LAContext alloc] init]; NSString *fallbackTitle = [self stringFromDictionary:options key:@"fallbackTitle"]; NSString *cancelTitle = [self stringFromDictionary:options key:@"cancelTitle"]; if (fallbackTitle.length > 0) { context.localizedFallbackTitle = fallbackTitle; } if (cancelTitle.length > 0) { context.localizedCancelTitle = cancelTitle; } BOOL allowPasscode = [self boolFromDictionary:options key:@"allowDeviceCredential" defaultValue:YES]; LAPolicy policy = allowPasscode ? LAPolicyDeviceOwnerAuthentication : LAPolicyDeviceOwnerAuthenticationWithBiometrics; NSError *availabilityError = nil; if (![context canEvaluatePolicy:policy error:&availabilityError]) { [self sendErrorForCommand:command code:@"BIOMETRIC_UNAVAILABLE" message:(availabilityError.localizedDescription ? availabilityError.localizedDescription : @"Biometric authentication is unavailable.")]; return; } NSString *reason = [self stringFromDictionary:options key:@"reason"]; if (reason.length == 0) { reason = @"Unlock"; } [context evaluatePolicy:policy localizedReason:reason reply:^(BOOL success, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ if (success) { NSDictionary *result = @{@"unlocked": @YES, @"biometryType": [self biometryTypeForContext:context]}; [self sendSuccessForCommand:command dictionary:result]; NSMutableDictionary *event = [result mutableCopy]; event[@"type"] = @"unlock"; [self sendEvent:event]; } else { [self sendErrorForCommand:command code:@"AUTHENTICATION_ERROR" message:(error.localizedDescription ? error.localizedDescription : @"Authentication failed.")]; } }); }]; } - (void)requestPushPermission:(CDVInvokedUrlCommand *)command { NSError *firebaseError = nil; if (![self ensureFirebaseConfigured:&firebaseError]) { [self sendErrorForCommand:command code:@"FIREBASE_NOT_CONFIGURED" message:(firebaseError.localizedDescription ? firebaseError.localizedDescription : @"Firebase is not configured.")]; return; } [FIRMessaging messaging].delegate = self; if (@available(iOS 10.0, *)) { UNAuthorizationOptions options = UNAuthorizationOptionAlert | UNAuthorizationOptionBadge | UNAuthorizationOptionSound; [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:options completionHandler:^(BOOL granted, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ if (error != nil) { [self sendErrorForCommand:command code:@"PUSH_PERMISSION_FAILED" message:error.localizedDescription]; return; } [[UIApplication sharedApplication] registerForRemoteNotifications]; [self sendSuccessForCommand:command dictionary:@{@"type": @"notification", @"granted": @(granted), @"missing": granted ? @[] : @[@"notification"]}]; }); }]; } else { UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound) categories:nil]; [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; [[UIApplication sharedApplication] registerForRemoteNotifications]; [self sendSuccessForCommand:command dictionary:@{@"type": @"notification", @"granted": @YES, @"missing": @[]}]; } } - (void)getPushPermissionStatus:(CDVInvokedUrlCommand *)command { if (@available(iOS 10.0, *)) { [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *settings) { dispatch_async(dispatch_get_main_queue(), ^{ [self sendSuccessForCommand:command dictionary:[self dictionaryFromNotificationSettings:settings]]; }); }]; } else { UIUserNotificationSettings *settings = [[UIApplication sharedApplication] currentUserNotificationSettings]; BOOL enabled = settings.types != UIUserNotificationTypeNone; [self sendSuccessForCommand:command dictionary:@{ @"type": @"notification", @"platform": @"iOS", @"enabled": @(enabled), @"granted": @(enabled), @"authorizationStatus": enabled ? @"authorized" : @"denied", @"alertEnabled": @((settings.types & UIUserNotificationTypeAlert) != 0), @"soundEnabled": @((settings.types & UIUserNotificationTypeSound) != 0), @"badgeEnabled": @((settings.types & UIUserNotificationTypeBadge) != 0), @"missing": enabled ? @[] : @[@"notification"] }]; } } - (void)getFirebaseToken:(CDVInvokedUrlCommand *)command { NSError *firebaseError = nil; if (![self ensureFirebaseConfigured:&firebaseError]) { [self sendErrorForCommand:command code:@"FIREBASE_NOT_CONFIGURED" message:(firebaseError.localizedDescription ? firebaseError.localizedDescription : @"Firebase is not configured.")]; return; } [FIRMessaging messaging].delegate = self; [[FIRMessaging messaging] tokenWithCompletion:^(NSString *token, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ if (error != nil || token.length == 0) { [self sendErrorForCommand:command code:@"FCM_TOKEN_FAILED" message:(error.localizedDescription ? error.localizedDescription : @"Failed to get Firebase token.")]; return; } [self sendSuccessForCommand:command dictionary:@{@"token": token}]; [self sendEvent:@{@"type": @"token", @"token": token}]; }); }]; } - (void)subscribeToTopic:(CDVInvokedUrlCommand *)command { NSString *topic = [command.arguments firstObject]; if (![topic isKindOfClass:[NSString class]] || topic.length == 0) { [self sendErrorForCommand:command code:@"INVALID_TOPIC" message:@"topic is required."]; return; } NSError *firebaseError = nil; if (![self ensureFirebaseConfigured:&firebaseError]) { [self sendErrorForCommand:command code:@"FIREBASE_NOT_CONFIGURED" message:(firebaseError.localizedDescription ? firebaseError.localizedDescription : @"Firebase is not configured.")]; return; } [[FIRMessaging messaging] subscribeToTopic:topic completion:^(NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ if (error != nil) { [self sendErrorForCommand:command code:@"FIREBASE_TASK_FAILED" message:error.localizedDescription]; } else { [self sendSuccessForCommand:command dictionary:nil]; } }); }]; } - (void)unsubscribeFromTopic:(CDVInvokedUrlCommand *)command { NSString *topic = [command.arguments firstObject]; if (![topic isKindOfClass:[NSString class]] || topic.length == 0) { [self sendErrorForCommand:command code:@"INVALID_TOPIC" message:@"topic is required."]; return; } NSError *firebaseError = nil; if (![self ensureFirebaseConfigured:&firebaseError]) { [self sendErrorForCommand:command code:@"FIREBASE_NOT_CONFIGURED" message:(firebaseError.localizedDescription ? firebaseError.localizedDescription : @"Firebase is not configured.")]; return; } [[FIRMessaging messaging] unsubscribeFromTopic:topic completion:^(NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ if (error != nil) { [self sendErrorForCommand:command code:@"FIREBASE_TASK_FAILED" message:error.localizedDescription]; } else { [self sendSuccessForCommand:command dictionary:nil]; } }); }]; } - (void)getInitialNotification:(CDVInvokedUrlCommand *)command { [self sendSuccessForCommand:command dictionary:self.initialNotification]; self.initialNotification = nil; } - (void)startNotificationListener:(CDVInvokedUrlCommand *)command { self.notificationCallbackId = command.callbackId; CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_NO_RESULT]; [result setKeepCallbackAsBool:YES]; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } - (void)stopNotificationListener:(CDVInvokedUrlCommand *)command { self.notificationCallbackId = nil; [self sendSuccessForCommand:command dictionary:nil]; } - (void)applyWifiCommand:(CDVInvokedUrlCommand *)command defaultJoinOnce:(BOOL)defaultJoinOnce { if (![NSThread isMainThread]) { dispatch_async(dispatch_get_main_queue(), ^{ [self applyWifiCommand:command defaultJoinOnce:defaultJoinOnce]; }); return; } NSDictionary *options = [self firstDictionaryArgument:command]; NSString *ssid = [self stringFromDictionary:options key:@"ssid"]; NSString *password = [self stringFromDictionary:options key:@"password"]; NSString *security = [[self stringFromDictionary:options key:@"security"] lowercaseString]; if (security.length == 0) { security = password.length == 0 ? @"open" : @"wpa2"; } BOOL isOpen = [security isEqualToString:@"open"] || [security isEqualToString:@"none"]; BOOL isWep = [security isEqualToString:@"wep"] || [self boolFromDictionary:options key:@"isWep" defaultValue:NO]; BOOL joinOnce = [self boolFromDictionary:options key:@"joinOnce" defaultValue:defaultJoinOnce]; BOOL hiddenSsid = [self boolFromDictionary:options key:@"hiddenSsid" defaultValue:NO]; BOOL resetExisting = [self boolFromDictionary:options key:@"resetExisting" defaultValue:YES]; if (ssid.length == 0) { [self sendErrorForCommand:command code:@"INVALID_WIFI_OPTIONS" message:@"ssid is required."]; return; } if (!isOpen && password.length == 0) { [self sendErrorForCommand:command code:@"INVALID_WIFI_OPTIONS" message:@"password is required for secured WiFi networks."]; return; } if (!isOpen && !isWep && (password.length < 8 || password.length > 63)) { [self sendErrorForCommand:command code:@"INVALID_WIFI_OPTIONS" message:@"WPA/WPA2 passphrase must be 8 to 63 characters."]; return; } #if TARGET_OS_SIMULATOR [self sendErrorForCommand:command code:@"UNSUPPORTED_IOS_SIMULATOR" message:@"iOS WiFi configuration requires a physical device."]; return; #endif if (@available(iOS 11.0, *)) { NSDictionary *context = @{ @"ssid": ssid, @"security": security, @"passwordLength": @(password.length), @"joinOnce": @(joinOnce), @"hiddenSsid": @(hiddenSsid), @"resetExisting": @(resetExisting) }; [self prepareAndApplyWifiConfigurationForCommand:command ssid:ssid password:password isOpen:isOpen isWep:isWep joinOnce:joinOnce hiddenSsid:hiddenSsid resetExisting:resetExisting context:context]; } else { [self sendErrorForCommand:command code:@"UNSUPPORTED_IOS_VERSION" message:@"WiFi configuration requires iOS 11 or newer."]; } } - (void)prepareAndApplyWifiConfigurationForCommand:(CDVInvokedUrlCommand *)command ssid:(NSString *)ssid password:(NSString *)password isOpen:(BOOL)isOpen isWep:(BOOL)isWep joinOnce:(BOOL)joinOnce hiddenSsid:(BOOL)hiddenSsid resetExisting:(BOOL)resetExisting context:(NSDictionary *)context API_AVAILABLE(ios(11.0)) { NEHotspotConfigurationManager *manager = [NEHotspotConfigurationManager sharedManager]; if (!resetExisting) { [self applyWifiConfigurationForCommand:command manager:manager ssid:ssid password:password isOpen:isOpen isWep:isWep joinOnce:joinOnce hiddenSsid:hiddenSsid context:context]; return; } [manager getConfiguredSSIDsWithCompletionHandler:^(NSArray *configuredSSIDs) { BOOL configuredByApp = [configuredSSIDs containsObject:ssid]; NSMutableDictionary *updatedContext = [context mutableCopy]; updatedContext[@"configuredByApp"] = @(configuredByApp); void (^apply)(void) = ^{ [self applyWifiConfigurationForCommand:command manager:manager ssid:ssid password:password isOpen:isOpen isWep:isWep joinOnce:joinOnce hiddenSsid:hiddenSsid context:updatedContext]; }; if (configuredByApp) { [manager removeConfigurationForSSID:ssid]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(500 * NSEC_PER_MSEC)), dispatch_get_main_queue(), apply); } else { dispatch_async(dispatch_get_main_queue(), apply); } }]; } - (void)applyWifiConfigurationForCommand:(CDVInvokedUrlCommand *)command manager:(NEHotspotConfigurationManager *)manager ssid:(NSString *)ssid password:(NSString *)password isOpen:(BOOL)isOpen isWep:(BOOL)isWep joinOnce:(BOOL)joinOnce hiddenSsid:(BOOL)hiddenSsid context:(NSDictionary *)context API_AVAILABLE(ios(11.0)) { NEHotspotConfiguration *configuration = nil; if (isOpen) { configuration = [[NEHotspotConfiguration alloc] initWithSSID:ssid]; } else { configuration = [[NEHotspotConfiguration alloc] initWithSSID:ssid passphrase:password isWEP:isWep]; } if (@available(iOS 13.0, *)) { configuration.hidden = hiddenSsid; } configuration.joinOnce = joinOnce; [manager applyConfiguration:configuration completionHandler:^(NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ if (error != nil && error.code != NEHotspotConfigurationErrorAlreadyAssociated) { [self sendWifiApplyErrorForCommand:command error:error context:context]; return; } NSDictionary *result = @{ @"ssid": ssid, @"platformMode": @"hotspotConfiguration", @"connected": @YES, @"saved": @(!joinOnce), @"alreadyAssociated": @(error != nil && error.code == NEHotspotConfigurationErrorAlreadyAssociated) }; [self sendSuccessForCommand:command dictionary:result]; }); }]; } - (void)sendWifiApplyErrorForCommand:(CDVInvokedUrlCommand *)command error:(NSError *)error context:(NSDictionary *)context { NSMutableDictionary *details = [NSMutableDictionary dictionary]; if (context != nil) { [details addEntriesFromDictionary:context]; } if (error.domain.length > 0) { details[@"nativeDomain"] = error.domain; } details[@"nativeCode"] = @(error.code); NSString *nativeError = [self hotspotConfigurationErrorNameForCode:error.code]; if (nativeError.length > 0) { details[@"nativeError"] = nativeError; } if (error.localizedFailureReason.length > 0) { details[@"nativeFailureReason"] = error.localizedFailureReason; } if (error.localizedRecoverySuggestion.length > 0) { details[@"nativeRecoverySuggestion"] = error.localizedRecoverySuggestion; } [self sendErrorForCommand:command code:@"WIFI_CONNECT_FAILED" message:[self wifiApplyErrorMessageForError:error] details:details]; } - (NSString *)wifiApplyErrorMessageForError:(NSError *)error { if (![error.domain isEqualToString:NEHotspotConfigurationErrorDomain]) { return error.localizedDescription.length > 0 ? error.localizedDescription : @"Unable to apply WiFi configuration."; } switch (error.code) { case 1: return @"Invalid WiFi SSID."; case 2: return @"Invalid WPA/WPA2/WPA3 passphrase."; case 3: return @"Invalid WEP passphrase."; case 4: return @"Invalid EAP settings."; case 5: return @"Invalid Hotspot 2.0 settings."; case 6: return @"Invalid Hotspot 2.0 domain name."; case 7: return @"The user denied the iOS WiFi connection prompt."; case 8: return @"iOS returned NEHotspotConfigurationErrorInternal. Check that the App ID, entitlements, and provisioning profile include Hotspot Configuration, then test on a physical device."; case 9: return @"Another iOS WiFi configuration request is already pending."; case 10: return @"iOS system WiFi configuration failed."; case 11: return @"iOS returned an unknown WiFi configuration error."; case 12: return @"joinOnce is not supported for this WiFi configuration."; case 14: return @"The app must be in the foreground to configure WiFi."; case 15: return @"Invalid SSID prefix."; default: return error.localizedDescription.length > 0 ? error.localizedDescription : @"Unable to apply WiFi configuration."; } } - (NSString *)hotspotConfigurationErrorNameForCode:(NSInteger)code { switch (code) { case 0: return @"invalid"; case 1: return @"invalidSSID"; case 2: return @"invalidWPAPassphrase"; case 3: return @"invalidWEPPassphrase"; case 4: return @"invalidEAPSettings"; case 5: return @"invalidHS20Settings"; case 6: return @"invalidHS20DomainName"; case 7: return @"userDenied"; case 8: return @"internal"; case 9: return @"pending"; case 10: return @"systemConfiguration"; case 11: return @"unknown"; case 12: return @"joinOnceNotSupported"; case 13: return @"alreadyAssociated"; case 14: return @"applicationIsNotInForeground"; case 15: return @"invalidSSIDPrefix"; default: return @""; } } - (NSDictionary *)biometricAvailability { LAContext *context = [[LAContext alloc] init]; NSError *error = nil; BOOL available = [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]; NSString *type = [self biometryTypeForContext:context]; if (available) { return @{@"available": @YES, @"biometryType": type, @"code": @"OK", @"message": @"Biometric authentication is available."}; } BOOL passcodeAvailable = [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:nil]; return @{ @"available": @NO, @"biometryType": type, @"passcodeAvailable": @(passcodeAvailable), @"code": @"BIOMETRIC_UNAVAILABLE", @"message": error.localizedDescription ? error.localizedDescription : @"Biometric authentication is unavailable." }; } - (NSString *)biometryTypeForContext:(LAContext *)context { if (@available(iOS 11.0, *)) { if (context.biometryType == LABiometryTypeFaceID) { return @"face"; } if (context.biometryType == LABiometryTypeTouchID) { return @"touch"; } if (@available(iOS 11.2, *)) { if (context.biometryType == LABiometryTypeNone) { return @"none"; } } return @"biometric"; } return @"touch"; } - (NSDictionary *)dictionaryFromNotificationSettings:(UNNotificationSettings *)settings API_AVAILABLE(ios(10.0)) { NSString *status = [self authorizationStatusName:settings.authorizationStatus]; BOOL enabled = settings.authorizationStatus == UNAuthorizationStatusAuthorized; if (@available(iOS 12.0, *)) { enabled = enabled || settings.authorizationStatus == UNAuthorizationStatusProvisional; } if (@available(iOS 14.0, *)) { enabled = enabled || settings.authorizationStatus == UNAuthorizationStatusEphemeral; } BOOL alertEnabled = settings.alertSetting == UNNotificationSettingEnabled; BOOL soundEnabled = settings.soundSetting == UNNotificationSettingEnabled; BOOL badgeEnabled = settings.badgeSetting == UNNotificationSettingEnabled; NSArray *missing = enabled ? @[] : @[@"notification"]; return @{ @"type": @"notification", @"platform": @"iOS", @"enabled": @(enabled), @"granted": @(enabled), @"authorizationStatus": status, @"alertEnabled": @(alertEnabled), @"soundEnabled": @(soundEnabled), @"badgeEnabled": @(badgeEnabled), @"missing": missing }; } - (NSString *)authorizationStatusName:(UNAuthorizationStatus)status API_AVAILABLE(ios(10.0)) { switch (status) { case UNAuthorizationStatusNotDetermined: return @"notDetermined"; case UNAuthorizationStatusDenied: return @"denied"; case UNAuthorizationStatusAuthorized: return @"authorized"; default: break; } if (@available(iOS 12.0, *)) { if (status == UNAuthorizationStatusProvisional) { return @"provisional"; } } if (@available(iOS 14.0, *)) { if (status == UNAuthorizationStatusEphemeral) { return @"ephemeral"; } } return @"unknown"; } - (BOOL)ensureFirebaseConfigured:(NSError **)error { if ([FIRApp defaultApp] != nil) { return YES; } NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"GoogleService-Info" ofType:@"plist"]; if (plistPath.length > 0) { [FIRApp configure]; return [FIRApp defaultApp] != nil; } NSString *googleAppId = [self infoStringForKey:@"BLUEMEME_FIREBASE_GOOGLE_APP_ID"]; NSString *gcmSenderId = [self infoStringForKey:@"BLUEMEME_FIREBASE_GCM_SENDER_ID"]; NSString *apiKey = [self infoStringForKey:@"BLUEMEME_FIREBASE_API_KEY"]; NSString *projectId = [self infoStringForKey:@"BLUEMEME_FIREBASE_PROJECT_ID"]; NSString *storageBucket = [self infoStringForKey:@"BLUEMEME_FIREBASE_STORAGE_BUCKET"]; if (googleAppId.length == 0 || gcmSenderId.length == 0) { if (error != nil) { *error = [NSError errorWithDomain:@"BluememeUtils" code:1001 userInfo:@{NSLocalizedDescriptionKey: @"Firebase options are missing."}]; } return NO; } FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:googleAppId GCMSenderID:gcmSenderId]; if (apiKey.length > 0) { options.APIKey = apiKey; } if (projectId.length > 0) { options.projectID = projectId; } if (storageBucket.length > 0) { options.storageBucket = storageBucket; } options.bundleID = [[NSBundle mainBundle] bundleIdentifier]; [FIRApp configureWithOptions:options]; return [FIRApp defaultApp] != nil; } - (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSString *)fcmToken { if (fcmToken.length == 0) { return; } [self sendEvent:@{@"type": @"token", @"token": fcmToken}]; } - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler API_AVAILABLE(ios(10.0)) { [self sendEvent:@{@"type": @"notification", @"notification": [self payloadFromNotification:notification]}]; if (self.previousNotificationDelegate != nil && self.previousNotificationDelegate != self && [self.previousNotificationDelegate respondsToSelector:@selector(userNotificationCenter:willPresentNotification:withCompletionHandler:)]) { [self.previousNotificationDelegate userNotificationCenter:center willPresentNotification:notification withCompletionHandler:completionHandler]; return; } completionHandler(UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionSound | UNNotificationPresentationOptionBadge); } - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler API_AVAILABLE(ios(10.0)) { NSDictionary *payload = [self payloadFromNotification:response.notification]; self.initialNotification = payload; [self sendEvent:@{@"type": @"notificationOpened", @"notification": payload}]; if (self.previousNotificationDelegate != nil && self.previousNotificationDelegate != self && [self.previousNotificationDelegate respondsToSelector:@selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)]) { [self.previousNotificationDelegate userNotificationCenter:center didReceiveNotificationResponse:response withCompletionHandler:completionHandler]; return; } completionHandler(); } - (NSDictionary *)payloadFromNotification:(UNNotification *)notification API_AVAILABLE(ios(10.0)) { NSMutableDictionary *payload = [NSMutableDictionary dictionary]; NSDictionary *userInfo = notification.request.content.userInfo ? notification.request.content.userInfo : @{}; payload[@"data"] = userInfo; if (notification.request.identifier.length > 0) { payload[@"identifier"] = notification.request.identifier; } NSMutableDictionary *apsNotification = [NSMutableDictionary dictionary]; if (notification.request.content.title.length > 0) { apsNotification[@"title"] = notification.request.content.title; } if (notification.request.content.body.length > 0) { apsNotification[@"body"] = notification.request.content.body; } if (notification.request.content.categoryIdentifier.length > 0) { apsNotification[@"category"] = notification.request.content.categoryIdentifier; } if (apsNotification.count > 0) { payload[@"notification"] = apsNotification; } return payload; } - (void)sendEvent:(NSDictionary *)event { if (self.notificationCallbackId.length == 0 || event == nil) { return; } CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:event]; [result setKeepCallbackAsBool:YES]; [self.commandDelegate sendPluginResult:result callbackId:self.notificationCallbackId]; } - (NSDictionary *)firstDictionaryArgument:(CDVInvokedUrlCommand *)command { id first = [command.arguments firstObject]; if ([first isKindOfClass:[NSDictionary class]]) { return first; } return @{}; } - (NSString *)stringFromDictionary:(NSDictionary *)dictionary key:(NSString *)key { id value = dictionary[key]; if ([value isKindOfClass:[NSString class]]) { return value; } if ([value respondsToSelector:@selector(stringValue)]) { return [value stringValue]; } return @""; } - (NSString *)safeString:(id)value { if ([value isKindOfClass:[NSString class]]) { return value; } if ([value respondsToSelector:@selector(stringValue)]) { return [value stringValue]; } if ([value respondsToSelector:@selector(description)]) { return [value description]; } return @""; } - (BOOL)boolFromDictionary:(NSDictionary *)dictionary key:(NSString *)key defaultValue:(BOOL)defaultValue { id value = dictionary[key]; if ([value isKindOfClass:[NSNumber class]]) { return [value boolValue]; } if ([value isKindOfClass:[NSString class]]) { NSString *lowercase = [value lowercaseString]; if ([lowercase isEqualToString:@"true"] || [lowercase isEqualToString:@"1"] || [lowercase isEqualToString:@"yes"]) { return YES; } if ([lowercase isEqualToString:@"false"] || [lowercase isEqualToString:@"0"] || [lowercase isEqualToString:@"no"]) { return NO; } } return defaultValue; } - (NSString *)infoStringForKey:(NSString *)key { id value = [[NSBundle mainBundle] objectForInfoDictionaryKey:key]; if ([value isKindOfClass:[NSString class]]) { return value; } return @""; } - (NSString *)getOrCreateDeviceKey { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSString *existing = [defaults stringForKey:BluememeDeviceKeyDefaultsKey]; if (existing.length > 0) { return existing; } NSString *generated = [[NSUUID UUID] UUIDString]; [defaults setObject:generated forKey:BluememeDeviceKeyDefaultsKey]; [defaults synchronize]; return generated; } - (void)sendSuccessForCommand:(CDVInvokedUrlCommand *)command dictionary:(NSDictionary *)dictionary { CDVPluginResult *result = dictionary == nil ? [CDVPluginResult resultWithStatus:CDVCommandStatus_OK] : [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dictionary]; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } - (void)sendErrorForCommand:(CDVInvokedUrlCommand *)command code:(NSString *)code message:(NSString *)message { [self sendErrorForCommand:command code:code message:message details:nil]; } - (void)sendErrorForCommand:(CDVInvokedUrlCommand *)command code:(NSString *)code message:(NSString *)message details:(NSDictionary *)details { NSMutableDictionary *error = details == nil ? [NSMutableDictionary dictionary] : [details mutableCopy]; error[@"code"] = code ? code : @"ERROR"; error[@"message"] = message ? message : @""; CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:error]; [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; } @end