iOS: имеет ли приложение по умолчанию какие-либо ограничения при выполнении сетевых запросов, когда приложение было убито (смахнуто)? - PullRequest
0 голосов
/ 17 июня 2020

Прежде всего хочу заметить, что я не опытный iOS разработчик, пишу приложение на React Native.

Суть проблемы:

Если мое приложение был удален, и прибыло уведомление pu sh от FCM, я хотел бы отправить сетевой запрос в ответ на действие пользователя в уведомлении, но это не удалось. Код даже не отправляет запрос, потому что журналов на стороне сервера не существует. А пока все работает нормально, если приложение было свернуто или находится в состоянии переднего плана.

Подробности:

Я тестирую текущую функциональность на iOS 10.3.3 .

Вот код:

AppDelegate.m:

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#import "AppDelegate.h"

#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTLinkingManager.h>
@import Firebase;
#import "FirebaseMessagingModule.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  ...

  [FIRApp configure];
  FirebaseMessagingModule* firebaseMessagingModule = [bridge moduleForClass:FirebaseMessagingModule.class];
  [FIRMessaging messaging].delegate = firebaseMessagingModule;

  /*
   Notifications require some setup
   */
  if ([UNUserNotificationCenter class] != nil) {
    // iOS 10 or later
    // For iOS 10 display notification (sent via APNS)
    [UNUserNotificationCenter currentNotificationCenter].delegate = firebaseMessagingModule;
    UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert |
        UNAuthorizationOptionSound | UNAuthorizationOptionBadge;
    [[UNUserNotificationCenter currentNotificationCenter]
        requestAuthorizationWithOptions:authOptions
        completionHandler:^(BOOL granted, NSError * _Nullable error) {
          // ...
        }];
  } else {
    // iOS 10 notifications aren't available; fall back to iOS 8-9 notifications.
    UIUserNotificationType allNotificationTypes =
    (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge);
    UIUserNotificationSettings *settings =
    [UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil];
    [application registerUserNotificationSettings:settings];
  }
  [application registerForRemoteNotifications];
  [firebaseMessagingModule register2FACategory];

  return YES;
}

...

@end

FirebaseMessagingModule.h:

//
//  FirebaseMessagingModule.h
//  bastionpassmobile
//
//  Created by Anotn on 25/05/2020.
//  Copyright © 2020 Facebook. All rights reserved.
//

#ifndef FirebaseMessagingModule_h
#define FirebaseMessagingModule_h


#endif /* FirebaseMessagingModule_h */

#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@import Firebase;

@interface FirebaseMessagingModule : NSObject <RCTBridgeModule, FIRMessagingDelegate, UNUserNotificationCenterDelegate>

@property NSString* token;

- (void)register2FACategory;

@end

FirebaseMessagingModule.m:

//
//  FirebaseMessagingModule.m
//  bastionpassmobile
//
//  Created by Anotn on 25/05/2020.
//  Copyright © 2020 Facebook. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "FirebaseMessagingModule.h"
@import Firebase;
#import "constants.h"
#import "MF_Base32Additions.h"
#import <CommonCrypto/CommonCrypto.h>

@implementation FirebaseMessagingModule

RCT_EXPORT_MODULE()

/*
 Registers 2FA category for remote notifications.
 */
- (void)register2FACategory
{
  UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];

  UNNotificationAction* twofaDeclineAction = [UNNotificationAction actionWithIdentifier:TWOFA_NOTIFICATION_ACTION_DECLINE_ID title:TWOFA_NOTIFICATION_ACTION_DECLINE_LABEL options:UNNotificationActionOptionAuthenticationRequired];
  UNNotificationAction* twofaConfirmAction = [UNNotificationAction actionWithIdentifier:TWOFA_NOTIFICATION_ACTION_CONFIRM_ID title:TWOFA_NOTIFICATION_ACTION_CONFIRM_LABEL options:UNNotificationActionOptionAuthenticationRequired];
  UNNotificationCategory* twofaCategory = [UNNotificationCategory categoryWithIdentifier:TWOFA_NOTIFICATION_CATEGORY_ID actions:@[twofaDeclineAction, twofaConfirmAction] intentIdentifiers:@[] options:UNNotificationCategoryOptionNone];
  NSSet* categories = [NSSet setWithObject:twofaCategory];

  [center setNotificationCategories:categories];
}

