diff --git a/ios/Classes/NlRebelicHockeyappModule.h b/ios/Classes/NlRebelicHockeyappModule.h index ad6c7e7..900d700 100644 --- a/ios/Classes/NlRebelicHockeyappModule.h +++ b/ios/Classes/NlRebelicHockeyappModule.h @@ -5,9 +5,12 @@ * and licensed under the Apache Public License (version 2) */ #import "TiModule.h" +#import + + +@interface NlRebelicHockeyappModule : TiModule + +- (NSString *)applicationLogForCrashManager:(BITCrashManager *)crashManager; -@interface NlRebelicHockeyappModule : TiModule -{ -} @end diff --git a/ios/Classes/NlRebelicHockeyappModule.m b/ios/Classes/NlRebelicHockeyappModule.m index 4023df8..369d75b 100644 --- a/ios/Classes/NlRebelicHockeyappModule.m +++ b/ios/Classes/NlRebelicHockeyappModule.m @@ -8,7 +8,9 @@ #import "TiBase.h" #import "TiHost.h" #import "TiUtils.h" -#import + +extern NSString * const TI_APPLICATION_ID; +static NSString * appCrashInfoKey; @implementation NlRebelicHockeyappModule @@ -35,6 +37,8 @@ -(void)startup [super startup]; NSLog(@"[INFO] %@ loaded",self); + + appCrashInfoKey = [NSString stringWithFormat:@"%@.%@", TI_APPLICATION_ID, @"crash_info"]; } -(void)shutdown:(id)sender @@ -92,8 +96,10 @@ -(void)start:(id)appId ENSURE_SINGLE_ARG(appId, NSString); [[BITHockeyManager sharedHockeyManager] configureWithIdentifier:appId]; + [[BITHockeyManager sharedHockeyManager].crashManager setDelegate:self]; [[BITHockeyManager sharedHockeyManager] startManager]; [[BITHockeyManager sharedHockeyManager].authenticator authenticateInstallation]; + } - (void)showFeedbackListView:(id)args @@ -106,4 +112,19 @@ - (void)showFeedbackComposeView:(id)args [[BITHockeyManager sharedHockeyManager].feedbackManager showFeedbackComposeView]; } + +- (NSString *)applicationLogForCrashManager:(BITCrashManager *)crashManager +{ + + NSString *appLog = [[NSUserDefaults standardUserDefaults] valueForKey:appCrashInfoKey]; + if (appLog == nil) { + return nil; + } + NSLog(@"appLog: %@", appLog); + [[NSUserDefaults standardUserDefaults] removeObjectForKey:appCrashInfoKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + return appLog; +} + @end diff --git a/ios/README.md b/ios/README.md index 4c6f3ca..94e8ce9 100644 --- a/ios/README.md +++ b/ios/README.md @@ -35,3 +35,21 @@ To show the modal feedback list view on iOS: ```javascript hockeyapp.showFeedbackListView(); ``` + + +## Getting UIApplicationMain exceptions + +* Copy the Titanium build plugin named `hockeyapp` from the `plugin` directory to your Ti project directory under the +`plugins` folder. +* Add the following to `tiapp.xml` + +```xml + + hockeyapp + +``` + +This will modify the `main.m` file generated by Titanium in order to catch NSExceptions involving UIApplicationMain. +The exception description is persisted in NSUserDefaults and used by the hockeyapp module for sending additional info +on the actual cause of the crash. + diff --git a/ios/manifest b/ios/manifest index 5c4d38d..84a71b6 100644 --- a/ios/manifest +++ b/ios/manifest @@ -2,7 +2,7 @@ # this is your module manifest and used by Titanium # during compilation, packaging, distribution, etc. # -version: 0.2 +version: 0.3 apiversion: 2 description: HockeyApp module author: Timan Rebel (Rebelcorp) diff --git a/ios/plugin/hockeyapp/1.0/hooks/.tern-port b/ios/plugin/hockeyapp/1.0/hooks/.tern-port new file mode 100644 index 0000000..ea06f7d --- /dev/null +++ b/ios/plugin/hockeyapp/1.0/hooks/.tern-port @@ -0,0 +1 @@ +54911 \ No newline at end of file diff --git a/ios/plugin/hockeyapp/1.0/hooks/plugin.js b/ios/plugin/hockeyapp/1.0/hooks/plugin.js new file mode 100644 index 0000000..ecc3e6b --- /dev/null +++ b/ios/plugin/hockeyapp/1.0/hooks/plugin.js @@ -0,0 +1,62 @@ +exports.cliVersion = '>=3.X'; + +var fs = require('fs'); +var path = require('path'); + + +var UIApplicationMainStatement = ' int retVal = UIApplicationMain(argc, argv, nil, @"TiApp");'; + +var replacementRows = [ + ' int retVal = 0;', + ' @try {\n', + ' retVal = UIApplicationMain(argc, argv, nil, @"TiApp");', + ' }', + ' @catch (NSException *e) {', + ' //we save the exception description to NSUserDefaults, this value will be picked up at next app start by hockeyapp', + ' [[NSUserDefaults standardUserDefaults] setObject:e.description forKey:[NSString stringWithFormat:@"%@.%@", TI_APPLICATION_ID, @"crash_info"]];', + ' [[NSUserDefaults standardUserDefaults] synchronize];', + ' //we need to crash here anyway for hockeyapp to notice', + ' @throw e;', + ' }'].join("\n"); + + + +exports.init = function(logger, config, cli, appc) { + if (cli.argv.platform != 'android') { + + cli.addHook('build.pre.compile', function(build, finished) { + logger.info('Replacing the populateIosFiles() function in the iOS Build plugin'); + var populateIosFiles = build.populateIosFiles; + if (populateIosFiles) { + build.populateIosFiles = function() { + + var result = populateIosFiles.call(build); + + var mainFile = path.join(this.buildDir, 'main.m'); + var mainContents = fs.readFileSync(mainFile); + if (!mainContents) { + logger.error('Cannot find main.m in build dir'); + process.exit(1); + } else { + var mainContentsStr = mainContents.toString(); + var uiAppMainInvocationMatch = mainContentsStr.match(/UIApplicationMain\(argc,\sargv,\snil,\s\@\"TiApp\"\)/g); + if (!uiAppMainInvocationMatch) { + logger.error('Cannot find UIApplicationMain invocation'); + process.exit(1); + } + //we inject the AppConnectUIApplication UIApplication subclass + logger.info('Modifying UIApplicationMain invocation in the generated main.m'); + mainContentsStr = mainContentsStr.replace(UIApplicationMainStatement, replacementRows); + fs.writeFileSync(mainFile, mainContentsStr); + } + + return result; + }; + } else { + logger.error('Can\'t inject hook into ios build'); + process.exit(1); + } + finished(); + }); + } +}; diff --git a/ios/titanium.xcconfig b/ios/titanium.xcconfig index 906e5d7..75242e9 100644 --- a/ios/titanium.xcconfig +++ b/ios/titanium.xcconfig @@ -4,7 +4,7 @@ // OF YOUR TITANIUM SDK YOU'RE BUILDING FOR // // -TITANIUM_SDK_VERSION = 3.2.2.GA +TITANIUM_SDK_VERSION = 3.2.3.GA //