commit
8d8daf3a6c
|
@ -136,6 +136,8 @@
|
|||
768A1A2B17FC9CD300E00ED8 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 768A1A2A17FC9CD300E00ED8 /* libz.dylib */; };
|
||||
76C87F19181EFCE600C4ACAB /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; };
|
||||
76EB054018170B33006006FC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03C318170B33006006FC /* AppDelegate.m */; };
|
||||
7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; };
|
||||
7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; };
|
||||
7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */; };
|
||||
7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
7BDCFC08242186E700641C39 /* NotificationServiceExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */; };
|
||||
|
@ -1098,6 +1100,8 @@
|
|||
76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; };
|
||||
76EB03C218170B33006006FC /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
|
||||
76EB03C318170B33006006FC /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
|
||||
7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = "<group>"; };
|
||||
7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = "<group>"; };
|
||||
7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SessionNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = "<group>"; };
|
||||
7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
|
@ -2089,6 +2093,7 @@
|
|||
B849789525D4A2F500D0D0B3 /* LinkPreviewView.swift */,
|
||||
B8D84EA225DF745A005A043E /* LinkPreviewState.swift */,
|
||||
B8EB20EF2640F7F000773E52 /* OpenGroupInvitationView.swift */,
|
||||
7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */,
|
||||
);
|
||||
path = "Content Views";
|
||||
sourceTree = "<group>";
|
||||
|
@ -2377,6 +2382,7 @@
|
|||
B8F5F60225EDE16F003BF8D4 /* DataExtractionNotification.swift */,
|
||||
C300A5E62554B07300555489 /* ExpirationTimerUpdate.swift */,
|
||||
C3DA9C0625AE7396008F7C7E /* ConfigurationMessage.swift */,
|
||||
7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */,
|
||||
);
|
||||
path = "Control Messages";
|
||||
sourceTree = "<group>";
|
||||
|
@ -4622,6 +4628,7 @@
|
|||
C3A3A156256E1B91004D228D /* ProtoUtils.m in Sources */,
|
||||
C3471ECB2555356A00297E91 /* MessageSender+Encryption.swift in Sources */,
|
||||
C352A32F2557549C00338F3E /* NotifyPNServerJob.swift in Sources */,
|
||||
7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */,
|
||||
C300A5F22554B09800555489 /* MessageSender.swift in Sources */,
|
||||
C3C2A74D2553A39700C340D1 /* VisibleMessage.swift in Sources */,
|
||||
C32C5AAD256DBE8F003C73A2 /* TSInfoMessage.m in Sources */,
|
||||
|
@ -4903,6 +4910,7 @@
|
|||
B8D0A25025E3678700C1835E /* LinkDeviceVC.swift in Sources */,
|
||||
3496957321A301A100DCFE74 /* OWSBackupJob.m in Sources */,
|
||||
B894D0752339EDCF00B4D94D /* NukeDataModal.swift in Sources */,
|
||||
7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */,
|
||||
B897621C25D201F7004F83B2 /* ScrollToBottomButton.swift in Sources */,
|
||||
346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */,
|
||||
45E5A6991F61E6DE001E4A8A /* MarqueeLabel.swift in Sources */,
|
||||
|
|
|
@ -7,37 +7,37 @@ extension ContextMenuVC {
|
|||
let work: () -> Void
|
||||
|
||||
static func reply(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
let title = "Reply"
|
||||
let title = NSLocalizedString("context_menu_reply", comment: "")
|
||||
return Action(icon: UIImage(named: "ic_reply")!, title: title) { delegate?.reply(viewItem) }
|
||||
}
|
||||
|
||||
static func copy(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
let title = "Copy"
|
||||
let title = NSLocalizedString("copy", comment: "")
|
||||
return Action(icon: UIImage(named: "ic_copy")!, title: title) { delegate?.copy(viewItem) }
|
||||
}
|
||||
|
||||
static func copySessionID(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
let title = "Copy Session ID"
|
||||
let title = NSLocalizedString("vc_conversation_settings_copy_session_id_button_title", comment: "")
|
||||
return Action(icon: UIImage(named: "ic_copy")!, title: title) { delegate?.copySessionID(viewItem) }
|
||||
}
|
||||
|
||||
static func delete(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
let title = "Delete"
|
||||
let title = NSLocalizedString("TXT_DELETE_TITLE", comment: "")
|
||||
return Action(icon: UIImage(named: "ic_trash")!, title: title) { delegate?.delete(viewItem) }
|
||||
}
|
||||
|
||||
static func save(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
let title = "Save"
|
||||
let title = NSLocalizedString("context_menu_save", comment: "")
|
||||
return Action(icon: UIImage(named: "ic_download")!, title: title) { delegate?.save(viewItem) }
|
||||
}
|
||||
|
||||
static func ban(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
let title = "Ban User"
|
||||
let title = NSLocalizedString("context_menu_ban_user", comment: "")
|
||||
return Action(icon: UIImage(named: "ic_block")!, title: title) { delegate?.ban(viewItem) }
|
||||
}
|
||||
|
||||
static func banAndDeleteAllMessages(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
let title = "Ban and Delete All"
|
||||
let title = NSLocalizedString("context_menu_ban_and_delete_all", comment: "")
|
||||
return Action(icon: UIImage(named: "ic_block")!, title: title) { delegate?.banAndDeleteAllMessages(viewItem) }
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ extension ContextMenuVC {
|
|||
}
|
||||
|
||||
// MARK: Delegate
|
||||
protocol ContextMenuActionDelegate : class {
|
||||
protocol ContextMenuActionDelegate : AnyObject {
|
||||
|
||||
func reply(_ viewItem: ConversationViewItem)
|
||||
func copy(_ viewItem: ConversationViewItem)
|
||||
|
|
|
@ -555,7 +555,83 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
|
|||
}
|
||||
|
||||
func delete(_ viewItem: ConversationViewItem) {
|
||||
viewItem.deleteAction()
|
||||
if (!self.isUnsendRequesEnabled) {
|
||||
viewItem.deleteAction()
|
||||
return
|
||||
}
|
||||
|
||||
func showInputAccessoryView() {
|
||||
UIView.animate(withDuration: 0.25, animations: {
|
||||
self.inputAccessoryView?.isHidden = false
|
||||
self.inputAccessoryView?.alpha = 1
|
||||
})
|
||||
}
|
||||
|
||||
if viewItem.interaction.interactionType() == .outgoingMessage,
|
||||
let message = viewItem.interaction as? TSMessage, message.serverHash != nil {
|
||||
let alertVC = UIAlertController.init(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
let deleteLocallyAction = UIAlertAction.init(title: NSLocalizedString("delete_message_for_me", comment: ""), style: .destructive) { _ in
|
||||
self.deleteLocally(viewItem)
|
||||
showInputAccessoryView()
|
||||
}
|
||||
alertVC.addAction(deleteLocallyAction)
|
||||
|
||||
var title = NSLocalizedString("delete_message_for_everyone", comment: "")
|
||||
if !viewItem.isGroupThread {
|
||||
title = String(format: NSLocalizedString("delete_message_for_me_and_recipient", comment: ""), viewItem.interaction.thread.name())
|
||||
}
|
||||
let deleteRemotelyAction = UIAlertAction.init(title: title, style: .destructive) { _ in
|
||||
self.deleteForEveryone(viewItem)
|
||||
showInputAccessoryView()
|
||||
}
|
||||
alertVC.addAction(deleteRemotelyAction)
|
||||
|
||||
let cancelAction = UIAlertAction.init(title: NSLocalizedString("TXT_CANCEL_TITLE", comment: ""), style: .cancel) {_ in
|
||||
showInputAccessoryView()
|
||||
}
|
||||
alertVC.addAction(cancelAction)
|
||||
|
||||
self.inputAccessoryView?.isHidden = true
|
||||
self.inputAccessoryView?.alpha = 0
|
||||
self.presentAlert(alertVC)
|
||||
} else {
|
||||
deleteLocally(viewItem)
|
||||
}
|
||||
}
|
||||
|
||||
private func buildUsendRequest(_ viewItem: ConversationViewItem) -> UnsendRequest? {
|
||||
if let message = viewItem.interaction as? TSMessage,
|
||||
message.isOpenGroupMessage || message.serverHash == nil { return nil }
|
||||
let unsendRequest = UnsendRequest()
|
||||
switch viewItem.interaction.interactionType() {
|
||||
case .incomingMessage:
|
||||
if let incomingMessage = viewItem.interaction as? TSIncomingMessage {
|
||||
unsendRequest.author = incomingMessage.authorId
|
||||
}
|
||||
case .outgoingMessage: unsendRequest.author = getUserHexEncodedPublicKey()
|
||||
default: return nil// Should never occur
|
||||
}
|
||||
unsendRequest.timestamp = viewItem.interaction.timestamp
|
||||
return unsendRequest
|
||||
}
|
||||
|
||||
func deleteLocally(_ viewItem: ConversationViewItem) {
|
||||
viewItem.deleteLocallyAction()
|
||||
if let unsendRequest = buildUsendRequest(viewItem) {
|
||||
SNMessagingKitConfiguration.shared.storage.write { transaction in
|
||||
MessageSender.send(unsendRequest, to: .contact(publicKey: getUserHexEncodedPublicKey()), using: transaction).retainUntilComplete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func deleteForEveryone(_ viewItem: ConversationViewItem) {
|
||||
viewItem.deleteLocallyAction()
|
||||
viewItem.deleteRemotelyAction()
|
||||
if let unsendRequest = buildUsendRequest(viewItem) {
|
||||
SNMessagingKitConfiguration.shared.storage.write { transaction in
|
||||
MessageSender.send(unsendRequest, in: self.thread, using: transaction as! YapDatabaseReadWriteTransaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func save(_ viewItem: ConversationViewItem) {
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
// • Photo rounding (the small corners don't have the correct rounding)
|
||||
// • Remaining search glitchiness
|
||||
|
||||
final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversationSettingsViewDelegate, ConversationSearchControllerDelegate, UITableViewDataSource, UITableViewDelegate {
|
||||
final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversationSettingsViewDelegate, ConversationSearchControllerDelegate, UITableViewDataSource, UITableViewDelegate {
|
||||
let isUnsendRequesEnabled = false // Switch this to true if unsend request is done on all platforms
|
||||
let thread: TSThread
|
||||
let focusedMessageID: String? // This isn't actually used ATM
|
||||
var unreadViewItems: [ConversationViewItem] = []
|
||||
|
@ -484,7 +485,7 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
|
|||
}
|
||||
|
||||
func updateUnreadCountView() {
|
||||
let visibleViewItems = (messagesTableView.indexPathsForVisibleRows ?? []).map { viewItems[$0.row] }
|
||||
let visibleViewItems = (messagesTableView.indexPathsForVisibleRows ?? []).map { viewItems[ifValid: $0.row] }
|
||||
for visibleItem in visibleViewItems {
|
||||
guard let index = unreadViewItems.firstIndex(where: { $0 === visibleItem }) else { continue }
|
||||
unreadViewItems.remove(at: index)
|
||||
|
|
|
@ -15,6 +15,7 @@ typedef NS_ENUM(NSInteger, OWSMessageCellType) {
|
|||
OWSMessageCellType_GenericAttachment,
|
||||
OWSMessageCellType_MediaMessage,
|
||||
OWSMessageCellType_OversizeTextDownloading,
|
||||
OWSMessageCellType_DeletedMessage
|
||||
};
|
||||
|
||||
NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
|
||||
|
@ -132,7 +133,10 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
|
|||
- (void)copyTextAction;
|
||||
- (void)shareMediaAction;
|
||||
- (void)saveMediaAction;
|
||||
- (void)deleteAction;
|
||||
- (void)deleteLocallyAction;
|
||||
- (void)deleteRemotelyAction;
|
||||
|
||||
- (void)deleteAction; // Remove this after the unsend request is enabled
|
||||
|
||||
- (BOOL)canCopyMedia;
|
||||
- (BOOL)canSaveMedia;
|
||||
|
|
|
@ -470,6 +470,11 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
|||
self.hasViewState = YES;
|
||||
|
||||
TSMessage *message = (TSMessage *)self.interaction;
|
||||
|
||||
if (message.isDeleted) {
|
||||
self.messageCellType = OWSMessageCellType_DeletedMessage;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for quoted replies _before_ media album handling,
|
||||
// since that logic may exit early.
|
||||
|
@ -967,6 +972,66 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
|||
return [self saveMediaAlbumItems:mediaAlbumItems];
|
||||
}
|
||||
|
||||
- (void)deleteLocallyAction
|
||||
{
|
||||
TSMessage *message = (TSMessage *)self.interaction;
|
||||
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
[MessageInvalidator invalidate:message with:transaction];
|
||||
[self.interaction removeWithTransaction:transaction];
|
||||
if (self.interaction.interactionType == OWSInteractionType_OutgoingMessage) {
|
||||
[LKStorage.shared cancelPendingMessageSendJobIfNeededForMessage:self.interaction.timestamp using:transaction];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)deleteRemotelyAction
|
||||
{
|
||||
TSMessage *message = (TSMessage *)self.interaction;
|
||||
|
||||
if (self.isGroupThread) {
|
||||
TSGroupThread *groupThread = (TSGroupThread *)self.interaction.thread;
|
||||
|
||||
// Only allow deletion on incoming and outgoing messages
|
||||
OWSInteractionType interationType = self.interaction.interactionType;
|
||||
if (interationType != OWSInteractionType_IncomingMessage && interationType != OWSInteractionType_OutgoingMessage) return;
|
||||
|
||||
if (groupThread.isOpenGroup) {
|
||||
// Make sure it's an open group message
|
||||
if (!message.isOpenGroupMessage) return;
|
||||
|
||||
// Get the open group
|
||||
SNOpenGroupV2 *openGroupV2 = [LKStorage.shared getV2OpenGroupForThreadID:groupThread.uniqueId];
|
||||
if (openGroupV2 == nil) return;
|
||||
|
||||
// If it's an incoming message the user must have moderator status
|
||||
if (self.interaction.interactionType == OWSInteractionType_IncomingMessage) {
|
||||
NSString *userPublicKey = [LKStorage.shared getUserPublicKey];
|
||||
if (![SNOpenGroupAPIV2 isUserModerator:userPublicKey forRoom:openGroupV2.room onServer:openGroupV2.server]) { return; }
|
||||
}
|
||||
|
||||
// Delete the message
|
||||
[[SNOpenGroupAPIV2 deleteMessageWithServerID:message.openGroupServerMessageID fromRoom:openGroupV2.room onServer:openGroupV2.server].catch(^(NSError *error) {
|
||||
// Roll back
|
||||
[self.interaction save];
|
||||
}) retainUntilComplete];
|
||||
} else {
|
||||
NSString *groupPublicKey = [LKGroupUtilities getDecodedGroupID:groupThread.groupModel.groupId];
|
||||
[[SNSnodeAPI deleteMessageForPublickKey:groupPublicKey serverHashes:@[message.serverHash]].catch(^(NSError *error) {
|
||||
// Roll back
|
||||
[self.interaction save];
|
||||
}) retainUntilComplete];
|
||||
}
|
||||
} else {
|
||||
TSContactThread *contactThread = (TSContactThread *)self.interaction.thread;
|
||||
[[SNSnodeAPI deleteMessageForPublickKey:contactThread.contactSessionID serverHashes:@[message.serverHash]].catch(^(NSError *error) {
|
||||
// Roll back
|
||||
[self.interaction save];
|
||||
}) retainUntilComplete];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Remove this after the unsend request is enabled
|
||||
- (void)deleteAction
|
||||
{
|
||||
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
|
|
|
@ -830,6 +830,17 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[updatedNeighborItemSet addObject:itemId];
|
||||
}
|
||||
}
|
||||
|
||||
// Add the following item of a deleted message to update
|
||||
// to show the date header of the deleted message if needed
|
||||
for (NSString *deletedItemId in deletedItemIdSet) {
|
||||
NSUInteger oldIndex = [oldItemIdList indexOfObject:deletedItemId];
|
||||
if (oldIndex != NSNotFound && oldIndex + 1 < oldItemIdList.count) {
|
||||
NSString *nextItemId = oldItemIdList[oldIndex + 1];
|
||||
[updatedItemSet addObject:nextItemId];
|
||||
[updatedNeighborItemSet addObject:nextItemId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Updates.
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
|
||||
final class DeletedMessageView : UIView {
|
||||
private let viewItem: ConversationViewItem
|
||||
private let textColor: UIColor
|
||||
|
||||
// MARK: Settings
|
||||
private static let iconSize: CGFloat = 18
|
||||
private static let iconImageViewSize: CGFloat = 30
|
||||
|
||||
// MARK: Lifecycle
|
||||
init(viewItem: ConversationViewItem, textColor: UIColor) {
|
||||
self.viewItem = viewItem
|
||||
self.textColor = textColor
|
||||
super.init(frame: CGRect.zero)
|
||||
setUpViewHierarchy()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
preconditionFailure("Use init(viewItem:textColor:) instead.")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure("Use init(viewItem:textColor:) instead.")
|
||||
}
|
||||
|
||||
private func setUpViewHierarchy() {
|
||||
// Image view
|
||||
let iconSize = DeletedMessageView.iconSize
|
||||
let icon = UIImage(named: "ic_trash")?.withTint(textColor)?.resizedImage(to: CGSize(width: iconSize, height: iconSize))
|
||||
let imageView = UIImageView(image: icon)
|
||||
imageView.contentMode = .center
|
||||
let iconImageViewSize = DeletedMessageView.iconImageViewSize
|
||||
imageView.set(.width, to: iconImageViewSize)
|
||||
imageView.set(.height, to: iconImageViewSize)
|
||||
// Body label
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.lineBreakMode = .byTruncatingTail
|
||||
titleLabel.text = NSLocalizedString("message_deleted", comment: "")
|
||||
titleLabel.textColor = textColor
|
||||
titleLabel.font = .systemFont(ofSize: Values.smallFontSize)
|
||||
// Stack view
|
||||
let stackView = UIStackView(arrangedSubviews: [ imageView, titleLabel ])
|
||||
stackView.axis = .horizontal
|
||||
stackView.alignment = .center
|
||||
stackView.isLayoutMarginsRelativeArrangement = true
|
||||
stackView.layoutMargins = UIEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 6)
|
||||
addSubview(stackView)
|
||||
stackView.pin(to: self, withInset: Values.smallSpacing)
|
||||
}
|
||||
}
|
||||
|
|
@ -202,7 +202,6 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
|
|||
doubleTapGestureRecognizer.numberOfTapsRequired = 2
|
||||
addGestureRecognizer(doubleTapGestureRecognizer)
|
||||
tapGestureRecognizer.require(toFail: doubleTapGestureRecognizer)
|
||||
addGestureRecognizer(panGestureRecognizer)
|
||||
}
|
||||
|
||||
// MARK: Updating
|
||||
|
@ -275,6 +274,12 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
|
|||
timerView.isHidden = !viewItem.isExpiringMessage
|
||||
timerViewOutgoingMessageConstraint.isActive = (direction == .outgoing)
|
||||
timerViewIncomingMessageConstraint.isActive = (direction == .incoming)
|
||||
// Swipe to reply
|
||||
if (message.isDeleted) {
|
||||
removeGestureRecognizer(panGestureRecognizer)
|
||||
} else {
|
||||
addGestureRecognizer(panGestureRecognizer)
|
||||
}
|
||||
}
|
||||
|
||||
private func populateHeader(for viewItem: ConversationViewItem) {
|
||||
|
@ -389,6 +394,10 @@ final class VisibleMessageCell : MessageCell, LinkPreviewViewDelegate {
|
|||
snContentView.addSubview(documentView)
|
||||
documentView.pin(to: snContentView)
|
||||
}
|
||||
case .deletedMessage:
|
||||
let deletedMessageView = DeletedMessageView(viewItem: viewItem, textColor: bodyLabelTextColor)
|
||||
snContentView.addSubview(deletedMessageView)
|
||||
deletedMessageView.pin(to: snContentView)
|
||||
default: return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -549,3 +549,12 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -549,3 +549,11 @@
|
|||
"vc_conversation_settings_notify_for_mentions_only_title" = "Notify for Mentions Only";
|
||||
"vc_conversation_settings_notify_for_mentions_only_explanation" = "When enabled, you'll only be notified for messages mentioning you.";
|
||||
"view_conversation_title_notify_for_mentions_only" = "Notifying for Mentions Only";
|
||||
"message_deleted" = "This message has been deleted";
|
||||
"delete_message_for_me" = "Delete just for me";
|
||||
"delete_message_for_everyone" = "Delete for everyone";
|
||||
"delete_message_for_me_and_recipient" = "Delete for me and %@";
|
||||
"context_menu_reply" = "Reply";
|
||||
"context_menu_save" = "Save";
|
||||
"context_menu_ban_user" = "Ban User";
|
||||
"context_menu_ban_and_delete_all" = "Ban and Delete All";
|
||||
|
|
|
@ -95,6 +95,7 @@ protocol NotificationPresenterAdaptee: class {
|
|||
func notify(category: AppNotificationCategory, title: String?, body: String, userInfo: [AnyHashable: Any], sound: OWSSound?, replacingIdentifier: String?)
|
||||
|
||||
func cancelNotifications(threadId: String)
|
||||
func cancelNotification(identifier: String)
|
||||
func clearAllNotifications()
|
||||
}
|
||||
|
||||
|
@ -219,6 +220,8 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
|
|||
let userInfo = [
|
||||
AppNotificationUserInfoKey.threadId: threadId
|
||||
]
|
||||
|
||||
let identifier: String = incomingMessage.notificationIdentifier ?? UUID().uuidString
|
||||
|
||||
DispatchQueue.main.async {
|
||||
notificationBody = MentionUtilities.highlightMentions(in: notificationBody!, threadID: thread.uniqueId!)
|
||||
|
@ -227,7 +230,8 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
|
|||
title: notificationTitle,
|
||||
body: notificationBody ?? "",
|
||||
userInfo: userInfo,
|
||||
sound: sound)
|
||||
sound: sound,
|
||||
replacingIdentifier: identifier)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,6 +264,13 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
|
|||
sound: sound)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public func cancelNotification(_ identifier: String) {
|
||||
DispatchQueue.main.async {
|
||||
self.adaptee.cancelNotification(identifier: identifier)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public func cancelNotifications(threadId: String) {
|
||||
|
|
|
@ -48,7 +48,7 @@ public final class BackgroundPoller : NSObject {
|
|||
// messages failed to parse.
|
||||
guard let envelope = SNProtoEnvelope.from(json),
|
||||
let data = try? envelope.serializedData() else { return nil }
|
||||
let job = MessageReceiveJob(data: data, isBackgroundPoll: true)
|
||||
let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: true)
|
||||
return job.execute()
|
||||
}
|
||||
return when(fulfilled: promises) // The promise returned by MessageReceiveJob never rejects
|
||||
|
|
|
@ -3,6 +3,7 @@ import PromiseKit
|
|||
|
||||
public final class MessageReceiveJob : NSObject, Job, NSCoding { // NSObject/NSCoding conformance is needed for YapDatabase compatibility
|
||||
public let data: Data
|
||||
public let serverHash: String?
|
||||
public let openGroupMessageServerID: UInt64?
|
||||
public let openGroupID: String?
|
||||
public let isBackgroundPoll: Bool
|
||||
|
@ -15,8 +16,9 @@ public final class MessageReceiveJob : NSObject, Job, NSCoding { // NSObject/NSC
|
|||
public static let maxFailureCount: UInt = 10
|
||||
|
||||
// MARK: Initialization
|
||||
public init(data: Data, openGroupMessageServerID: UInt64? = nil, openGroupID: String? = nil, isBackgroundPoll: Bool) {
|
||||
public init(data: Data, serverHash: String? = nil, openGroupMessageServerID: UInt64? = nil, openGroupID: String? = nil, isBackgroundPoll: Bool) {
|
||||
self.data = data
|
||||
self.serverHash = serverHash
|
||||
self.openGroupMessageServerID = openGroupMessageServerID
|
||||
self.openGroupID = openGroupID
|
||||
self.isBackgroundPoll = isBackgroundPoll
|
||||
|
@ -32,6 +34,7 @@ public final class MessageReceiveJob : NSObject, Job, NSCoding { // NSObject/NSC
|
|||
let id = coder.decodeObject(forKey: "id") as! String?,
|
||||
let isBackgroundPoll = coder.decodeObject(forKey: "isBackgroundPoll") as! Bool? else { return nil }
|
||||
self.data = data
|
||||
self.serverHash = coder.decodeObject(forKey: "serverHash") as! String?
|
||||
self.openGroupMessageServerID = coder.decodeObject(forKey: "openGroupMessageServerID") as! UInt64?
|
||||
self.openGroupID = coder.decodeObject(forKey: "openGroupID") as! String?
|
||||
self.isBackgroundPoll = isBackgroundPoll
|
||||
|
@ -41,6 +44,7 @@ public final class MessageReceiveJob : NSObject, Job, NSCoding { // NSObject/NSC
|
|||
|
||||
public func encode(with coder: NSCoder) {
|
||||
coder.encode(data, forKey: "data")
|
||||
coder.encode(serverHash, forKey: "serverHash")
|
||||
coder.encode(openGroupMessageServerID, forKey: "openGroupMessageServerID")
|
||||
coder.encode(openGroupID, forKey: "openGroupID")
|
||||
coder.encode(isBackgroundPoll, forKey: "isBackgroundPoll")
|
||||
|
@ -62,6 +66,7 @@ public final class MessageReceiveJob : NSObject, Job, NSCoding { // NSObject/NSC
|
|||
do {
|
||||
let isRetry = (self.failureCount != 0)
|
||||
let (message, proto) = try MessageReceiver.parse(self.data, openGroupMessageServerID: self.openGroupMessageServerID, isRetry: isRetry, using: transaction)
|
||||
message.serverHash = self.serverHash
|
||||
try MessageReceiver.handle(message, associatedWithProto: proto, openGroupID: self.openGroupID, isBackgroundPoll: self.isBackgroundPoll, using: transaction)
|
||||
self.handleSuccess()
|
||||
seal.fulfill(())
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
import SessionUtilitiesKit
|
||||
|
||||
@objc(SNUnsendRequest)
|
||||
public final class UnsendRequest: ControlMessage {
|
||||
public var timestamp: UInt64?
|
||||
public var author: String?
|
||||
|
||||
public override var isSelfSendValid: Bool { true }
|
||||
|
||||
// MARK: Validation
|
||||
public override var isValid: Bool {
|
||||
guard super.isValid else { return false }
|
||||
return timestamp != nil && author != nil
|
||||
}
|
||||
|
||||
// MARK: Initialization
|
||||
public override init() { super.init() }
|
||||
|
||||
internal init(timestamp: UInt64, author: String) {
|
||||
super.init()
|
||||
self.timestamp = timestamp
|
||||
self.author = author
|
||||
}
|
||||
|
||||
// MARK: Coding
|
||||
public required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
if let timestamp = coder.decodeObject(forKey: "timestamp") as! UInt64? { self.timestamp = timestamp }
|
||||
if let author = coder.decodeObject(forKey: "author") as! String? { self.author = author }
|
||||
}
|
||||
|
||||
public override func encode(with coder: NSCoder) {
|
||||
super.encode(with: coder)
|
||||
coder.encode(timestamp, forKey: "timestamp")
|
||||
coder.encode(author, forKey: "author")
|
||||
}
|
||||
|
||||
// MARK: Proto Conversion
|
||||
public override class func fromProto(_ proto: SNProtoContent) -> UnsendRequest? {
|
||||
guard let unsendRequestProto = proto.unsendRequest else { return nil }
|
||||
let timestamp = unsendRequestProto.timestamp
|
||||
let author = unsendRequestProto.author
|
||||
return UnsendRequest(timestamp: timestamp, author: author)
|
||||
}
|
||||
|
||||
public override func toProto(using transaction: YapDatabaseReadWriteTransaction) -> SNProtoContent? {
|
||||
guard let timestamp = timestamp, let author = author else {
|
||||
SNLog("Couldn't construct unsend request proto from: \(self).")
|
||||
return nil
|
||||
}
|
||||
let unsendRequestProto = SNProtoUnsendRequest.builder(timestamp: timestamp, author: author)
|
||||
let contentProto = SNProtoContent.builder()
|
||||
do {
|
||||
contentProto.setUnsendRequest(try unsendRequestProto.build())
|
||||
return try contentProto.build()
|
||||
} catch {
|
||||
SNLog("Couldn't construct unsend request proto from: \(self).")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Description
|
||||
public override var description: String {
|
||||
"""
|
||||
UnsendRequest(
|
||||
timestamp: \(timestamp?.description ?? "null")
|
||||
author: \(author?.description ?? "null")
|
||||
)
|
||||
"""
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ public class Message : NSObject, NSCoding { // NSObject/NSCoding conformance is
|
|||
public var groupPublicKey: String?
|
||||
public var openGroupServerMessageID: UInt64?
|
||||
public var openGroupServerTimestamp: UInt64?
|
||||
public var serverHash: String?
|
||||
|
||||
public var ttl: UInt64 { 14 * 24 * 60 * 60 * 1000 }
|
||||
public var isSelfSendValid: Bool { false }
|
||||
|
@ -35,6 +36,7 @@ public class Message : NSObject, NSCoding { // NSObject/NSCoding conformance is
|
|||
if let groupPublicKey = coder.decodeObject(forKey: "groupPublicKey") as! String? { self.groupPublicKey = groupPublicKey }
|
||||
if let openGroupServerMessageID = coder.decodeObject(forKey: "openGroupServerMessageID") as! UInt64? { self.openGroupServerMessageID = openGroupServerMessageID }
|
||||
if let openGroupServerTimestamp = coder.decodeObject(forKey: "openGroupServerTimestamp") as! UInt64? { self.openGroupServerTimestamp = openGroupServerTimestamp }
|
||||
if let serverHash = coder.decodeObject(forKey: "serverHash") as! String? { self.serverHash = serverHash }
|
||||
}
|
||||
|
||||
public func encode(with coder: NSCoder) {
|
||||
|
@ -47,6 +49,7 @@ public class Message : NSObject, NSCoding { // NSObject/NSCoding conformance is
|
|||
coder.encode(groupPublicKey, forKey: "groupPublicKey")
|
||||
coder.encode(openGroupServerMessageID, forKey: "openGroupServerMessageID")
|
||||
coder.encode(openGroupServerTimestamp, forKey: "openGroupServerTimestamp")
|
||||
coder.encode(serverHash, forKey: "serverHash")
|
||||
}
|
||||
|
||||
// MARK: Proto Conversion
|
||||
|
|
|
@ -22,7 +22,8 @@ public extension TSIncomingMessage {
|
|||
serverTimestamp: visibleMessage.openGroupServerTimestamp as NSNumber?,
|
||||
wasReceivedByUD: true,
|
||||
openGroupInvitationName: visibleMessage.openGroupInvitation?.name,
|
||||
openGroupInvitationURL: visibleMessage.openGroupInvitation?.url
|
||||
openGroupInvitationURL: visibleMessage.openGroupInvitation?.url,
|
||||
serverHash: visibleMessage.serverHash
|
||||
)
|
||||
result.openGroupServerMessageID = openGroupServerMessageID
|
||||
return result
|
||||
|
|
|
@ -18,6 +18,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@property (nonatomic, readonly) BOOL isUserMentioned;
|
||||
|
||||
@property (nonatomic, readonly, nullable) NSString *notificationIdentifier;
|
||||
|
||||
- (instancetype)initMessageWithTimestamp:(uint64_t)timestamp
|
||||
inThread:(nullable TSThread *)thread
|
||||
messageBody:(nullable NSString *)body
|
||||
|
@ -62,7 +64,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
serverTimestamp:(nullable NSNumber *)serverTimestamp
|
||||
wasReceivedByUD:(BOOL)wasReceivedByUD
|
||||
openGroupInvitationName:(nullable NSString *)openGroupInvitationName
|
||||
openGroupInvitationURL:(nullable NSString *)openGroupInvitationURL NS_DESIGNATED_INITIALIZER;
|
||||
openGroupInvitationURL:(nullable NSString *)openGroupInvitationURL
|
||||
serverHash:(nullable NSString*)serverHash NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
|
@ -88,6 +91,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (void)markAsReadNowWithSendReadReceipt:(BOOL)sendReadReceipt
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
- (void)setNotificationIdentifier:(NSString * _Nullable)notificationIdentifier
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -56,6 +56,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
wasReceivedByUD:(BOOL)wasReceivedByUD
|
||||
openGroupInvitationName:(nullable NSString *)openGroupInvitationName
|
||||
openGroupInvitationURL:(nullable NSString *)openGroupInvitationURL
|
||||
serverHash:(nullable NSString *)serverHash
|
||||
{
|
||||
self = [super initMessageWithTimestamp:timestamp
|
||||
inThread:thread
|
||||
|
@ -66,7 +67,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
quotedMessage:quotedMessage
|
||||
linkPreview:linkPreview
|
||||
openGroupInvitationName:openGroupInvitationName
|
||||
openGroupInvitationURL:openGroupInvitationURL];
|
||||
openGroupInvitationURL:openGroupInvitationURL
|
||||
serverHash:serverHash];
|
||||
|
||||
if (!self) {
|
||||
return self;
|
||||
|
@ -77,6 +79,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
_read = NO;
|
||||
_serverTimestamp = serverTimestamp;
|
||||
_wasReceivedByUD = wasReceivedByUD;
|
||||
_notificationIdentifier = nil;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
@ -128,6 +131,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return (self.body != nil && [self.body containsString:[NSString stringWithFormat:@"@%@", userPublicKey]]) || (self.quotedMessage != nil && [self.quotedMessage.authorId isEqualToString:userPublicKey]);
|
||||
}
|
||||
|
||||
- (void)setNotificationIdentifier:(NSString * _Nullable)notificationIdentifier transaction:(nonnull YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
_notificationIdentifier = notificationIdentifier;
|
||||
[self saveWithTransaction:transaction];
|
||||
}
|
||||
|
||||
#pragma mark - OWSReadTracking
|
||||
|
||||
- (BOOL)shouldAffectUnreadCounts
|
||||
|
|
|
@ -57,7 +57,8 @@ NSUInteger TSInfoMessageSchemaVersion = 1;
|
|||
quotedMessage:nil
|
||||
linkPreview:nil
|
||||
openGroupInvitationName:nil
|
||||
openGroupInvitationURL:nil];
|
||||
openGroupInvitationURL:nil
|
||||
serverHash:nil];
|
||||
|
||||
if (!self) {
|
||||
return self;
|
||||
|
|
|
@ -38,6 +38,8 @@ extern const NSUInteger kOversizeTextMessageSizeThreshold;
|
|||
@property (nonatomic, readonly) BOOL isOpenGroupMessage;
|
||||
@property (nonatomic, readonly, nullable) NSString *openGroupInvitationName;
|
||||
@property (nonatomic, readonly, nullable) NSString *openGroupInvitationURL;
|
||||
@property (nonatomic, nullable) NSString *serverHash;
|
||||
@property (nonatomic) BOOL isDeleted;
|
||||
|
||||
- (instancetype)initInteractionWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread NS_UNAVAILABLE;
|
||||
|
||||
|
@ -50,7 +52,8 @@ extern const NSUInteger kOversizeTextMessageSizeThreshold;
|
|||
quotedMessage:(nullable TSQuotedMessage *)quotedMessage
|
||||
linkPreview:(nullable OWSLinkPreview *)linkPreview
|
||||
openGroupInvitationName:(nullable NSString *)openGroupInvitationName
|
||||
openGroupInvitationURL:(nullable NSString *)openGroupInvitationURL NS_DESIGNATED_INITIALIZER;
|
||||
openGroupInvitationURL:(nullable NSString *)openGroupInvitationURL
|
||||
serverHash:(nullable NSString *)serverHash NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
|
@ -80,6 +83,8 @@ extern const NSUInteger kOversizeTextMessageSizeThreshold;
|
|||
|
||||
- (void)updateWithLinkPreview:(OWSLinkPreview *)linkPreview transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
- (void)updateForDeletionWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -65,6 +65,7 @@ const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024;
|
|||
linkPreview:(nullable OWSLinkPreview *)linkPreview
|
||||
openGroupInvitationName:(nullable NSString *)openGroupInvitationName
|
||||
openGroupInvitationURL:(nullable NSString *)openGroupInvitationURL
|
||||
serverHash:(nullable NSString *)serverHash
|
||||
{
|
||||
self = [super initInteractionWithTimestamp:timestamp inThread:thread];
|
||||
|
||||
|
@ -84,6 +85,8 @@ const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024;
|
|||
_openGroupServerMessageID = 0;
|
||||
_openGroupInvitationName = openGroupInvitationName;
|
||||
_openGroupInvitationURL = openGroupInvitationURL;
|
||||
_serverHash = serverHash;
|
||||
_isDeleted = false;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
@ -421,6 +424,23 @@ const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024;
|
|||
}];
|
||||
}
|
||||
|
||||
- (void)updateForDeletionWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
[self applyChangeToSelfAndLatestCopy:transaction
|
||||
changeBlock:^(TSMessage *message) {
|
||||
[message setBody:nil];
|
||||
[message setServerHash:nil];
|
||||
for (NSString *attachmentId in message.attachmentIds) {
|
||||
TSAttachment *_Nullable attachment =
|
||||
[TSAttachment fetchObjectWithUniqueID:attachmentId transaction:transaction];
|
||||
if (attachment) {
|
||||
[attachment removeWithTransaction:transaction];
|
||||
}
|
||||
}
|
||||
[message setIsDeleted:true];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -30,7 +30,8 @@ import SessionUtilitiesKit
|
|||
quotedMessage: TSQuotedMessage.from(visibleMessage.quote),
|
||||
linkPreview: OWSLinkPreview.from(visibleMessage.linkPreview),
|
||||
openGroupInvitationName: visibleMessage.openGroupInvitation?.name,
|
||||
openGroupInvitationURL: visibleMessage.openGroupInvitation?.url
|
||||
openGroupInvitationURL: visibleMessage.openGroupInvitation?.url,
|
||||
serverHash: visibleMessage.serverHash
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,7 +94,8 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) {
|
|||
quotedMessage:(nullable TSQuotedMessage *)quotedMessage
|
||||
linkPreview:(nullable OWSLinkPreview *)linkPreview
|
||||
openGroupInvitationName:(nullable NSString *)openGroupInvitationName
|
||||
openGroupInvitationURL:(nullable NSString *)openGroupInvitationURL NS_DESIGNATED_INITIALIZER;
|
||||
openGroupInvitationURL:(nullable NSString *)openGroupInvitationURL
|
||||
serverHash:(nullable NSString *)serverHash NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
|
|
|
@ -154,7 +154,8 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt
|
|||
quotedMessage:quotedMessage
|
||||
linkPreview:linkPreview
|
||||
openGroupInvitationName:nil
|
||||
openGroupInvitationURL:nil];
|
||||
openGroupInvitationURL:nil
|
||||
serverHash:nil];
|
||||
}
|
||||
|
||||
+ (instancetype)outgoingMessageInThread:(nullable TSThread *)thread
|
||||
|
@ -173,7 +174,8 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt
|
|||
quotedMessage:nil
|
||||
linkPreview:nil
|
||||
openGroupInvitationName:nil
|
||||
openGroupInvitationURL:nil];
|
||||
openGroupInvitationURL:nil
|
||||
serverHash:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initOutgoingMessageWithTimestamp:(uint64_t)timestamp
|
||||
|
@ -188,6 +190,7 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt
|
|||
linkPreview:(nullable OWSLinkPreview *)linkPreview
|
||||
openGroupInvitationName:(nullable NSString *)openGroupInvitationName
|
||||
openGroupInvitationURL:(nullable NSString *)openGroupInvitationURL
|
||||
serverHash:(nullable NSString *)serverHash
|
||||
{
|
||||
self = [super initMessageWithTimestamp:timestamp
|
||||
inThread:thread
|
||||
|
@ -198,7 +201,8 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt
|
|||
quotedMessage:quotedMessage
|
||||
linkPreview:linkPreview
|
||||
openGroupInvitationName:openGroupInvitationName
|
||||
openGroupInvitationURL:openGroupInvitationURL];
|
||||
openGroupInvitationURL:openGroupInvitationURL
|
||||
serverHash:serverHash];
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
@ -559,6 +563,14 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt
|
|||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Delete
|
||||
|
||||
- (void)updateForDeletionWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
[super updateForDeletionWithTransaction:transaction];
|
||||
[self removeWithTransaction:transaction];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -338,6 +338,118 @@ extension SNProtoTypingMessage.SNProtoTypingMessageBuilder {
|
|||
|
||||
#endif
|
||||
|
||||
// MARK: - SNProtoUnsendRequest
|
||||
|
||||
@objc public class SNProtoUnsendRequest: NSObject {
|
||||
|
||||
// MARK: - SNProtoUnsendRequestBuilder
|
||||
|
||||
@objc public class func builder(timestamp: UInt64, author: String) -> SNProtoUnsendRequestBuilder {
|
||||
return SNProtoUnsendRequestBuilder(timestamp: timestamp, author: author)
|
||||
}
|
||||
|
||||
// asBuilder() constructs a builder that reflects the proto's contents.
|
||||
@objc public func asBuilder() -> SNProtoUnsendRequestBuilder {
|
||||
let builder = SNProtoUnsendRequestBuilder(timestamp: timestamp, author: author)
|
||||
return builder
|
||||
}
|
||||
|
||||
@objc public class SNProtoUnsendRequestBuilder: NSObject {
|
||||
|
||||
private var proto = SessionProtos_UnsendRequest()
|
||||
|
||||
@objc fileprivate override init() {}
|
||||
|
||||
@objc fileprivate init(timestamp: UInt64, author: String) {
|
||||
super.init()
|
||||
|
||||
setTimestamp(timestamp)
|
||||
setAuthor(author)
|
||||
}
|
||||
|
||||
@objc public func setTimestamp(_ valueParam: UInt64) {
|
||||
proto.timestamp = valueParam
|
||||
}
|
||||
|
||||
@objc public func setAuthor(_ valueParam: String) {
|
||||
proto.author = valueParam
|
||||
}
|
||||
|
||||
@objc public func build() throws -> SNProtoUnsendRequest {
|
||||
return try SNProtoUnsendRequest.parseProto(proto)
|
||||
}
|
||||
|
||||
@objc public func buildSerializedData() throws -> Data {
|
||||
return try SNProtoUnsendRequest.parseProto(proto).serializedData()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate let proto: SessionProtos_UnsendRequest
|
||||
|
||||
@objc public let timestamp: UInt64
|
||||
|
||||
@objc public let author: String
|
||||
|
||||
private init(proto: SessionProtos_UnsendRequest,
|
||||
timestamp: UInt64,
|
||||
author: String) {
|
||||
self.proto = proto
|
||||
self.timestamp = timestamp
|
||||
self.author = author
|
||||
}
|
||||
|
||||
@objc
|
||||
public func serializedData() throws -> Data {
|
||||
return try self.proto.serializedData()
|
||||
}
|
||||
|
||||
@objc public class func parseData(_ serializedData: Data) throws -> SNProtoUnsendRequest {
|
||||
let proto = try SessionProtos_UnsendRequest(serializedData: serializedData)
|
||||
return try parseProto(proto)
|
||||
}
|
||||
|
||||
fileprivate class func parseProto(_ proto: SessionProtos_UnsendRequest) throws -> SNProtoUnsendRequest {
|
||||
guard proto.hasTimestamp else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: timestamp")
|
||||
}
|
||||
let timestamp = proto.timestamp
|
||||
|
||||
guard proto.hasAuthor else {
|
||||
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: author")
|
||||
}
|
||||
let author = proto.author
|
||||
|
||||
// MARK: - Begin Validation Logic for SNProtoUnsendRequest -
|
||||
|
||||
// MARK: - End Validation Logic for SNProtoUnsendRequest -
|
||||
|
||||
let result = SNProtoUnsendRequest(proto: proto,
|
||||
timestamp: timestamp,
|
||||
author: author)
|
||||
return result
|
||||
}
|
||||
|
||||
@objc public override var debugDescription: String {
|
||||
return "\(proto)"
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
extension SNProtoUnsendRequest {
|
||||
@objc public func serializedDataIgnoringErrors() -> Data? {
|
||||
return try! self.serializedData()
|
||||
}
|
||||
}
|
||||
|
||||
extension SNProtoUnsendRequest.SNProtoUnsendRequestBuilder {
|
||||
@objc public func buildIgnoringErrors() -> SNProtoUnsendRequest? {
|
||||
return try! self.build()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// MARK: - SNProtoContent
|
||||
|
||||
@objc public class SNProtoContent: NSObject {
|
||||
|
@ -366,6 +478,9 @@ extension SNProtoTypingMessage.SNProtoTypingMessageBuilder {
|
|||
if let _value = dataExtractionNotification {
|
||||
builder.setDataExtractionNotification(_value)
|
||||
}
|
||||
if let _value = unsendRequest {
|
||||
builder.setUnsendRequest(_value)
|
||||
}
|
||||
return builder
|
||||
}
|
||||
|
||||
|
@ -395,6 +510,10 @@ extension SNProtoTypingMessage.SNProtoTypingMessageBuilder {
|
|||
proto.dataExtractionNotification = valueParam.proto
|
||||
}
|
||||
|
||||
@objc public func setUnsendRequest(_ valueParam: SNProtoUnsendRequest) {
|
||||
proto.unsendRequest = valueParam.proto
|
||||
}
|
||||
|
||||
@objc public func build() throws -> SNProtoContent {
|
||||
return try SNProtoContent.parseProto(proto)
|
||||
}
|
||||
|
@ -416,18 +535,22 @@ extension SNProtoTypingMessage.SNProtoTypingMessageBuilder {
|
|||
|
||||
@objc public let dataExtractionNotification: SNProtoDataExtractionNotification?
|
||||
|
||||
@objc public let unsendRequest: SNProtoUnsendRequest?
|
||||
|
||||
private init(proto: SessionProtos_Content,
|
||||
dataMessage: SNProtoDataMessage?,
|
||||
receiptMessage: SNProtoReceiptMessage?,
|
||||
typingMessage: SNProtoTypingMessage?,
|
||||
configurationMessage: SNProtoConfigurationMessage?,
|
||||
dataExtractionNotification: SNProtoDataExtractionNotification?) {
|
||||
dataExtractionNotification: SNProtoDataExtractionNotification?,
|
||||
unsendRequest: SNProtoUnsendRequest?) {
|
||||
self.proto = proto
|
||||
self.dataMessage = dataMessage
|
||||
self.receiptMessage = receiptMessage
|
||||
self.typingMessage = typingMessage
|
||||
self.configurationMessage = configurationMessage
|
||||
self.dataExtractionNotification = dataExtractionNotification
|
||||
self.unsendRequest = unsendRequest
|
||||
}
|
||||
|
||||
@objc
|
||||
|
@ -466,6 +589,11 @@ extension SNProtoTypingMessage.SNProtoTypingMessageBuilder {
|
|||
dataExtractionNotification = try SNProtoDataExtractionNotification.parseProto(proto.dataExtractionNotification)
|
||||
}
|
||||
|
||||
var unsendRequest: SNProtoUnsendRequest? = nil
|
||||
if proto.hasUnsendRequest {
|
||||
unsendRequest = try SNProtoUnsendRequest.parseProto(proto.unsendRequest)
|
||||
}
|
||||
|
||||
// MARK: - Begin Validation Logic for SNProtoContent -
|
||||
|
||||
// MARK: - End Validation Logic for SNProtoContent -
|
||||
|
@ -475,7 +603,8 @@ extension SNProtoTypingMessage.SNProtoTypingMessageBuilder {
|
|||
receiptMessage: receiptMessage,
|
||||
typingMessage: typingMessage,
|
||||
configurationMessage: configurationMessage,
|
||||
dataExtractionNotification: dataExtractionNotification)
|
||||
dataExtractionNotification: dataExtractionNotification,
|
||||
unsendRequest: unsendRequest)
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
|
@ -196,6 +196,39 @@ extension SessionProtos_TypingMessage.Action: CaseIterable {
|
|||
|
||||
#endif // swift(>=4.2)
|
||||
|
||||
struct SessionProtos_UnsendRequest {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
/// @required
|
||||
var timestamp: UInt64 {
|
||||
get {return _timestamp ?? 0}
|
||||
set {_timestamp = newValue}
|
||||
}
|
||||
/// Returns true if `timestamp` has been explicitly set.
|
||||
var hasTimestamp: Bool {return self._timestamp != nil}
|
||||
/// Clears the value of `timestamp`. Subsequent reads from it will return its default value.
|
||||
mutating func clearTimestamp() {self._timestamp = nil}
|
||||
|
||||
/// @required
|
||||
var author: String {
|
||||
get {return _author ?? String()}
|
||||
set {_author = newValue}
|
||||
}
|
||||
/// Returns true if `author` has been explicitly set.
|
||||
var hasAuthor: Bool {return self._author != nil}
|
||||
/// Clears the value of `author`. Subsequent reads from it will return its default value.
|
||||
mutating func clearAuthor() {self._author = nil}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
|
||||
fileprivate var _timestamp: UInt64? = nil
|
||||
fileprivate var _author: String? = nil
|
||||
}
|
||||
|
||||
struct SessionProtos_Content {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
|
@ -246,6 +279,15 @@ struct SessionProtos_Content {
|
|||
/// Clears the value of `dataExtractionNotification`. Subsequent reads from it will return its default value.
|
||||
mutating func clearDataExtractionNotification() {self._dataExtractionNotification = nil}
|
||||
|
||||
var unsendRequest: SessionProtos_UnsendRequest {
|
||||
get {return _unsendRequest ?? SessionProtos_UnsendRequest()}
|
||||
set {_unsendRequest = newValue}
|
||||
}
|
||||
/// Returns true if `unsendRequest` has been explicitly set.
|
||||
var hasUnsendRequest: Bool {return self._unsendRequest != nil}
|
||||
/// Clears the value of `unsendRequest`. Subsequent reads from it will return its default value.
|
||||
mutating func clearUnsendRequest() {self._unsendRequest = nil}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
|
@ -255,6 +297,7 @@ struct SessionProtos_Content {
|
|||
fileprivate var _typingMessage: SessionProtos_TypingMessage? = nil
|
||||
fileprivate var _configurationMessage: SessionProtos_ConfigurationMessage? = nil
|
||||
fileprivate var _dataExtractionNotification: SessionProtos_DataExtractionNotification? = nil
|
||||
fileprivate var _unsendRequest: SessionProtos_UnsendRequest? = nil
|
||||
}
|
||||
|
||||
struct SessionProtos_KeyPair {
|
||||
|
@ -1504,6 +1547,50 @@ extension SessionProtos_TypingMessage.Action: SwiftProtobuf._ProtoNameProviding
|
|||
]
|
||||
}
|
||||
|
||||
extension SessionProtos_UnsendRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = _protobuf_package + ".UnsendRequest"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "timestamp"),
|
||||
2: .same(proto: "author"),
|
||||
]
|
||||
|
||||
public var isInitialized: Bool {
|
||||
if self._timestamp == nil {return false}
|
||||
if self._author == nil {return false}
|
||||
return true
|
||||
}
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularUInt64Field(value: &self._timestamp) }()
|
||||
case 2: try { try decoder.decodeSingularStringField(value: &self._author) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if let v = self._timestamp {
|
||||
try visitor.visitSingularUInt64Field(value: v, fieldNumber: 1)
|
||||
}
|
||||
if let v = self._author {
|
||||
try visitor.visitSingularStringField(value: v, fieldNumber: 2)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: SessionProtos_UnsendRequest, rhs: SessionProtos_UnsendRequest) -> Bool {
|
||||
if lhs._timestamp != rhs._timestamp {return false}
|
||||
if lhs._author != rhs._author {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = _protobuf_package + ".Content"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
|
@ -1512,6 +1599,7 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
|
|||
6: .same(proto: "typingMessage"),
|
||||
7: .same(proto: "configurationMessage"),
|
||||
8: .same(proto: "dataExtractionNotification"),
|
||||
9: .same(proto: "unsendRequest"),
|
||||
]
|
||||
|
||||
public var isInitialized: Bool {
|
||||
|
@ -1520,6 +1608,7 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
|
|||
if let v = self._typingMessage, !v.isInitialized {return false}
|
||||
if let v = self._configurationMessage, !v.isInitialized {return false}
|
||||
if let v = self._dataExtractionNotification, !v.isInitialized {return false}
|
||||
if let v = self._unsendRequest, !v.isInitialized {return false}
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -1534,6 +1623,7 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
|
|||
case 6: try { try decoder.decodeSingularMessageField(value: &self._typingMessage) }()
|
||||
case 7: try { try decoder.decodeSingularMessageField(value: &self._configurationMessage) }()
|
||||
case 8: try { try decoder.decodeSingularMessageField(value: &self._dataExtractionNotification) }()
|
||||
case 9: try { try decoder.decodeSingularMessageField(value: &self._unsendRequest) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
@ -1555,6 +1645,9 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
|
|||
if let v = self._dataExtractionNotification {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 8)
|
||||
}
|
||||
if let v = self._unsendRequest {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 9)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
|
@ -1564,6 +1657,7 @@ extension SessionProtos_Content: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
|
|||
if lhs._typingMessage != rhs._typingMessage {return false}
|
||||
if lhs._configurationMessage != rhs._configurationMessage {return false}
|
||||
if lhs._dataExtractionNotification != rhs._dataExtractionNotification {return false}
|
||||
if lhs._unsendRequest != rhs._unsendRequest {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -34,12 +34,20 @@ message TypingMessage {
|
|||
required Action action = 2;
|
||||
}
|
||||
|
||||
message UnsendRequest {
|
||||
// @required
|
||||
required uint64 timestamp = 1;
|
||||
// @required
|
||||
required string author = 2;
|
||||
}
|
||||
|
||||
message Content {
|
||||
optional DataMessage dataMessage = 1;
|
||||
optional ReceiptMessage receiptMessage = 5;
|
||||
optional TypingMessage typingMessage = 6;
|
||||
optional ConfigurationMessage configurationMessage = 7;
|
||||
optional DataExtractionNotification dataExtractionNotification = 8;
|
||||
optional UnsendRequest unsendRequest = 9;
|
||||
}
|
||||
|
||||
message KeyPair {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import SignalCoreKit
|
||||
import SessionSnodeKit
|
||||
|
||||
extension MessageReceiver {
|
||||
|
||||
|
@ -14,6 +15,7 @@ extension MessageReceiver {
|
|||
case let message as DataExtractionNotification: handleDataExtractionNotification(message, using: transaction)
|
||||
case let message as ExpirationTimerUpdate: handleExpirationTimerUpdate(message, using: transaction)
|
||||
case let message as ConfigurationMessage: handleConfigurationMessage(message, using: transaction)
|
||||
case let message as UnsendRequest: handleUnsendRequest(message, using: transaction)
|
||||
case let message as VisibleMessage: try handleVisibleMessage(message, associatedWithProto: proto, openGroupID: openGroupID, isBackgroundPoll: isBackgroundPoll, using: transaction)
|
||||
default: fatalError()
|
||||
}
|
||||
|
@ -218,6 +220,34 @@ extension MessageReceiver {
|
|||
|
||||
|
||||
|
||||
// MARK: - Unsend Requests
|
||||
|
||||
public static func handleUnsendRequest(_ message: UnsendRequest, using transaction: Any) {
|
||||
guard message.sender == message.author else { return }
|
||||
let userPublicKey = getUserHexEncodedPublicKey()
|
||||
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||
if let author = message.author, let timestamp = message.timestamp {
|
||||
let localMessage: TSMessage?
|
||||
if userPublicKey == message.sender { localMessage = TSOutgoingMessage.find(withTimestamp: timestamp) }
|
||||
else { localMessage = TSIncomingMessage.find(withAuthorId: author, timestamp: timestamp, transaction: transaction) }
|
||||
|
||||
if let messageToDelete = localMessage {
|
||||
if let incomingMessage = messageToDelete as? TSIncomingMessage {
|
||||
incomingMessage.markAsReadNow(withSendReadReceipt: false, transaction: transaction)
|
||||
if let notificationIdentifier = incomingMessage.notificationIdentifier, !notificationIdentifier.isEmpty {
|
||||
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [notificationIdentifier])
|
||||
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [notificationIdentifier])
|
||||
}
|
||||
}
|
||||
if let serverHash = messageToDelete.serverHash {
|
||||
SnodeAPI.deleteMessage(publicKey: author, serverHashes: [serverHash]).retainUntilComplete()
|
||||
}
|
||||
messageToDelete.updateForDeletion(with: transaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Visible Messages
|
||||
|
||||
@discardableResult
|
||||
|
@ -288,6 +318,7 @@ extension MessageReceiver {
|
|||
// Notify the user if needed
|
||||
guard (isMainAppAndActive || isBackgroundPoll), let tsIncomingMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction) as? TSIncomingMessage,
|
||||
let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) else { return tsMessageID }
|
||||
tsIncomingMessage.setNotificationIdentifier(UUID().uuidString, transaction: transaction)
|
||||
DispatchQueue.main.async {
|
||||
Storage.read { transaction in
|
||||
SSKEnvironment.shared.notificationsManager!.notifyUser(for: tsIncomingMessage, in: thread, transaction: transaction)
|
||||
|
|
|
@ -124,6 +124,7 @@ public enum MessageReceiver {
|
|||
if let dataExtractionNotification = DataExtractionNotification.fromProto(proto) { return dataExtractionNotification }
|
||||
if let expirationTimerUpdate = ExpirationTimerUpdate.fromProto(proto) { return expirationTimerUpdate }
|
||||
if let configurationMessage = ConfigurationMessage.fromProto(proto) { return configurationMessage }
|
||||
if let unsendRequest = UnsendRequest.fromProto(proto) { return unsendRequest }
|
||||
if let visibleMessage = VisibleMessage.fromProto(proto) { return visibleMessage }
|
||||
return nil
|
||||
}()
|
||||
|
|
|
@ -134,8 +134,9 @@ public final class MessageSender : NSObject {
|
|||
// • a configuration message
|
||||
// • a sync message
|
||||
// • a closed group control message of type `new`
|
||||
// • an unsend request
|
||||
let isNewClosedGroupControlMessage = given(message as? ClosedGroupControlMessage) { if case .new = $0.kind { return true } else { return false } } ?? false
|
||||
guard !isSelfSend || message is ConfigurationMessage || isSyncMessage || isNewClosedGroupControlMessage else {
|
||||
guard !isSelfSend || message is ConfigurationMessage || isSyncMessage || isNewClosedGroupControlMessage || message is UnsendRequest else {
|
||||
storage.write(with: { transaction in
|
||||
MessageSender.handleSuccessfulMessageSend(message, to: destination, using: transaction)
|
||||
seal.fulfill(())
|
||||
|
@ -207,12 +208,15 @@ public final class MessageSender : NSObject {
|
|||
let promiseCount = promises.count
|
||||
var errorCount = 0
|
||||
promises.forEach {
|
||||
let _ = $0.done(on: DispatchQueue.global(qos: .userInitiated)) { _ in
|
||||
let _ = $0.done(on: DispatchQueue.global(qos: .userInitiated)) { rawResponse in
|
||||
guard !isSuccess else { return } // Succeed as soon as the first promise succeeds
|
||||
isSuccess = true
|
||||
storage.write(with: { transaction in
|
||||
let json = rawResponse as? JSON
|
||||
let hash = json?["hash"] as? String
|
||||
message.serverHash = hash
|
||||
MessageSender.handleSuccessfulMessageSend(message, to: destination, isSyncMessage: isSyncMessage, using: transaction)
|
||||
var shouldNotify = (message is VisibleMessage && !isSyncMessage)
|
||||
var shouldNotify = ((message is VisibleMessage || message is UnsendRequest) && !isSyncMessage)
|
||||
/*
|
||||
if let closedGroupControlMessage = message as? ClosedGroupControlMessage, case .new = closedGroupControlMessage.kind {
|
||||
shouldNotify = true
|
||||
|
@ -328,6 +332,10 @@ public final class MessageSender : NSObject {
|
|||
Storage.shared.addReceivedMessageTimestamp(message.sentTimestamp!, using: transaction)
|
||||
// Get the visible message if possible
|
||||
if let tsMessage = TSOutgoingMessage.find(withTimestamp: message.sentTimestamp!) {
|
||||
// When the sync message is successfully sent, the hash value of this TSOutgoingMessage
|
||||
// will be replaced by the hash value of the sync message. Since the hash value of the
|
||||
// real message has no use when we delete a message. It is OK to let it be.
|
||||
tsMessage.serverHash = message.serverHash
|
||||
// Track the open group server message ID
|
||||
tsMessage.openGroupServerMessageID = message.openGroupServerMessageID ?? 0
|
||||
// Mark the message as sent
|
||||
|
|
|
@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
inThread:(TSThread *)thread
|
||||
transaction:(YapDatabaseReadTransaction *)transaction;
|
||||
|
||||
- (void)cancelNotification:(NSString *)identifier;
|
||||
- (void)clearAllNotifications;
|
||||
|
||||
@end
|
||||
|
|
|
@ -117,7 +117,7 @@ public final class ClosedGroupPoller : NSObject {
|
|||
guard let envelope = SNProtoEnvelope.from(json) else { return }
|
||||
do {
|
||||
let data = try envelope.serializedData()
|
||||
let job = MessageReceiveJob(data: data, isBackgroundPoll: false)
|
||||
let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: false)
|
||||
SNMessagingKitConfiguration.shared.storage.write { transaction in
|
||||
SessionMessagingKit.JobQueue.shared.add(job, using: transaction)
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ public final class Poller : NSObject {
|
|||
guard let envelope = SNProtoEnvelope.from(json) else { return }
|
||||
do {
|
||||
let data = try envelope.serializedData()
|
||||
let job = MessageReceiveJob(data: data, isBackgroundPoll: false)
|
||||
let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: false)
|
||||
SNMessagingKitConfiguration.shared.storage.write { transaction in
|
||||
SessionMessagingKit.JobQueue.shared.add(job, using: transaction)
|
||||
}
|
||||
|
|
|
@ -314,6 +314,13 @@ BOOL IsNoteToSelfEnabled(void)
|
|||
if (interaction.isDynamicInteraction) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([interaction isKindOfClass:[TSMessage class]]) {
|
||||
TSMessage *message = (TSMessage *)interaction;
|
||||
if (message.isDeleted) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
|
|
@ -67,6 +67,11 @@ public final class NotificationServiceExtension : UNNotificationServiceExtension
|
|||
return self.completeSilenty()
|
||||
}
|
||||
}
|
||||
// Store the notification identifier for unsend request to cancel this notification
|
||||
tsIncomingMessage.setNotificationIdentifier(request.identifier, transaction: transaction)
|
||||
case let unsendRequest as UnsendRequest:
|
||||
MessageReceiver.handleUnsendRequest(unsendRequest, using: transaction)
|
||||
return self.completeSilenty()
|
||||
case let closedGroupControlMessage as ClosedGroupControlMessage:
|
||||
// TODO: We could consider actually handling the update here. Not sure if there's enough time though, seeing as though
|
||||
// in some cases we need to send messages (e.g. our sender key) to a number of other users.
|
||||
|
|
|
@ -14,6 +14,7 @@ public final class Snode : NSObject, NSCoding { // NSObject/NSCoding conformance
|
|||
case getSwarm = "get_snodes_for_pubkey"
|
||||
case getMessages = "retrieve"
|
||||
case sendMessage = "store"
|
||||
case deleteMessage = "delete"
|
||||
case oxenDaemonRPCCall = "oxend_request"
|
||||
case getInfo = "info"
|
||||
case clearAllData = "delete_all"
|
||||
|
|
|
@ -454,6 +454,56 @@ public final class SnodeAPI : NSObject {
|
|||
return promise
|
||||
}
|
||||
|
||||
@objc(deleteMessageForPublickKey:serverHashes:)
|
||||
public static func objc_deleteMessage(publicKey: String, serverHashes: [String]) -> AnyPromise {
|
||||
AnyPromise.from(deleteMessage(publicKey: publicKey, serverHashes: serverHashes))
|
||||
}
|
||||
|
||||
public static func deleteMessage(publicKey: String, serverHashes: [String]) -> Promise<[String:Bool]> {
|
||||
let storage = SNSnodeKitConfiguration.shared.storage
|
||||
guard let userX25519PublicKey = storage.getUserPublicKey(),
|
||||
let userED25519KeyPair = storage.getUserED25519KeyPair() else { return Promise(error: Error.noKeyPair) }
|
||||
let publicKey = Features.useTestnet ? publicKey.removing05PrefixIfNeeded() : publicKey
|
||||
return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
|
||||
getSwarm(for: publicKey).then2 { swarm -> Promise<[String:Bool]> in
|
||||
let snode = swarm.randomElement()!
|
||||
let verificationData = (Snode.Method.deleteMessage.rawValue + serverHashes.joined(separator: "")).data(using: String.Encoding.utf8)!
|
||||
guard let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey) else { throw Error.signingFailed }
|
||||
let parameters: JSON = [
|
||||
"pubkey" : userX25519PublicKey,
|
||||
"pubkey_ed25519" : userED25519KeyPair.publicKey.toHexString(),
|
||||
"messages": serverHashes,
|
||||
"signature": signature.toBase64()!
|
||||
]
|
||||
return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
|
||||
invoke(.deleteMessage, on: snode, associatedWith: publicKey, parameters: parameters).map2{ rawResponse -> [String:Bool] in
|
||||
guard let json = rawResponse as? JSON, let swarm = json["swarm"] as? JSON else { throw HTTP.Error.invalidJSON }
|
||||
var result: [String:Bool] = [:]
|
||||
for (snodePublicKey, rawJSON) in swarm {
|
||||
guard let json = rawJSON as? JSON else { throw HTTP.Error.invalidJSON }
|
||||
let isFailed = json["failed"] as? Bool ?? false
|
||||
if !isFailed {
|
||||
guard let hashes = json["deleted"] as? [String], let signature = json["signature"] as? String else { throw HTTP.Error.invalidJSON }
|
||||
// The signature format is ( PUBKEY_HEX || RMSG[0] || ... || RMSG[N] || DMSG[0] || ... || DMSG[M] )
|
||||
let verificationData = (userX25519PublicKey + serverHashes.joined(separator: "") + hashes.joined(separator: "")).data(using: String.Encoding.utf8)!
|
||||
let isValid = sodium.sign.verify(message: Bytes(verificationData), publicKey: Bytes(Data(hex: snodePublicKey)), signature: Bytes(Data(base64Encoded: signature)!))
|
||||
result[snodePublicKey] = isValid
|
||||
} else {
|
||||
if let reason = json["reason"] as? String, let statusCode = json["code"] as? String {
|
||||
SNLog("Couldn't delete data from: \(snodePublicKey) due to error: \(reason) (\(statusCode)).")
|
||||
} else {
|
||||
SNLog("Couldn't delete data from: \(snodePublicKey).")
|
||||
}
|
||||
result[snodePublicKey] = false
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation.
|
||||
public static func clearAllData() -> Promise<[String:Bool]> {
|
||||
let storage = SNSnodeKitConfiguration.shared.storage
|
||||
|
|
|
@ -8,6 +8,10 @@ public class NoopNotificationsManager: NSObject, NotificationsProtocol {
|
|||
public func notifyUser(for incomingMessage: TSIncomingMessage, in thread: TSThread, transaction: YapDatabaseReadTransaction) {
|
||||
owsFailDebug("")
|
||||
}
|
||||
|
||||
public func cancelNotification(_ identifier: String) {
|
||||
owsFailDebug("")
|
||||
}
|
||||
|
||||
public func clearAllNotifications() {
|
||||
Logger.warn("clearAllNotifications")
|
||||
|
|
Loading…
Reference in New Issue