Support for MITM/key change interface.

This commit is contained in:
Frederic Jacobs 2014-12-11 00:05:41 +01:00
parent d90d27995c
commit f67e0d13f0
13 changed files with 249 additions and 91 deletions

View File

@ -83,7 +83,7 @@ EXTERNAL SOURCES:
CHECKOUT OPTIONS:
JSQMessagesViewController:
:commit: bc976a04d906ab3e5316148ee66de172a6e370b2
:commit: 8703ce020c86054ca32fbfdede0b4bded4428994
:git: https://github.com/dtsbourg/JSQMessagesViewController
SocketRocket:
:commit: d0585af165

2
Pods

@ -1 +1 @@
Subproject commit 6641c0c61b183015950fb020a53a43a993bbb28f
Subproject commit 9601c2be9292f740e40d530ec35ea7182d0a8441

View File

@ -396,6 +396,7 @@
B6C6AE561A305ED1006BAF8F /* textsecure.cer in Resources */ = {isa = PBXBuildFile; fileRef = B6C6AE541A305ED1006BAF8F /* textsecure.cer */; };
B6C93C4E199567AD00EDF894 /* DebugLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = B6C93C4D199567AD00EDF894 /* DebugLogger.m */; };
B6CBF53F1A254BD1000D4184 /* ContactDetailCell.m in Sources */ = {isa = PBXBuildFile; fileRef = B6CBF53E1A254BD1000D4184 /* ContactDetailCell.m */; };
B6E314C91A38FAAF00A41AFB /* TSFingerprintGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = B6E314C81A38FAAF00A41AFB /* TSFingerprintGenerator.m */; };
B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; };
B90418E7183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; };
B96A3100187DA1B600648F3E /* HelveticaNeueLTStd-Bd.otf in Resources */ = {isa = PBXBuildFile; fileRef = B96A30FE187DA1B600648F3E /* HelveticaNeueLTStd-Bd.otf */; };
@ -1041,6 +1042,8 @@
B6C93C4D199567AD00EDF894 /* DebugLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugLogger.m; sourceTree = "<group>"; };
B6CBF53D1A254BD1000D4184 /* ContactDetailCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ContactDetailCell.h; path = "Signal/src/view controllers/ContactDetailCell.h"; sourceTree = SOURCE_ROOT; };
B6CBF53E1A254BD1000D4184 /* ContactDetailCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ContactDetailCell.m; path = "Signal/src/view controllers/ContactDetailCell.m"; sourceTree = SOURCE_ROOT; };
B6E314C71A38FAAF00A41AFB /* TSFingerprintGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSFingerprintGenerator.h; sourceTree = "<group>"; };
B6E314C81A38FAAF00A41AFB /* TSFingerprintGenerator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSFingerprintGenerator.m; sourceTree = "<group>"; };
B90418E4183E9DD40038554A /* DateUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateUtil.h; sourceTree = "<group>"; };
B90418E5183E9DD40038554A /* DateUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DateUtil.m; sourceTree = "<group>"; };
B96A30FE187DA1B600648F3E /* HelveticaNeueLTStd-Bd.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "HelveticaNeueLTStd-Bd.otf"; sourceTree = "<group>"; };
@ -2331,6 +2334,8 @@
children = (
B6B0963C1A1D25ED008BFAA6 /* SecurityUtils.h */,
B6B0963D1A1D25ED008BFAA6 /* SecurityUtils.m */,
B6E314C71A38FAAF00A41AFB /* TSFingerprintGenerator.h */,
B6E314C81A38FAAF00A41AFB /* TSFingerprintGenerator.m */,
);
path = Security;
sourceTree = "<group>";
@ -3194,6 +3199,7 @@
E16E5BEF18AAC40200B7C403 /* EC25KeyAgreementProtocol.m in Sources */,
B6B096901A1D25ED008BFAA6 /* Cryptography.m in Sources */,
76EB064018170B33006006FC /* AnonymousTerminator.m in Sources */,
B6E314C91A38FAAF00A41AFB /* TSFingerprintGenerator.m in Sources */,
B6B096721A1D25ED008BFAA6 /* TSMessage.m in Sources */,
76EB058818170B33006006FC /* PropertyListPreferences.m in Sources */,
76EB05B218170B33006006FC /* DH3KKeyAgreementProtocol.m in Sources */,