- (void)messaging:(FIRMessaging *)messaging
    didReceiveRegistrationToken:(NSString *)fcmToken
    NS_SWIFT_NAME(messaging(_:didReceiveRegistrationToken:))
{
  [self setToken:fcmToken];
}

- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler __API_AVAILABLE(macos(10.14), ios(10.0), watchos(3.0), tvos(10.0))
{
  completionHandler(UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionSound);
}

- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler __API_AVAILABLE(macos(10.14), ios(10.0), watchos(3.0)) __API_UNAVAILABLE(tvos)
{
  BOOL is2FAConfirm = [response.actionIdentifier isEqualToString:TWOFA_NOTIFICATION_ACTION_CONFIRM_ID];
  BOOL is2FADecline = [response.actionIdentifier isEqualToString:TWOFA_NOTIFICATION_ACTION_DECLINE_ID];

  if (is2FAConfirm || is2FADecline) {
    // Retrieve notification data
    NSDictionary* userInfo = response.notification.request.content.userInfo;
    NSString* sessionId = userInfo[TWOFA_NOTIFICATION_DATA_SESSIONID_KEY];
    NSString* username = userInfo[TWOFA_NOTIFICATION_DATA_USERNAME_KEY];
    NSString* stage = userInfo[TWOFA_NOTIFICATION_DATA_STAGE_KEY];

    // Retrieve storage data
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    NSString* authTokensString = [userDefaults valueForKey:STORAGE_KEY_AUTH_TOKENS];
    NSData* authTokensData = [authTokensString dataUsingEncoding:NSUTF8StringEncoding];
    NSInputStream* authTokensStream = [NSInputStream inputStreamWithData:authTokensData];
    [authTokensStream open];
    NSData* authTokens = [NSJSONSerialization JSONObjectWithStream:authTokensStream options:0 error:nil];
    [authTokensStream close];
    NSDictionary<NSString*, NSString*>* clientIds = [authTokens valueForKey:STORAGE_KEY_CLIENT_IDS];
    NSString* clientId = [clientIds valueForKey:username];

    NSURLSessionConfiguration* urlSessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"twoFAConfirmation"];
    urlSessionConfiguration.networkServiceType = NSURLNetworkServiceTypeBackground;
    urlSessionConfiguration.discretionary = NO;
    urlSessionConfiguration.HTTPAdditionalHeaders = @{
      @"Content-Type": @"application/json",
      @"Accept": @"application/json",
      @"X-Client-Type": @"mobile"
    };
    NSURLSession* urlSession = [NSURLSession sessionWithConfiguration:urlSessionConfiguration];
    NSURL* url = [NSURL URLWithString:[API_URL stringByAppendingString:is2FAConfirm ? API_ENDPOINT_2FA_APPROVE : API_ENDPOINT_2FA_REJECT]];
    NSMutableURLRequest* urlRequest = [NSMutableURLRequest requestWithURL:url];
    urlRequest.HTTPMethod = @"POST";
    urlRequest.allHTTPHeaderFields = @{
      @"Content-Type": @"application/json",
      @"Accept": @"application/json",
      @"X-Client-Type": @"mobile"
    };

    if (is2FADecline) {
      urlRequest.HTTPBody = [NSJSONSerialization dataWithJSONObject:@{
        TWOFA_CONFIRMATION_REQUEST_KEY_SESSIONID: sessionId,
        TWOFA_CONFIRMATION_REQUEST_KEY_AUTH_CLIENTID: clientId
      } options:0 error:nil];
      NSURLSessionUploadTask* rejectConfirmationTask = [urlSession uploadTaskWithStreamedRequest:urlRequest];
      [rejectConfirmationTask resume];
    } else if (is2FAConfirm) {
      NSString* hotpSecretsString = [userDefaults valueForKey:STORAGE_KEY_HOTP_SECRETS];
      NSData* hotpSecretsData = [hotpSecretsString dataUsingEncoding:NSUTF8StringEncoding];
      NSInputStream* hotpSecretsStream = [NSInputStream inputStreamWithData:hotpSecretsData];
      [hotpSecretsStream open];
      NSData* hotpSecrets = [NSJSONSerialization JSONObjectWithStream:hotpSecretsStream options:0 error:nil];
      [hotpSecretsStream close];
      NSString* hotpSecret = [hotpSecrets valueForKey:username];
      NSString* totpCode = [self getTotpCode:hotpSecret];

      urlRequest.HTTPBody = [NSJSONSerialization dataWithJSONObject:@{
        TWOFA_CONFIRMATION_REQUEST_KEY_SESSIONID: sessionId,
        TWOFA_CONFIRMATION_REQUEST_KEY_AUTH_CLIENTID: clientId,
        TWOFA_CONFIRMATION_REQUEST_KEY_TOKEN: totpCode
      } options:0 error:nil];
      NSURLSessionUploadTask* approveConfirmationTask = [urlSession uploadTaskWithStreamedRequest:urlRequest];
      [approveConfirmationTask resume];
    }
  } else {
     NSLog(@"Undefined type of action");
  }
  completionHandler();
}

