mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
176 lines
7.6 KiB
Swift
176 lines
7.6 KiB
Swift
2 years ago
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||
|
|
||
|
import Foundation
|
||
2 years ago
|
import Combine
|
||
2 years ago
|
import GRDB
|
||
|
import SignalCoreKit
|
||
|
import SessionUtilitiesKit
|
||
|
import SessionSnodeKit
|
||
|
|
||
|
public enum GroupLeavingJob: JobExecutor {
|
||
2 years ago
|
public static var maxFailureCount: Int = 0
|
||
2 years ago
|
public static var requiresThreadId: Bool = true
|
||
|
public static var requiresInteractionId: Bool = true
|
||
|
|
||
|
public static func run(
|
||
2 years ago
|
_ job: Job,
|
||
2 years ago
|
queue: DispatchQueue,
|
||
2 years ago
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||
|
deferred: @escaping (Job, Dependencies) -> (),
|
||
2 years ago
|
using dependencies: Dependencies = Dependencies()
|
||
2 years ago
|
) {
|
||
2 years ago
|
guard
|
||
|
let detailsData: Data = job.details,
|
||
2 years ago
|
let details: Details = try? JSONDecoder(using: dependencies).decode(Details.self, from: detailsData),
|
||
2 years ago
|
let threadId: String = job.threadId,
|
||
2 years ago
|
let interactionId: Int64 = job.interactionId
|
||
2 years ago
|
else {
|
||
2 years ago
|
SNLog("[GroupLeavingJob] Failed due to missing details")
|
||
2 years ago
|
return failure(job, JobRunnerError.missingRequiredDetails, true, dependencies)
|
||
2 years ago
|
}
|
||
|
|
||
2 years ago
|
let destination: Message.Destination = .closedGroup(groupPublicKey: threadId)
|
||
2 years ago
|
|
||
2 years ago
|
dependencies[singleton: .storage]
|
||
2 years ago
|
.writePublisher { db -> HTTP.PreparedRequest<Void> in
|
||
2 years ago
|
guard (try? SessionThread.exists(db, id: threadId)) == true else {
|
||
2 years ago
|
SNLog("[GroupLeavingJob] Failed due to non-existent group conversation")
|
||
2 years ago
|
throw MessageSenderError.noThread
|
||
2 years ago
|
}
|
||
2 years ago
|
guard (try? ClosedGroup.exists(db, id: threadId)) == true else {
|
||
2 years ago
|
SNLog("[GroupLeavingJob] Failed due to non-existent group")
|
||
2 years ago
|
throw MessageSenderError.invalidClosedGroupUpdate
|
||
2 years ago
|
}
|
||
|
|
||
2 years ago
|
return try MessageSender.preparedSend(
|
||
2 years ago
|
db,
|
||
|
message: ClosedGroupControlMessage(
|
||
|
kind: .memberLeft
|
||
|
),
|
||
|
to: destination,
|
||
|
namespace: destination.defaultNamespace,
|
||
|
interactionId: job.interactionId,
|
||
2 years ago
|
fileIds: [],
|
||
2 years ago
|
isSyncMessage: false,
|
||
|
using: dependencies
|
||
2 years ago
|
)
|
||
2 years ago
|
}
|
||
2 years ago
|
.flatMap { $0.send(using: dependencies) }
|
||
|
.subscribe(on: queue, using: dependencies)
|
||
|
.receive(on: queue, using: dependencies)
|
||
2 years ago
|
.sinkUntilComplete(
|
||
|
receiveCompletion: { result in
|
||
2 years ago
|
let failureChanges: [ConfigColumnAssignment] = [
|
||
|
Interaction.Columns.variant
|
||
2 years ago
|
.set(to: Interaction.Variant.infoGroupCurrentUserErrorLeaving),
|
||
2 years ago
|
Interaction.Columns.body.set(to: "group_unable_to_leave".localized())
|
||
|
]
|
||
|
let successfulChanges: [ConfigColumnAssignment] = [
|
||
|
Interaction.Columns.variant
|
||
2 years ago
|
.set(to: Interaction.Variant.infoLegacyGroupCurrentUserLeft),
|
||
2 years ago
|
Interaction.Columns.body.set(to: "GROUP_YOU_LEFT".localized())
|
||
|
]
|
||
|
|
||
|
// Handle the appropriate response
|
||
2 years ago
|
dependencies[singleton: .storage].writeAsync { db in
|
||
2 years ago
|
// If it failed due to one of these errors then clear out any associated data (as somehow
|
||
|
// the 'SessionThread' exists but not the data required to send the 'MEMBER_LEFT' message
|
||
|
// which would leave the user in a state where they can't leave the group)
|
||
|
let errorsToSucceed: [MessageSenderError] = [
|
||
|
.invalidClosedGroupUpdate,
|
||
|
.noKeyPair
|
||
2 years ago
|
]
|
||
2 years ago
|
let shouldSucceed: Bool = {
|
||
|
switch result {
|
||
|
case .failure(let error as MessageSenderError): return errorsToSucceed.contains(error)
|
||
|
case .failure: return false
|
||
|
default: return true
|
||
2 years ago
|
}
|
||
2 years ago
|
}()
|
||
|
|
||
|
// Update the transaction
|
||
|
try Interaction
|
||
|
.filter(id: interactionId)
|
||
|
.updateAll(
|
||
|
db,
|
||
|
(shouldSucceed ? successfulChanges : failureChanges)
|
||
|
)
|
||
|
|
||
|
// If we succeed in leaving then we should try to clear the group data
|
||
|
guard shouldSucceed else { return }
|
||
|
|
||
|
// Update the group (if the admin leaves the group is disbanded)
|
||
2 years ago
|
let userSessionId: SessionId = getUserSessionId(db, using: dependencies)
|
||
2 years ago
|
let wasAdminUser: Bool = GroupMember
|
||
|
.filter(GroupMember.Columns.groupId == threadId)
|
||
2 years ago
|
.filter(GroupMember.Columns.profileId == userSessionId.hexString)
|
||
2 years ago
|
.filter(GroupMember.Columns.role == GroupMember.Role.admin)
|
||
|
.isNotEmpty(db)
|
||
|
|
||
|
if wasAdminUser {
|
||
|
try GroupMember
|
||
|
.filter(GroupMember.Columns.groupId == threadId)
|
||
|
.deleteAll(db)
|
||
|
}
|
||
|
else {
|
||
|
try GroupMember
|
||
|
.filter(GroupMember.Columns.groupId == threadId)
|
||
2 years ago
|
.filter(GroupMember.Columns.profileId == userSessionId.hexString)
|
||
2 years ago
|
.deleteAll(db)
|
||
|
}
|
||
|
|
||
|
// Clear out the group info as needed
|
||
2 years ago
|
try ClosedGroup.removeData(
|
||
2 years ago
|
db,
|
||
2 years ago
|
threadIds: [threadId],
|
||
|
dataToRemove: (details.deleteThread ?
|
||
|
.allData :
|
||
|
[.poller, .pushNotifications, .libSessionState]
|
||
|
),
|
||
|
calledFromConfigHandling: false,
|
||
|
using: dependencies
|
||
2 years ago
|
)
|
||
2 years ago
|
}
|
||
2 years ago
|
|
||
2 years ago
|
success(job, false, dependencies)
|
||
2 years ago
|
}
|
||
|
)
|
||
2 years ago
|
}
|
||
|
}
|
||
|
|
||
|
// MARK: - GroupLeavingJob.Details
|
||
|
|
||
|
extension GroupLeavingJob {
|
||
|
public struct Details: Codable {
|
||
|
private enum CodingKeys: String, CodingKey {
|
||
2 years ago
|
case deleteThread
|
||
2 years ago
|
}
|
||
|
|
||
2 years ago
|
public let deleteThread: Bool
|
||
2 years ago
|
|
||
|
// MARK: - Initialization
|
||
|
|
||
2 years ago
|
public init(deleteThread: Bool) {
|
||
2 years ago
|
self.deleteThread = deleteThread
|
||
2 years ago
|
}
|
||
|
|
||
|
// MARK: - Codable
|
||
|
|
||
|
public init(from decoder: Decoder) throws {
|
||
|
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
|
||
|
|
||
|
self = Details(
|
||
2 years ago
|
deleteThread: try container.decode(Bool.self, forKey: .deleteThread)
|
||
2 years ago
|
)
|
||
|
}
|
||
|
|
||
|
public func encode(to encoder: Encoder) throws {
|
||
|
var container: KeyedEncodingContainer<CodingKeys> = encoder.container(keyedBy: CodingKeys.self)
|
||
2 years ago
|
|
||
2 years ago
|
try container.encode(deleteThread, forKey: .deleteThread)
|
||
2 years ago
|
}
|
||
|
}
|
||
|
}
|
||
|
|