View File

@ -19,21 +19,23 @@ typedef NS_ENUM(int32_t, TSErrorMessageType){
TSErrorMessageMissingKeyId,
TSErrorMessageInvalidMessage,
TSErrorMessageDuplicateMessage,
TSErrorMessageInvalidVersion
TSErrorMessageInvalidVersion,
};
+ (instancetype)corruptedMessageWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction;
+ (instancetype)invalidVersionWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction;
+ (instancetype)missingKeyIdWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction;
+ (instancetype)invalidKeyExceptionWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction;
+ (instancetype)untrustedKeyWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction;
+ (instancetype)missingSessionWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction;
+ (instancetype)userNotRegisteredErrorMessageInThread:(TSThread*)thread;
/**
* Methods on TSErrorMessageWrongTrustedIdentityKey error types
*/
- (instancetype)initWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread failedMessageType:(TSErrorMessageType)errorMessageType;
+ (instancetype)untrustedKeyWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction;
- (NSData*)retryBody;
- (BOOL)supportsRetry;
- (void)acceptNewIdentityKey;
- (NSString*)newIdentityKey;
@property (nonatomic, readonly) TSErrorMessageType errorType;

View File

@ -8,9 +8,28 @@
#import "TSErrorMessage.h"
#import "NSDate+millisecondTimeStamp.h"
#import "TSStorageManager+IdentityKeyStore.h"
#import <AxolotlKit/PreKeyWhisperMessage.h>
#import <AxolotlKit/NSData+keyVersionByte.h>
#import "TSMessagesManager.h"
#import "TSFingerprintGenerator.h"
@interface TSErrorMessage()
@property NSData *pushSignal;
@end
@implementation TSErrorMessage
- (instancetype)initForUnknownIdentityKeyWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread incomingPushSignal:(NSData*)signal{
self = [self initWithTimestamp:timestamp inThread:thread failedMessageType:TSErrorMessageWrongTrustedIdentityKey];
if (self) {
_pushSignal = signal;
}
return self;
}
- (instancetype)initWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread failedMessageType:(TSErrorMessageType)errorMessageType{
self = [super initWithTimestamp:timestamp inThread:thread messageBody:nil attachements:nil];
@ -21,6 +40,11 @@
return self;
}
- (instancetype)initWithSignal:(IncomingPushMessageSignal*)signal transaction:(YapDatabaseReadWriteTransaction*)transaction failedMessageType:(TSErrorMessageType)errorMessageType{
TSContactThread *contactThread = [TSContactThread threadWithContactId:signal.source transaction:transaction];
return [self initWithTimestamp:signal.timestamp inThread:contactThread failedMessageType:errorMessageType];
}
- (NSString*)description{
switch (_errorType) {
case TSErrorMessageNoSession:
@ -36,45 +60,65 @@
case TSErrorMessageInvalidKeyException:
return @"The recipient's key is not valid.";
case TSErrorMessageWrongTrustedIdentityKey:
return @"Your contact's identity key changed. Tap to verify and accept new key";
return @"Identity key changed. Tap to verify new key.";
default:
return @"An unknown error occured";
break;
}
}
+ (instancetype)userNotRegisteredErrorMessageInThread:(TSThread*)thread{
return [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:thread];
+ (instancetype)corruptedMessageWithSignal:(IncomingPushMessageSignal *)signal withTransaction:(YapDatabaseReadWriteTransaction *)transaction{
return [[self alloc] initWithSignal:signal transaction:transaction failedMessageType:TSErrorMessageInvalidMessage];
}
+ (instancetype)invalidVersionWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction{
TSContactThread *contactThread = [TSContactThread threadWithContactId:preKeyMessage.source transaction:transaction];
TSErrorMessage *errorMessage = [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:contactThread failedMessageType:TSErrorMessageInvalidVersion];
return errorMessage;
+ (instancetype)invalidVersionWithSignal:(IncomingPushMessageSignal*)signal withTransaction:(YapDatabaseReadWriteTransaction*)transaction{
return [[self alloc] initWithSignal:signal transaction:transaction failedMessageType:TSErrorMessageInvalidVersion];
}
+ (instancetype)missingKeyIdWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction{
TSContactThread *contactThread = [TSContactThread threadWithContactId:preKeyMessage.source transaction:transaction];
TSErrorMessage *errorMessage = [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:contactThread failedMessageType:TSErrorMessageMissingKeyId];
return errorMessage;
+ (instancetype)missingKeyIdWithSignal:(IncomingPushMessageSignal*)signal withTransaction:(YapDatabaseReadWriteTransaction*)transaction{
return [[self alloc] initWithSignal:signal transaction:transaction failedMessageType:TSErrorMessageMissingKeyId];
}
+ (instancetype)invalidKeyExceptionWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction{
TSContactThread *contactThread = [TSContactThread threadWithContactId:preKeyMessage.source transaction:transaction];
TSErrorMessage *errorMessage = [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:contactThread failedMessageType:TSErrorMessageInvalidKeyException];
return errorMessage;
+ (instancetype)invalidKeyExceptionWithSignal:(IncomingPushMessageSignal*)signal withTransaction:(YapDatabaseReadWriteTransaction*)transaction{
return [[self alloc] initWithSignal:signal transaction:transaction failedMessageType:TSErrorMessageInvalidKeyException];
}
+ (instancetype)untrustedKeyWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction{
TSContactThread *contactThread = [TSContactThread threadWithContactId:preKeyMessage.source transaction:transaction];
TSErrorMessage *errorMessage = [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:contactThread failedMessageType:TSErrorMessageWrongTrustedIdentityKey];
TSErrorMessage *errorMessage = [[self alloc] initForUnknownIdentityKeyWithTimestamp:preKeyMessage.timestamp inThread:contactThread incomingPushSignal:preKeyMessage.data];
return errorMessage;
}
+ (instancetype)missingSessionWithSignal:(IncomingPushMessageSignal*)preKeyMessage withTransaction:(YapDatabaseReadWriteTransaction*)transaction{
TSContactThread *contactThread = [TSContactThread threadWithContactId:preKeyMessage.source transaction:transaction];
TSErrorMessage *errorMessage = [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:contactThread failedMessageType:TSErrorMessageNoSession];
return errorMessage;
+ (instancetype)missingSessionWithSignal:(IncomingPushMessageSignal*)signal withTransaction:(YapDatabaseReadWriteTransaction*)transaction{
return [[self alloc] initWithSignal:signal transaction:transaction failedMessageType:TSErrorMessageNoSession];
}
- (void)acceptNewIdentityKey{
if (_errorType != TSErrorMessageWrongTrustedIdentityKey || !_pushSignal) {
return;
}
TSStorageManager *storage = [TSStorageManager sharedManager];
IncomingPushMessageSignal *signal = [IncomingPushMessageSignal parseFromData:_pushSignal];
PreKeyWhisperMessage *message = [[PreKeyWhisperMessage alloc] initWithData:signal.message];
NSData *newKey = [message.identityKey removeKeyType];
[storage saveRemoteIdentity:newKey recipientId:signal.source];
[[TSMessagesManager sharedManager] handleMessageSignal:signal];
//TODO: Decrypt any other messages encrypted with that new identity key automatically.
}
- (NSString *)newIdentityKey{
if (_errorType != TSErrorMessageWrongTrustedIdentityKey || !_pushSignal) {
return @"";
}
IncomingPushMessageSignal *signal = [IncomingPushMessageSignal parseFromData:_pushSignal];
PreKeyWhisperMessage *message = [[PreKeyWhisperMessage alloc] initWithData:signal.message];
NSData *identityKey = [message.identityKey removeKeyType];
return [TSFingerprintGenerator getFingerprintForDisplay:identityKey];
}
@end

View File

@ -12,9 +12,12 @@
typedef NS_ENUM(NSInteger, TSInfoMessageType){
TSInfoMessageTypeSessionDidEnd,
TSInfoMessageUserNotRegistered,
TSInfoMessageTypeUnsupportedMessage
};
+ (instancetype)userNotRegisteredMessageInThread:(TSThread*)thread transaction:(YapDatabaseReadWriteTransaction*)transaction;
@property TSInfoMessageType messageType;
- (instancetype)initWithTimestamp:(uint64_t)timestamp inThread:(TSContactThread *)contact messageType:(TSInfoMessageType)infoMessage;

View File

@ -7,11 +7,12 @@
//
#import "TSInfoMessage.h"
#import "NSDate+millisecondTimeStamp.h"
@implementation TSInfoMessage
- (instancetype)initWithTimestamp:(uint64_t)timestamp inThread:(TSContactThread *)contact messageType:(TSInfoMessageType)infoMessage{
self = [super initWithTimestamp:timestamp inThread:contact messageBody:@"Placeholder for info message." attachements:nil];
self = [super initWithTimestamp:timestamp inThread:contact messageBody:nil attachements:nil];
if (self) {
_messageType = infoMessage;
@ -20,12 +21,19 @@
return self;
}
+ (instancetype)userNotRegisteredMessageInThread:(TSContactThread*)thread transaction:(YapDatabaseReadWriteTransaction*)transaction{
return [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:thread messageType:TSInfoMessageUserNotRegistered];
}
- (NSString *)description{
switch (_messageType) {
case TSInfoMessageTypeSessionDidEnd:
return @"Secure session ended.";
case TSInfoMessageTypeUnsupportedMessage:
return @"Media messages are currently not supported.";
case TSInfoMessageUserNotRegistered:
return @"The user is not registered.";
default:
break;
}

View File

@ -29,6 +29,7 @@
#import "TSRecipientPrekeyRequest.h"
#import "TSErrorMessage.h"
#import "TSInfoMessage.h"
#import "TSContactThread.h"
#import "TSGroupThread.h"
@ -99,7 +100,7 @@ dispatch_queue_t sendingQueue() {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[recipient removeWithTransaction:transaction];
[message setMessageState:TSOutgoingMessageStateUnsent];
[[TSErrorMessage userNotRegisteredErrorMessageInThread:thread] saveWithTransaction:transaction];
[[TSInfoMessage userNotRegisteredMessageInThread:thread transaction:transaction] saveWithTransaction:transaction];
}];
break;
}

View File

@ -8,6 +8,7 @@
#import "TSMessagesManager.h"
#import <AxolotlKit/AxolotlExceptions.h>
#import <AxolotlKit/SessionCipher.h>
#import "Cryptography.h"
@ -224,10 +225,12 @@
TSThread *thread;
if (groupId) {
TSGroupThread *gThread = [TSGroupThread threadWithGroupId:groupId];
[gThread saveWithTransaction:transaction];
incomingMessage = [[TSIncomingMessage alloc] initWithTimestamp:timeStamp inThread:gThread authorId:message.source messageBody:body attachements:nil];
thread = gThread;
} else{
TSContactThread *cThread = [TSContactThread threadWithContactId:message.source transaction:transaction];
[cThread saveWithTransaction:transaction];
incomingMessage = [[TSIncomingMessage alloc] initWithTimestamp:timeStamp inThread:cThread messageBody:body attachements:nil];
thread = cThread;
}
@ -238,9 +241,27 @@
}
- (void)processException:(NSException*)exception pushSignal:(IncomingPushMessageSignal*)signal{
DDLogError(@"Got exception: %@", exception.description);
DDLogError(@"Got exception: %@ of type: %@", exception.description, exception.name);
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
TSErrorMessage *errorMessage = [[TSErrorMessage alloc] initWithTimestamp:signal.timestamp inThread:[TSContactThread threadWithContactId:signal.source transaction:transaction] failedMessageType:TSErrorMessageNoSession];
TSErrorMessage *errorMessage;
if ([exception.name isEqualToString:NoSessionException]) {
errorMessage = [TSErrorMessage missingSessionWithSignal:signal withTransaction:transaction];
} else if ([exception.name isEqualToString:InvalidKeyException]){
errorMessage = [TSErrorMessage invalidKeyExceptionWithSignal:signal withTransaction:transaction];
} else if ([exception.name isEqualToString:InvalidKeyIdException]){
errorMessage = [TSErrorMessage invalidKeyExceptionWithSignal:signal withTransaction:transaction];
} else if ([exception.name isEqualToString:DuplicateMessageException]){
// Duplicate messages are dismissed
return ;
} else if ([exception.name isEqualToString:InvalidVersionException]){
errorMessage = [TSErrorMessage invalidVersionWithSignal:signal withTransaction:transaction];
} else if ([exception.name isEqualToString:UntrustedIdentityKeyException]){
errorMessage = [TSErrorMessage untrustedKeyWithSignal:signal withTransaction:transaction];
} else {
errorMessage = [TSErrorMessage corruptedMessageWithSignal:signal withTransaction:transaction];
}
[errorMessage saveWithTransaction:transaction];
}];

View File

@ -0,0 +1,15 @@
//
// TSFingerprintGenerator.h
// Signal
//
// Created by Frederic Jacobs on 10/12/14.
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface TSFingerprintGenerator : NSObject
+ (NSString*)getFingerprintForDisplay:(NSData*)identityKey;
@end

View File

@ -0,0 +1,37 @@
//
// TSFingerprintGenerator.m
// Signal
//
// Created by Frederic Jacobs on 10/12/14.
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
//
#import "NSData+hexString.h"
#import <AxolotlKit/NSData+keyVersionByte.h>
#import "TSFingerprintGenerator.h"
@implementation TSFingerprintGenerator
+ (NSString*)getFingerprintForDisplay:(NSData*)identityKey {
// idea here is to insert a space every two characters. there is probably a cleverer/more native way to do this.
identityKey = [identityKey prependKeyType];
NSString *fingerprint = [identityKey hexadecimalString];
__block NSString* formattedFingerprint = @"";
[fingerprint enumerateSubstringsInRange:NSMakeRange(0, [fingerprint length])
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:
^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
if (substringRange.location % 2 != 0 && substringRange.location != [fingerprint length]-1) {
substring = [substring stringByAppendingString:@" "];
}
formattedFingerprint = [formattedFingerprint stringByAppendingString:substring];
}];
return formattedFingerprint;
}
@end

View File

@ -16,6 +16,8 @@
#import "TSStorageManager.h"
#import "TSStorageManager+IdentityKeyStore.h"
#import "TSFingerprintGenerator.h"
@interface FingerprintViewController ()
@property TSContactThread *thread;
@end
@ -26,25 +28,6 @@
self.thread = (TSContactThread*)thread;
}
- (NSString*)getFingerprintForDisplay:(NSData*)identityKey {
// idea here is to insert a space every two characters. there is probably a cleverer/more native way to do this.
identityKey = [identityKey prependKeyType];
NSString *fingerprint = [identityKey hexadecimalString];
__block NSString* formattedFingerprint = @"";
[fingerprint enumerateSubstringsInRange:NSMakeRange(0, [fingerprint length])
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:
^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
if (substringRange.location % 2 != 0 && substringRange.location != [fingerprint length]-1) {
substring = [substring stringByAppendingString:@" "];
}
formattedFingerprint = [formattedFingerprint stringByAppendingString:substring];
}];
return formattedFingerprint;
}
- (void)viewDidLoad {
[super viewDidLoad];
@ -59,10 +42,10 @@
{
self.contactFingerprintTitleLabel.text = self.thread.name;
NSData *identityKey = [[TSStorageManager sharedManager] identityKeyForRecipientId:self.thread.contactIdentifier];
self.contactFingerprintLabel.text = [self getFingerprintForDisplay:identityKey];
self.contactFingerprintLabel.text = [TSFingerprintGenerator getFingerprintForDisplay:identityKey];
NSData *myPublicKey = [[TSStorageManager sharedManager] identityKeyPair].publicKey;
self.userFingerprintLabel.text = [self getFingerprintForDisplay:myPublicKey];
self.userFingerprintLabel.text = [TSFingerprintGenerator getFingerprintForDisplay:myPublicKey];
[UIView animateWithDuration:0.6 delay:0. options:UIViewAnimationOptionCurveEaseInOut animations:^{
[self.view setAlpha:1];

View File

@ -31,9 +31,12 @@
#import "TSStorageManager.h"
#import "TSDatabaseView.h"
#import <YapDatabase/YapDatabaseView.h>
#import "TSInteraction.h"
#import "TSMessageAdapter.h"
#import "TSErrorMessage.h"
#import "TSIncomingMessage.h"
#import "TSInteraction.h"
#import "TSMessagesManager+sendMessages.h"
#import "NSDate+millisecondTimeStamp.h"
@ -451,57 +454,88 @@ typedef enum : NSUInteger {
- (void)collectionView:(JSQMessagesCollectionView *)collectionView didTapMessageBubbleAtIndexPath:(NSIndexPath *)indexPath
{
TSMessageAdapter * messageItem = [collectionView.dataSource collectionView:collectionView messageDataForItemAtIndexPath:indexPath];
TSMessageAdapter *messageItem = [collectionView.dataSource collectionView:collectionView messageDataForItemAtIndexPath:indexPath];
TSInteraction *interaction = [self interactionAtIndexPath:indexPath];
BOOL isMessage = (messageItem.messageType == TSIncomingMessageAdapter) || (messageItem.messageType == TSOutgoingMessageAdapter);
BOOL isMediaMessage = isMessage ? [messageItem isMediaMessage] : NO;
if (isMediaMessage) {
id<JSQMessageMediaData> messageMedia = [messageItem media];
switch (messageItem.messageType) {
case TSOutgoingMessageAdapter:
if (messageItem.messageState == TSOutgoingMessageStateUnsent) {
[self handleUnsentMessageTap:(TSOutgoingMessage*)interaction];
}
case TSIncomingMessageAdapter:{
if ([messageMedia isKindOfClass:JSQPhotoMediaItem.class]) {
//is a photo
tappedImage = ((JSQPhotoMediaItem*)messageMedia).image ;
[self performSegueWithIdentifier:@"fullImage" sender:self];
BOOL isMediaMessage = [messageItem isMediaMessage];
} else if ([messageMedia isKindOfClass:JSQVideoMediaItem.class]) {
//is a video
}
if (isMediaMessage) {
id<JSQMessageMediaData> messageMedia = [messageItem media];
if ([messageMedia isKindOfClass:JSQPhotoMediaItem.class]) {
//is a photo
tappedImage = ((JSQPhotoMediaItem*)messageMedia).image ;
[self performSegueWithIdentifier:@"fullImage" sender:self];
} else if ([messageMedia isKindOfClass:JSQVideoMediaItem.class]) {
//is a video
}
}
break;}
case TSErrorMessageAdapter:
[self handleErrorMessageTap:(TSErrorMessage*)interaction];
break;
case TSInfoMessageAdapter:
break;
default:
break;
}
BOOL isUnsent = messageItem.messageState == TSOutgoingMessageStateAttemptingOut && [messageItem.senderId isEqualToString:self.senderId];
if (isMessage && isUnsent)
{
[DJWActionSheet showInView:self.tabBarController.view withTitle:nil cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Delete" otherButtonTitles:@[@"Send again"] tapBlock:^(DJWActionSheet *actionSheet, NSInteger tappedButtonIndex) {
}
#pragma mark Bubble User Actions
- (void)handleUnsentMessageTap:(TSOutgoingMessage*)message{
[DJWActionSheet showInView:self.tabBarController.view withTitle:nil cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Delete" otherButtonTitles:@[@"Send again"] tapBlock:^(DJWActionSheet *actionSheet, NSInteger tappedButtonIndex) {
if (tappedButtonIndex == actionSheet.cancelButtonIndex) {
NSLog(@"User Cancelled");
} else if (tappedButtonIndex == actionSheet.destructiveButtonIndex) {
[self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction){
[message removeWithTransaction:transaction];
}];
}else {
[[TSMessagesManager sharedManager] sendMessage:message inThread:self.thread];
[self finishSendingMessage];
}
}];
}
- (void)handleErrorMessageTap:(TSErrorMessage*)message{
if (message.errorType == TSErrorMessageWrongTrustedIdentityKey) {
NSString *newKeyFingerprint = [message newIdentityKey];
NSString *messageString = [NSString stringWithFormat:@"Do you want to accept %@'s new identity key: %@", _thread.name, newKeyFingerprint];
NSArray *actions = @[@"Accept new identity key", @"Copy new identity key to pasteboard"];
[DJWActionSheet showInView:self.tabBarController.view withTitle:messageString cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Delete" otherButtonTitles:actions tapBlock:^(DJWActionSheet *actionSheet, NSInteger tappedButtonIndex) {
if (tappedButtonIndex == actionSheet.cancelButtonIndex) {
NSLog(@"User Cancelled");
} else if (tappedButtonIndex == actionSheet.destructiveButtonIndex) {
[self.uiDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction){
TSOutgoingMessage * message = (TSOutgoingMessage*)messageItem;
[self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction){
[message removeWithTransaction:transaction];
[self finishSendingMessage];
}];
}else {
} else {
switch (tappedButtonIndex) {
case 0:
{
TSOutgoingMessage * message = (TSOutgoingMessage*)messageItem;
[[TSMessagesManager sharedManager] sendMessage:message inThread:self.thread];
[self finishSendingMessage];
[message acceptNewIdentityKey];
break;
case 1:
[[UIPasteboard generalPasteboard] setString:newKeyFingerprint];
break;
}
default:
break;
}
}
}];
}
}
#pragma mark - Navigation
@ -514,7 +548,6 @@ typedef enum : NSUInteger {
} else if ([segue.identifier isEqualToString:@"fingerprintSegue"]){
FingerprintViewController *vc = [segue destinationViewController];
TSContactThread *thread = (TSContactThread*) self.thread;
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[vc configWithThread:self.thread];
}];
@ -697,8 +730,7 @@ typedef enum : NSUInteger {
return numberOfMessages;
}
- (TSMessageAdapter*)messageAtIndexPath:(NSIndexPath *)indexPath
{
- (TSInteraction*)interactionAtIndexPath:(NSIndexPath*)indexPath {
__block TSInteraction *message = nil;
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
YapDatabaseViewTransaction *viewTransaction = [transaction ext:TSMessageDatabaseViewExtensionName];
@ -714,7 +746,13 @@ typedef enum : NSUInteger {
message = [viewTransaction objectAtRow:row inSection:section withMappings:self.messageMappings];
NSParameterAssert(message != nil);
}];
return [TSMessageAdapter messageViewDataWithInteraction:message inThread:self.thread];
return message;
}
- (TSMessageAdapter*)messageAtIndexPath:(NSIndexPath *)indexPath {
TSInteraction *interaction = [self interactionAtIndexPath:indexPath];
return [TSMessageAdapter messageViewDataWithInteraction:interaction inThread:self.thread];
}
#pragma mark Accessory View