Compare commits
2 Commits
master
...
charlesmch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f2a48ec99 | ||
|
|
d857730601 |
@ -34,6 +34,7 @@ PODS:
|
||||
- Mantle/extobjc (= 2.1.0)
|
||||
- Mantle/extobjc (2.1.0)
|
||||
- ProtocolBuffers (1.9.11)
|
||||
- Reachability (3.2)
|
||||
- SAMKeychain (1.5.2)
|
||||
- SignalServiceKit (0.9.0):
|
||||
- '25519'
|
||||
@ -42,6 +43,7 @@ PODS:
|
||||
- CocoaLumberjack
|
||||
- libPhoneNumber-iOS
|
||||
- Mantle
|
||||
- Reachability
|
||||
- SAMKeychain
|
||||
- SocketRocket
|
||||
- TwistedOakCollapsingFutures
|
||||
@ -132,8 +134,9 @@ SPEC CHECKSUMS:
|
||||
libPhoneNumber-iOS: f721ae4d5854bce60934f9fb9b0b28e8e68913cb
|
||||
Mantle: 2fa750afa478cd625a94230fbf1c13462f29395b
|
||||
ProtocolBuffers: d509225eb2ea43d9582a59e94348fcf86e2abd65
|
||||
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
|
||||
SAMKeychain: 1865333198217411f35327e8da61b43de79b635b
|
||||
SignalServiceKit: 2ad8d86da055e24ac3ea0354ec1d4b13251af28f
|
||||
SignalServiceKit: 2b6e0587fc6b4754e053b840083ed4ca9f32459d
|
||||
SocketRocket: dbb1554b8fc288ef8ef370d6285aeca7361be31e
|
||||
SQLCipher: 43d12c0eb9c57fb438749618fc3ce0065509a559
|
||||
TwistedOakCollapsingFutures: f359b90f203e9ab13dfb92c9ff41842a7fe1cd0c
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
308D7DFA789594CEA62740D9 /* libPods-TSKitiOSTestAppTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C0DC1A83C39CBC09FB2405A3 /* libPods-TSKitiOSTestAppTests.a */; };
|
||||
34D99C891F2250FF00D284D6 /* OWSAnalyticsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D99C881F2250FF00D284D6 /* OWSAnalyticsTests.m */; };
|
||||
45046FE01D95A6130015EFF2 /* TSMessagesManagerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 45046FDF1D95A6130015EFF2 /* TSMessagesManagerTest.m */; };
|
||||
450E3C9A1D96DD2600BF4EB6 /* OWSDisappearingMessagesJobTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 450E3C991D96DD2600BF4EB6 /* OWSDisappearingMessagesJobTest.m */; };
|
||||
4516E3E81DD153CC00DC4206 /* TSGroupThreadTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4516E3E71DD153CC00DC4206 /* TSGroupThreadTest.m */; };
|
||||
@ -64,6 +65,7 @@
|
||||
/* Begin PBXFileReference section */
|
||||
1A50A62A8930EE2BC9B8AC11 /* Pods-TSKitiOSTestApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TSKitiOSTestApp.release.xcconfig"; path = "Pods/Target Support Files/Pods-TSKitiOSTestApp/Pods-TSKitiOSTestApp.release.xcconfig"; sourceTree = "<group>"; };
|
||||
31DFDA8F9523F5B15EA2376B /* Pods-TSKitiOSTestApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TSKitiOSTestApp.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TSKitiOSTestApp/Pods-TSKitiOSTestApp.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
34D99C881F2250FF00D284D6 /* OWSAnalyticsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSAnalyticsTests.m; sourceTree = "<group>"; };
|
||||
36DA6C703F99948D553F4E3F /* Pods-TSKitiOSTestAppTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TSKitiOSTestAppTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TSKitiOSTestAppTests/Pods-TSKitiOSTestAppTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
45046FDF1D95A6130015EFF2 /* TSMessagesManagerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TSMessagesManagerTest.m; path = ../../../tests/Messages/TSMessagesManagerTest.m; sourceTree = "<group>"; };
|
||||
450E3C991D96DD2600BF4EB6 /* OWSDisappearingMessagesJobTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSDisappearingMessagesJobTest.m; path = ../../../tests/Messages/OWSDisappearingMessagesJobTest.m; sourceTree = "<group>"; };
|
||||
@ -201,6 +203,7 @@
|
||||
children = (
|
||||
45458B731CC342B600A02153 /* CryptographyTests.m */,
|
||||
45458B741CC342B600A02153 /* MessagePaddingTests.m */,
|
||||
34D99C881F2250FF00D284D6 /* OWSAnalyticsTests.m */,
|
||||
);
|
||||
name = Util;
|
||||
path = ../../../tests/Util;
|
||||
@ -563,6 +566,7 @@
|
||||
45E741B61E5D14E800735842 /* OWSIncomingMessageFinderTest.m in Sources */,
|
||||
452EE6D51D4AC43300E934BA /* OWSOrphanedDataCleanerTest.m in Sources */,
|
||||
450E3C9A1D96DD2600BF4EB6 /* OWSDisappearingMessagesJobTest.m in Sources */,
|
||||
34D99C891F2250FF00D284D6 /* OWSAnalyticsTests.m in Sources */,
|
||||
452EE6CF1D4A754C00E934BA /* TSThreadTest.m in Sources */,
|
||||
45C6A09A1D2F029B007D8AC0 /* TSMessageTest.m in Sources */,
|
||||
453E1FCF1DA8313100DDD7B7 /* OWSMessageSenderTest.m in Sources */,
|
||||
|
||||
24
README.md
24
README.md
@ -1,27 +1,3 @@
|
||||
# SignalServiceKit has Moved
|
||||
|
||||
Per https://github.com/WhisperSystems/Signal-iOS/pull/2341 we've moved
|
||||
the SignalServiceKit codebase into the primary Signal-iOS repository at
|
||||
https://github.com/WhisperSystems/Signal-iOS. As such, this repository
|
||||
will no longer be updated.
|
||||
|
||||
Don't worry - we will continue to make updates to SignalServiceKit, and
|
||||
you can continue to use it in your projects as before. The only
|
||||
difference is where the code lives.
|
||||
|
||||
If you are using Cocoapods, staying up to date is as simple as modifying
|
||||
a line in your Podfile from this:
|
||||
|
||||
```
|
||||
- pod 'SignalServiceKit', git: 'https://github.com/WhisperSystems/SignalServiceKit.git'
|
||||
```
|
||||
|
||||
To this:
|
||||
|
||||
```
|
||||
+ pod 'SignalServiceKit', git: 'https://github.com/WhisperSystems/Signal-iOS.git'
|
||||
```
|
||||
|
||||
# SignalServiceKit
|
||||
|
||||
SignalServiceKit is an Objective-C library for communicating with the Signal
|
||||
|
||||
@ -17,37 +17,15 @@ An Objective-C library for communicating with the Signal messaging service.
|
||||
|
||||
s.homepage = "https://github.com/WhisperSystems/SignalServiceKit"
|
||||
s.license = 'GPLv3'
|
||||
s.author = { "Whisper Systems" => "ios@whispersystems.com" }
|
||||
s.author = { "Frederic Jacobs" => "github@fredericjacobs.com" }
|
||||
s.source = { :git => "https://github.com/WhisperSystems/SignalServiceKit.git", :tag => s.version.to_s }
|
||||
s.social_media_url = 'https://twitter.com/WhipserSystems'
|
||||
|
||||
deprecation_message = <<EOS
|
||||
installing SignalServiceKit via the Signal-iOS repository.
|
||||
|
||||
To get future updates, point your Podfile at the new location. Simply change this:
|
||||
|
||||
pod 'SignalServiceKit', git: 'https://github.com/WhisperSystems/SignalServiceKit.git'
|
||||
|
||||
To this:
|
||||
|
||||
pod 'SignalServiceKit', git: 'https://github.com/WhisperSystems/Signal-iOS.git'
|
||||
|
||||
Sorry for the disruption!
|
||||
|
||||
EOS
|
||||
|
||||
s.deprecated_in_favor_of = deprecation_message
|
||||
s.social_media_url = 'https://twitter.com/FredericJacobs'
|
||||
|
||||
s.platform = :ios, '8.0'
|
||||
#s.ios.deployment_target = '8.0'
|
||||
#s.osx.deployment_target = '10.9'
|
||||
s.requires_arc = true
|
||||
|
||||
# By not including any actual files, upgrading users will see
|
||||
# that they need to point upgrades to the new source at
|
||||
# https://github.com/WhisperSystems/Signal-iOS
|
||||
# Details in README.md
|
||||
s.source_files = 'README.md'
|
||||
s.source_files = 'src/**/*.{h,m,mm}'
|
||||
|
||||
s.resources = ['src/Security/PinningCertificate/textsecure.cer',
|
||||
'src/Security/PinningCertificate/GIAG2.crt']
|
||||
@ -64,4 +42,5 @@ EOS
|
||||
s.dependency 'libPhoneNumber-iOS'
|
||||
s.dependency 'SAMKeychain'
|
||||
s.dependency 'TwistedOakCollapsingFutures'
|
||||
s.dependency 'Reachability'
|
||||
end
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
//
|
||||
|
||||
#import "TSPreKeyManager.h"
|
||||
#import "NSDate+OWS.h"
|
||||
#import "NSURLSessionDataTask+StatusCode.h"
|
||||
#import "OWSIdentityManager.h"
|
||||
#import "TSNetworkManager.h"
|
||||
@ -13,17 +14,17 @@
|
||||
// Time before deletion of signed prekeys (measured in seconds)
|
||||
//
|
||||
// Currently we retain signed prekeys for at least 7 days.
|
||||
static const CGFloat kSignedPreKeysDeletionTime = 7 * 24 * 60 * 60;
|
||||
static const NSTimeInterval kSignedPreKeysDeletionTime = 7 * kDayInterval;
|
||||
|
||||
// Time before rotation of signed prekeys (measured in seconds)
|
||||
//
|
||||
// Currently we rotate signed prekeys every 2 days (48 hours).
|
||||
static const CGFloat kSignedPreKeyRotationTime = 2 * 24 * 60 * 60;
|
||||
static const NSTimeInterval kSignedPreKeyRotationTime = 2 * kDayInterval;
|
||||
|
||||
// How often we check prekey state on app activation.
|
||||
//
|
||||
// Currently we check prekey state every 12 hours.
|
||||
static const CGFloat kPreKeyCheckFrequencySeconds = 12 * 60 * 60;
|
||||
static const NSTimeInterval kPreKeyCheckFrequencySeconds = 12 * kHourInterval;
|
||||
|
||||
// We generate 100 one-time prekeys at a time. We should replenish
|
||||
// whenever ~2/3 of them have been consumed.
|
||||
@ -40,7 +41,7 @@ static const NSUInteger kMaxPrekeyUpdateFailureCount = 5;
|
||||
// before the message sending is disabled.
|
||||
//
|
||||
// Current value is 10 days (240 hours).
|
||||
static const CGFloat kSignedPreKeyUpdateFailureMaxFailureDuration = 10 * 24 * 60 * 60;
|
||||
static const NSTimeInterval kSignedPreKeyUpdateFailureMaxFailureDuration = 10 * kDayInterval;
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@ -180,7 +181,11 @@ static const CGFloat kSignedPreKeyUpdateFailureMaxFailureDuration = 10 * 24 * 60
|
||||
[TSPreKeyManager clearPreKeyUpdateFailureCount];
|
||||
}
|
||||
failure:^(NSURLSessionDataTask *task, NSError *error) {
|
||||
OWSAnalyticsError(@"Prekey update failed (%@): %@", description, error);
|
||||
if (modeCopy == RefreshPreKeysMode_SignedAndOneTime) {
|
||||
OWSProdErrorWNSError(@"error_prekeys_update_failed_signed_and_onetime", error);
|
||||
} else {
|
||||
OWSProdErrorWNSError(@"error_prekeys_update_failed_just_signed", error);
|
||||
}
|
||||
|
||||
// Mark the prekeys as _NOT_ checked on failure.
|
||||
[self markPreKeysAsNotChecked];
|
||||
@ -387,10 +392,12 @@ static const CGFloat kSignedPreKeyUpdateFailureMaxFailureDuration = 10 * 24 * 60
|
||||
}
|
||||
}
|
||||
|
||||
OWSAnalyticsInfo(@"%@ Deleting old signed prekey: %@, wasAcceptedByService: %d",
|
||||
self.tag,
|
||||
[dateFormatter stringFromDate:signedPrekey.generatedAt],
|
||||
signedPrekey.wasAcceptedByService);
|
||||
OWSProdInfoWParams(@"prekeys_deleted_old_signed_prekey", ^{
|
||||
return (@{
|
||||
@"generated" : [dateFormatter stringFromDate:signedPrekey.generatedAt],
|
||||
@"accepted" : @(signedPrekey.wasAcceptedByService),
|
||||
});
|
||||
});
|
||||
|
||||
oldSignedPreKeyCount--;
|
||||
[storageManager removeSignedPreKey:signedPrekey.Id];
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
// Created by Michael Kirk on 9/23/16.
|
||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSDisappearingMessagesConfiguration.h"
|
||||
#import "NSDate+OWS.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
const uint32_t OWSDisappearingMessagesConfigurationDefaultExpirationDuration = 60 * 60 * 24; // 1 day.
|
||||
// 1 day.
|
||||
const uint32_t OWSDisappearingMessagesConfigurationDefaultExpirationDuration = kDayInterval;
|
||||
|
||||
@interface OWSDisappearingMessagesConfiguration ()
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
#import "OWSDisappearingMessagesJob.h"
|
||||
#import "ContactsManagerProtocol.h"
|
||||
#import "NSDate+OWS.h"
|
||||
#import "NSDate+millisecondTimeStamp.h"
|
||||
#import "NSTimer+OWS.h"
|
||||
#import "OWSDisappearingConfigurationUpdateInfoMessage.h"
|
||||
@ -300,7 +301,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (NSTimeInterval)maxDelaySeconds
|
||||
{
|
||||
// Don't run less often than once per N minutes.
|
||||
return 5 * 60.f;
|
||||
return 5 * kMinuteInterval;
|
||||
}
|
||||
|
||||
// Waits the maximum amount of time to run again.
|
||||
|
||||
@ -836,7 +836,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
||||
AssertIsOnSendingQueue();
|
||||
|
||||
if ([TSPreKeyManager isAppLockedDueToPreKeyUpdateFailures]) {
|
||||
OWSAnalyticsError(@"Message send failed due to prekey update failures");
|
||||
OWSProdError(@"message_send_error_failed_due_to_prekey_update_failures");
|
||||
|
||||
// Retry prekey update every time user tries to send a message while app
|
||||
// is disabled due to prekey update failures.
|
||||
@ -878,7 +878,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
||||
// We expect it to happen whenever Bob reinstalls, and Alice messages Bob before
|
||||
// she can pull down his latest identity.
|
||||
// If it's happening a lot, we should rethink our profile fetching strategy.
|
||||
OWSAnalyticsInfo(@"Message send failed due to untrusted key.");
|
||||
OWSProdInfo(@"message_send_error_failed_due_to_untrusted_key");
|
||||
|
||||
NSString *localizedErrorDescriptionFormat
|
||||
= NSLocalizedString(@"FAILED_SENDING_BECAUSE_UNTRUSTED_IDENTITY_KEY",
|
||||
|
||||
@ -42,6 +42,23 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#define kOWSProdAssertParameterEnvelopeIsLegacy @"envelope_is_legacy"
|
||||
#define kOWSProdAssertParameterEnvelopeDescription @"envelope_description"
|
||||
#define kOWSProdAssertParameterEnvelopeEncryptedLength @"encrypted_length"
|
||||
|
||||
#define AnalyticsParametersFromEnvelope(__envelope) \
|
||||
^{ \
|
||||
NSData *__encryptedData = __envelope.hasContent ? __envelope.content : __envelope.legacyMessage; \
|
||||
return (@{ \
|
||||
kOWSProdAssertParameterEnvelopeIsLegacy : @(__envelope.hasLegacyMessage), \
|
||||
kOWSProdAssertParameterEnvelopeDescription : [self descriptionForEnvelopeType:__envelope], \
|
||||
kOWSProdAssertParameterEnvelopeEncryptedLength : @(__encryptedData.length), \
|
||||
}); \
|
||||
}
|
||||
|
||||
#define OWSProdErrorWEnvelope(__analyticsEventName, __envelope) \
|
||||
OWSProdErrorWParams(__analyticsEventName, AnalyticsParametersFromEnvelope(__envelope))
|
||||
|
||||
@interface TSMessagesManager ()
|
||||
|
||||
@property (nonatomic, readonly) id<OWSCallMessageHandler> callMessageHandler;
|
||||
@ -135,39 +152,37 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#pragma mark - Debugging
|
||||
|
||||
- (NSString *)descriptionForEnvelopeType:(OWSSignalServiceProtosEnvelope *)envelope
|
||||
{
|
||||
OWSAssert(envelope != nil);
|
||||
switch (envelope.type) {
|
||||
case OWSSignalServiceProtosEnvelopeTypeReceipt:
|
||||
return @"DeliveryReceipt";
|
||||
case OWSSignalServiceProtosEnvelopeTypeUnknown:
|
||||
// Shouldn't happen
|
||||
OWSProdFail(@"message_manager_error_envelope_type_unknown");
|
||||
return @"Unknown";
|
||||
case OWSSignalServiceProtosEnvelopeTypeCiphertext:
|
||||
return @"SignalEncryptedMessage";
|
||||
case OWSSignalServiceProtosEnvelopeTypeKeyExchange:
|
||||
// Unsupported
|
||||
OWSProdFail(@"message_manager_error_envelope_type_key_exchange");
|
||||
return @"KeyExchange";
|
||||
case OWSSignalServiceProtosEnvelopeTypePrekeyBundle:
|
||||
return @"PreKeyEncryptedMessage";
|
||||
default:
|
||||
// Shouldn't happen
|
||||
OWSProdFail(@"message_manager_error_envelope_type_other");
|
||||
return @"Other";
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)descriptionForEnvelope:(OWSSignalServiceProtosEnvelope *)envelope
|
||||
{
|
||||
OWSAssert(envelope != nil);
|
||||
NSString *envelopeType;
|
||||
switch (envelope.type) {
|
||||
case OWSSignalServiceProtosEnvelopeTypeReceipt:
|
||||
envelopeType = @"DeliveryReceipt";
|
||||
break;
|
||||
case OWSSignalServiceProtosEnvelopeTypeUnknown:
|
||||
// Shouldn't happen
|
||||
OWSAssert(NO);
|
||||
envelopeType = @"Unknown";
|
||||
break;
|
||||
case OWSSignalServiceProtosEnvelopeTypeCiphertext:
|
||||
envelopeType = @"SignalEncryptedMessage";
|
||||
break;
|
||||
case OWSSignalServiceProtosEnvelopeTypeKeyExchange:
|
||||
// Unsupported
|
||||
OWSAssert(NO);
|
||||
envelopeType = @"KeyExchange";
|
||||
break;
|
||||
case OWSSignalServiceProtosEnvelopeTypePrekeyBundle:
|
||||
envelopeType = @"PreKeyEncryptedMessage";
|
||||
break;
|
||||
default:
|
||||
// Shouldn't happen
|
||||
OWSAssert(NO);
|
||||
envelopeType = @"Other";
|
||||
break;
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"<Envelope type: %@, source: %@.%d, timestamp: %llu content.length: %lu />",
|
||||
envelopeType,
|
||||
[self descriptionForEnvelopeType:envelope],
|
||||
envelope.source,
|
||||
(unsigned int)envelope.sourceDevice,
|
||||
envelope.timestamp,
|
||||
@ -189,7 +204,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
} else if (content.hasNullMessage) {
|
||||
return [NSString stringWithFormat:@"<NullMessage: %@ />", content.nullMessage];
|
||||
} else {
|
||||
OWSAssert(NO);
|
||||
// Don't fire an analytics event; if we ever add a new content type, we'd generate a ton of
|
||||
// analytics traffic.
|
||||
OWSFail(@"Unknown content type.");
|
||||
return @"UnknownContent";
|
||||
}
|
||||
}
|
||||
@ -233,9 +250,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
[description appendString:@"ContactRequest"];
|
||||
} else if (syncMessage.request.type == OWSSignalServiceProtosSyncMessageRequestTypeGroups) {
|
||||
[description appendString:@"GroupRequest"];
|
||||
} else if (syncMessage.request.type == OWSSignalServiceProtosSyncMessageRequestTypeBlocked) {
|
||||
[description appendString:@"BlockedRequest"];
|
||||
} else {
|
||||
// Shouldn't happen
|
||||
OWSAssert(NO);
|
||||
OWSFail(@"Unknown sync message request type");
|
||||
[description appendString:@"UnknownRequest"];
|
||||
}
|
||||
} else if (syncMessage.hasBlocked) {
|
||||
@ -248,7 +267,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
[description appendString:verifiedString];
|
||||
} else {
|
||||
// Shouldn't happen
|
||||
OWSAssert(NO);
|
||||
OWSFail(@"Unknown sync message type");
|
||||
[description appendString:@"Unknown"];
|
||||
}
|
||||
|
||||
@ -295,6 +314,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
envelope.source,
|
||||
(unsigned int)envelope.sourceDevice,
|
||||
error);
|
||||
OWSProdError(@"message_manager_error_could_not_handle_secure_message");
|
||||
}
|
||||
completion();
|
||||
}];
|
||||
@ -312,6 +332,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
envelope.source,
|
||||
(unsigned int)envelope.sourceDevice,
|
||||
error);
|
||||
OWSProdError(@"message_manager_error_could_not_handle_prekey_bundle");
|
||||
}
|
||||
completion();
|
||||
}];
|
||||
@ -336,6 +357,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
DDLogError(@"Received an incorrectly formatted protocol buffer: %@", exception.debugDescription);
|
||||
OWSProdFailWNSException(@"message_manager_error_invalid_protocol_message", exception);
|
||||
}
|
||||
|
||||
completion();
|
||||
@ -367,15 +389,18 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
NSData *encryptedData
|
||||
= messageEnvelope.hasContent ? messageEnvelope.content : messageEnvelope.legacyMessage;
|
||||
if (!encryptedData) {
|
||||
DDLogError(@"Skipping message envelope which had no encrypted data.");
|
||||
OWSProdFail(@"message_manager_error_message_envelope_has_no_content");
|
||||
completion(nil);
|
||||
return;
|
||||
}
|
||||
|
||||
NSUInteger kMaxEncryptedDataLength = 250 * 1024;
|
||||
if (encryptedData.length > kMaxEncryptedDataLength) {
|
||||
DDLogError(@"Skipping message envelope with oversize encrypted data: %lu.",
|
||||
(unsigned long)encryptedData.length);
|
||||
OWSProdErrorWParams(@"message_manager_error_oversize_message", ^{
|
||||
return (@{
|
||||
@"message_size" : @(encryptedData.length),
|
||||
});
|
||||
});
|
||||
completion(nil);
|
||||
return;
|
||||
}
|
||||
@ -424,7 +449,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
// DEPRECATED - Remove after all clients have been upgraded.
|
||||
NSData *encryptedData = preKeyEnvelope.hasContent ? preKeyEnvelope.content : preKeyEnvelope.legacyMessage;
|
||||
if (!encryptedData) {
|
||||
DDLogError(@"Skipping message envelope which had no encrypted data");
|
||||
OWSProdFail(@"message_manager_error_prekey_bundle_envelope_has_no_content");
|
||||
completion(nil);
|
||||
return;
|
||||
}
|
||||
@ -816,7 +841,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
NSData *groupId = dataMessage.hasGroup ? dataMessage.group.id : nil;
|
||||
if (!groupId) {
|
||||
OWSAssert(groupId);
|
||||
OWSFail(@"Group info request is missing group id.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1009,24 +1034,30 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
__block TSErrorMessage *errorMessage;
|
||||
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
if ([exception.name isEqualToString:NoSessionException]) {
|
||||
OWSProdErrorWEnvelope(@"message_manager_error_no_session", envelope);
|
||||
errorMessage = [TSErrorMessage missingSessionWithEnvelope:envelope withTransaction:transaction];
|
||||
} else if ([exception.name isEqualToString:InvalidKeyException]) {
|
||||
OWSProdErrorWEnvelope(@"message_manager_error_invalid_key", envelope);
|
||||
errorMessage = [TSErrorMessage invalidKeyExceptionWithEnvelope:envelope withTransaction:transaction];
|
||||
} else if ([exception.name isEqualToString:InvalidKeyIdException]) {
|
||||
OWSProdErrorWEnvelope(@"message_manager_error_invalid_key_id", envelope);
|
||||
errorMessage = [TSErrorMessage invalidKeyExceptionWithEnvelope:envelope withTransaction:transaction];
|
||||
} else if ([exception.name isEqualToString:DuplicateMessageException]) {
|
||||
// Duplicate messages are dismissed
|
||||
return;
|
||||
} else if ([exception.name isEqualToString:InvalidVersionException]) {
|
||||
OWSProdErrorWEnvelope(@"message_manager_error_invalid_message_version", envelope);
|
||||
errorMessage = [TSErrorMessage invalidVersionWithEnvelope:envelope withTransaction:transaction];
|
||||
} else if ([exception.name isEqualToString:UntrustedIdentityKeyException]) {
|
||||
// Should no longer get here, since we now record the new identity for incoming messages.
|
||||
OWSProdErrorWEnvelope(@"message_manager_error_untrusted_identity_key_exception", envelope);
|
||||
OWSFail(@"%@ Failed to trust identity on incoming message from: %@.%d",
|
||||
self.tag,
|
||||
envelope.source,
|
||||
envelope.sourceDevice);
|
||||
return;
|
||||
} else {
|
||||
OWSProdErrorWEnvelope(@"message_manager_error_corrupt_message", envelope);
|
||||
errorMessage = [TSErrorMessage corruptedMessageWithEnvelope:envelope withTransaction:transaction];
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
//
|
||||
|
||||
#import "OWSOrphanedDataCleaner.h"
|
||||
#import "NSDate+OWS.h"
|
||||
#import "TSAttachmentStream.h"
|
||||
#import "TSInteraction.h"
|
||||
#import "TSMessage.h"
|
||||
@ -134,7 +135,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
#ifdef SSK_BUILDING_FOR_TESTS
|
||||
const NSTimeInterval kMinimumOrphanAge = 0.f;
|
||||
#else
|
||||
const NSTimeInterval kMinimumOrphanAge = 15 * 60.f;
|
||||
const NSTimeInterval kMinimumOrphanAge = 15 * kMinuteInterval;
|
||||
#endif
|
||||
|
||||
if (!shouldCleanup) {
|
||||
|
||||
@ -117,14 +117,14 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
|
||||
// The best we can try to do is to discard the current database
|
||||
// and behave like a clean install.
|
||||
|
||||
OWSAnalyticsCritical(@"Could not load database");
|
||||
OWSProdError(@"storage_error_could_not_load_database");
|
||||
|
||||
// Try to reset app by deleting database.
|
||||
// Disabled resetting storage until we have better data on why this happens.
|
||||
// [self resetSignalStorage];
|
||||
|
||||
if (![self tryToLoadDatabase]) {
|
||||
OWSAnalyticsCritical(@"Could not load database (second attempt)");
|
||||
OWSProdError(@"storage_error_could_not_load_database_second_attempt");
|
||||
|
||||
[NSException raise:TSStorageManagerExceptionNameNoDatabase format:@"Failed to initialize database."];
|
||||
}
|
||||
@ -353,8 +353,7 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
|
||||
|
||||
BOOL shouldHavePassword = [NSFileManager.defaultManager fileExistsAtPath:[self dbPath]];
|
||||
if (shouldHavePassword) {
|
||||
OWSAnalyticsCriticalWithParameters(@"Could not retrieve database password from keychain",
|
||||
@{ @"ErrorCode" : @(keyFetchError.code) });
|
||||
OWSProdErrorWNSError(@"storage_error_could_not_load_database_second_attempt", keyFetchError);
|
||||
}
|
||||
|
||||
// Try to reset app by deleting database.
|
||||
|
||||
14
src/Util/NSDate+OWS.h
Executable file
14
src/Util/NSDate+OWS.h
Executable file
@ -0,0 +1,14 @@
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// These NSTimeInterval constants provide simplified durations for readability.
|
||||
#define kMinuteInterval 60
|
||||
#define kHourInterval (60 * kMinuteInterval)
|
||||
#define kDayInterval (24 * kHourInterval)
|
||||
#define kWeekInterval (7 * kDayInterval)
|
||||
#define kMonthInterval (30 * kDayInterval)
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -1,9 +1,10 @@
|
||||
//
|
||||
// OWSAnalytics.h
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// TODO: We probably don't need all of these levels.
|
||||
typedef NS_ENUM(NSUInteger, OWSAnalyticsSeverity) {
|
||||
OWSAnalyticsSeverityDebug = 0,
|
||||
OWSAnalyticsSeverityInfo = 1,
|
||||
@ -32,41 +33,139 @@ typedef NS_ENUM(NSUInteger, OWSAnalyticsSeverity) {
|
||||
@interface OWSAnalytics : NSObject
|
||||
|
||||
// description: A non-empty string without any leading whitespace.
|
||||
// This should conform to our analytics event naming conventions.
|
||||
// "category_event_name", e.g. "database_error_no_database_file_found".
|
||||
// parameters: Optional.
|
||||
// If non-nil, the keys should all be non-empty NSStrings.
|
||||
// Values should be NSStrings or NSNumbers.
|
||||
+ (void)logEvent:(NSString *)description
|
||||
+ (void)logEvent:(NSString *)eventName
|
||||
severity:(OWSAnalyticsSeverity)severity
|
||||
parameters:(NSDictionary *)parameters
|
||||
location:(const char *)location;
|
||||
parameters:(nullable NSDictionary *)parameters
|
||||
location:(const char *)location
|
||||
line:(int)line;
|
||||
|
||||
+ (void)appLaunchDidBegin;
|
||||
|
||||
+ (void)appLaunchDidComplete;
|
||||
|
||||
@end
|
||||
|
||||
#define OWSAnalyticsLogEvent(severityLevel, frmt, ...) \
|
||||
[OWSAnalytics logEvent:[NSString stringWithFormat:frmt, ##__VA_ARGS__] \
|
||||
severity:severityLevel \
|
||||
parameters:nil \
|
||||
location:__PRETTY_FUNCTION__];
|
||||
typedef NSDictionary<NSString *, id> *_Nonnull (^OWSProdAssertParametersBlock)();
|
||||
|
||||
#define OWSAnalyticsLogEventWithParameters(severityLevel, frmt, params) \
|
||||
[OWSAnalytics logEvent:frmt severity:severityLevel parameters:params location:__PRETTY_FUNCTION__];
|
||||
#define kOWSProdAssertParameterDescription @"description"
|
||||
#define kOWSProdAssertParameterNSErrorDomain @"nserror_domain"
|
||||
#define kOWSProdAssertParameterNSErrorCode @"nserror_code"
|
||||
#define kOWSProdAssertParameterNSErrorDescription @"nserror_description"
|
||||
#define kOWSProdAssertParameterNSExceptionName @"nsexception_name"
|
||||
#define kOWSProdAssertParameterNSExceptionReason @"nsexception_reason"
|
||||
#define kOWSProdAssertParameterNSExceptionClassName @"nsexception_classname"
|
||||
|
||||
#define OWSAnalyticsDebug(frmt, ...) OWSAnalyticsLogEvent(OWSAnalyticsSeverityDebug, frmt, ##__VA_ARGS__)
|
||||
#define OWSAnalyticsDebugWithParameters(description, params) \
|
||||
OWSAnalyticsLogEventWithParameters(OWSAnalyticsSeverityDebug, description, params)
|
||||
// These methods should be used to assert errors for which we want to fire analytics events.
|
||||
//
|
||||
// In production, returns __Value, the assert value, so that we can handle this case.
|
||||
// In debug builds, asserts.
|
||||
//
|
||||
// parametersBlock is of type OWSProdAssertParametersBlock.
|
||||
// The "C" variants (e.g. OWSProdAssert() vs. OWSProdCAssert() should be used in free functions,
|
||||
// where there is no self.
|
||||
//
|
||||
#define OWSProdAssertWParamsTemplate(__value, __analyticsEventName, __parametersBlock, __assertMacro) \
|
||||
{ \
|
||||
if (!(BOOL)(__value)) { \
|
||||
NSDictionary<NSString *, id> *__eventParameters = (__parametersBlock ? __parametersBlock() : nil); \
|
||||
[DDLog flushLog]; \
|
||||
[OWSAnalytics logEvent:__analyticsEventName \
|
||||
severity:OWSAnalyticsSeverityError \
|
||||
parameters:__eventParameters \
|
||||
location:__PRETTY_FUNCTION__ \
|
||||
line:__LINE__]; \
|
||||
} \
|
||||
__assertMacro(__value); \
|
||||
return (BOOL)(__value); \
|
||||
}
|
||||
|
||||
#define OWSAnalyticsInfo(frmt, ...) OWSAnalyticsLogEvent(OWSAnalyticsSeverityInfo, frmt, ##__VA_ARGS__)
|
||||
#define OWSAnalyticsInfoWithParameters(description, params) \
|
||||
OWSAnalyticsLogEventWithParameters(OWSAnalyticsSeverityInfo, description, params)
|
||||
#define OWSProdAssertWParams(__value, __analyticsEventName, __parametersBlock) \
|
||||
OWSProdAssertWParamsTemplate(__value, __analyticsEventName, __parametersBlock, OWSAssert)
|
||||
|
||||
#define OWSAnalyticsWarn(frmt, ...) OWSAnalyticsLogEvent(OWSAnalyticsSeverityWarn, frmt, ##__VA_ARGS__)
|
||||
#define OWSAnalyticsWarnWithParameters(description, params) \
|
||||
OWSAnalyticsLogEventWithParameters(OWSAnalyticsSeverityWarn, description, params)
|
||||
#define OWSProdCAssertWParams(__value, __analyticsEventName, __parametersBlock) \
|
||||
OWSProdAssertWParamsTemplate(__value, __analyticsEventName, __parametersBlock, OWSCAssert)
|
||||
|
||||
#define OWSAnalyticsError(frmt, ...) OWSAnalyticsLogEvent(OWSAnalyticsSeverityError, frmt, ##__VA_ARGS__)
|
||||
#define OWSAnalyticsErrorWithParameters(description, params) \
|
||||
OWSAnalyticsLogEventWithParameters(OWSAnalyticsSeverityError, description, params)
|
||||
#define OWSProdAssert(__value, __analyticsEventName) OWSProdAssertWParams(__value, __analyticsEventName, nil)
|
||||
|
||||
#define OWSAnalyticsCritical(frmt, ...) OWSAnalyticsLogEvent(OWSAnalyticsSeverityCritical, frmt, ##__VA_ARGS__)
|
||||
#define OWSAnalyticsCriticalWithParameters(description, params) \
|
||||
OWSAnalyticsLogEventWithParameters(OWSAnalyticsSeverityCritical, description, params)
|
||||
#define OWSProdCAssert(__value, __analyticsEventName) OWSProdCAssertWParams(__value, __analyticsEventName, nil)
|
||||
|
||||
#define OWSProdFailWParamsTemplate(__analyticsEventName, __parametersBlock, __failMacro) \
|
||||
{ \
|
||||
NSDictionary<NSString *, id> *__eventParameters \
|
||||
= (__parametersBlock ? ((OWSProdAssertParametersBlock)__parametersBlock)() : nil); \
|
||||
[OWSAnalytics logEvent:__analyticsEventName \
|
||||
severity:OWSAnalyticsSeverityCritical \
|
||||
parameters:__eventParameters \
|
||||
location:__PRETTY_FUNCTION__ \
|
||||
line:__LINE__]; \
|
||||
__failMacro(__analyticsEventName); \
|
||||
}
|
||||
|
||||
#define OWSProdFailWParams(__analyticsEventName, __parametersBlock) \
|
||||
OWSProdFailWParamsTemplate(__analyticsEventName, __parametersBlock, OWSFail)
|
||||
#define OWSProdCFailWParams(__analyticsEventName, __parametersBlock) \
|
||||
OWSProdFailWParamsTemplate(__analyticsEventName, __parametersBlock, OWSCFail)
|
||||
|
||||
#define OWSProdFail(__analyticsEventName) OWSProdFailWParams(__analyticsEventName, nil)
|
||||
|
||||
#define OWSProdCFail(__analyticsEventName) OWSProdCFailWParams(__analyticsEventName, nil)
|
||||
|
||||
#define AnalyticsParametersFromNSError(__nserror) \
|
||||
^{ \
|
||||
return (@{ \
|
||||
kOWSProdAssertParameterNSErrorDomain : __nserror.domain, \
|
||||
kOWSProdAssertParameterNSErrorCode : @(__nserror.code), \
|
||||
kOWSProdAssertParameterNSErrorDescription : __nserror.description, \
|
||||
}); \
|
||||
}
|
||||
|
||||
#define AnalyticsParametersFromNSException(__exception) \
|
||||
^{ \
|
||||
return (@{ \
|
||||
kOWSProdAssertParameterNSExceptionName : __exception.name, \
|
||||
kOWSProdAssertParameterNSExceptionReason : __exception.reason, \
|
||||
kOWSProdAssertParameterNSExceptionClassName : NSStringFromClass([__exception class]), \
|
||||
}); \
|
||||
}
|
||||
|
||||
#define OWSProdFailWNSError(__analyticsEventName, __nserror) \
|
||||
OWSProdFailWParams(__analyticsEventName, AnalyticsParametersFromNSError(__nserror))
|
||||
|
||||
#define OWSProdFailWNSException(__analyticsEventName, __exception) \
|
||||
OWSProdFailWParams(__analyticsEventName, AnalyticsParametersFromNSException(__exception))
|
||||
|
||||
#define OWSProdEventWParams(__severityLevel, __analyticsEventName, __parametersBlock) \
|
||||
{ \
|
||||
NSDictionary<NSString *, id> *__eventParameters \
|
||||
= (__parametersBlock ? ((OWSProdAssertParametersBlock)__parametersBlock)() : nil); \
|
||||
[OWSAnalytics logEvent:__analyticsEventName \
|
||||
severity:OWSAnalyticsSeverityCritical \
|
||||
parameters:__eventParameters \
|
||||
location:__PRETTY_FUNCTION__ \
|
||||
line:__LINE__]; \
|
||||
}
|
||||
|
||||
#define OWSProdErrorWParams(__analyticsEventName, __parametersBlock) \
|
||||
OWSProdEventWParams(OWSAnalyticsSeverityCritical, __analyticsEventName, __parametersBlock)
|
||||
|
||||
#define OWSProdError(__analyticsEventName) OWSProdEventWParams(OWSAnalyticsSeverityCritical, __analyticsEventName, nil)
|
||||
|
||||
#define OWSProdInfoWParams(__analyticsEventName, __parametersBlock) \
|
||||
OWSProdEventWParams(OWSAnalyticsSeverityInfo, __analyticsEventName, __parametersBlock)
|
||||
|
||||
#define OWSProdInfo(__analyticsEventName) OWSProdEventWParams(OWSAnalyticsSeverityInfo, __analyticsEventName, nil)
|
||||
|
||||
#define OWSProdCFail(__analyticsEventName) OWSProdCFailWParams(__analyticsEventName, nil)
|
||||
|
||||
#define OWSProdErrorWNSError(__analyticsEventName, __nserror) \
|
||||
OWSProdErrorWParams(__analyticsEventName, AnalyticsParametersFromNSError(__nserror))
|
||||
|
||||
#define OWSProdErrorWNSException(__analyticsEventName, __exception) \
|
||||
OWSProdErrorWParams(__analyticsEventName, AnalyticsParametersFromNSException(__exception))
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@ -1,12 +1,53 @@
|
||||
//
|
||||
// OWSAnalytics.m
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <CocoaLumberjack/CocoaLumberjack.h>
|
||||
|
||||
#import "OWSAnalytics.h"
|
||||
#import "AppVersion.h"
|
||||
#import "TSStorageManager.h"
|
||||
#import <CocoaLumberjack/CocoaLumberjack.h>
|
||||
#import <Reachability/Reachability.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
|
||||
#define NO_SIGNAL_ANALYTICS
|
||||
|
||||
#else
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
// TODO: Disable analytics for debug builds.
|
||||
//#define NO_SIGNAL_ANALYTICS
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
NSString *const kOWSAnalytics_EventsCollection = @"kOWSAnalytics_EventsCollection";
|
||||
|
||||
NSString *const kOWSAnalytics_Collection = @"kOWSAnalytics_Collection";
|
||||
NSString *const kOWSAnalytics_KeyLaunchCount = @"kOWSAnalytics_KeyLaunchCount";
|
||||
NSString *const kOWSAnalytics_KeyLaunchCompleteCount = @"kOWSAnalytics_KeyLaunchCompleteCount";
|
||||
|
||||
// Percentage of analytics events to discard. 0 <= x <= 100.
|
||||
const int kOWSAnalytics_DiscardFrequency = 0;
|
||||
|
||||
@interface OWSAnalytics ()
|
||||
|
||||
@property (nonatomic, readonly) TSStorageManager *storageManager;
|
||||
@property (nonatomic, readonly) Reachability *reachability;
|
||||
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
|
||||
|
||||
@property (atomic) BOOL hasRequestInFlight;
|
||||
|
||||
@property (atomic) NSNumber *launchCount;
|
||||
@property (atomic) NSNumber *launchCompleteCount;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation OWSAnalytics
|
||||
|
||||
@ -16,27 +57,223 @@
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [self new];
|
||||
// TODO: If we ever log these events to disk,
|
||||
// we may want to protect these file(s) like TSStorageManager.
|
||||
instance = [[self alloc] initDefault];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (void)logEvent:(NSString *)description
|
||||
severity:(OWSAnalyticsSeverity)severity
|
||||
parameters:(NSDictionary *)parameters
|
||||
location:(const char *)location
|
||||
- (instancetype)initDefault
|
||||
{
|
||||
TSStorageManager *storageManager = [TSStorageManager sharedManager];
|
||||
|
||||
[[self sharedInstance] logEvent:description severity:severity parameters:parameters location:location];
|
||||
return [self initWithStorageManager:storageManager];
|
||||
}
|
||||
|
||||
- (void)logEvent:(NSString *)description
|
||||
severity:(OWSAnalyticsSeverity)severity
|
||||
parameters:(NSDictionary *)parameters
|
||||
location:(const char *)location
|
||||
- (instancetype)initWithStorageManager:(TSStorageManager *)storageManager
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
OWSAssert(storageManager);
|
||||
|
||||
_storageManager = storageManager;
|
||||
// Use a newDatabaseConnection so as not to block other reads in the launch path.
|
||||
_dbConnection = storageManager.newDatabaseConnection;
|
||||
_reachability = [Reachability reachabilityForInternetConnection];
|
||||
|
||||
[self observeNotifications];
|
||||
|
||||
OWSSingletonAssert();
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)observeNotifications
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(reachabilityChanged)
|
||||
name:kReachabilityChangedNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(applicationDidBecomeActive)
|
||||
name:UIApplicationDidBecomeActiveNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)reachabilityChanged
|
||||
{
|
||||
OWSAssert([NSThread isMainThread]);
|
||||
|
||||
[self tryToSyncEvents];
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive
|
||||
{
|
||||
OWSAssert([NSThread isMainThread]);
|
||||
|
||||
[self tryToSyncEvents];
|
||||
}
|
||||
|
||||
- (void)tryToSyncEvents
|
||||
{
|
||||
// Don't try to sync if:
|
||||
//
|
||||
// * There's no network available.
|
||||
// * There's already a sync request in flight.
|
||||
if (!self.reachability.isReachable || self.hasRequestInFlight) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_async(self.serialQueue, ^{
|
||||
__block NSString *firstEventKey = nil;
|
||||
__block NSDictionary *firstEventDictionary = nil;
|
||||
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
// Take any event. We don't need to deliver them in any particular order.
|
||||
[transaction enumerateKeysInCollection:kOWSAnalytics_EventsCollection
|
||||
usingBlock:^(NSString *key, BOOL *_Nonnull stop) {
|
||||
firstEventKey = key;
|
||||
*stop = YES;
|
||||
}];
|
||||
if (!firstEventKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
firstEventDictionary = [transaction objectForKey:firstEventKey inCollection:kOWSAnalytics_EventsCollection];
|
||||
OWSAssert(firstEventDictionary);
|
||||
OWSAssert([firstEventDictionary isKindOfClass:[NSDictionary class]]);
|
||||
}];
|
||||
|
||||
if (!firstEventDictionary) {
|
||||
return;
|
||||
}
|
||||
|
||||
DDLogDebug(@"%@ trying to deliver event: %@", self.tag, firstEventKey);
|
||||
self.hasRequestInFlight = YES;
|
||||
// Until we integrate with an analytics platform, behave as though all event delivery succeeds.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.hasRequestInFlight = NO;
|
||||
|
||||
BOOL success = YES;
|
||||
if (success) {
|
||||
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
// Remove from queue.
|
||||
[transaction removeObjectForKey:firstEventKey inCollection:kOWSAnalytics_EventsCollection];
|
||||
}];
|
||||
}
|
||||
|
||||
// Wait a second between network requests / retries.
|
||||
dispatch_after(
|
||||
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self tryToSyncEvents];
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)serialQueue
|
||||
{
|
||||
static dispatch_queue_t queue = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
queue = dispatch_queue_create("org.whispersystems.analytics.serial", DISPATCH_QUEUE_SERIAL);
|
||||
});
|
||||
return queue;
|
||||
}
|
||||
|
||||
- (NSDictionary<NSString *, id> *)eventSuperProperties
|
||||
{
|
||||
NSMutableDictionary<NSString *, id> *result = [NSMutableDictionary new];
|
||||
if (AppVersion.instance.firstAppVersion) {
|
||||
result[@"app_version_first"] = AppVersion.instance.firstAppVersion;
|
||||
}
|
||||
if (AppVersion.instance.lastAppVersion) {
|
||||
result[@"app_version_last"] = AppVersion.instance.lastAppVersion;
|
||||
}
|
||||
if (AppVersion.instance.currentAppVersion) {
|
||||
result[@"app_version_current"] = AppVersion.instance.currentAppVersion;
|
||||
}
|
||||
NSNumber *launchCount = self.launchCount;
|
||||
if (launchCount) {
|
||||
result[@"launch_count"] = @([self orderOfMagnitudeOf:launchCount.longValue]);
|
||||
}
|
||||
// TODO: Order of magnitude: thread count.
|
||||
// TODO: Order of magnitude: total message count.
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (long)orderOfMagnitudeOf:(long)value
|
||||
{
|
||||
return [OWSAnalytics orderOfMagnitudeOf:value];
|
||||
}
|
||||
|
||||
+ (long)orderOfMagnitudeOf:(long)value
|
||||
{
|
||||
if (value <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return (long)round(pow(10, floor(log10(value))));
|
||||
}
|
||||
|
||||
- (void)addEvent:(NSString *)eventName properties:(NSDictionary *)properties
|
||||
{
|
||||
OWSAssert(eventName.length > 0);
|
||||
|
||||
uint32_t discardValue = arc4random_uniform(101);
|
||||
if (discardValue < kOWSAnalytics_DiscardFrequency) {
|
||||
DDLogVerbose(@"Discarding event: %@", eventName);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef NO_SIGNAL_ANALYTICS
|
||||
dispatch_async(self.serialQueue, ^{
|
||||
// Add super properties.
|
||||
NSMutableDictionary *eventProperties = (properties ? [properties mutableCopy] : [NSMutableDictionary new]);
|
||||
[eventProperties addEntriesFromDictionary:self.eventSuperProperties];
|
||||
|
||||
NSDictionary *eventDictionary = [eventProperties copy];
|
||||
OWSAssert(eventDictionary);
|
||||
NSString *eventKey = [NSUUID UUID].UUIDString;
|
||||
DDLogDebug(@"%@ enqueuing event: %@", self.tag, eventKey);
|
||||
|
||||
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
const int kMaxQueuedEvents = 5000;
|
||||
if ([transaction numberOfKeysInCollection:kOWSAnalytics_EventsCollection] > kMaxQueuedEvents) {
|
||||
DDLogError(@"%@ Event queue overflow.", self.tag);
|
||||
return;
|
||||
}
|
||||
|
||||
[transaction setObject:eventDictionary forKey:eventKey inCollection:kOWSAnalytics_EventsCollection];
|
||||
}];
|
||||
|
||||
[self tryToSyncEvents];
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (void)logEvent:(NSString *)eventName
|
||||
severity:(OWSAnalyticsSeverity)severity
|
||||
parameters:(nullable NSDictionary *)parameters
|
||||
location:(const char *)location
|
||||
line:(int)line
|
||||
{
|
||||
[[self sharedInstance] logEvent:eventName severity:severity parameters:parameters location:location line:line];
|
||||
}
|
||||
|
||||
- (void)logEvent:(NSString *)eventName
|
||||
severity:(OWSAnalyticsSeverity)severity
|
||||
parameters:(nullable NSDictionary *)parameters
|
||||
location:(const char *)location
|
||||
line:(int)line
|
||||
{
|
||||
DDLogFlag logFlag;
|
||||
BOOL async = YES;
|
||||
switch (severity) {
|
||||
@ -64,13 +301,81 @@
|
||||
}
|
||||
|
||||
// Log the event.
|
||||
NSString *logString = [NSString stringWithFormat:@"%s:%d %@", location, line, eventName];
|
||||
if (!parameters) {
|
||||
LOG_MAYBE(async, LOG_LEVEL_DEF, logFlag, 0, nil, location, @"%@", description);
|
||||
LOG_MAYBE(async, LOG_LEVEL_DEF, logFlag, 0, nil, location, @"%@", logString);
|
||||
} else {
|
||||
LOG_MAYBE(async, LOG_LEVEL_DEF, logFlag, 0, nil, location, @"%@ %@", description, parameters);
|
||||
LOG_MAYBE(async, LOG_LEVEL_DEF, logFlag, 0, nil, location, @"%@ %@", logString, parameters);
|
||||
}
|
||||
if (!async) {
|
||||
[DDLog flushLog];
|
||||
}
|
||||
|
||||
// Do nothing. We don't yet serialize or transmit analytics events.
|
||||
NSMutableDictionary *eventProperties = (parameters ? [parameters mutableCopy] : [NSMutableDictionary new]);
|
||||
eventProperties[@"event_location"] = [NSString stringWithFormat:@"%s:%d", location, line];
|
||||
[self addEvent:eventName properties:eventProperties];
|
||||
}
|
||||
|
||||
#pragma mark - Logging
|
||||
|
||||
+ (void)appLaunchDidBegin
|
||||
{
|
||||
[self.sharedInstance appLaunchDidBegin];
|
||||
}
|
||||
|
||||
- (void)appLaunchDidBegin
|
||||
{
|
||||
OWSProdInfo(@"app_launch");
|
||||
|
||||
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
||||
NSNumber *oldLaunchCount =
|
||||
[transaction objectForKey:kOWSAnalytics_KeyLaunchCount inCollection:kOWSAnalytics_Collection];
|
||||
NSNumber *newLaunchCount = @(oldLaunchCount.longValue + 1);
|
||||
self.launchCount = newLaunchCount;
|
||||
|
||||
NSNumber *oldLaunchCompleteCount =
|
||||
[transaction objectForKey:kOWSAnalytics_KeyLaunchCompleteCount inCollection:kOWSAnalytics_Collection];
|
||||
self.launchCompleteCount = @(oldLaunchCompleteCount.longValue);
|
||||
}];
|
||||
[TSStorageManager.sharedManager.newDatabaseConnection
|
||||
asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
|
||||
[transaction setObject:self.launchCount
|
||||
forKey:kOWSAnalytics_KeyLaunchCount
|
||||
inCollection:kOWSAnalytics_Collection];
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)appLaunchDidComplete
|
||||
{
|
||||
[self.sharedInstance appLaunchDidComplete];
|
||||
}
|
||||
|
||||
- (void)appLaunchDidComplete
|
||||
{
|
||||
OWSProdInfo(@"app_launch_complete");
|
||||
|
||||
self.launchCompleteCount = @(self.launchCompleteCount.longValue + 1);
|
||||
|
||||
[TSStorageManager.sharedManager.newDatabaseConnection
|
||||
asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
|
||||
[transaction setObject:self.launchCompleteCount
|
||||
forKey:kOWSAnalytics_KeyLaunchCompleteCount
|
||||
inCollection:kOWSAnalytics_Collection];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Logging
|
||||
|
||||
+ (NSString *)tag
|
||||
{
|
||||
return [NSString stringWithFormat:@"[%@]", self.class];
|
||||
}
|
||||
|
||||
- (NSString *)tag
|
||||
{
|
||||
return self.class.tag;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
49
tests/Util/OWSAnalyticsTests.m
Normal file
49
tests/Util/OWSAnalyticsTests.m
Normal file
@ -0,0 +1,49 @@
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSData+Base64.h"
|
||||
#import "OWSAnalytics.h"
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWSAnalyticsTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@interface OWSAnalytics (Test)
|
||||
|
||||
+ (long)orderOfMagnitudeOf:(long)value;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OWSAnalyticsTests
|
||||
|
||||
- (void)testOrderOfMagnitudeOf
|
||||
{
|
||||
XCTAssertEqual(0, [OWSAnalytics orderOfMagnitudeOf:-1]);
|
||||
XCTAssertEqual(0, [OWSAnalytics orderOfMagnitudeOf:0]);
|
||||
XCTAssertEqual(1, [OWSAnalytics orderOfMagnitudeOf:1]);
|
||||
XCTAssertEqual(1, [OWSAnalytics orderOfMagnitudeOf:5]);
|
||||
XCTAssertEqual(1, [OWSAnalytics orderOfMagnitudeOf:9]);
|
||||
XCTAssertEqual(10, [OWSAnalytics orderOfMagnitudeOf:10]);
|
||||
XCTAssertEqual(10, [OWSAnalytics orderOfMagnitudeOf:11]);
|
||||
XCTAssertEqual(10, [OWSAnalytics orderOfMagnitudeOf:19]);
|
||||
XCTAssertEqual(10, [OWSAnalytics orderOfMagnitudeOf:99]);
|
||||
XCTAssertEqual(100, [OWSAnalytics orderOfMagnitudeOf:100]);
|
||||
XCTAssertEqual(100, [OWSAnalytics orderOfMagnitudeOf:303]);
|
||||
XCTAssertEqual(100, [OWSAnalytics orderOfMagnitudeOf:999]);
|
||||
XCTAssertEqual(1000, [OWSAnalytics orderOfMagnitudeOf:1000]);
|
||||
XCTAssertEqual(1000, [OWSAnalytics orderOfMagnitudeOf:3030]);
|
||||
XCTAssertEqual(10000, [OWSAnalytics orderOfMagnitudeOf:10000]);
|
||||
XCTAssertEqual(10000, [OWSAnalytics orderOfMagnitudeOf:30303]);
|
||||
XCTAssertEqual(10000, [OWSAnalytics orderOfMagnitudeOf:99999]);
|
||||
XCTAssertEqual(100000, [OWSAnalytics orderOfMagnitudeOf:100000]);
|
||||
XCTAssertEqual(100000, [OWSAnalytics orderOfMagnitudeOf:303030]);
|
||||
XCTAssertEqual(100000, [OWSAnalytics orderOfMagnitudeOf:999999]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
Loading…
Reference in New Issue
Block a user