diff --git a/Agent/General/NewRelicAgentInternal.h b/Agent/General/NewRelicAgentInternal.h index c348f133..7b665d22 100644 --- a/Agent/General/NewRelicAgentInternal.h +++ b/Agent/General/NewRelicAgentInternal.h @@ -13,6 +13,8 @@ #import "NRMAUserActionFacade.h" #import "NRMAURLTransformer.h" +#import + // Keys used for harvester data request. #define NEW_RELIC_APP_VERSION_HEADER_KEY @"X-NewRelic-App-Version" #define NEW_RELIC_OS_NAME_HEADER_KEY @"X-NewRelic-OS-Name" diff --git a/Agent/General/NewRelicAgentInternal.m b/Agent/General/NewRelicAgentInternal.m index e5168e4d..fb14eedf 100644 --- a/Agent/General/NewRelicAgentInternal.m +++ b/Agent/General/NewRelicAgentInternal.m @@ -149,6 +149,18 @@ - (id) initWithCollectorAddress:(NSString*)collectorHost self = [super init]; if (self) { + + + // TODO: Check if this is the right place for this code? + if (@available(iOS 13.0, *)) { + [[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:@"com.newrelic.NRApp.bitcode" usingQueue:nil launchHandler:^(__kindof BGTask * _Nonnull task) { + [self handleAppRefreshTask: task]; + }]; + } else { + // Fallback on earlier versions + } + + self.appWillTerminate = NO; [NRMACPUVitals setAppStartCPUTime]; if ([UIApplication sharedApplication].applicationState != UIApplicationStateBackground) { @@ -186,6 +198,9 @@ - (id) initWithCollectorAddress:(NSString*)collectorHost selector:@selector(applicationWillTerminate) name:UIApplicationWillTerminateNotification object:[UIApplication sharedApplication]]; + + // TODO: Is this the right place to put this? + [[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum]; NRLOG_INFO(@"Agent enabled"); @@ -644,7 +659,7 @@ - (void) applicationDidEnterBackground { if ([[UIDevice currentDevice] respondsToSelector:@selector(isMultitaskingSupported)] && [[UIDevice currentDevice] isMultitaskingSupported]) { - + // Current One time background task is dispatched upon going to the background. UIApplication* application = [UIApplication sharedApplication]; // Mark the start of our background task. @@ -692,6 +707,8 @@ - (void) applicationDidEnterBackground { if (self.appWillTerminate) { return; } + + // Currently this is where the actual harvest occurs when we go to background NRLOG_VERBOSE(@"Harvesting data in background"); [[[NRMAHarvestController harvestController] harvester] execute]; #ifndef DISABLE_NRMA_EXCEPTION_WRAPPER @@ -700,7 +717,13 @@ - (void) applicationDidEnterBackground { class:NSStringFromClass([NRMAHarvester class]) selector:@"execute"]; } @finally { - [self agentShutdown]; + + + // What would happen if we didn't call agentShutdown? + + NRLOG_VERBOSE(@"used to agentShutdown."); + + // [self agentShutdown]; } #endif @@ -709,6 +732,11 @@ - (void) applicationDidEnterBackground { [application endBackgroundTask:background_task]; // Invalidate the background_task. background_task = UIBackgroundTaskInvalid; + + + + // Schedule the next background harvest. + [self scheduleHeartbeatTask]; } }); } else { @@ -742,6 +770,59 @@ - (void) agentShutdown { [NRMALastActivityTraceController clearLastActivityStamp]; } +// We only support background featch in iOS 13+ +- (void) scheduleHeartbeatTask { + if (@available(iOS 13.0, *)) { + // TODO: Pass instrumented app bundle id + BGAppRefreshTaskRequest *request = [[BGAppRefreshTaskRequest alloc] initWithIdentifier:@"com.newrelic.NRApp.bitcode"]; + request.earliestBeginDate = [NSDate dateWithTimeIntervalSinceNow:5 * 60]; + + NSError *error = nil; + + [[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&error]; + + if (error) { + NRLOG_ERROR(@"Error schedule heartbeat: %@", error); + } + else { + NRLOG_VERBOSE(@"Scheduled heartbeat"); + + } + } else { + // Fallback on earlier versions + } + +} + +- (void) handleAppRefreshTask:(BGAppRefreshTask *)task { + NRLOG_VERBOSE(@"handleAppRefreshTask BGAppRefreshTask"); + if (@available(iOS 13.0, *)) { + + [task setExpirationHandler:^{ + __weak BGTask *weakTask = task; + if (weakTask) { + [weakTask setTaskCompletedWithSuccess:false]; + } + //TODO: Invalidate and cancel the harvest request + // PokeManager.urlSession.invalidateAndCancel() + + }]; + + [[[NRMAHarvestController harvestController] harvester] execute]; + + // TODO: Make sure this is the right place to call this. + [task setTaskCompletedWithSuccess:true]; + + // We always reschedule the heartbeat task. + [self scheduleHeartbeatTask]; + } + else { + NRLOG_VERBOSE(@"No background tasks pre iOS 13"); + + } + +} + + (BOOL) harvestNow { return [NRMAHarvestController harvestNow]; } diff --git a/Agent/HandledException/NRMAHexBackgroundUploader.h b/Agent/HandledException/NRMAHexBackgroundUploader.h index daffd53a..a0838fcd 100644 --- a/Agent/HandledException/NRMAHexBackgroundUploader.h +++ b/Agent/HandledException/NRMAHexBackgroundUploader.h @@ -6,10 +6,10 @@ #import #import "NRMAConnection.h" -@interface NRMAHexBackgroundUploader : NRMAConnection -@property(strong) NSString* hexHost; - -- (instancetype) initWithHexHost:(NSString*)hexHost; - -- (void) sendData:(NSData*)data; -@end +//@interface NRMAHexBackgroundUploader : NRMAConnection +//@property(strong) NSString* hexHost; +// +//- (instancetype) initWithHexHost:(NSString*)hexHost; +// +//- (void) sendData:(NSData*)data; +//@end diff --git a/Agent/HandledException/NRMAHexBackgroundUploader.mm b/Agent/HandledException/NRMAHexBackgroundUploader.mm index e63fb76e..0439e762 100644 --- a/Agent/HandledException/NRMAHexBackgroundUploader.mm +++ b/Agent/HandledException/NRMAHexBackgroundUploader.mm @@ -3,46 +3,46 @@ // Copyright © 2023 New Relic. All rights reserved. // -#import "NRMAHexBackgroundUploader.h" -#import "NRMASessionIdentifierManager.h" - -@interface NRMAHexBackgroundUploader () -@property(strong) NRMASessionIdentifierManager* sessionIdManager; -@property(strong) NSURLSession* session; -@end - -@implementation NRMAHexBackgroundUploader - -- (instancetype) initWithHexHost:(NSString*)hexHost { - self = [super init]; - if (self) { - self.sessionIdManager = [[[NRMASessionIdentifierManager alloc] init] autorelease]; - NSURLSessionConfiguration* backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[self.sessionIdManager sessionIdentifier]]; - self.session = [NSURLSession sessionWithConfiguration:backgroundConfiguration - delegate:self - delegateQueue:[NSOperationQueue currentQueue]]; - self.hexHost = hexHost; - } - return self; -} - -- (void) sendData:(NSData*)data { - - NSMutableURLRequest* request = [self newPostWithURI:self.hexHost]; - request.HTTPMethod = @"POST"; - request.HTTPBody = data; - [request setValue:@"application/octet-stream" forHTTPHeaderField:@"Content-Type"]; - NSURLSessionUploadTask* uploadTask = [self.session uploadTaskWithStreamedRequest:request]; - [uploadTask resume]; -} - -- (void) dealloc { - [self.session invalidateAndCancel]; - [self.session release]; - [self.hexHost release]; - [self.sessionIdManager release]; - - [super dealloc]; -} - -@end +//#import "NRMAHexBackgroundUploader.h" +//#import "NRMASessionIdentifierManager.h" +// +//@interface NRMAHexBackgroundUploader () +//@property(strong) NRMASessionIdentifierManager* sessionIdManager; +//@property(strong) NSURLSession* session; +//@end +// +//@implementation NRMAHexBackgroundUploader +// +//- (instancetype) initWithHexHost:(NSString*)hexHost { +// self = [super init]; +// if (self) { +// self.sessionIdManager = [[[NRMASessionIdentifierManager alloc] init] autorelease]; +// NSURLSessionConfiguration* backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[self.sessionIdManager sessionIdentifier]]; +// self.session = [NSURLSession sessionWithConfiguration:backgroundConfiguration +// delegate:self +// delegateQueue:[NSOperationQueue currentQueue]]; +// self.hexHost = hexHost; +// } +// return self; +//} +// +//- (void) sendData:(NSData*)data { +// +// NSMutableURLRequest* request = [self newPostWithURI:self.hexHost]; +// request.HTTPMethod = @"POST"; +// request.HTTPBody = data; +// [request setValue:@"application/octet-stream" forHTTPHeaderField:@"Content-Type"]; +// NSURLSessionUploadTask* uploadTask = [self.session uploadTaskWithStreamedRequest:request]; +// [uploadTask resume]; +//} +// +//- (void) dealloc { +// [self.session invalidateAndCancel]; +// [self.session release]; +// [self.hexHost release]; +// [self.sessionIdManager release]; +// +// [super dealloc]; +//} +// +//@end diff --git a/Agent/HandledException/NRMASessionIdentifierManager.h b/Agent/HandledException/NRMASessionIdentifierManager.h index 60cb1695..ffdf6f15 100644 --- a/Agent/HandledException/NRMASessionIdentifierManager.h +++ b/Agent/HandledException/NRMASessionIdentifierManager.h @@ -3,8 +3,8 @@ // Copyright © 2023 New Relic. All rights reserved. // -#import - -@interface NRMASessionIdentifierManager : NSObject -- (NSString*) sessionIdentifier; -@end +//#import +// +//@interface NRMASessionIdentifierManager : NSObject +//- (NSString*) sessionIdentifier; +//@end diff --git a/Agent/HandledException/NRMASessionIdentifierManager.mm b/Agent/HandledException/NRMASessionIdentifierManager.mm index 2abe90d8..74146ab3 100644 --- a/Agent/HandledException/NRMASessionIdentifierManager.mm +++ b/Agent/HandledException/NRMASessionIdentifierManager.mm @@ -3,41 +3,40 @@ // Copyright © 2023 New Relic. All rights reserved. // -#import "NRMASessionIdentifierManager.h" - -#define kNRMA_SESSION_ID_KEY @"NewRelicSessionIdentifier" - -@interface NRMASessionIdentifierManager () -@property(strong) NSString* identifier; -@end - -@implementation NRMASessionIdentifierManager - -- (NSString*) sessionIdentifier { - if (self.identifier) return self.identifier; - - self.identifier = [[NSUserDefaults standardUserDefaults] objectForKey:kNRMA_SESSION_ID_KEY]; - - if(self.identifier) return self.identifier; - - self.identifier = [[NSUUID UUID] UUIDString]; - - [self storeIdentifier:self.identifier]; - - return self.identifier; -} - -- (void) storeIdentifier:(NSString*)uuid { - [[NSUserDefaults standardUserDefaults] setObject:uuid - forKey:kNRMA_SESSION_ID_KEY]; - [[NSUserDefaults standardUserDefaults] synchronize]; -} - -// Function included for testing. -+ (void) purge { - [[NSUserDefaults standardUserDefaults] setObject:nil - forKey:kNRMA_SESSION_ID_KEY]; - [[NSUserDefaults standardUserDefaults] synchronize]; -} - -@end +//#import "NRMASessionIdentifierManager.h" +// +//#define kNRMA_SESSION_ID_KEY @"NewRelicSessionIdentifier" +// +//@interface NRMASessionIdentifierManager () +//@property(strong) NSString* identifier; +//@end +// +//@implementation NRMASessionIdentifierManager +// +//- (NSString*) sessionIdentifier { +// if (self.identifier) return self.identifier; +// +// self.identifier = [[NSUserDefaults standardUserDefaults] objectForKey:kNRMA_SESSION_ID_KEY]; +// +// if(self.identifier) return self.identifier; +// +// self.identifier = [[NSUUID UUID] UUIDString]; +// +// [self storeIdentifier:self.identifier]; +// +// return self.identifier; +//} +// +//- (void) storeIdentifier:(NSString*)uuid { +// [[NSUserDefaults standardUserDefaults] setObject:uuid +// forKey:kNRMA_SESSION_ID_KEY]; +// [[NSUserDefaults standardUserDefaults] synchronize]; +//} +// +//// Function included for testing. +//+ (void) purge { +// [[NSUserDefaults standardUserDefaults] setObject:nil +// forKey:kNRMA_SESSION_ID_KEY]; +// [[NSUserDefaults standardUserDefaults] synchronize]; +//} +//@end diff --git a/Test Harness/NRTestApp/NRTestApp/AppDelegate.swift b/Test Harness/NRTestApp/NRTestApp/AppDelegate.swift index 94abb8be..6f7e24ca 100644 --- a/Test Harness/NRTestApp/NRTestApp/AppDelegate.swift +++ b/Test Harness/NRTestApp/NRTestApp/AppDelegate.swift @@ -87,4 +87,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } + + + // Background fetch handling\ + + // TODO: Must add this and then we will capture in NewRelicAgentInternal + func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + + NewRelic.logVerbose("performFetchWithCompletionHandler called") + completionHandler(.newData) + } } diff --git a/Test Harness/NRTestApp/NRTestApp/Info.plist b/Test Harness/NRTestApp/NRTestApp/Info.plist index 0655762d..aa1f4014 100644 --- a/Test Harness/NRTestApp/NRTestApp/Info.plist +++ b/Test Harness/NRTestApp/NRTestApp/Info.plist @@ -2,6 +2,10 @@ + BGTaskSchedulerPermittedIdentifiers + + com.newrelic.NRApp.bitcode + ITSAppUsesNonExemptEncryption NSAppTransportSecurity @@ -26,5 +30,10 @@ + UIBackgroundModes + + fetch + processing + diff --git a/Tests/Unit-Tests/NewRelicAgentTests/Exception-Handler-Tests/NRMACrashReportTest.m b/Tests/Unit-Tests/NewRelicAgentTests/Exception-Handler-Tests/NRMACrashReportTest.m index 6bb55f0c..70a360fe 100644 --- a/Tests/Unit-Tests/NewRelicAgentTests/Exception-Handler-Tests/NRMACrashReportTest.m +++ b/Tests/Unit-Tests/NewRelicAgentTests/Exception-Handler-Tests/NRMACrashReportTest.m @@ -154,6 +154,8 @@ - (void) testCrashReport { XCTAssertNoThrow([report JSONObject]); } +// TODO: Debug occasional crash on CFUUIDCreateString(NULL, report.uuidRef) +// NRMACrashDataWriter -(void) testCrashDataWriter { NSError *error; diff --git a/Tests/Unit-Tests/NewRelicAgentTests/Handled-Exception-Tests/TestSessionIdentifierManger.m b/Tests/Unit-Tests/NewRelicAgentTests/Handled-Exception-Tests/TestSessionIdentifierManger.m index aa5986b3..f101fb33 100644 --- a/Tests/Unit-Tests/NewRelicAgentTests/Handled-Exception-Tests/TestSessionIdentifierManger.m +++ b/Tests/Unit-Tests/NewRelicAgentTests/Handled-Exception-Tests/TestSessionIdentifierManger.m @@ -1,54 +1,54 @@ +//// +//// TestSessionIdentifierManger.m +//// NewRelic +//// +//// Created by Bryce Buchanan on 7/24/17. +//// Copyright © 2023 New Relic. All rights reserved. +//// // -// TestSessionIdentifierManger.m -// NewRelic -// -// Created by Bryce Buchanan on 7/24/17. -// Copyright © 2023 New Relic. All rights reserved. -// - -#import -#import "NRMASessionIdentifierManager.h" - -@interface NRMASessionIdentifierManager () -+ (void) purge; -- (void) setIdentifier:(NSString*)identifier; -@end - -@interface TestSessionIdentifierManger : XCTestCase - -@end - -@implementation TestSessionIdentifierManger - -- (void)setUp { - [super setUp]; - [NRMASessionIdentifierManager purge]; -} - -- (void)tearDown { - [NRMASessionIdentifierManager purge]; - [super tearDown]; -} - -- (void)testPersistentIdentifier { - NRMASessionIdentifierManager* manager = [[NRMASessionIdentifierManager alloc] init]; - - NSString* identifier = [manager sessionIdentifier]; - - XCTAssertNotNil(identifier); - - NRMASessionIdentifierManager* manager2 = [[NRMASessionIdentifierManager alloc] init]; - - XCTAssertEqualObjects(identifier, [manager2 sessionIdentifier]); -} - - -- (void) testLocal { - NSString* value = @"MySessionIdentifier"; - NRMASessionIdentifierManager* manager = [[NRMASessionIdentifierManager alloc] init]; - [manager setIdentifier:value]; - XCTAssertTrue([manager.sessionIdentifier isEqualToString:value]); -} - - -@end +//#import +//#import "NRMASessionIdentifierManager.h" +// +//@interface NRMASessionIdentifierManager () +//+ (void) purge; +//- (void) setIdentifier:(NSString*)identifier; +//@end +// +//@interface TestSessionIdentifierManger : XCTestCase +// +//@end +// +//@implementation TestSessionIdentifierManger +// +//- (void)setUp { +// [super setUp]; +// [NRMASessionIdentifierManager purge]; +//} +// +//- (void)tearDown { +// [NRMASessionIdentifierManager purge]; +// [super tearDown]; +//} +// +//- (void)testPersistentIdentifier { +// NRMASessionIdentifierManager* manager = [[NRMASessionIdentifierManager alloc] init]; +// +// NSString* identifier = [manager sessionIdentifier]; +// +// XCTAssertNotNil(identifier); +// +// NRMASessionIdentifierManager* manager2 = [[NRMASessionIdentifierManager alloc] init]; +// +// XCTAssertEqualObjects(identifier, [manager2 sessionIdentifier]); +//} +// +// +//- (void) testLocal { +// NSString* value = @"MySessionIdentifier"; +// NRMASessionIdentifierManager* manager = [[NRMASessionIdentifierManager alloc] init]; +// [manager setIdentifier:value]; +// XCTAssertTrue([manager.sessionIdentifier isEqualToString:value]); +//} +// +// +//@end