Replace Crashlytics/Fabric with Countly & Sentry.
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#import "MPAppDelegate_InApp.h"
|
||||
#import <Countly/Countly.h>
|
||||
|
||||
@interface MPAppDelegate_Shared(InApp_Private)<SKProductsRequestDelegate, SKPaymentTransactionObserver>
|
||||
@end
|
||||
@@ -204,13 +205,16 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
|
||||
[queue finishTransaction:transaction];
|
||||
|
||||
if ([[MPConfig get].sendInfo boolValue]) {
|
||||
#ifdef CRASHLYTICS
|
||||
SKProduct *product = self.products[transaction.payment.productIdentifier];
|
||||
for (int q = 0; q < transaction.payment.quantity; ++q)
|
||||
[Answers logPurchaseWithPrice:product.price currency:[product.priceLocale objectForKey:NSLocaleCurrencyCode]
|
||||
success:@YES itemName:product.localizedTitle itemType:@"InApp"
|
||||
itemId:product.productIdentifier customAttributes:attributes];
|
||||
#endif
|
||||
[attributes addEntriesFromDictionary:@{
|
||||
@"id": product.productIdentifier,
|
||||
@"name": product.localizedTitle,
|
||||
@"price": product.price.description,
|
||||
@"currency": [product.priceLocale objectForKey:NSLocaleCurrencyCode],
|
||||
@"state" : @"success",
|
||||
@"quantity": @(transaction.payment.quantity).description,
|
||||
}];
|
||||
[Countly.sharedInstance recordEvent:@"purchase" segmentation:attributes];
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -229,16 +233,16 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
|
||||
[queue finishTransaction:transaction];
|
||||
|
||||
if ([[MPConfig get].sendInfo boolValue]) {
|
||||
#ifdef CRASHLYTICS
|
||||
SKProduct *product = self.products[transaction.payment.productIdentifier];
|
||||
[Answers logPurchaseWithPrice:product.price currency:[product.priceLocale objectForKey:NSLocaleCurrencyCode]
|
||||
success:@NO itemName:product.localizedTitle itemType:@"InApp" itemId:product.productIdentifier
|
||||
customAttributes:@{
|
||||
@"state" : @"Failed",
|
||||
@"quantity": @(transaction.payment.quantity),
|
||||
@"reason" : [transaction.error localizedFailureReason]?: [transaction.error localizedDescription],
|
||||
}];
|
||||
#endif
|
||||
[Countly.sharedInstance recordEvent:@"purchase" segmentation:@{
|
||||
@"id": product.productIdentifier,
|
||||
@"name": product.localizedTitle,
|
||||
@"price": product.price.description,
|
||||
@"currency": [product.priceLocale objectForKey:NSLocaleCurrencyCode],
|
||||
@"state" : @"failed",
|
||||
@"quantity": @(transaction.payment.quantity).description,
|
||||
@"reason" : [transaction.error localizedFailureReason]?: [transaction.error localizedDescription],
|
||||
}];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@@ -16,7 +16,8 @@
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#import <Crashlytics/Answers.h>
|
||||
#import <Countly/Countly.h>
|
||||
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
|
||||
@@ -122,6 +123,10 @@
|
||||
if (self.key)
|
||||
self.key = nil;
|
||||
|
||||
if ([[MPConfig get].sendInfo boolValue]) {
|
||||
[Countly.sharedInstance userLoggedOut];
|
||||
}
|
||||
|
||||
self.activeUser = nil;
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPSignedOutNotification object:self userInfo:@{ @"animated": @(animated) }];
|
||||
}
|
||||
@@ -180,11 +185,11 @@
|
||||
dbg( @"Automatic login failed for user: %@", user.userID );
|
||||
|
||||
if ([[MPConfig get].sendInfo boolValue]) {
|
||||
#ifdef CRASHLYTICS
|
||||
[Answers logLoginWithMethod:password? @"Password": @"Automatic" success:@NO customAttributes:@{
|
||||
@"algorithm": @(user.algorithm.version),
|
||||
[Countly.sharedInstance recordEvent:@"login" segmentation:@{
|
||||
@"method" : password? @"Password": @"Automatic",
|
||||
@"state" : @"failed",
|
||||
@"algorithm": @(user.algorithm.version).description,
|
||||
}];
|
||||
#endif
|
||||
}
|
||||
|
||||
return NO;
|
||||
@@ -207,13 +212,13 @@
|
||||
|
||||
@try {
|
||||
if ([[MPConfig get].sendInfo boolValue]) {
|
||||
#ifdef CRASHLYTICS
|
||||
[[Crashlytics sharedInstance] setUserName:user.userID];
|
||||
[Countly.sharedInstance userLoggedIn:user.userID];
|
||||
|
||||
[Answers logLoginWithMethod:password? @"Password": @"Automatic" success:@YES customAttributes:@{
|
||||
@"algorithm": @(user.algorithm.version),
|
||||
[Countly.sharedInstance recordEvent:@"login" segmentation:@{
|
||||
@"method" : password? @"Password": @"Automatic",
|
||||
@"state" : @"success",
|
||||
@"algorithm": @(user.algorithm.version).description,
|
||||
}];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@catch (id exception) {
|
||||
|
@@ -216,7 +216,7 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
|
||||
self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
|
||||
self.mainManagedObjectContext.parentContext = self.privateManagedObjectContext;
|
||||
if (@available(iOS 10.0, macOS 10.12, *))
|
||||
if (@available( iOS 10.0, macOS 10.12, * ))
|
||||
self.mainManagedObjectContext.automaticallyMergesChangesFromParent = YES;
|
||||
else
|
||||
// When privateManagedObjectContext is saved, import the changes into mainManagedObjectContext.
|
||||
@@ -565,38 +565,51 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
saveInContext:(NSManagedObjectContext *)context {
|
||||
|
||||
// Read metadata for the import file.
|
||||
MPMarshalledInfo *info = mpw_marshal_read( importData.UTF8String );
|
||||
if (info->format == MPMarshalFormatNone)
|
||||
MPMarshalledFile *file = mpw_marshal_read( NULL, importData.UTF8String );
|
||||
if (!file)
|
||||
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
|
||||
@"type" : @(MPMarshalErrorInternal),
|
||||
NSLocalizedDescriptionKey: @"Could not process Master Password import data.",
|
||||
}]), @"While importing sites." );
|
||||
if (file->error.type != MPMarshalSuccess) {
|
||||
MPMarshalErrorType type = file->error.type;
|
||||
mpw_marshal_file_free( &file );
|
||||
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
|
||||
@"type" : @(type),
|
||||
NSLocalizedDescriptionKey: @"Could not parse Master Password import data.",
|
||||
}]), @"While importing sites." );
|
||||
}
|
||||
if (file->info->format == MPMarshalFormatNone) {
|
||||
mpw_marshal_file_free( &file );
|
||||
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
|
||||
@"type" : @(MPMarshalErrorFormat),
|
||||
NSLocalizedDescriptionKey: @"This is not a Master Password import file.",
|
||||
}]), @"While importing sites." );
|
||||
}
|
||||
|
||||
// Get master password for import file.
|
||||
MPKey *importKey;
|
||||
NSString *importMasterPassword;
|
||||
do {
|
||||
importMasterPassword = askImportPassword( @(info->fullName) );
|
||||
importMasterPassword = askImportPassword( @(file->info->fullName) );
|
||||
if (!importMasterPassword) {
|
||||
inf( @"Import cancelled." );
|
||||
mpw_marshal_info_free( &info );
|
||||
mpw_marshal_file_free( &file );
|
||||
return MPError( ([NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]), @"" );
|
||||
}
|
||||
|
||||
importKey = [[MPKey alloc] initForFullName:@(info->fullName) withMasterPassword:importMasterPassword];
|
||||
} while ([[[importKey keyIDForAlgorithm:MPAlgorithmForVersion( info->algorithm )] encodeHex]
|
||||
caseInsensitiveCompare:@(info->keyID)] != NSOrderedSame);
|
||||
importKey = [[MPKey alloc] initForFullName:@(file->info->fullName) withMasterPassword:importMasterPassword];
|
||||
} while ([[[importKey keyIDForAlgorithm:MPAlgorithmForVersion( file->info->algorithm )] encodeHex]
|
||||
caseInsensitiveCompare:@(file->info->keyID)] != NSOrderedSame);
|
||||
|
||||
// Parse import data.
|
||||
MPMarshalError importError = { .type = MPMarshalSuccess };
|
||||
MPMarshalledUser *importUser = mpw_marshal_auth( importData.UTF8String, info->format, importMasterPassword.UTF8String, &importError );
|
||||
mpw_marshal_info_free( &info );
|
||||
MPMarshalledUser *importUser = mpw_marshal_auth( file, mpw_masterKeyProvider_str( importMasterPassword.UTF8String ) );
|
||||
|
||||
@try {
|
||||
if (!importUser || importError.type != MPMarshalSuccess)
|
||||
if (!importUser || file->error.type != MPMarshalSuccess)
|
||||
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
|
||||
@"type" : @(importError.type),
|
||||
NSLocalizedDescriptionKey: @(importError.description),
|
||||
@"type" : @(file->error.type),
|
||||
NSLocalizedDescriptionKey: @(file->error.message),
|
||||
}]), @"While importing sites." );
|
||||
|
||||
// Find an existing user to update.
|
||||
@@ -654,9 +667,10 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
else {
|
||||
// Create new site.
|
||||
id<MPAlgorithm> algorithm = MPAlgorithmForVersion( importSite->algorithm );
|
||||
Class entityType = [algorithm classOfType:importSite->type];
|
||||
Class entityType = [algorithm classOfType:importSite->resultType];
|
||||
if (!entityType)
|
||||
return MPMakeError( @"Invalid site type in import file: %@ has type %lu", @(importSite->siteName), (long)importSite->type );
|
||||
return MPMakeError( @"Invalid site type in import file: %@ has type %lu", @(importSite->siteName),
|
||||
(long)importSite->resultType );
|
||||
|
||||
MPSiteEntity *site = (MPSiteEntity *)[entityType insertNewObjectInContext:context];
|
||||
site.user = user;
|
||||
@@ -676,7 +690,9 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
return nil;
|
||||
}
|
||||
@finally {
|
||||
mpw_marshal_file_free( &importUser );
|
||||
mpw_marshal_file_free( &file );
|
||||
mpw_marshal_user_free( &importUser );
|
||||
mpw_masterKeyProvider_free();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -684,13 +700,13 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
usingKey:(MPKey *)userKey {
|
||||
|
||||
site.name = @(importSite->siteName);
|
||||
if (importSite->content)
|
||||
[site.algorithm importPassword:@(importSite->content) protectedByKey:importKey intoSite:site usingKey:userKey];
|
||||
site.type = importSite->type;
|
||||
if (importSite->resultState)
|
||||
[site.algorithm importPassword:@(importSite->resultState) protectedByKey:importKey intoSite:site usingKey:userKey];
|
||||
site.type = importSite->resultType;
|
||||
if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
|
||||
((MPGeneratedSiteEntity *)site).counter = importSite->counter;
|
||||
site.algorithm = MPAlgorithmForVersion( importSite->algorithm );
|
||||
site.loginName = importSite->loginContent? @(importSite->loginContent): nil;
|
||||
site.loginName = importSite->loginState? @(importSite->loginState): nil;
|
||||
site.loginGenerated = importSite->loginType & MPResultTypeClassTemplate;
|
||||
site.url = importSite->url? @(importSite->url): nil;
|
||||
site.uses = importSite->uses;
|
||||
@@ -703,10 +719,10 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
|
||||
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPUserEntity *user = [self activeUserInContext:context];
|
||||
NSString *masterPassword = askImportPassword( user.name );
|
||||
|
||||
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", user.userID );
|
||||
MPMarshalledUser *exportUser = mpw_marshal_user( user.name.UTF8String, masterPassword.UTF8String, user.algorithm.version );
|
||||
MPMarshalledUser *exportUser = mpw_marshal_user( user.name.UTF8String,
|
||||
mpw_masterKeyProvider_str( askImportPassword( user.name ).UTF8String ), user.algorithm.version );
|
||||
exportUser->redacted = !revealPasswords;
|
||||
exportUser->avatar = (unsigned int)user.avatar;
|
||||
exportUser->defaultType = user.defaultType;
|
||||
@@ -722,8 +738,8 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
|
||||
MPMarshalledSite *exportSite = mpw_marshal_site( exportUser,
|
||||
site.name.UTF8String, site.type, counter, site.algorithm.version );
|
||||
exportSite->content = content.UTF8String;
|
||||
exportSite->loginContent = site.loginName.UTF8String;
|
||||
exportSite->resultState = content.UTF8String;
|
||||
exportSite->loginState = site.loginName.UTF8String;
|
||||
exportSite->loginType = site.loginGenerated? MPResultTypeTemplateName: MPResultTypeStatefulPersonal;
|
||||
exportSite->url = site.url.UTF8String;
|
||||
exportSite->uses = (unsigned int)site.uses;
|
||||
@@ -733,19 +749,21 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
|
||||
mpw_marshal_question( exportSite, siteQuestion.keyword.UTF8String );
|
||||
}
|
||||
|
||||
char *export = NULL;
|
||||
MPMarshalError exportError = (MPMarshalError){ .type= MPMarshalSuccess };
|
||||
mpw_marshal_write( &export, MPMarshalFormatFlat, exportUser, &exportError );
|
||||
MPMarshalledFile *exportFile = NULL;
|
||||
const char *export = mpw_marshal_write( MPMarshalFormatFlat, &exportFile, exportUser );
|
||||
NSString *mpsites = nil;
|
||||
if (export && exportError.type == MPMarshalSuccess)
|
||||
if (export && exportFile && exportFile->error.type == MPMarshalSuccess)
|
||||
mpsites = [NSString stringWithCString:export encoding:NSUTF8StringEncoding];
|
||||
mpw_free_string( &export );
|
||||
|
||||
resultBlock( mpsites, exportError.type == MPMarshalSuccess? nil:
|
||||
resultBlock( mpsites, exportFile && exportFile->error.type == MPMarshalSuccess? nil:
|
||||
[NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
|
||||
@"type" : @(exportError.type),
|
||||
NSLocalizedDescriptionKey: @(exportError.description),
|
||||
@"type" : @(exportFile? exportFile->error.type: MPMarshalErrorInternal),
|
||||
NSLocalizedDescriptionKey: @(exportFile? exportFile->error.message: nil),
|
||||
}] );
|
||||
mpw_marshal_file_free( &exportFile );
|
||||
mpw_marshal_user_free( &exportUser );
|
||||
mpw_masterKeyProvider_free();
|
||||
}];
|
||||
}
|
||||
|
||||
|
@@ -16,6 +16,7 @@
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#import "MPConfig.h"
|
||||
#import "MPAppDelegate_Shared.h"
|
||||
|
||||
@implementation MPConfig
|
||||
|
@@ -16,8 +16,7 @@
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#import <Crashlytics/Crashlytics.h>
|
||||
#import <Crashlytics/Answers.h>
|
||||
#import <Sentry/Sentry.h>
|
||||
|
||||
__BEGIN_DECLS
|
||||
extern NSString *const MPErrorDomain;
|
||||
@@ -34,28 +33,21 @@ extern NSString *const MPFoundInconsistenciesNotification;
|
||||
|
||||
extern NSString *const MPSitesImportedNotificationUserKey;
|
||||
extern NSString *const MPInconsistenciesFixResultUserKey;
|
||||
|
||||
__END_DECLS
|
||||
|
||||
#ifdef CRASHLYTICS
|
||||
#define MPError(error_, message, ...) ({ \
|
||||
#define MPError(error_, message_, ...) ({ \
|
||||
NSError *__error = error_; \
|
||||
err( message @"%@%@", ##__VA_ARGS__, __error && [message length]? @"\n": @"", [__error fullDescription]?: @"" ); \
|
||||
err( message_ @"%@%@", ##__VA_ARGS__, __error && [message_ length]? @"\n": @"", [__error fullDescription]?: @"" ); \
|
||||
\
|
||||
if (__error && [[MPConfig get].sendInfo boolValue]) { \
|
||||
[[Crashlytics sharedInstance] recordError:__error withAdditionalUserInfo:@{ \
|
||||
@"location": strf( @"%@:%d %@", @(basename((char *)__FILE__)), __LINE__, NSStringFromSelector(_cmd) ), \
|
||||
}]; \
|
||||
SentryEvent *event = [[SentryEvent alloc] initWithLevel:kSentrySeverityError]; \
|
||||
event.message = strf(@"%@: %@", message_, [__error localizedDescription]); \
|
||||
event.logger = @"MPError"; \
|
||||
[SentryClient.sharedClient appendStacktraceToEvent:event]; \
|
||||
[SentryClient.sharedClient sendEvent:event withCompletionHandler:nil]; \
|
||||
} \
|
||||
__error; \
|
||||
})
|
||||
#else
|
||||
#define MPError(error_, message, ...) ({ \
|
||||
NSError *__error = error_; \
|
||||
err( message @"%@%@", ##__VA_ARGS__, __error? @"\n": @"", [__error fullDescription]?: @"" ); \
|
||||
__error; \
|
||||
})
|
||||
#endif
|
||||
|
||||
#define MPMakeError(message, ...) ({ \
|
||||
MPError( [NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{ \
|
||||
|
@@ -68,7 +68,6 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
|
||||
#ifdef CRASHLYTICS
|
||||
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
|
||||
if ([crashlyticsAPIKey length]) {
|
||||
inf(@"Initializing Crashlytics");
|
||||
@@ -92,7 +91,6 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
CLSLog( @"Crashlytics (%@) initialized for: %@ v%@.", //
|
||||
[Crashlytics sharedInstance].version, [PearlInfoPlist get].CFBundleName, [PearlInfoPlist get].CFBundleVersion );
|
||||
}
|
||||
#endif
|
||||
|
||||
// Setup delegates and listeners.
|
||||
[MPConfig get].delegate = self;
|
||||
@@ -650,25 +648,4 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:NSStringFromSelector( configKey )];
|
||||
}
|
||||
|
||||
#pragma mark - Crashlytics
|
||||
|
||||
- (NSDictionary *)crashlyticsInfo {
|
||||
|
||||
static NSDictionary *crashlyticsInfo = nil;
|
||||
if (crashlyticsInfo == nil)
|
||||
crashlyticsInfo = [[NSDictionary alloc] initWithContentsOfURL:
|
||||
[[NSBundle mainBundle] URLForResource:@"Crashlytics" withExtension:@"plist"]];
|
||||
|
||||
return crashlyticsInfo;
|
||||
}
|
||||
|
||||
- (NSString *)crashlyticsAPIKey {
|
||||
|
||||
NSString *crashlyticsAPIKey = NSNullToNil( [[self crashlyticsInfo] valueForKeyPath:@"API Key"] );
|
||||
if (![crashlyticsAPIKey length])
|
||||
wrn( @"Crashlytics API key not set. Crash logs won't be recorded." );
|
||||
|
||||
return crashlyticsAPIKey;
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -53,20 +53,6 @@
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>[auto]</string>
|
||||
<key>Fabric</key>
|
||||
<dict>
|
||||
<key>APIKey</key>
|
||||
<string>0d10c90776f5ef5acd01ddbeaca9a6cba4814560</string>
|
||||
<key>Kits</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>KitInfo</key>
|
||||
<dict/>
|
||||
<key>KitName</key>
|
||||
<string>Crashlytics</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.productivity</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
@@ -58,18 +58,24 @@ MP_LIBS_END
|
||||
mpw_log_sink( level, file, line, function, format, ##__VA_ARGS__ ); \
|
||||
} while (0)
|
||||
|
||||
#define MPW_LOG mpw_log_os
|
||||
|
||||
#include "mpw-util.h"
|
||||
|
||||
#ifdef __OBJC__
|
||||
|
||||
#import "Pearl-Prefix.pch"
|
||||
|
||||
#define MPW_LOG(level, file, line, function, format, ...) mpw_log_os(level, file, line, function, strf(format, ##__VA_ARGS__))
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "MPTypes.h"
|
||||
#import "MPiOSConfig.h"
|
||||
#elif TARGET_OS_OSX
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "MPTypes.h"
|
||||
#import "MPMacConfig.h"
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#define MPW_LOG mpw_log_os
|
||||
|
||||
#endif
|
||||
|
||||
#include "mpw-util.h"
|
||||
|
28
platform-darwin/Source/iOS/MPSecrets.h
Normal file
28
platform-darwin/Source/iOS/MPSecrets.h
Normal file
@@ -0,0 +1,28 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
extern NSString *appSecret;
|
||||
extern NSString *appSalt;
|
||||
extern NSString *sentryDSN;
|
||||
extern NSString *countlyKey;
|
||||
extern NSString *countlySalt;
|
||||
|
||||
NSString *decrypt(NSString *appSecret);
|
||||
NSString *digest(NSString *value);
|
75
platform-darwin/Source/iOS/MPSecrets.m
Normal file
75
platform-darwin/Source/iOS/MPSecrets.m
Normal file
@@ -0,0 +1,75 @@
|
||||
//==============================================================================
|
||||
// This file is part of Master Password.
|
||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
||||
//
|
||||
// Master Password is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Master Password is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You can find a copy of the GNU General Public License in the
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#import "MPSecrets.h"
|
||||
#import "base64.h"
|
||||
|
||||
// printf <secret> | openssl enc -[ed] -aes-128-cbc -a -A -K <appSecret> -iv 0
|
||||
NSString *appSecret = @"";
|
||||
NSString *appSalt = @"";
|
||||
NSString *sentryDSN = @"";
|
||||
NSString *countlyKey = @"";
|
||||
NSString *countlySalt = @"";
|
||||
|
||||
NSString *decrypt(NSString *secret) {
|
||||
|
||||
if (!secret)
|
||||
return nil;
|
||||
|
||||
const char *secretString = [secret cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
size_t length = mpw_base64_decode_max( secretString );
|
||||
if (!length)
|
||||
return nil;
|
||||
|
||||
size_t keyLength;
|
||||
const uint8_t *key = mpw_unhex( [appSecret cStringUsingEncoding:NSUTF8StringEncoding], &keyLength );
|
||||
if (!key)
|
||||
return nil;
|
||||
|
||||
uint8_t *base64 = calloc( length, sizeof( uint8_t ) );
|
||||
length = mpw_base64_decode( base64, secretString );
|
||||
|
||||
const void *plain = mpw_aes_decrypt( key, keyLength, base64, &length );
|
||||
mpw_free( &key, keyLength );
|
||||
@try {
|
||||
return [[NSString alloc] initWithBytes:plain length:length encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
@finally {
|
||||
mpw_free( &plain, length );
|
||||
}
|
||||
}
|
||||
|
||||
NSString *digest(NSString *value) {
|
||||
|
||||
if (!value)
|
||||
return nil;
|
||||
|
||||
size_t appSaltLength, valueLength;
|
||||
const void *appSaltString = [decrypt( appSalt ) cStringUsingEncoding:NSUTF8StringEncoding length:&appSaltLength];
|
||||
const void *valueString = [value cStringUsingEncoding:NSUTF8StringEncoding length:&valueLength];
|
||||
const uint8_t *digest = mpw_hash_hmac_sha256( appSaltString, appSaltLength, valueString, valueLength );
|
||||
if (!digest)
|
||||
return nil;
|
||||
|
||||
@try {
|
||||
return [[NSString alloc] initWithBytes:mpw_hex( digest, 32 ) length:16 encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
@finally {
|
||||
mpw_free( &digest, 32 );
|
||||
}
|
||||
}
|
@@ -57,6 +57,8 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||
self.dataSource = [NSMutableArray new];
|
||||
|
||||
self.view.backgroundColor = [UIColor clearColor];
|
||||
if (@available( iOS 11, * ))
|
||||
self.collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||
[self.collectionView automaticallyAdjustInsetsForKeyboard];
|
||||
self.searchBar.autocapitalizationType = UITextAutocapitalizationTypeNone;
|
||||
if ([self.searchBar respondsToSelector:@selector( keyboardAppearance )])
|
||||
|
@@ -16,7 +16,8 @@
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#import <Crashlytics/Answers.h>
|
||||
#import <Countly/Countly.h>
|
||||
|
||||
#import "MPUsersViewController.h"
|
||||
#import "MPEntities.h"
|
||||
#import "MPAvatarCell.h"
|
||||
@@ -228,14 +229,10 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
|
||||
user.name = newUserName;
|
||||
|
||||
if ([[MPConfig get].sendInfo boolValue]) {
|
||||
#ifdef CRASHLYTICS
|
||||
[Answers logSignUpWithMethod:@"Manual"
|
||||
success:@YES
|
||||
customAttributes:@{
|
||||
@"algorithm": @(user.algorithm.version),
|
||||
@"avatar" : @(user.avatar),
|
||||
}];
|
||||
#endif
|
||||
[Countly.sharedInstance recordEvent:@"new-user" segmentation:@{
|
||||
@"algorithm": @(user.algorithm.version).description,
|
||||
@"avatar" : @(user.avatar).description,
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,7 +663,7 @@ referenceSizeForFooterInSection:(NSInteger)section {
|
||||
[self removeObservers];
|
||||
[self observeKeyPath:@"avatarCollectionView.contentOffset" withBlock:
|
||||
^(id from, id to, NSKeyValueChange cause, MPUsersViewController *self) {
|
||||
[self updateAvatarVisibility];
|
||||
PearlMainQueue( ^{ [self updateAvatarVisibility]; } );
|
||||
}];
|
||||
|
||||
PearlAddNotificationObserver( UIApplicationDidEnterBackgroundNotification, nil, [NSOperationQueue mainQueue],
|
||||
|
@@ -21,6 +21,10 @@
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "MPStoreViewController.h"
|
||||
#import "mpw-marshal.h"
|
||||
#import "MPSecrets.h"
|
||||
|
||||
#import <Sentry/Sentry.h>
|
||||
#import <Countly/Countly.h>
|
||||
|
||||
@interface MPiOSAppDelegate()<UIDocumentInteractionControllerDelegate>
|
||||
|
||||
@@ -35,11 +39,10 @@
|
||||
|
||||
static dispatch_once_t once = 0;
|
||||
dispatch_once( &once, ^{
|
||||
[PearlLogger get].printLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelDebug: PearlLogLevelInfo;
|
||||
[PearlLogger get].historyLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelTrace: PearlLogLevelInfo;
|
||||
#ifdef DEBUG
|
||||
[PearlLogger get].printLevel = PearlLogLevelTrace;
|
||||
#else
|
||||
[PearlLogger get].printLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelDebug: PearlLogLevelInfo;
|
||||
//[PearlLogger get].printLevel = PearlLogLevelTrace;
|
||||
#endif
|
||||
} );
|
||||
}
|
||||
@@ -47,34 +50,73 @@
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
|
||||
@try {
|
||||
// [[NSBundle mainBundle] mutableInfoDictionary][@"CFBundleDisplayName"] = @"Master Password";
|
||||
// [[NSBundle mainBundle] mutableLocalizedInfoDictionary][@"CFBundleDisplayName"] = @"Master Password";
|
||||
|
||||
#ifdef CRASHLYTICS
|
||||
NSString *crashlyticsAPIKey = [self crashlyticsAPIKey];
|
||||
if ([crashlyticsAPIKey length]) {
|
||||
inf( @"Initializing Crashlytics" );
|
||||
#if DEBUG
|
||||
[Crashlytics sharedInstance].debugMode = YES;
|
||||
// Sentry
|
||||
SentryClient.sharedClient = [[SentryClient alloc] initWithDsn:decrypt( sentryDSN ) didFailWithError:nil];
|
||||
#ifdef DEBUG
|
||||
SentryClient.sharedClient.environment = @"Development";
|
||||
#elif PUBLIC
|
||||
SentryClient.sharedClient.environment = @"Public";
|
||||
#else
|
||||
SentryClient.sharedClient.environment = @"Private";
|
||||
#endif
|
||||
[[Crashlytics sharedInstance] setUserIdentifier:[PearlKeyChain deviceIdentifier]];
|
||||
[[Crashlytics sharedInstance] setObjectValue:[PearlKeyChain deviceIdentifier] forKey:@"deviceIdentifier"];
|
||||
[[Crashlytics sharedInstance] setUserName:@"Anonymous"];
|
||||
[Crashlytics startWithAPIKey:crashlyticsAPIKey];
|
||||
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
||||
PearlLogLevel level = PearlLogLevelWarn;
|
||||
if ([[MPConfig get].sendInfo boolValue])
|
||||
level = PearlLogLevelDebug;
|
||||
SentryClient.sharedClient.enabled = [MPiOSConfig get].sendInfo;
|
||||
[SentryClient.sharedClient enableAutomaticBreadcrumbTracking];
|
||||
[SentryClient.sharedClient startCrashHandlerWithError:nil];
|
||||
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
|
||||
PearlLogLevel level = PearlLogLevelWarn;
|
||||
if ([[MPConfig get].sendInfo boolValue])
|
||||
level = PearlLogLevelDebug;
|
||||
|
||||
if (message.level >= level)
|
||||
CLSLog( @"%@", [message messageDescription] );
|
||||
if (message.level >= level) {
|
||||
SentrySeverity sentryLevel = kSentrySeverityInfo;
|
||||
switch (message.level) {
|
||||
case PearlLogLevelTrace:
|
||||
sentryLevel = kSentrySeverityDebug;
|
||||
break;
|
||||
case PearlLogLevelDebug:
|
||||
sentryLevel = kSentrySeverityDebug;
|
||||
break;
|
||||
case PearlLogLevelInfo:
|
||||
sentryLevel = kSentrySeverityInfo;
|
||||
break;
|
||||
case PearlLogLevelWarn:
|
||||
sentryLevel = kSentrySeverityWarning;
|
||||
break;
|
||||
case PearlLogLevelError:
|
||||
sentryLevel = kSentrySeverityError;
|
||||
break;
|
||||
case PearlLogLevelFatal:
|
||||
sentryLevel = kSentrySeverityFatal;
|
||||
break;
|
||||
}
|
||||
SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] initWithLevel:sentryLevel category:@"Pearl"];
|
||||
breadcrumb.type = @"log";
|
||||
breadcrumb.message = message.message;
|
||||
breadcrumb.timestamp = message.occurrence;
|
||||
breadcrumb.data = @{ @"file": message.fileName, @"line": @(message.lineNumber), @"function": message.function };
|
||||
[SentryClient.sharedClient.breadcrumbs addBreadcrumb:breadcrumb];
|
||||
}
|
||||
|
||||
return YES;
|
||||
}];
|
||||
CLSLog( @"Crashlytics (%@) initialized for: %@ v%@.", //
|
||||
[Crashlytics sharedInstance].version, [PearlInfoPlist get].CFBundleName, [PearlInfoPlist get].CFBundleVersion );
|
||||
}
|
||||
return YES;
|
||||
}];
|
||||
|
||||
// Countly
|
||||
CountlyConfig *countlyConfig = [CountlyConfig new];
|
||||
countlyConfig.host = @"https://countly.lyndir.app";
|
||||
countlyConfig.appKey = decrypt( countlyKey );
|
||||
countlyConfig.features = @[ CLYPushNotifications ];
|
||||
countlyConfig.requiresConsent = true;
|
||||
#if PUBLIC
|
||||
countlyConfig.pushTestMode = nil;
|
||||
#elif DEBUG
|
||||
countlyConfig.pushTestMode = CLYPushTestModeDevelopment;
|
||||
#else
|
||||
countlyConfig.pushTestMode = CLYPushTestModeTestFlightOrAdHoc;
|
||||
#endif
|
||||
countlyConfig.alwaysUsePOST = true;
|
||||
countlyConfig.deviceID = [PearlKeyChain deviceIdentifier];
|
||||
countlyConfig.secretSalt = decrypt( countlySalt );
|
||||
[Countly.sharedInstance startWithConfig:countlyConfig];
|
||||
|
||||
[self.hangDetector = [[PearlHangDetector alloc] initWithHangAction:^(NSTimeInterval hangTime) {
|
||||
MPError( [NSError errorWithDomain:MPErrorDomain code:MPErrorHangCode userInfo:@{
|
||||
@@ -111,21 +153,21 @@
|
||||
case MPFixableResultNoProblems:
|
||||
break;
|
||||
case MPFixableResultProblemsFixed: {
|
||||
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Inconsistencies Fixed" message:
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Inconsistencies Fixed" message:
|
||||
@"Some inconsistencies were detected in your sites.\n"
|
||||
@"All issues were fixed."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self presentViewController:controller animated:YES completion:nil];
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self presentViewController:alert animated:YES completion:nil];
|
||||
break;
|
||||
}
|
||||
case MPFixableResultProblemsNotFixed: {
|
||||
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Inconsistencies Found" message:
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Inconsistencies Found" message:
|
||||
@"Some inconsistencies were detected in your sites.\n"
|
||||
@"Not all issues could be fixed. Try signing in to each user or checking the logs."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self presentViewController:controller animated:YES completion:nil];
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self presentViewController:alert animated:YES completion:nil];
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -138,12 +180,12 @@
|
||||
|
||||
NSString *latestFeatures = [MPStoreViewController latestStoreFeatures];
|
||||
if (latestFeatures) {
|
||||
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"New Features" message:
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"New Features" message:
|
||||
strf( @"The following features are now available in the store:\n\n%@•••\n\n"
|
||||
@"Find the store from the user pull‑down after logging in.", latestFeatures )
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Thanks" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:controller animated:YES completion:nil];
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Thanks" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
}
|
||||
@catch (id exception) {
|
||||
@@ -167,22 +209,22 @@
|
||||
MPError( error, @"While reading imported sites from %@.", url );
|
||||
|
||||
if (!importedSitesData) {
|
||||
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Error" message:
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Error" message:
|
||||
strf( @"Master Password couldn't read the import sites.\n\n%@",
|
||||
(id)[error localizedDescription]?: error )
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:controller animated:YES completion:nil];
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
|
||||
if (!importedSitesString) {
|
||||
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Error" message:
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Error" message:
|
||||
@"Master Password couldn't understand the import file."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:controller animated:YES completion:nil];
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -204,35 +246,35 @@
|
||||
PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:@"Importing"];
|
||||
[self importSites:importData askImportPassword:^NSString *(NSString *userName) {
|
||||
return PearlAwait( ^(void (^setResult)(id)) {
|
||||
UIAlertController *controller = [UIAlertController alertControllerWithTitle:strf( @"Importing Sites For\n%@", userName ) message:
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Importing Sites For\n%@", userName ) message:
|
||||
@"Enter the master password used to create this export file."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[controller addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
textField.secureTextEntry = YES;
|
||||
}];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Import" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
setResult( controller.textFields.firstObject.text );
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Import" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
setResult( alert.textFields.firstObject.text );
|
||||
}]];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
|
||||
setResult( nil );
|
||||
}]];
|
||||
[self.navigationController presentViewController:controller animated:YES completion:nil];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
} );
|
||||
} askUserPassword:^NSString *(NSString *userName) {
|
||||
return PearlAwait( (id)^(void (^setResult)(id)) {
|
||||
UIAlertController *controller = [UIAlertController alertControllerWithTitle:strf( @"Master Password For\n%@", userName ) message:
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For\n%@", userName ) message:
|
||||
@"Enter the current master password for this user."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[controller addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
textField.secureTextEntry = YES;
|
||||
}];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Import" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
setResult( controller.textFields.firstObject.text );
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Import" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
setResult( alert.textFields.firstObject.text );
|
||||
}]];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
|
||||
setResult( nil );
|
||||
}]];
|
||||
[self.navigationController presentViewController:controller animated:YES completion:nil];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
} );
|
||||
} result:^(NSError *error) {
|
||||
[activityOverlay cancelOverlayAnimated:YES];
|
||||
@@ -262,19 +304,20 @@
|
||||
|
||||
PearlNotMainQueue( ^{
|
||||
NSString *importData = [UIPasteboard generalPasteboard].string;
|
||||
MPMarshalInfo *importInfo = mpw_marshal_read_info( importData.UTF8String );
|
||||
if (importInfo->format != MPMarshalFormatNone) {
|
||||
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Import Sites?" message:
|
||||
MPMarshalledFile *importFile = mpw_marshal_read( NULL, importData.UTF8String );
|
||||
if (importFile && importFile->error.type == MPMarshalSuccess && importFile->info->format != MPMarshalFormatNone) {
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Import Sites?" message:
|
||||
@"We've detected Master Password import sites on your pasteboard, would you like to import them?"
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Import Sites" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
||||
[self importSites:importData];
|
||||
[UIPasteboard generalPasteboard].string = @"";
|
||||
}]];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"No" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:controller animated:YES completion:nil];
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Import Sites" style:UIAlertActionStyleDefault handler:
|
||||
^(UIAlertAction *action) {
|
||||
[self importSites:importData];
|
||||
[UIPasteboard generalPasteboard].string = @"";
|
||||
}]];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"No" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
mpw_marshal_info_free( &importInfo );
|
||||
mpw_marshal_file_free( &importFile );
|
||||
} );
|
||||
|
||||
[super applicationDidBecomeActive:application];
|
||||
@@ -341,27 +384,27 @@
|
||||
- (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController {
|
||||
|
||||
if (![PearlEMail canSendMail]) {
|
||||
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Feedback" message:
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Feedback" message:
|
||||
@"Have a question, comment, issue or just saying thanks?\n\n"
|
||||
@"We'd love to hear what you think!\n"
|
||||
@"masterpassword@lyndir.com"
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:controller animated:YES completion:nil];
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
else if (logs) {
|
||||
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Feedback" message:
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Feedback" message:
|
||||
@"Have a question, comment, issue or just saying thanks?\n\n"
|
||||
@"If you're having trouble, it may help us if you can first reproduce the problem "
|
||||
@"and then include log files in your message."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Include Logs" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Include Logs" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
[self openFeedbackWithLogs:YES forVC:viewController];
|
||||
}]];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"No Logs" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"No Logs" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
[self openFeedbackWithLogs:NO forVC:viewController];
|
||||
}]];
|
||||
[self.navigationController presentViewController:controller animated:YES completion:nil];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
else
|
||||
[self openFeedbackWithLogs:NO forVC:viewController];
|
||||
@@ -401,83 +444,83 @@
|
||||
|
||||
static dispatch_once_t once = 0;
|
||||
dispatch_once( &once, ^{
|
||||
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Failed To Load Sites" message:
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Failed To Load Sites" message:
|
||||
@"Master Password was unable to open your sites history.\n"
|
||||
@"This may be due to corruption. You can either reset Master Password and "
|
||||
@"recreate your user, or E-Mail us your logs and leave your corrupt store as-is for now."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"E-Mail Logs" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
[self openFeedbackWithLogs:YES forVC:nil];
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"E-Mail Logs" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
[self openFeedbackWithLogs:YES forVC:nil];
|
||||
}]];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Reset" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
[self deleteAndResetStore];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Reset" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
[self deleteAndResetStore];
|
||||
}]];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Ignore" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:controller animated:YES completion:nil];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Ignore" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
} );
|
||||
}
|
||||
|
||||
- (void)showExportForVC:(UIViewController *)viewController {
|
||||
|
||||
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Exporting Your Sites" message:
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Exporting Your Sites" message:
|
||||
@"An export is great for keeping a backup list of your accounts.\n\n"
|
||||
@"When the file is ready, you will be able to mail it to yourself.\n"
|
||||
@"You can open it with a text editor or with Master Password if you need to restore your list of sites."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Export Sites" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
UIAlertController *controller_ = [UIAlertController alertControllerWithTitle:@"Show Passwords?" message:
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Export Sites" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
UIAlertController *sheet = [UIAlertController alertControllerWithTitle:@"Show Passwords?" message:
|
||||
@"Would you like to make all your passwords visible in the export file?\n\n"
|
||||
@"A safe export will include all sites but make their passwords invisible.\n"
|
||||
@"It is great as a backup and remains safe when fallen in the wrong hands."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[controller_ addAction:[UIAlertAction actionWithTitle:@"Safe Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
[self showExportRevealPasswords:NO forVC:viewController];
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
[sheet addAction:[UIAlertAction actionWithTitle:@"Safe Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
[self showExportRevealPasswords:NO forVC:viewController];
|
||||
}]];
|
||||
[controller_ addAction:[UIAlertAction actionWithTitle:@"Show Passwords" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
[self showExportRevealPasswords:YES forVC:viewController];
|
||||
[sheet addAction:[UIAlertAction actionWithTitle:@"Show Passwords" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
[self showExportRevealPasswords:YES forVC:viewController];
|
||||
}]];
|
||||
[controller_ addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:controller_ animated:YES completion:nil];
|
||||
[sheet addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:sheet animated:YES completion:nil];
|
||||
}]];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:controller animated:YES completion:nil];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)showExportRevealPasswords:(BOOL)revealPasswords forVC:(UIViewController *)viewController {
|
||||
|
||||
if (![PearlEMail canSendMail]) {
|
||||
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Cannot Send Mail" message:
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Cannot Send Mail" message:
|
||||
@"Your device is not yet set up for sending mail.\n"
|
||||
@"Close Master Password, go into Settings and add a Mail account."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:controller animated:YES completion:nil];
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
[self exportSitesRevealPasswords:revealPasswords askExportPassword:^NSString *(NSString *userName) {
|
||||
return PearlAwait( ^(void (^setResult)(id)) {
|
||||
UIAlertController *controller = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName ) message:
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName ) message:
|
||||
@"Enter the user's master password to create an export file."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[controller addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
||||
textField.secureTextEntry = YES;
|
||||
}];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
setResult( controller.textFields.firstObject.text );
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
setResult( alert.textFields.firstObject.text );
|
||||
}]];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
|
||||
setResult( nil );
|
||||
}]];
|
||||
[self.navigationController presentViewController:controller animated:YES completion:nil];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
} );
|
||||
} result:^(NSString *mpsites, NSError *error) {
|
||||
if (!mpsites || error) {
|
||||
MPError( error, @"Failed to export mpsites." );
|
||||
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Export Error" message:[error localizedDescription]
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:controller animated:YES completion:nil];
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Error" message:[error localizedDescription]
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -486,9 +529,9 @@
|
||||
NSString *exportFileName = strf( @"%@ (%@).mpsites",
|
||||
[self activeUserForMainThread].name, [exportDateFormatter stringFromDate:[NSDate date]] );
|
||||
|
||||
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Export Destination" message:nil
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Send As E-Mail" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Destination" message:nil
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Send As E-Mail" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
NSString *message;
|
||||
if (revealPasswords)
|
||||
message = strf( @"Export of Master Password sites with passwords included.\n\n"
|
||||
@@ -511,11 +554,12 @@
|
||||
[PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
|
||||
attachments:[[PearlEMailAttachment alloc]
|
||||
initWithContent:[mpsites dataUsingEncoding:NSUTF8StringEncoding]
|
||||
mimeType:@"text/plain" fileName:exportFileName],
|
||||
mimeType:@"text/plain"
|
||||
fileName:exportFileName],
|
||||
nil];
|
||||
return;
|
||||
}]];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Share / Airdrop" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Share / Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
|
||||
inDomains:NSUserDomainMask] lastObject];
|
||||
NSURL *exportURL = [[applicationSupportURL
|
||||
@@ -532,19 +576,19 @@
|
||||
[self.interactionController presentOpenInMenuFromRect:CGRectZero inView:viewController.view animated:YES];
|
||||
}
|
||||
}]];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:controller animated:YES completion:nil];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset {
|
||||
|
||||
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Changing Master Password" message:
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Changing Master Password" message:
|
||||
@"If you continue, you'll be able to set a new master password.\n\n"
|
||||
@"Changing your master password will cause all your generated passwords to change!\n"
|
||||
@"Changing the master password back to the old one will cause your passwords to revert as well."
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Abort" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Abort" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
|
||||
[moc performBlockAndWait:^{
|
||||
inf( @"Clearing keyID for user: %@.", user.userID );
|
||||
user.keyID = nil;
|
||||
@@ -556,8 +600,8 @@
|
||||
if (didReset)
|
||||
didReset();
|
||||
}]];
|
||||
[controller addAction:[UIAlertAction actionWithTitle:@"Abort" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:controller animated:YES completion:nil];
|
||||
[alert addAction:[UIAlertAction actionWithTitle:@"Abort" style:UIAlertActionStyleCancel handler:nil]];
|
||||
[self.navigationController presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - UIDocumentInteractionControllerDelegate
|
||||
@@ -581,51 +625,41 @@
|
||||
|
||||
// Send info
|
||||
if ([[MPConfig get].sendInfo boolValue]) {
|
||||
[Countly.sharedInstance giveConsentForAllFeatures];
|
||||
|
||||
if ([PearlLogger get].printLevel > PearlLogLevelInfo)
|
||||
[PearlLogger get].printLevel = PearlLogLevelInfo;
|
||||
|
||||
#ifdef CRASHLYTICS
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].rememberLogin boolValue] forKey:@"rememberLogin"];
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[MPConfig get].sendInfo boolValue] forKey:@"sendInfo"];
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].helpHidden boolValue] forKey:@"helpHidden"];
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[MPiOSConfig get].showSetup boolValue] forKey:@"showQuickStart"];
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[PearlConfig get].firstRun boolValue] forKey:@"firstRun"];
|
||||
[[Crashlytics sharedInstance] setIntValue:[[PearlConfig get].launchCount intValue] forKey:@"launchCount"];
|
||||
[[Crashlytics sharedInstance] setBoolValue:[[PearlConfig get].askForReviews boolValue] forKey:@"askForReviews"];
|
||||
[[Crashlytics sharedInstance] setIntValue:[[PearlConfig get].reviewAfterLaunches intValue] forKey:@"reviewAfterLaunches"];
|
||||
[[Crashlytics sharedInstance] setObjectValue:[PearlConfig get].reviewedVersion forKey:@"reviewedVersion"];
|
||||
[[Crashlytics sharedInstance] setBoolValue:[PearlDeviceUtils isSimulator] forKey:@"simulator"];
|
||||
[[Crashlytics sharedInstance] setBoolValue:[PearlDeviceUtils isAppEncrypted] forKey:@"encrypted"];
|
||||
[[Crashlytics sharedInstance] setBoolValue:[PearlDeviceUtils isJailbroken] forKey:@"jailbroken"];
|
||||
[[Crashlytics sharedInstance] setObjectValue:[PearlDeviceUtils platform] forKey:@"platform"];
|
||||
NSMutableDictionary *prefs = [NSMutableDictionary new];
|
||||
prefs[@"rememberLogin"] = [MPConfig get].rememberLogin;
|
||||
prefs[@"sendInfo"] = [MPConfig get].sendInfo;
|
||||
prefs[@"helpHidden"] = [MPiOSConfig get].helpHidden;
|
||||
prefs[@"showQuickStart"] = [MPiOSConfig get].showSetup;
|
||||
prefs[@"firstRun"] = [PearlConfig get].firstRun;
|
||||
prefs[@"launchCount"] = [PearlConfig get].launchCount;
|
||||
prefs[@"askForReviews"] = [PearlConfig get].askForReviews;
|
||||
prefs[@"reviewAfterLaunches"] = [PearlConfig get].reviewAfterLaunches;
|
||||
prefs[@"reviewedVersion"] = [PearlConfig get].reviewedVersion;
|
||||
prefs[@"simulator"] = @([PearlDeviceUtils isSimulator]);
|
||||
prefs[@"encrypted"] = @([PearlDeviceUtils isAppEncrypted]);
|
||||
prefs[@"jailbroken"] = @([PearlDeviceUtils isJailbroken]);
|
||||
prefs[@"platform"] = [PearlDeviceUtils platform];
|
||||
#ifdef APPSTORE
|
||||
[[Crashlytics sharedInstance] setBoolValue:[PearlDeviceUtils isAppEncrypted] forKey:@"reviewedVersion"];
|
||||
prefs[@"reviewedVersion"] = @([PearlDeviceUtils isAppEncrypted]);
|
||||
#else
|
||||
[[Crashlytics sharedInstance] setBoolValue:YES forKey:@"reviewedVersion"];
|
||||
#endif
|
||||
prefs[@"reviewedVersion"] = @(YES);
|
||||
#endif
|
||||
PearlMainQueueOperation( ^{
|
||||
if (![[SentryClient.sharedClient.extra dictionaryWithValuesForKeys:prefs.allKeys] isEqualToDictionary:prefs]) {
|
||||
NSMutableDictionary *extra = [SentryClient.sharedClient.extra mutableCopy]?: [NSMutableDictionary dictionary];
|
||||
[extra addEntriesFromDictionary:prefs];
|
||||
SentryClient.sharedClient.extra = extra;
|
||||
}
|
||||
} );
|
||||
}
|
||||
else {
|
||||
[Countly.sharedInstance cancelConsentForAllFeatures];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Crashlytics
|
||||
|
||||
- (NSDictionary *)crashlyticsInfo {
|
||||
|
||||
static NSDictionary *crashlyticsInfo = nil;
|
||||
if (crashlyticsInfo == nil)
|
||||
crashlyticsInfo = [[NSDictionary alloc] initWithContentsOfURL:
|
||||
[[NSBundle mainBundle] URLForResource:@"Fabric" withExtension:@"plist"]];
|
||||
|
||||
return crashlyticsInfo;
|
||||
}
|
||||
|
||||
- (NSString *)crashlyticsAPIKey {
|
||||
|
||||
NSString *crashlyticsAPIKey = NSNullToNil( [[self crashlyticsInfo] valueForKeyPath:@"API Key"] );
|
||||
if (![crashlyticsAPIKey length])
|
||||
wrn( @"Crashlytics API key not set. Crash logs won't be recorded." );
|
||||
|
||||
return crashlyticsAPIKey;
|
||||
}
|
||||
|
||||
@end
|
||||
|
@@ -16,6 +16,8 @@
|
||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
||||
//==============================================================================
|
||||
|
||||
#import "MPiOSConfig.h"
|
||||
|
||||
@implementation MPiOSConfig
|
||||
|
||||
@dynamic helpHidden, siteInfoHidden, showSetup, actionsTipShown, typeTipShown, loginNameTipShown, traceMode, dictationSearch, allowDowngrade;
|
||||
|
@@ -41,20 +41,6 @@
|
||||
<string>[auto]</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>[auto]</string>
|
||||
<key>Fabric</key>
|
||||
<dict>
|
||||
<key>APIKey</key>
|
||||
<string>0d10c90776f5ef5acd01ddbeaca9a6cba4814560</string>
|
||||
<key>Kits</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>KitInfo</key>
|
||||
<dict/>
|
||||
<key>KitName</key>
|
||||
<string>Crashlytics</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user