Cleaned up the outdated message logic and fixed a few bugs
Fixed a bug where showing hidden conversations would appear at the bottom of the list Fixed a bug where you would be kicked from a one-to-one conversation when opening it if you had hidden it Fixed a bug where joining a community via URL wasn't automatically opening the community Fixed a bug where the community poller could trigger again before the previous poll completed Fixed an edge-case where community messages could be missed if the app crashed at the right time when processing a poll response
This commit is contained in:
parent
44469d9078
commit
1ba060b7f0
|
@ -1587,6 +1587,10 @@ final class ConversationVC: BaseVC, SessionUtilRespondingViewController, Convers
|
||||||
self.scrollButton.alpha = self.getScrollButtonOpacity()
|
self.scrollButton.alpha = self.getScrollButtonOpacity()
|
||||||
self.unreadCountView.alpha = self.scrollButton.alpha
|
self.unreadCountView.alpha = self.scrollButton.alpha
|
||||||
|
|
||||||
|
// The initial scroll can trigger this logic but we already mark the initially focused message
|
||||||
|
// as read so don't run the below until the user actually scrolls after the initial layout
|
||||||
|
guard self.didFinishInitialLayout else { return }
|
||||||
|
|
||||||
// We want to mark messages as read while we scroll, so grab the newest message and mark
|
// We want to mark messages as read while we scroll, so grab the newest message and mark
|
||||||
// everything older as read
|
// everything older as read
|
||||||
//
|
//
|
||||||
|
|
|
@ -311,6 +311,19 @@ extension GlobalSearchViewController {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If it's a one-to-one thread then make sure the thread exists before pushing to it (in case the
|
||||||
|
// contact has been hidden)
|
||||||
|
if threadVariant == .contact {
|
||||||
|
Storage.shared.write { db in
|
||||||
|
try SessionThread.fetchOrCreate(
|
||||||
|
db,
|
||||||
|
id: threadId,
|
||||||
|
variant: threadVariant,
|
||||||
|
shouldBeVisible: nil // Don't change current state
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let viewController: ConversationVC = ConversationVC(
|
let viewController: ConversationVC = ConversationVC(
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
threadVariant: threadVariant,
|
threadVariant: threadVariant,
|
||||||
|
|
|
@ -159,10 +159,10 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
joinOpenGroup(roomToken: room, server: server, publicKey: publicKey)
|
joinOpenGroup(roomToken: room, server: server, publicKey: publicKey, shouldOpenCommunity: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func joinOpenGroup(roomToken: String, server: String, publicKey: String, shouldOpenCommunity: Bool = false) {
|
fileprivate func joinOpenGroup(roomToken: String, server: String, publicKey: String, shouldOpenCommunity: Bool) {
|
||||||
guard !isJoining, let navigationController: UINavigationController = navigationController else { return }
|
guard !isJoining, let navigationController: UINavigationController = navigationController else { return }
|
||||||
|
|
||||||
isJoining = true
|
isJoining = true
|
||||||
|
|
|
@ -277,15 +277,9 @@ public extension Message {
|
||||||
return processedMessage
|
return processedMessage
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
// If we get 'selfSend' or 'duplicateControlMessage' errors then we still want to insert
|
// For some error cases we want to update the last hash so do so
|
||||||
// the SnodeReceivedMessageInfo to prevent retrieving and attempting to process the same
|
if (error as? MessageReceiverError)?.shouldUpdateLastHash == true {
|
||||||
// message again (as well as ensure the next poll doesn't retrieve the same message)
|
_ = try? rawMessage.info.inserted(db)
|
||||||
switch error {
|
|
||||||
case MessageReceiverError.selfSend, MessageReceiverError.duplicateControlMessage:
|
|
||||||
_ = try? rawMessage.info.inserted(db)
|
|
||||||
break
|
|
||||||
|
|
||||||
default: break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error
|
throw error
|
||||||
|
|
|
@ -605,18 +605,19 @@ public final class OpenGroupManager {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let seqNo: Int64? = messages.map { $0.seqNo }.max()
|
|
||||||
let sortedMessages: [OpenGroupAPI.Message] = messages
|
let sortedMessages: [OpenGroupAPI.Message] = messages
|
||||||
.filter { $0.deleted != true }
|
.filter { $0.deleted != true }
|
||||||
.sorted { lhs, rhs in lhs.id < rhs.id }
|
.sorted { lhs, rhs in lhs.id < rhs.id }
|
||||||
var messageServerIdsToRemove: [Int64] = messages
|
var messageServerInfoToRemove: [(id: Int64, seqNo: Int64)] = messages
|
||||||
.filter { $0.deleted == true }
|
.filter { $0.deleted == true }
|
||||||
.map { $0.id }
|
.map { ($0.id, $0.seqNo) }
|
||||||
|
let updateSeqNo: (Database, String, inout Int64, Int64, OGMDependencies) -> () = { db, openGroupId, lastValidSeqNo, seqNo, dependencies in
|
||||||
if let seqNo: Int64 = seqNo {
|
// Only update the data if the 'seqNo' is larger than the lastValidSeqNo (only want it to increase)
|
||||||
|
guard seqNo > lastValidSeqNo else { return }
|
||||||
|
|
||||||
// Update the 'openGroupSequenceNumber' value (Note: SOGS V4 uses the 'seqNo' instead of the 'serverId')
|
// Update the 'openGroupSequenceNumber' value (Note: SOGS V4 uses the 'seqNo' instead of the 'serverId')
|
||||||
_ = try? OpenGroup
|
_ = try? OpenGroup
|
||||||
.filter(id: openGroup.id)
|
.filter(id: openGroupId)
|
||||||
.updateAll(db, OpenGroup.Columns.sequenceNumber.set(to: seqNo))
|
.updateAll(db, OpenGroup.Columns.sequenceNumber.set(to: seqNo))
|
||||||
|
|
||||||
// Update pendingChange cache
|
// Update pendingChange cache
|
||||||
|
@ -624,13 +625,18 @@ public final class OpenGroupManager {
|
||||||
$0.pendingChanges = $0.pendingChanges
|
$0.pendingChanges = $0.pendingChanges
|
||||||
.filter { $0.seqNo == nil || $0.seqNo! > seqNo }
|
.filter { $0.seqNo == nil || $0.seqNo! > seqNo }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the inout value
|
||||||
|
lastValidSeqNo = seqNo
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the messages
|
// Process the messages
|
||||||
|
var lastValidSeqNo: Int64 = -1
|
||||||
sortedMessages.forEach { message in
|
sortedMessages.forEach { message in
|
||||||
if message.base64EncodedData == nil && message.reactions == nil {
|
if message.base64EncodedData == nil && message.reactions == nil {
|
||||||
messageServerIdsToRemove.append(Int64(message.id))
|
messageServerInfoToRemove.append((message.id, message.seqNo))
|
||||||
return
|
|
||||||
|
return updateSeqNo(db, openGroup.id, &lastValidSeqNo, message.seqNo, dependencies)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle messages
|
// Handle messages
|
||||||
|
@ -657,6 +663,7 @@ public final class OpenGroupManager {
|
||||||
associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData),
|
associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData),
|
||||||
dependencies: dependencies
|
dependencies: dependencies
|
||||||
)
|
)
|
||||||
|
updateSeqNo(db, openGroup.id, &lastValidSeqNo, message.seqNo, dependencies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
@ -701,6 +708,7 @@ public final class OpenGroupManager {
|
||||||
openGroupMessageServerId: message.id,
|
openGroupMessageServerId: message.id,
|
||||||
openGroupReactions: reactions
|
openGroupReactions: reactions
|
||||||
)
|
)
|
||||||
|
updateSeqNo(db, openGroup.id, &lastValidSeqNo, message.seqNo, dependencies)
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
SNLog("Couldn't handle open group reactions due to error: \(error).")
|
SNLog("Couldn't handle open group reactions due to error: \(error).")
|
||||||
|
@ -709,12 +717,18 @@ public final class OpenGroupManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle any deletions that are needed
|
// Handle any deletions that are needed
|
||||||
guard !messageServerIdsToRemove.isEmpty else { return }
|
guard !messageServerInfoToRemove.isEmpty else { return }
|
||||||
|
|
||||||
|
let messageServerIdsToRemove: [Int64] = messageServerInfoToRemove.map { $0.id }
|
||||||
_ = try? Interaction
|
_ = try? Interaction
|
||||||
.filter(Interaction.Columns.threadId == openGroup.threadId)
|
.filter(Interaction.Columns.threadId == openGroup.threadId)
|
||||||
.filter(messageServerIdsToRemove.contains(Interaction.Columns.openGroupServerMessageId))
|
.filter(messageServerIdsToRemove.contains(Interaction.Columns.openGroupServerMessageId))
|
||||||
.deleteAll(db)
|
.deleteAll(db)
|
||||||
|
|
||||||
|
// Update the seqNo for deletions
|
||||||
|
if let lastDeletionSeqNo: Int64 = messageServerInfoToRemove.map({ $0.seqNo }).max() {
|
||||||
|
updateSeqNo(db, openGroup.id, &lastValidSeqNo, lastDeletionSeqNo, dependencies)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static func handleDirectMessages(
|
internal static func handleDirectMessages(
|
||||||
|
|
|
@ -34,6 +34,19 @@ public enum MessageReceiverError: LocalizedError {
|
||||||
default: return true
|
default: return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var shouldUpdateLastHash: Bool {
|
||||||
|
switch self {
|
||||||
|
// If we get one of these errors then we still want to update the last hash to prevent
|
||||||
|
// retrieving and attempting to process the same messages again (as well as ensure the
|
||||||
|
// next poll doesn't retrieve the same message - these errors are essentially considered
|
||||||
|
// "already successfully processed")
|
||||||
|
case .selfSend, .duplicateControlMessage, .outdatedMessage:
|
||||||
|
return true
|
||||||
|
|
||||||
|
default: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public var errorDescription: String? {
|
public var errorDescription: String? {
|
||||||
switch self {
|
switch self {
|
||||||
|
|
|
@ -13,31 +13,8 @@ extension MessageReceiver {
|
||||||
threadVariant: SessionThread.Variant,
|
threadVariant: SessionThread.Variant,
|
||||||
message: CallMessage
|
message: CallMessage
|
||||||
) throws {
|
) throws {
|
||||||
let timestampMs: Int64 = (message.sentTimestamp.map { Int64($0) } ?? SnodeAPI.currentOffsetTimestampMs())
|
|
||||||
|
|
||||||
// Only support calls from contact threads
|
// Only support calls from contact threads
|
||||||
guard
|
guard threadVariant == .contact else { return }
|
||||||
threadVariant == .contact,
|
|
||||||
/// Only process the message if the thread `shouldBeVisible` or it was sent after the libSession buffer period
|
|
||||||
(
|
|
||||||
SessionThread
|
|
||||||
.filter(id: threadId)
|
|
||||||
.filter(SessionThread.Columns.shouldBeVisible == true)
|
|
||||||
.isNotEmpty(db) ||
|
|
||||||
SessionUtil.conversationInConfig(
|
|
||||||
db,
|
|
||||||
threadId: threadId,
|
|
||||||
threadVariant: threadVariant,
|
|
||||||
visibleOnly: true
|
|
||||||
) ||
|
|
||||||
SessionUtil.canPerformChange(
|
|
||||||
db,
|
|
||||||
threadId: threadId,
|
|
||||||
targetConfig: .contacts,
|
|
||||||
changeTimestampMs: timestampMs
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else { return }
|
|
||||||
|
|
||||||
switch message.kind {
|
switch message.kind {
|
||||||
case .preOffer: try MessageReceiver.handleNewCallMessage(db, message: message)
|
case .preOffer: try MessageReceiver.handleNewCallMessage(db, message: message)
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import GRDB
|
import GRDB
|
||||||
import SessionSnodeKit
|
import SessionSnodeKit
|
||||||
import SessionUtilitiesKit
|
|
||||||
|
|
||||||
extension MessageReceiver {
|
extension MessageReceiver {
|
||||||
internal static func handleDataExtractionNotification(
|
internal static func handleDataExtractionNotification(
|
||||||
|
@ -12,46 +11,12 @@ extension MessageReceiver {
|
||||||
threadVariant: SessionThread.Variant,
|
threadVariant: SessionThread.Variant,
|
||||||
message: DataExtractionNotification
|
message: DataExtractionNotification
|
||||||
) throws {
|
) throws {
|
||||||
let timestampMs: Int64 = (
|
|
||||||
message.sentTimestamp.map { Int64($0) } ??
|
|
||||||
SnodeAPI.currentOffsetTimestampMs()
|
|
||||||
)
|
|
||||||
|
|
||||||
guard
|
guard
|
||||||
threadVariant == .contact,
|
threadVariant == .contact,
|
||||||
let sender: String = message.sender,
|
let sender: String = message.sender,
|
||||||
let messageKind: DataExtractionNotification.Kind = message.kind
|
let messageKind: DataExtractionNotification.Kind = message.kind
|
||||||
else { throw MessageReceiverError.invalidMessage }
|
else { throw MessageReceiverError.invalidMessage }
|
||||||
|
|
||||||
/// Only process the message if the thread `shouldBeVisible` or it was sent after the libSession buffer period
|
|
||||||
guard
|
|
||||||
SessionThread
|
|
||||||
.filter(id: threadId)
|
|
||||||
.filter(SessionThread.Columns.shouldBeVisible == true)
|
|
||||||
.isNotEmpty(db) ||
|
|
||||||
SessionUtil.conversationInConfig(
|
|
||||||
db,
|
|
||||||
threadId: threadId,
|
|
||||||
threadVariant: threadVariant,
|
|
||||||
visibleOnly: true
|
|
||||||
) ||
|
|
||||||
SessionUtil.canPerformChange(
|
|
||||||
db,
|
|
||||||
threadId: threadId,
|
|
||||||
targetConfig: {
|
|
||||||
switch threadVariant {
|
|
||||||
case .contact:
|
|
||||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db)
|
|
||||||
|
|
||||||
return (threadId == currentUserPublicKey ? .userProfile : .contacts)
|
|
||||||
|
|
||||||
default: return .userGroups
|
|
||||||
}
|
|
||||||
}(),
|
|
||||||
changeTimestampMs: timestampMs
|
|
||||||
)
|
|
||||||
else { throw MessageReceiverError.outdatedMessage }
|
|
||||||
|
|
||||||
_ = try Interaction(
|
_ = try Interaction(
|
||||||
serverHash: message.serverHash,
|
serverHash: message.serverHash,
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
|
@ -62,7 +27,10 @@ extension MessageReceiver {
|
||||||
case .mediaSaved: return .infoMediaSavedNotification
|
case .mediaSaved: return .infoMediaSavedNotification
|
||||||
}
|
}
|
||||||
}(),
|
}(),
|
||||||
timestampMs: timestampMs
|
timestampMs: (
|
||||||
|
message.sentTimestamp.map { Int64($0) } ??
|
||||||
|
SnodeAPI.currentOffsetTimestampMs()
|
||||||
|
)
|
||||||
).inserted(db)
|
).inserted(db)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,6 @@ extension MessageReceiver {
|
||||||
dependencies: SMKDependencies
|
dependencies: SMKDependencies
|
||||||
) throws {
|
) throws {
|
||||||
let userPublicKey = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
let userPublicKey = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||||
let timestampMs: Int64 = (
|
|
||||||
message.sentTimestamp.map { Int64($0) } ??
|
|
||||||
SnodeAPI.currentOffsetTimestampMs()
|
|
||||||
)
|
|
||||||
var blindedContactIds: [String] = []
|
var blindedContactIds: [String] = []
|
||||||
|
|
||||||
// Ignore messages which were sent from the current user
|
// Ignore messages which were sent from the current user
|
||||||
|
@ -25,26 +21,6 @@ extension MessageReceiver {
|
||||||
let senderId: String = message.sender
|
let senderId: String = message.sender
|
||||||
else { throw MessageReceiverError.invalidMessage }
|
else { throw MessageReceiverError.invalidMessage }
|
||||||
|
|
||||||
/// Only process the message if the thread `shouldBeVisible` or it was sent after the libSession buffer period
|
|
||||||
guard
|
|
||||||
SessionThread
|
|
||||||
.filter(id: senderId)
|
|
||||||
.filter(SessionThread.Columns.shouldBeVisible == true)
|
|
||||||
.isNotEmpty(db) ||
|
|
||||||
SessionUtil.conversationInConfig(
|
|
||||||
db,
|
|
||||||
threadId: senderId,
|
|
||||||
threadVariant: .contact,
|
|
||||||
visibleOnly: true
|
|
||||||
) ||
|
|
||||||
SessionUtil.canPerformChange(
|
|
||||||
db,
|
|
||||||
threadId: senderId,
|
|
||||||
targetConfig: .contacts,
|
|
||||||
changeTimestampMs: timestampMs
|
|
||||||
)
|
|
||||||
else { throw MessageReceiverError.outdatedMessage }
|
|
||||||
|
|
||||||
// Update profile if needed (want to do this regardless of whether the message exists or
|
// Update profile if needed (want to do this regardless of whether the message exists or
|
||||||
// not to ensure the profile info gets sync between a users devices at every chance)
|
// not to ensure the profile info gets sync between a users devices at every chance)
|
||||||
if let profile = message.profile {
|
if let profile = message.profile {
|
||||||
|
@ -160,7 +136,10 @@ extension MessageReceiver {
|
||||||
threadId: unblindedThread.id,
|
threadId: unblindedThread.id,
|
||||||
authorId: senderId,
|
authorId: senderId,
|
||||||
variant: .infoMessageRequestAccepted,
|
variant: .infoMessageRequestAccepted,
|
||||||
timestampMs: timestampMs
|
timestampMs: (
|
||||||
|
message.sentTimestamp.map { Int64($0) } ??
|
||||||
|
SnodeAPI.currentOffsetTimestampMs()
|
||||||
|
)
|
||||||
).inserted(db)
|
).inserted(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,32 +23,6 @@ extension MessageReceiver {
|
||||||
// seconds to maintain the accuracy)
|
// seconds to maintain the accuracy)
|
||||||
let messageSentTimestamp: TimeInterval = (TimeInterval(message.sentTimestamp ?? 0) / 1000)
|
let messageSentTimestamp: TimeInterval = (TimeInterval(message.sentTimestamp ?? 0) / 1000)
|
||||||
let isMainAppActive: Bool = (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false)
|
let isMainAppActive: Bool = (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false)
|
||||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
|
||||||
|
|
||||||
/// Only process the message if the thread `shouldBeVisible` or it was sent after the libSession buffer period
|
|
||||||
guard
|
|
||||||
SessionThread
|
|
||||||
.filter(id: threadId)
|
|
||||||
.filter(SessionThread.Columns.shouldBeVisible == true)
|
|
||||||
.isNotEmpty(db) ||
|
|
||||||
SessionUtil.conversationInConfig(
|
|
||||||
db,
|
|
||||||
threadId: threadId,
|
|
||||||
threadVariant: threadVariant,
|
|
||||||
visibleOnly: true
|
|
||||||
) ||
|
|
||||||
SessionUtil.canPerformChange(
|
|
||||||
db,
|
|
||||||
threadId: threadId,
|
|
||||||
targetConfig: {
|
|
||||||
switch threadVariant {
|
|
||||||
case .contact: return (threadId == currentUserPublicKey ? .userProfile : .contacts)
|
|
||||||
default: return .userGroups
|
|
||||||
}
|
|
||||||
}(),
|
|
||||||
changeTimestampMs: (message.sentTimestamp.map { Int64($0) } ?? SnodeAPI.currentOffsetTimestampMs())
|
|
||||||
)
|
|
||||||
else { throw MessageReceiverError.outdatedMessage }
|
|
||||||
|
|
||||||
// Update profile if needed (want to do this regardless of whether the message exists or
|
// Update profile if needed (want to do this regardless of whether the message exists or
|
||||||
// not to ensure the profile info gets sync between a users devices at every chance)
|
// not to ensure the profile info gets sync between a users devices at every chance)
|
||||||
|
@ -90,6 +64,7 @@ extension MessageReceiver {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the message variant so we can run variant-specific behaviours
|
// Store the message variant so we can run variant-specific behaviours
|
||||||
|
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||||
let thread: SessionThread = try SessionThread
|
let thread: SessionThread = try SessionThread
|
||||||
.fetchOrCreate(db, id: threadId, variant: threadVariant, shouldBeVisible: nil)
|
.fetchOrCreate(db, id: threadId, variant: threadVariant, shouldBeVisible: nil)
|
||||||
let maybeOpenGroup: OpenGroup? = {
|
let maybeOpenGroup: OpenGroup? = {
|
||||||
|
|
|
@ -192,6 +192,15 @@ public enum MessageReceiver {
|
||||||
SessionUtil.conversationInConfig(db, threadId: threadId, threadVariant: threadVariant, visibleOnly: false)
|
SessionUtil.conversationInConfig(db, threadId: threadId, threadVariant: threadVariant, visibleOnly: false)
|
||||||
else { throw MessageReceiverError.requiredThreadNotInConfig }
|
else { throw MessageReceiverError.requiredThreadNotInConfig }
|
||||||
|
|
||||||
|
// Throw if the message is outdated and shouldn't be processed
|
||||||
|
try throwIfMessageOutdated(
|
||||||
|
db,
|
||||||
|
message: message,
|
||||||
|
threadId: threadId,
|
||||||
|
threadVariant: threadVariant,
|
||||||
|
dependencies: dependencies
|
||||||
|
)
|
||||||
|
|
||||||
switch message {
|
switch message {
|
||||||
case let message as ReadReceipt:
|
case let message as ReadReceipt:
|
||||||
try MessageReceiver.handleReadReceipt(
|
try MessageReceiver.handleReadReceipt(
|
||||||
|
@ -315,7 +324,8 @@ public enum MessageReceiver {
|
||||||
.filter(id: threadId)
|
.filter(id: threadId)
|
||||||
.updateAllAndConfig(
|
.updateAllAndConfig(
|
||||||
db,
|
db,
|
||||||
SessionThread.Columns.shouldBeVisible.set(to: true)
|
SessionThread.Columns.shouldBeVisible.set(to: true),
|
||||||
|
SessionThread.Columns.pinnedPriority.set(to: SessionUtil.visiblePriority)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -344,4 +354,44 @@ public enum MessageReceiver {
|
||||||
try reaction.with(interactionId: interactionId).insert(db)
|
try reaction.with(interactionId: interactionId).insert(db)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func throwIfMessageOutdated(
|
||||||
|
_ db: Database,
|
||||||
|
message: Message,
|
||||||
|
threadId: String,
|
||||||
|
threadVariant: SessionThread.Variant,
|
||||||
|
dependencies: SMKDependencies = SMKDependencies()
|
||||||
|
) throws {
|
||||||
|
switch message {
|
||||||
|
case is ReadReceipt: return // No visible artifact created so better to keep for more reliable read states
|
||||||
|
case is UnsendRequest: return // We should always process the removal of messages just in case
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the state of the conversation and the validity of the message
|
||||||
|
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||||
|
let conversationVisibleInConfig: Bool = SessionUtil.conversationInConfig(
|
||||||
|
db,
|
||||||
|
threadId: threadId,
|
||||||
|
threadVariant: threadVariant,
|
||||||
|
visibleOnly: true
|
||||||
|
)
|
||||||
|
let canPerformChange: Bool = SessionUtil.canPerformChange(
|
||||||
|
db,
|
||||||
|
threadId: threadId,
|
||||||
|
targetConfig: {
|
||||||
|
switch threadVariant {
|
||||||
|
case .contact: return (threadId == currentUserPublicKey ? .userProfile : .contacts)
|
||||||
|
default: return .userGroups
|
||||||
|
}
|
||||||
|
}(),
|
||||||
|
changeTimestampMs: (message.sentTimestamp.map { Int64($0) } ?? SnodeAPI.currentOffsetTimestampMs())
|
||||||
|
)
|
||||||
|
|
||||||
|
// If the thread is visible or the message was sent more recently than the last config message (minus
|
||||||
|
// buffer period) then we should process the message, if not then throw as the message is outdated
|
||||||
|
guard !conversationVisibleInConfig && !canPerformChange else { return }
|
||||||
|
|
||||||
|
throw MessageReceiverError.outdatedMessage
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,16 +53,32 @@ extension OpenGroupAPI {
|
||||||
.fetchOne(db)
|
.fetchOne(db)
|
||||||
}
|
}
|
||||||
.defaulting(to: 0)
|
.defaulting(to: 0)
|
||||||
|
let lastPollStart: TimeInterval = Date().timeIntervalSince1970
|
||||||
let nextPollInterval: TimeInterval = getInterval(for: minPollFailureCount, minInterval: Poller.minPollInterval, maxInterval: Poller.maxPollInterval)
|
let nextPollInterval: TimeInterval = getInterval(for: minPollFailureCount, minInterval: Poller.minPollInterval, maxInterval: Poller.maxPollInterval)
|
||||||
|
|
||||||
poll(using: dependencies).sinkUntilComplete()
|
// Wait until the last poll completes before polling again ensuring we don't poll any faster than
|
||||||
timer = Timer.scheduledTimerOnMainThread(withTimeInterval: nextPollInterval, repeats: false) { [weak self] timer in
|
// the 'nextPollInterval' value
|
||||||
timer.invalidate()
|
poll(using: dependencies)
|
||||||
|
.sinkUntilComplete(
|
||||||
Threading.pollerQueue.async {
|
receiveCompletion: { [weak self] _ in
|
||||||
self?.pollRecursively(using: dependencies)
|
let currentTime: TimeInterval = Date().timeIntervalSince1970
|
||||||
}
|
let remainingInterval: TimeInterval = max(0, nextPollInterval - (currentTime - lastPollStart))
|
||||||
}
|
|
||||||
|
guard remainingInterval > 0 else {
|
||||||
|
return Threading.pollerQueue.async {
|
||||||
|
self?.pollRecursively(using: dependencies)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self?.timer = Timer.scheduledTimerOnMainThread(withTimeInterval: remainingInterval, repeats: false) { timer in
|
||||||
|
timer.invalidate()
|
||||||
|
|
||||||
|
Threading.pollerQueue.async {
|
||||||
|
self?.pollRecursively(using: dependencies)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func poll(
|
public func poll(
|
||||||
|
|
|
@ -34,11 +34,14 @@ internal extension SessionUtil {
|
||||||
return !allColumnsThatTriggerConfigUpdate.isDisjoint(with: targetColumns)
|
return !allColumnsThatTriggerConfigUpdate.isDisjoint(with: targetColumns)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A `0` `priority` value indicates visible, but not pinned
|
||||||
|
static let visiblePriority: Int32 = 0
|
||||||
|
|
||||||
/// A negative `priority` value indicates hidden
|
/// A negative `priority` value indicates hidden
|
||||||
static let hiddenPriority: Int32 = -1
|
static let hiddenPriority: Int32 = -1
|
||||||
|
|
||||||
static func shouldBeVisible(priority: Int32) -> Bool {
|
static func shouldBeVisible(priority: Int32) -> Bool {
|
||||||
return (priority >= 0)
|
return (priority >= SessionUtil.visiblePriority)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func performAndPushChange(
|
static func performAndPushChange(
|
||||||
|
@ -127,8 +130,8 @@ internal extension SessionUtil {
|
||||||
guard noteToSelf.shouldBeVisible else { return SessionUtil.hiddenPriority }
|
guard noteToSelf.shouldBeVisible else { return SessionUtil.hiddenPriority }
|
||||||
|
|
||||||
return noteToSelf.pinnedPriority
|
return noteToSelf.pinnedPriority
|
||||||
.map { Int32($0 == 0 ? 0 : max($0, 1)) }
|
.map { Int32($0 == 0 ? SessionUtil.visiblePriority : max($0, 1)) }
|
||||||
.defaulting(to: 0)
|
.defaulting(to: SessionUtil.visiblePriority)
|
||||||
}(),
|
}(),
|
||||||
in: conf
|
in: conf
|
||||||
)
|
)
|
||||||
|
@ -154,8 +157,8 @@ internal extension SessionUtil {
|
||||||
guard thread.shouldBeVisible else { return SessionUtil.hiddenPriority }
|
guard thread.shouldBeVisible else { return SessionUtil.hiddenPriority }
|
||||||
|
|
||||||
return thread.pinnedPriority
|
return thread.pinnedPriority
|
||||||
.map { Int32($0 == 0 ? 0 : max($0, 1)) }
|
.map { Int32($0 == 0 ? SessionUtil.visiblePriority : max($0, 1)) }
|
||||||
.defaulting(to: 0)
|
.defaulting(to: SessionUtil.visiblePriority)
|
||||||
}()
|
}()
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -176,8 +179,8 @@ internal extension SessionUtil {
|
||||||
CommunityInfo(
|
CommunityInfo(
|
||||||
urlInfo: urlInfo,
|
urlInfo: urlInfo,
|
||||||
priority: thread.pinnedPriority
|
priority: thread.pinnedPriority
|
||||||
.map { Int32($0 == 0 ? 0 : max($0, 1)) }
|
.map { Int32($0 == 0 ? SessionUtil.visiblePriority : max($0, 1)) }
|
||||||
.defaulting(to: 0)
|
.defaulting(to: SessionUtil.visiblePriority)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -197,8 +200,8 @@ internal extension SessionUtil {
|
||||||
LegacyGroupInfo(
|
LegacyGroupInfo(
|
||||||
id: thread.id,
|
id: thread.id,
|
||||||
priority: thread.pinnedPriority
|
priority: thread.pinnedPriority
|
||||||
.map { Int32($0 == 0 ? 0 : max($0, 1)) }
|
.map { Int32($0 == 0 ? SessionUtil.visiblePriority : max($0, 1)) }
|
||||||
.defaulting(to: 0)
|
.defaulting(to: SessionUtil.visiblePriority)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
in: conf
|
in: conf
|
||||||
|
|
|
@ -75,6 +75,14 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Throw if the message is outdated and shouldn't be processed
|
||||||
|
try MessageReceiver.throwIfMessageOutdated(
|
||||||
|
db,
|
||||||
|
message: processedMessage.messageInfo.message,
|
||||||
|
threadId: processedMessage.threadId,
|
||||||
|
threadVariant: processedMessage.threadVariant
|
||||||
|
)
|
||||||
|
|
||||||
switch processedMessage.messageInfo.message {
|
switch processedMessage.messageInfo.message {
|
||||||
case let visibleMessage as VisibleMessage:
|
case let visibleMessage as VisibleMessage:
|
||||||
let interactionId: Int64 = try MessageReceiver.handleVisibleMessage(
|
let interactionId: Int64 = try MessageReceiver.handleVisibleMessage(
|
||||||
|
@ -174,7 +182,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
|
||||||
catch {
|
catch {
|
||||||
if let error = error as? MessageReceiverError, error.isRetryable {
|
if let error = error as? MessageReceiverError, error.isRetryable {
|
||||||
switch error {
|
switch error {
|
||||||
case .invalidGroupPublicKey, .noGroupKeyPair: self.completeSilenty()
|
case .invalidGroupPublicKey, .noGroupKeyPair, .outdatedMessage: self.completeSilenty()
|
||||||
default: self.handleFailure(for: notificationContent)
|
default: self.handleFailure(for: notificationContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue