diff --git a/Signal/src/Profiles/OWSProfileManager.m b/Signal/src/Profiles/OWSProfileManager.m index e520211ec..f432caec1 100644 --- a/Signal/src/Profiles/OWSProfileManager.m +++ b/Signal/src/Profiles/OWSProfileManager.m @@ -10,7 +10,7 @@ #import #import #import -#import +#import #import #import #import @@ -423,17 +423,20 @@ static const NSInteger kProfileKeyLength = 16; OWSAssert(failureBlock); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - // TODO: Do we need to use NSDataBase64EncodingOptions? - NSString *_Nullable localProfileNameBase64 = [[self encryptProfileString:localProfileName] base64EncodedString]; - NSString *_Nullable avatarUrlBase64 = [[avatarUrl dataUsingEncoding:NSUTF8StringEncoding] base64EncodedString]; - NSString *_Nullable avatarDigestBase64 = [avatarDigest base64EncodedString]; + NSData *_Nullable profileNameEncrypted = [self encryptProfileString:localProfileName]; - // TODO: - if (YES) { - successBlock(); - return; - } - failureBlock(); + TSSetProfileRequest *request = [[TSSetProfileRequest alloc] initWithProfileName:profileNameEncrypted + avatarUrl:avatarUrl + avatarDigest:avatarDigest]; + + [self.networkManager makeRequest:request + success:^(NSURLSessionDataTask *task, id responseObject) { + successBlock(); + } + failure:^(NSURLSessionDataTask *task, NSError *error) { + DDLogError(@"%@ Failed to update profile with error: %@", self.tag, error); + failureBlock(); + }]; }); } @@ -669,46 +672,51 @@ static const NSInteger kProfileKeyLength = 16; - (void)updateProfileForRecipientId:(NSString *)recipientId profileNameEncrypted:(NSData *_Nullable)profileNameEncrypted avatarUrlData:(NSData *_Nullable)avatarUrlData - avatarDigest:(NSData *_Nullable)avatarDigest + avatarDigest:(NSData *_Nullable)avatarDigestParam { OWSAssert(recipientId.length > 0); - UserProfile *userProfile = [self getOrBuildUserProfileForRecipientId:recipientId]; - if (!userProfile.profileKey) { - return; - } + // Ensure decryption, etc. off main thread. + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSString *_Nullable profileName = - [self decryptProfileString:profileNameEncrypted profileKey:userProfile.profileKey]; - NSString *_Nullable avatarUrl - = (avatarUrlData ? [[NSString alloc] initWithData:avatarUrlData encoding:NSUTF8StringEncoding] : nil); + UserProfile *userProfile = [self getOrBuildUserProfileForRecipientId:recipientId]; + if (!userProfile.profileKey) { + return; + } - if (!avatarUrl || !avatarDigest) { - // If either avatar url or digest is missing, skip both. - avatarUrl = nil; - avatarDigest = nil; - } + NSString *_Nullable profileName = + [self decryptProfileString:profileNameEncrypted profileKey:userProfile.profileKey]; + NSString *_Nullable avatarUrl + = (avatarUrlData ? [[NSString alloc] initWithData:avatarUrlData encoding:NSUTF8StringEncoding] : nil); + NSData *_Nullable avatarDigest = avatarDigestParam; - BOOL isAvatarSame = ([self isNullableStringEqual:userProfile.avatarUrl toString:avatarUrl] && - [self isNullableDataEqual:userProfile.avatarDigest toData:avatarDigest]); + if (!avatarUrl || !avatarDigest) { + // If either avatar url or digest is missing, skip both. + avatarUrl = nil; + avatarDigest = nil; + } - dispatch_async(dispatch_get_main_queue(), ^{ - userProfile.profileName = profileName; - userProfile.avatarUrl = avatarUrl; - userProfile.avatarDigest = avatarDigest; + BOOL isAvatarSame = ([self isNullableStringEqual:userProfile.avatarUrl toString:avatarUrl] && + [self isNullableDataEqual:userProfile.avatarDigest toData:avatarDigest]); - if (!isAvatarSame) { - // Evacuate avatar image cache. - [self.otherUsersProfileAvatarImageCache removeObjectForKey:recipientId]; + dispatch_async(dispatch_get_main_queue(), ^{ + userProfile.profileName = profileName; + userProfile.avatarUrl = avatarUrl; + userProfile.avatarDigest = avatarDigest; + + if (!isAvatarSame) { + // Evacuate avatar image cache. + [self.otherUsersProfileAvatarImageCache removeObjectForKey:recipientId]; - if (avatarUrl) { - [self downloadProfileAvatarWithUrl:avatarUrl recipientId:recipientId]; + if (avatarUrl) { + [self downloadProfileAvatarWithUrl:avatarUrl recipientId:recipientId]; + } } - } - userProfile.lastUpdateDate = [NSDate new]; + userProfile.lastUpdateDate = [NSDate new]; - [self saveUserProfile:userProfile]; + [self saveUserProfile:userProfile]; + }); }); } diff --git a/Signal/src/ViewControllers/ProfileViewController.m b/Signal/src/ViewControllers/ProfileViewController.m index 14a62076f..45094027c 100644 --- a/Signal/src/ViewControllers/ProfileViewController.m +++ b/Signal/src/ViewControllers/ProfileViewController.m @@ -213,13 +213,15 @@ NS_ASSUME_NONNULL_BEGIN { __weak ProfileViewController *weakSelf = self; [OWSProfileManager.sharedManager updateLocalProfileName:self.nameTextField.text - avatarImage:self.avatar - success:^{ - [weakSelf.navigationController popViewControllerAnimated:YES]; - } - failure:^{ - // TODO: Handle failure. - }]; + avatarImage:self.avatar + success:^{ + [weakSelf.navigationController popViewControllerAnimated:YES]; + } + failure:^{ + [OWSAlerts showAlertWithTitle:NSLocalizedString(@"ALERT_ERROR_TITLE", @"") + message:NSLocalizedString(@"PROFILE_VIEW_ERROR_UPDATE_FAILED", + @"Error message shown when a profile update fails.")]; + }]; } #pragma mark - UITextFieldDelegate diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index a9162dc0a..1d4934e52 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -1069,6 +1069,9 @@ /* Label for action that clear's the user's profile avatar */ "PROFILE_VIEW_CLEAR_AVATAR" = "Clear Avatar"; +/* Error message shown when a profile update fails. */ +"PROFILE_VIEW_ERROR_UPDATE_FAILED" = "Profile update failed."; + /* Default text for the profile name field of the profile view. */ "PROFILE_VIEW_NAME_DEFAULT_TEXT" = "Enter your name."; diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentMessageTranscript.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentMessageTranscript.m index 11e25a15f..c74088880 100644 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentMessageTranscript.m +++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSOutgoingSentMessageTranscript.m @@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN * Normally this is private, but we need to embed this * data structure within our own. */ -- (OWSSignalServiceProtosDataMessage *)buildDataMessage:(NSString *)recipientId; +- (OWSSignalServiceProtosDataMessage *)buildDataMessage:(NSString *_Nullable)recipientId; @end diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m index 0a2d0e6e1..16616aa8c 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m @@ -455,10 +455,9 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec return builder; } -- (OWSSignalServiceProtosDataMessage *)buildDataMessage:(NSString *)recipientId +- (OWSSignalServiceProtosDataMessage *)buildDataMessage:(NSString *_Nullable)recipientId { OWSAssert(self.thread); - OWSAssert(recipientId.length > 0); OWSSignalServiceProtosDataMessageBuilder *builder = [self dataMessageBuilder]; [builder addLocalProfileKeyIfNecessary:self.thread recipientId:recipientId]; diff --git a/SignalServiceKit/src/Network/API/Requests/TSSetProfileRequest.h b/SignalServiceKit/src/Network/API/Requests/TSSetProfileRequest.h new file mode 100644 index 000000000..663ef1126 --- /dev/null +++ b/SignalServiceKit/src/Network/API/Requests/TSSetProfileRequest.h @@ -0,0 +1,19 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "TSRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface TSSetProfileRequest : TSRequest + +- (nullable instancetype)initWithProfileName:(NSData *_Nullable)profileNameEncrypted + avatarUrl:(NSString *_Nullable)avatarUrl + avatarDigest:(NSData *_Nullable)avatarDigest; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Network/API/Requests/TSSetProfileRequest.m b/SignalServiceKit/src/Network/API/Requests/TSSetProfileRequest.m new file mode 100644 index 000000000..0d10c88eb --- /dev/null +++ b/SignalServiceKit/src/Network/API/Requests/TSSetProfileRequest.m @@ -0,0 +1,37 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "TSSetProfileRequest.h" +#import "NSData+Base64.h" +#import "TSConstants.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation TSSetProfileRequest + +- (nullable instancetype)initWithProfileName:(NSData *_Nullable)profileNameEncrypted + avatarUrl:(NSString *_Nullable)avatarUrl + avatarDigest:(NSData *_Nullable)avatarDigest +{ + + self = [super initWithURL:[NSURL URLWithString:textSecureSetProfileAPI]]; + + self.HTTPMethod = @"PUT"; + + if (profileNameEncrypted.length > 0) { + self.parameters[@"name"] = [profileNameEncrypted base64EncodedString]; + } + if (avatarUrl.length > 0) { + self.parameters[@"avatar"] = [[avatarUrl dataUsingEncoding:NSUTF8StringEncoding] base64EncodedString]; + } + if (avatarDigest.length > 0) { + self.parameters[@"avatarDigest"] = [avatarDigest base64EncodedString]; + } + + return self; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Protocols/ProtoBuf+OWS.h b/SignalServiceKit/src/Protocols/ProtoBuf+OWS.h index b614090ec..1a2d18ef2 100644 --- a/SignalServiceKit/src/Protocols/ProtoBuf+OWS.h +++ b/SignalServiceKit/src/Protocols/ProtoBuf+OWS.h @@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN @interface OWSSignalServiceProtosDataMessageBuilder (OWS) -- (void)addLocalProfileKeyIfNecessary:(TSThread *)thread recipientId:(NSString *)recipientId; +- (void)addLocalProfileKeyIfNecessary:(TSThread *)thread recipientId:(NSString *_Nullable)recipientId; @end diff --git a/SignalServiceKit/src/Protocols/ProtoBuf+OWS.m b/SignalServiceKit/src/Protocols/ProtoBuf+OWS.m index 62431895e..ac7ca54f7 100644 --- a/SignalServiceKit/src/Protocols/ProtoBuf+OWS.m +++ b/SignalServiceKit/src/Protocols/ProtoBuf+OWS.m @@ -12,11 +12,9 @@ NS_ASSUME_NONNULL_BEGIN @implementation PBGeneratedMessageBuilder (OWS) -- (BOOL)shouldMessageHaveLocalProfileKey:(TSThread *)thread recipientId:(NSString *)recipientId -// recipient:(SignalRecipient *)recipient +- (BOOL)shouldMessageHaveLocalProfileKey:(TSThread *)thread recipientId:(NSString *_Nullable)recipientId { OWSAssert(thread); - OWSAssert(recipientId.length > 0); id profileManager = [TextSecureKitEnv sharedEnv].profileManager; @@ -25,7 +23,7 @@ NS_ASSUME_NONNULL_BEGIN // // For Group threads, we want to include the profile key IFF the // recipient OR the group is in the whitelist. - if ([profileManager isUserInProfileWhitelist:recipientId]) { + if (recipientId.length > 0 && [profileManager isUserInProfileWhitelist:recipientId]) { return YES; } else if ([profileManager isThreadInProfileWhitelist:thread]) { return YES; @@ -46,10 +44,9 @@ NS_ASSUME_NONNULL_BEGIN @implementation OWSSignalServiceProtosDataMessageBuilder (OWS) -- (void)addLocalProfileKeyIfNecessary:(TSThread *)thread recipientId:(NSString *)recipientId +- (void)addLocalProfileKeyIfNecessary:(TSThread *)thread recipientId:(NSString *_Nullable)recipientId { OWSAssert(thread); - OWSAssert(recipientId.length > 0); if ([self shouldMessageHaveLocalProfileKey:thread recipientId:recipientId]) { [self setProfileKey:self.localProfileKey]; diff --git a/SignalServiceKit/src/TSConstants.h b/SignalServiceKit/src/TSConstants.h index d7702ca1d..150848fb3 100644 --- a/SignalServiceKit/src/TSConstants.h +++ b/SignalServiceKit/src/TSConstants.h @@ -41,6 +41,7 @@ typedef enum { kSMSVerification, kPhoneNumberVerification } VerificationTranspor #define textSecureDeviceProvisioningAPIFormat @"v1/provisioning/%@" #define textSecureDevicesAPIFormat @"v1/devices/%@" #define textSecureProfileAPIFormat @"v1/profile/%@" +#define textSecureSetProfileAPI @"v1/profile" #pragma mark Push RegistrationSpecific Constants typedef NS_ENUM(NSInteger, TSPushRegistrationError) {