Outgoing Read Receipts
// FREEBIE
This commit is contained in:
parent
580781e3e4
commit
acb89f0b0f
14
src/Devices/OWSReadReceiptObserver.h
Normal file
14
src/Devices/OWSReadReceiptObserver.h
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TSMessagesManager;
|
||||
|
||||
@interface OWSReadReceiptObserver : NSObject
|
||||
|
||||
- (instancetype)initWithMessagesManager:(TSMessagesManager *)messagesManager;
|
||||
- (void)startObserving;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
110
src/Devices/OWSReadReceiptObserver.m
Normal file
110
src/Devices/OWSReadReceiptObserver.m
Normal file
@ -0,0 +1,110 @@
|
||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
|
||||
#import "OWSReadReceiptObserver.h"
|
||||
#import "OWSReadReceipt.h"
|
||||
#import "OWSReadReceiptsMessage.h"
|
||||
#import "TSContactThread.h"
|
||||
#import "TSIncomingMessage.h"
|
||||
#import "TSMessagesManager+sendMessages.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWSReadReceiptObserver ()
|
||||
|
||||
@property (atomic) NSMutableArray<OWSReadReceipt *> *readReceiptsQueue;
|
||||
@property (nonatomic, readonly) TSMessagesManager *messagesManager;
|
||||
@property BOOL isObserving;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OWSReadReceiptObserver
|
||||
|
||||
- (instancetype)initWithMessagesManager:(TSMessagesManager *)messagesManager
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
_readReceiptsQueue = [NSMutableArray new];
|
||||
_messagesManager = messagesManager;
|
||||
_isObserving = NO;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)startObserving
|
||||
{
|
||||
if (self.isObserving) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.isObserving = true;
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleReadNotification:)
|
||||
name:TSIncomingMessageWasReadOnThisDeviceNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
- (void)handleReadNotification:(NSNotification *)notification
|
||||
{
|
||||
if (![notification.object isKindOfClass:[TSIncomingMessage class]]) {
|
||||
DDLogError(@"Read receipt notifier got unexpected object: %@", notification.object);
|
||||
return;
|
||||
}
|
||||
|
||||
TSIncomingMessage *message = (TSIncomingMessage *)notification.object;
|
||||
|
||||
// Only groupthread sets authorId, thus this crappy code.
|
||||
// TODO ALL incoming messages should have an authorId.
|
||||
NSString *messageAuthorId;
|
||||
if (message.authorId) { // Group Thread
|
||||
messageAuthorId = message.authorId;
|
||||
} else { // Contact Thread
|
||||
messageAuthorId = [TSContactThread contactIdFromThreadId:message.uniqueThreadId];
|
||||
}
|
||||
|
||||
OWSReadReceipt *readReceipt = [[OWSReadReceipt alloc] initWithSenderId:messageAuthorId timestamp:message.timestamp];
|
||||
[self.readReceiptsQueue addObject:readReceipt];
|
||||
|
||||
// Wait a bit to bundle up read receipts into one request.
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[weakSelf performSelector:@selector(sendAllReadReceiptsInQueue) withObject:nil afterDelay:2.0];
|
||||
}
|
||||
|
||||
- (void)sendAllReadReceiptsInQueue
|
||||
{
|
||||
// Synchronized so we don't lose any read receipts while replacing the queue
|
||||
__block NSArray<OWSReadReceipt *> *receiptsToSend;
|
||||
@synchronized(self)
|
||||
{
|
||||
if (self.readReceiptsQueue.count > 0) {
|
||||
receiptsToSend = [self.readReceiptsQueue copy];
|
||||
self.readReceiptsQueue = [NSMutableArray new];
|
||||
}
|
||||
}
|
||||
|
||||
if (receiptsToSend) {
|
||||
[self sendReadReceipts:receiptsToSend];
|
||||
} else {
|
||||
DDLogVerbose(@"Read receipts queue already drained.");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)sendReadReceipts:(NSArray<OWSReadReceipt *> *)readReceipts
|
||||
{
|
||||
OWSReadReceiptsMessage *message = [[OWSReadReceiptsMessage alloc] initWithReadReceipts:readReceipts];
|
||||
|
||||
[self.messagesManager sendMessage:message
|
||||
inThread:nil
|
||||
success:^{
|
||||
DDLogInfo(@"Successfully sent %ld read receipt", (unsigned long)readReceipts.count);
|
||||
}
|
||||
failure:^{
|
||||
DDLogError(@"Failed to send read receipt");
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
15
src/Devices/OWSReadReceiptsMessage.h
Normal file
15
src/Devices/OWSReadReceiptsMessage.h
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
|
||||
#import "OWSOutgoingSyncMessage.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class OWSReadReceipt;
|
||||
|
||||
@interface OWSReadReceiptsMessage : OWSOutgoingSyncMessage
|
||||
|
||||
- (instancetype)initWithReadReceipts:(NSArray<OWSReadReceipt *> *)readReceipts;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
45
src/Devices/OWSReadReceiptsMessage.m
Normal file
45
src/Devices/OWSReadReceiptsMessage.m
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
|
||||
#import "OWSReadReceiptsMessage.h"
|
||||
#import "OWSReadReceipt.h"
|
||||
#import "OWSSignalServiceProtos.pb.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWSReadReceiptsMessage ()
|
||||
|
||||
@property (nonatomic, readonly) NSArray<OWSReadReceipt *> *readReceipts;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OWSReadReceiptsMessage
|
||||
|
||||
- (instancetype)initWithReadReceipts:(NSArray<OWSReadReceipt *> *)readReceipts
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
_readReceipts = [readReceipts copy];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (OWSSignalServiceProtosSyncMessage *)buildSyncMessage
|
||||
{
|
||||
OWSSignalServiceProtosSyncMessageBuilder *syncMessageBuilder = [OWSSignalServiceProtosSyncMessageBuilder new];
|
||||
for (OWSReadReceipt *readReceipt in self.readReceipts) {
|
||||
OWSSignalServiceProtosSyncMessageReadBuilder *readProtoBuilder =
|
||||
[OWSSignalServiceProtosSyncMessageReadBuilder new];
|
||||
[readProtoBuilder setSender:readReceipt.senderId];
|
||||
[readProtoBuilder setTimestamp:readReceipt.timestamp];
|
||||
[syncMessageBuilder addRead:[readProtoBuilder build]];
|
||||
}
|
||||
|
||||
return [syncMessageBuilder build];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -45,13 +45,15 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
- (void)process
|
||||
{
|
||||
DDLogInfo(@"Processing %ld read receipts.", self.readReceipts.count);
|
||||
DDLogInfo(@"Processing %ld read receipts.", (unsigned long)self.readReceipts.count);
|
||||
for (OWSReadReceipt *readReceipt in self.readReceipts) {
|
||||
TSIncomingMessage *message =
|
||||
[TSIncomingMessage findMessageWithAuthorId:readReceipt.senderId timestamp:readReceipt.timestamp];
|
||||
if (message) {
|
||||
[message markAsRead];
|
||||
[message markAsReadFromReadReceipt];
|
||||
} else {
|
||||
// TODO keep read receipts around so that if we get the receipt before the message,
|
||||
// we can immediately mark the message as read once we get it.
|
||||
DDLogWarn(@"Couldn't find message for read receipt. Message not synced?");
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,13 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation OWSOutgoingSyncMessage
|
||||
|
||||
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
// override superclass with no-op.
|
||||
//
|
||||
// There's no need to save this message, since it's not displayed to the user.
|
||||
}
|
||||
|
||||
- (BOOL)shouldSyncTranscript
|
||||
{
|
||||
return NO;
|
||||
|
||||
@ -10,7 +10,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@interface OWSSyncContactsMessage : OWSOutgoingSyncMessage
|
||||
|
||||
- (instancetype)initWithContactsManager:(id<ContactsManagerProtocol>)contactsManager;
|
||||
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
- (NSData *)buildPlainTextAttachmentData;
|
||||
|
||||
@end
|
||||
|
||||
@ -6,7 +6,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWSSyncGroupsMessage : OWSOutgoingSyncMessage
|
||||
|
||||
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
- (NSData *)buildPlainTextAttachmentData;
|
||||
|
||||
@end
|
||||
|
||||
@ -17,14 +17,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return [super initWithTimestamp:[NSDate ows_millisecondTimeStamp]];
|
||||
}
|
||||
|
||||
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
// no-op
|
||||
|
||||
// There's no need to save this message, since it's not displayed to the user.
|
||||
// Furthermore if we did save it, we probably don't want to save the conctactsManager property.
|
||||
}
|
||||
|
||||
- (OWSSignalServiceProtosSyncMessage *)buildSyncMessage
|
||||
{
|
||||
|
||||
|
||||
@ -8,6 +8,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@class TSContactThread;
|
||||
@class TSGroupThread;
|
||||
|
||||
extern NSString *const TSIncomingMessageWasReadOnThisDeviceNotification;
|
||||
|
||||
@interface TSIncomingMessage : TSMessage
|
||||
|
||||
/**
|
||||
@ -104,11 +106,17 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@property (nonatomic, readonly) NSDate *receivedAt;
|
||||
|
||||
/*
|
||||
* Marks a message as having been read and broadcasts a TSIncomingMessageWasReadNotification
|
||||
* Marks a message as having been read on this device (as opposed to responding to a remote read receipt).
|
||||
*
|
||||
*/
|
||||
- (void)markAsRead;
|
||||
- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
/**
|
||||
* Similar to markAsReadWithTransaction, but doesn't send out read receipts.
|
||||
* Used for *responding* to a remote read receipt.
|
||||
*/
|
||||
- (void)markAsReadFromReadReceipt;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@ -9,6 +9,8 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingMessageWasReadOnThisDeviceNotification";
|
||||
|
||||
@implementation TSIncomingMessage
|
||||
|
||||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
@ -98,14 +100,21 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return foundMessage;
|
||||
}
|
||||
|
||||
- (void)markAsRead
|
||||
- (void)markAsReadFromReadReceipt
|
||||
{
|
||||
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
[self markAsReadWithTransaction:transaction];
|
||||
[self markAsReadWithoutNotificationWithTransaction:transaction];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
[self markAsReadWithoutNotificationWithTransaction:transaction];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:TSIncomingMessageWasReadOnThisDeviceNotification
|
||||
object:self];
|
||||
}
|
||||
|
||||
- (void)markAsReadWithoutNotificationWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
_read = YES;
|
||||
[self saveWithTransaction:transaction];
|
||||
|
||||
@ -69,7 +69,7 @@
|
||||
DDLogWarn(@"Received an unknown message type");
|
||||
break;
|
||||
default:
|
||||
DDLogWarn(@"Received unhandled envelope type: %d", envelope.type);
|
||||
DDLogWarn(@"Received unhandled envelope type: %d", (int)envelope.type);
|
||||
break;
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user