Create managers folder

This commit is contained in:
SBiOSoftWhare
2018-07-06 18:09:47 +02:00
parent 53e9214b1d
commit e00fba775a
14 changed files with 80 additions and 69 deletions
+63
View File
@@ -0,0 +1,63 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import <Foundation/Foundation.h>
#import <MatrixSDK/MatrixSDK.h>
#import "DecryptionFailureTracker.h"
/**
`Analytics` sends analytics to an analytics tool.
*/
@interface Analytics : NSObject <MXAnalyticsDelegate, MXDecryptionFailureDelegate>
/**
Returns the shared Analytics manager.
@return the shared Analytics manager.
*/
+ (instancetype)sharedInstance;
/**
Start doing analytics if the settings `enableCrashReport` is enabled.
*/
- (void)start;
/**
Stop doing analytics.
*/
- (void)stop;
/**
Track a screen display.
@param screenName the name of the displayed screen.
*/
- (void)trackScreen:(NSString*)screenName;
/**
Flush analytics data.
*/
- (void)dispatch;
/**
Track how long the launch screen has been displayed to the end user.
@param seconds the duration in seconds.
*/
- (void)trackLaunchScreenDisplayDuration: (NSTimeInterval)seconds;
@end
+189
View File
@@ -0,0 +1,189 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "Analytics.h"
#import "AppDelegate.h"
#import "Riot-Swift.h"
// All metrics are store under a Piwik category called "Metrics".
// Then, there are 2 Piwik actions: "iOS.startup" and "iOS.stats" (these actions
// are namespaced by plaform to have a nice rendering on the Piwik website).
// Then, we use constants defined by the Matrix SDK as Piwik Names (ex:"mountData")
NSString *const kAnalyticsMetricsCategory = @"Metrics";
NSString *const kAnalyticsMetricsActionPattern = @"iOS.%@";
// E2E telemetry is stored under a Piwik category called "E2E".
NSString *const kAnalyticsE2eCategory = @"E2E";
NSString *const kAnalyticsE2eDecryptionFailureAction = @"Decryption failure";
@import PiwikTracker;
@implementation Analytics
+ (instancetype)sharedInstance
{
static Analytics *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[Analytics alloc] init];
});
return sharedInstance;
}
- (void)start
{
NSDictionary *piwikConfig = [[NSUserDefaults standardUserDefaults] objectForKey:@"piwik"];
[PiwikTracker configureSharedInstanceWithSiteID:piwikConfig[@"siteId"]
baseURL:[NSURL URLWithString:piwikConfig[@"url"]]
userAgent:@"iOSPiwikTracker"];
// Check whether the user has enabled the sending of crash reports.
if (RiotSettings.shared.enableCrashReport)
{
[PiwikTracker shared].isOptedOut = NO;
[[PiwikTracker shared] setCustomVariableWithIndex:1 name:@"App Platform" value:@"iOS Platform"];
[[PiwikTracker shared] setCustomVariableWithIndex:2 name:@"App Version" value:[AppDelegate theDelegate].appVersion];
// The language is either the one selected by the user within the app
// or, else, the one configured by the OS
NSString *language = [NSBundle mxk_language] ? [NSBundle mxk_language] : [[NSBundle mainBundle] preferredLocalizations][0];
[[PiwikTracker shared] setCustomVariableWithIndex:4 name:@"Chosen Language" value:language];
MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject;
if (account)
{
[[PiwikTracker shared] setCustomVariableWithIndex:7 name:@"Homeserver URL" value:account.mxCredentials.homeServer];
[[PiwikTracker shared] setCustomVariableWithIndex:8 name:@"Identity Server URL" value:account.identityServerURL];
}
// TODO: We should also track device and os version
// But that needs to be decided for all platforms
// Catch and log crashes
[MXLogger logCrashes:YES];
[MXLogger setBuildVersion:[AppDelegate theDelegate].build];
#ifdef DEBUG
// Disable analytics in debug as it pollutes stats
[PiwikTracker shared].isOptedOut = YES;
#endif
}
else
{
NSLog(@"[AppDelegate] The user decided to not send analytics");
[PiwikTracker shared].isOptedOut = YES;
[MXLogger logCrashes:NO];
}
}
- (void)stop
{
[PiwikTracker shared].isOptedOut = YES;
[MXLogger logCrashes:NO];
}
- (void)trackScreen:(NSString *)screenName
{
// Use the same pattern as Android
NSString *appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"];
NSString *appVersion = [AppDelegate theDelegate].appVersion;
[[PiwikTracker shared] trackWithView:@[@"ios", appName, appVersion, screenName]
url:nil];
}
- (void)dispatch
{
[[PiwikTracker shared] dispatch];
}
- (void)trackLaunchScreenDisplayDuration:(NSTimeInterval)seconds
{
NSString *action = [NSString stringWithFormat:kAnalyticsMetricsActionPattern, kMXAnalyticsStartupCategory];
[[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory
action:action
name:kMXAnalyticsStartupLaunchScreen
number:@(seconds * 1000)
url:nil];
}
#pragma mark - MXAnalyticsDelegate
- (void)trackStartupStorePreloadDuration: (NSTimeInterval)seconds
{
NSString *action = [NSString stringWithFormat:kAnalyticsMetricsActionPattern, kMXAnalyticsStartupCategory];
[[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory
action:action
name:kMXAnalyticsStartupStorePreload
number:@(seconds * 1000)
url:nil];
}
- (void)trackStartupMountDataDuration: (NSTimeInterval)seconds
{
NSString *action = [NSString stringWithFormat:kAnalyticsMetricsActionPattern, kMXAnalyticsStartupCategory];
[[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory
action:action
name:kMXAnalyticsStartupMountData
number:@(seconds * 1000)
url:nil];
}
- (void)trackStartupSyncDuration: (NSTimeInterval)seconds isInitial: (BOOL)isInitial
{
NSString *action = [NSString stringWithFormat:kAnalyticsMetricsActionPattern, kMXAnalyticsStartupCategory];
[[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory
action:action
name:isInitial ? kMXAnalyticsStartupInititialSync : kMXAnalyticsStartupIncrementalSync
number:@(seconds * 1000)
url:nil];
}
- (void)trackRoomCount: (NSUInteger)roomCount
{
NSString *action = [NSString stringWithFormat:kAnalyticsMetricsActionPattern, kMXAnalyticsStatsCategory];
[[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory
action:action
name:kMXAnalyticsStatsRooms
number:@(roomCount)
url:nil];
}
#pragma mark - MXDecryptionFailureDelegate
- (void)trackFailures:(NSDictionary<NSString *,NSNumber *> *)failuresCounts
{
for (NSString *reason in failuresCounts)
{
[[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsE2eCategory
action:kAnalyticsE2eDecryptionFailureAction
name:reason
number:failuresCounts[reason]
url:nil];
}
}
@end
@@ -0,0 +1,51 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import <Foundation/Foundation.h>
/**
Failure reasons as defined in https://docs.google.com/document/d/1es7cTCeJEXXfRCTRgZerAM2Wg5ZerHjvlpfTW-gsOfI.
*/
struct DecryptionFailureReasonStruct
{
__unsafe_unretained NSString * const unspecified;
__unsafe_unretained NSString * const olmKeysNotSent;
__unsafe_unretained NSString * const olmIndexError;
__unsafe_unretained NSString * const unexpected;
};
extern const struct DecryptionFailureReasonStruct DecryptionFailureReason;
/**
`DecryptionFailure` represents a decryption failure.
*/
@interface DecryptionFailure : NSObject
/**
The id of the event that was unabled to decrypt.
*/
@property (nonatomic) NSString *failedEventId;
/**
The time the failure has been reported.
*/
@property (nonatomic, readonly) NSTimeInterval ts;
/**
Decryption failure reason.
*/
@property (nonatomic) NSString *reason;
@end
@@ -0,0 +1,38 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "DecryptionFailure.h"
const struct DecryptionFailureReasonStruct DecryptionFailureReason = {
.unspecified = @"unspecified_error",
.olmKeysNotSent = @"olm_keys_not_sent_error",
.olmIndexError = @"olm_index_error",
.unexpected = @"unexpected_error"
};
@implementation DecryptionFailure
- (instancetype)init
{
self = [super init];
if (self)
{
_ts = [NSDate date].timeIntervalSince1970;
}
return self;
}
@end
@@ -0,0 +1,71 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "DecryptionFailure.h"
@import MatrixSDK;
@protocol MXDecryptionFailureDelegate;
@interface DecryptionFailureTracker : NSObject
/**
Returns the shared tracker.
@return the shared tracker.
*/
+ (instancetype)sharedInstance;
/**
The delegate object to receive analytics events.
*/
@property (nonatomic) id<MXDecryptionFailureDelegate> delegate;
/**
Report an event unable to decrypt.
This error can be momentary. The DecryptionFailureTracker will check if it gets
fixed. Else, it will generate a failure (@see `trackFailures`).
@param event the event.
@param roomState the room state when the event was received.
@param userId my user id.
*/
- (void)reportUnableToDecryptErrorForEvent:(MXEvent*)event withRoomState:(MXRoomState*)roomState myUser:(NSString*)userId;
/**
Flush current data.
*/
- (void)dispatch;
@end
/**
The `MXDecryptionFailureDelegate` protocol receives some stats computed by
`DecryptionFailureTracker`.
*/
@protocol MXDecryptionFailureDelegate <NSObject>
/**
Stats for decryption failures.
@param failuresCounts the number of errors per failure reason.
*/
- (void)trackFailures:(NSDictionary<NSString*, NSNumber*> *)failuresCounts;
@end
@@ -0,0 +1,167 @@
/*
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "DecryptionFailureTracker.h"
// Call `checkFailures` every `CHECK_INTERVAL`
#define CHECK_INTERVAL 5
// Give events a chance to be decrypted by waiting `GRACE_PERIOD` before counting
// and reporting them as failures
#define GRACE_PERIOD 60
@interface DecryptionFailureTracker()
{
// Reported failures
// Every `CHECK_INTERVAL`, this list is checked for failures that happened
// more than`GRACE_PERIOD` ago. Those that did are reported to the delegate.
NSMutableDictionary<NSString* /* eventId */, DecryptionFailure*> *reportedFailures;
// Event ids of failures that were tracked previously
NSMutableSet<NSString*> *trackedEvents;
// Timer for periodic check
NSTimer *checkFailuresTimer;
}
@end
@implementation DecryptionFailureTracker
+ (instancetype)sharedInstance
{
static DecryptionFailureTracker *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[DecryptionFailureTracker alloc] init];
});
return sharedInstance;
}
- (instancetype)init
{
self = [super init];
if (self)
{
reportedFailures = [NSMutableDictionary dictionary];
trackedEvents = [NSMutableSet set];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(eventDidDecrypt:) name:kMXEventDidDecryptNotification object:nil];
checkFailuresTimer = [NSTimer scheduledTimerWithTimeInterval:CHECK_INTERVAL
target:self
selector:@selector(checkFailures)
userInfo:nil
repeats:YES];
}
return self;
}
- (void)reportUnableToDecryptErrorForEvent:(MXEvent *)event withRoomState:(MXRoomState *)roomState myUser:(NSString *)userId
{
if (reportedFailures[event.eventId] || [trackedEvents containsObject:event.eventId])
{
return;
}
// Filter out "expected" UTDs
// We cannot decrypt messages sent before the user joined the room
MXRoomMember *myUser = [roomState memberWithUserId:userId];
if (!myUser || myUser.membership != MXMembershipJoin)
{
return;
}
DecryptionFailure *decryptionFailure = [[DecryptionFailure alloc] init];
decryptionFailure.failedEventId = event.eventId;
// Categorise the error
switch (event.decryptionError.code)
{
case MXDecryptingErrorUnknownInboundSessionIdCode:
decryptionFailure.reason = DecryptionFailureReason.olmKeysNotSent;
break;
case MXDecryptingErrorOlmCode:
decryptionFailure.reason = DecryptionFailureReason.olmIndexError;
break;
case MXDecryptingErrorEncryptionNotEnabledCode:
case MXDecryptingErrorUnableToDecryptCode:
decryptionFailure.reason = DecryptionFailureReason.unexpected;
break;
default:
decryptionFailure.reason = DecryptionFailureReason.unspecified;
break;
}
reportedFailures[event.eventId] = decryptionFailure;
}
- (void)dispatch
{
[self checkFailures];
}
#pragma mark - Private methods
/**
Mark reported failures that occured before tsNow - GRACE_PERIOD as failures that should be
tracked.
*/
- (void)checkFailures
{
NSTimeInterval tsNow = [NSDate date].timeIntervalSince1970;
NSMutableArray *failuresToTrack = [NSMutableArray array];
for (DecryptionFailure *reportedFailure in reportedFailures.allValues)
{
if (reportedFailure.ts < tsNow - GRACE_PERIOD)
{
[failuresToTrack addObject:reportedFailure];
[reportedFailures removeObjectForKey:reportedFailure.failedEventId];
[trackedEvents addObject:reportedFailure.failedEventId];
}
}
if (_delegate && failuresToTrack.count)
{
// Sort failures by error reason
NSMutableDictionary<NSString*, NSNumber*> *failuresCounts = [NSMutableDictionary dictionary];
for (DecryptionFailure *failure in failuresToTrack)
{
failuresCounts[failure.reason] = @(failuresCounts[failure.reason].unsignedIntegerValue + 1);
}
NSLog(@"[DecryptionFailureTracker] trackFailures: %@", failuresCounts);
[_delegate trackFailures:failuresCounts];
}
}
- (void)eventDidDecrypt:(NSNotification *)notif
{
// Could be an event in the reportedFailures, remove it
MXEvent *event = notif.object;
[reportedFailures removeObjectForKey:event.eventId];
}
@end