/*
 Returns totp code for provided hotp secret.
 */
- (NSString*)getTotpCode:(NSString*)hotpSecret
{
  NSString* hotpSecretDecoded = [NSString stringFromBase32String:hotpSecret];
  const char *cKey = [hotpSecretDecoded cStringUsingEncoding:NSUTF8StringEncoding];
  const uint64_t cData = CFSwapInt64([[NSDate date] timeIntervalSince1970] * 1000.0 / 30000);
  unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH];
  CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), &cData, sizeof(cData), cHMAC);
  NSData* hash = [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];
  int offset = *(int*)[[hash subdataWithRange:NSMakeRange([hash length] - 1, 1)] bytes] & 0x0f;
  const long* dbc1 = [[hash subdataWithRange:NSMakeRange(offset, (offset + 4) - offset)] bytes];
  long dbc2 = -(~CFSwapInt32(*dbc1) + 1) & 0x7fffffff;
  long totp = dbc2 % (long)pow(10, TWOFA_OTP_LENGTH);
  NSString* totpCode = @"";
  NSString* totpCodeSource = [[NSNumber numberWithLong:totp] stringValue];
  while (([totpCode length] + [totpCodeSource length]) < TWOFA_OTP_LENGTH) {
    totpCode = [totpCode stringByAppendingString:@"0"];
  }
  return [totpCode stringByAppendingString:totpCodeSource];
}

/*
 Resolves with FCM registration token or rejected if token was not provided.
 */
RCT_REMAP_METHOD(getToken, getTokenResolver:(RCTPromiseResolveBlock)resolve getTokenRejecter:(RCTPromiseRejectBlock)reject)
{
  if (self.token) {
    resolve(self.token);
  } else {
    reject(@"0", @"Failed on get token", [[NSError alloc] init]);
  }
}

/*
 Resolves with FCM registration token or rejected if attempt to retrieve token will be failed.
 */
RCT_REMAP_METHOD(retrieveToken, retrieveTokenResolver:(RCTPromiseResolveBlock)resolve retrieveTokenRejecter:(RCTPromiseRejectBlock)reject)
{
  [[FIRInstanceID instanceID] instanceIDWithHandler:^(FIRInstanceIDResult * _Nullable result,
                                                      NSError * _Nullable error) {
    if (error != nil) {
      reject(@"0", @"Failed to retrieve token", [[NSError alloc] init]);
    } else {
      resolve(result.token);
    }
  }];
}

@end

Любая помощь будет принята с благодарностью!

[ОБНОВЛЕНИЕ]: тестировщик в нашей компании проверил текущую функциональность и на iOS 13,5 работает, даже если приложение было убито. Может, на это как-то влияют настройки устройства или ограничения старых SDK?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...